Skip to content

Commit

Permalink
Compiled example docs + watch mode
Browse files Browse the repository at this point in the history
  • Loading branch information
jscheiny committed May 18, 2024
1 parent b9a6617 commit 16e8326
Show file tree
Hide file tree
Showing 9 changed files with 187 additions and 116 deletions.
17 changes: 17 additions & 0 deletions docs/examples/intro.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// START
import { Length, Measure, meters, seconds, Time, Velocity } from "safe-units";

const length: Length = Measure.of(30, meters);
const time: Time = Measure.of(15, seconds);
const velocity: Velocity = length.over(time);

console.log(length.toString()); // 30 m
console.log(time.toString()); // 15 s
console.log(velocity.toString()); // 2 m * s^-1

// @ts-expect-error ERROR: A measure of m*s isn't assignable to a measure of m/s.
const error: Velocity = length.times(time);
// END

// Ensure that error is used
console.log(error);
9 changes: 9 additions & 0 deletions docs/examples/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"noEmit": true,
"lib": ["es5"],
"baseUrl": ".",
"paths": { "safe-units": ["../../src"] }
}
}
15 changes: 2 additions & 13 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,8 @@

Safe Units is a type safe library for using units of measurement in TypeScript. Check it out on [github](https://github.com/jscheiny/safe-units). Safe Units provides an implementation of an SI based unit system but is flexible enough to allow users to create their own unit systems which can be independent or can interoperate with the built-in units. Users can also make unit systems for any numeric type they'd like not just the JavaScript `number` type. This library requires TypeScript 3.2 or higher.

```typescript
import { Length, Measure, meters, seconds, Time, Velocity } from "safe-units";

const length: Length = Measure.of(30, meters);
const time: Time = Measure.of(15, seconds);
const velocity: Velocity = length.over(time);

console.log(length.toString()); // 30 m
console.log(time.toString()); // 15 s
console.log(velocity.toString()); // 2 m * s^-1

const error: Velocity = length.times(time);
// ERROR: A measure of m*s isn't assignable to a measure of m/s.
```example
intro.ts
```

**Features include:**
Expand Down
102 changes: 102 additions & 0 deletions docsgen/emit.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from "fs";
import { basename, extname, join } from "path";
import * as React from "react";
import * as ReactDOMServer from "react-dom/server";
import { getStyles } from "typestyle";
import { Page } from "./page";
import { PageModel } from "./pageModel";

const renderPageHtml = (title: string, body: string, inlineStyles: string, linkedStyles: string) => `<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>${title}</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>${inlineStyles}</style>
${linkedStyles}
<link href="https://fonts.googleapis.com/css?family=Open+Sans:400,700|Ubuntu+Mono" rel="stylesheet">
</head>
<body>${body}</body>
</html>`;

export function emitDocs() {
const docsDir = "docs";
const buildDir = join(docsDir, "build");
if (!existsSync(buildDir)) {
mkdirSync(buildDir);
}

const stylesDir = join(buildDir, "styles");
if (!existsSync(stylesDir)) {
mkdirSync(stylesDir);
}

const imagesContextPath = "images";
const imagesInDir = join(docsDir, imagesContextPath);
const imagesOutDir = join(buildDir, imagesContextPath);
if (!existsSync(imagesOutDir)) {
mkdirSync(imagesOutDir);
}

const pages = readdirSync("docs")
.filter(path => extname(path) === ".md")
.map(path => join("docs", path))
.map(PageModel.from);

const orderPath = join("docs", "order.txt");
let order: string[] = [];
if (existsSync(orderPath) && !statSync(orderPath).isDirectory()) {
order = readFileSync(orderPath, "utf-8").split("\n");
}

function orderBy(order: string[]): (a: PageModel, b: PageModel) => number {
const getIndex = (page: PageModel) => {
if (page.name === "index") {
return -Infinity;
}
let index = order.indexOf(page.name);
if (index === -1) {
index = order.indexOf("*");
}
return index === -1 ? Infinity : index;
};

return (a, b) => {
const aIndex = getIndex(a);
const bIndex = getIndex(b);
if (aIndex < bIndex) {
return -1;
} else if (aIndex > bIndex) {
return 1;
} else {
return a.title.toLowerCase() < b.title.toLowerCase() ? -1 : 1;
}
};
}

function buildLinkedStyles(inPath: string): string {
const name = basename(inPath);
const outPath = join(stylesDir, name);
copyFileSync(inPath, outPath);
return `<link rel="stylesheet" type="text/css" media="screen" href="styles/${name}" />`;
}

const linkedStyles = ["docs/styles/highlight.css", "node_modules/normalize.css/normalize.css"]
.map(buildLinkedStyles)
.join("\n");

readdirSync(imagesInDir).forEach(path => {
const inPath = join(imagesInDir, path);
const outPath = join(imagesOutDir, path);
copyFileSync(inPath, outPath);
});

pages.sort(orderBy(order));
pages.forEach((page, index) => {
const body = ReactDOMServer.renderToString(<Page pages={pages} pageIndex={index} />);
const title = page.name === "index" ? page.title : `${page.title} | Safe Units`;
const html = renderPageHtml(title, body, getStyles(), linkedStyles);
writeFileSync(join(buildDir, page.path), html);
});
}
3 changes: 3 additions & 0 deletions docsgen/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { emitDocs } from "./emit";

emitDocs();
100 changes: 0 additions & 100 deletions docsgen/index.tsx

This file was deleted.

36 changes: 35 additions & 1 deletion docsgen/markdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import highlight from "highlight.js";
import * as React from "react";
import { createNodeId, getNodeText } from "./markdownUtils";
import { component } from "./style";
import { join } from "path";
import { existsSync, readFileSync } from "fs";

interface MarkdownProps {
root: Node;
Expand Down Expand Up @@ -44,7 +46,8 @@ export const Markdown: MarkdownComponent = ({ root }) => {
case "thematic_break":
return <hr />;
case "code_block": {
const highlightedCode = highlight.highlight(root.literal ?? "", { language: "typescript" }).value;
const text = getCodeBlockText(root);
const highlightedCode = highlight.highlight(text, { language: "typescript" }).value;
return (
<pre>
<CodeBlock className="hljs typescript" dangerouslySetInnerHTML={{ __html: highlightedCode }} />
Expand Down Expand Up @@ -140,6 +143,37 @@ function getHeadingTag(level: number): keyof JSX.IntrinsicElements {
}
}

function getCodeBlockText(root: Node): string {
if (root.info !== "example") {
return root.literal ?? "";
}

if (root.literal == null) {
throw new Error("Expected example code block to have a reference to an example file.");
}

const examplePath = join("docs", "examples", root.literal.trim());
console.log(examplePath);
if (!existsSync(examplePath)) {
throw new Error(`Example file not found: ${examplePath}`);
}

const contents = readFileSync(examplePath, "utf-8");

const lines = contents.split("\n");
const startLine = lines.findIndex(line => EXAMPLE_START_REGEX.test(line));
const endLine = lines.findIndex(line => EXAMPLE_END_REGX.test(line));

if (startLine === -1 || endLine === -1) {
return contents;
}

return lines.slice(startLine + 1, endLine).join("\n");
}

const EXAMPLE_START_REGEX = /^\/\/\s+start\s*/gi;
const EXAMPLE_END_REGX = /^\/\/\s+end\s*/gi;

const CodeBlock = component("code-block", "code", {
borderRadius: 3,
$nest: {
Expand Down
13 changes: 13 additions & 0 deletions docsgen/watch.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { watch } from "fs";
import { emitDocs } from "./emit";

console.log("Generating initial docs...");
emitDocs();
console.log("Watching for docs changes...");
watch("docs", {}, (_eventType, filename) => {
if (filename?.endsWith(".md")) {
console.log(`Change detected in ${filename}. Rebuilding docs...`);
emitDocs();
console.log("Done");
}
});
8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,17 @@
"build": "tsc -p src",
"clean": "rimraf dist docs/build",
"compile:docs": "tsc -p docsgen",
"docs": "npm-run-all -s compile:docs node:docs",
"compile:examples": "tsc -p docs/examples",
"docs": "npm-run-all -s compile:docs compile:examples node:docs",
"docs:watch": "npm-run-all -s compile:docs compile:examples node:docs:watch",
"lint:docs": "eslint --config eslint.config.mjs docsgen",
"lint:examples": "eslint --config eslint.config.mjs docs/examples",
"lint:src": "eslint --config eslint.config.mjs src",
"lint:dist": "./scripts/check-typings.sh",
"lint:test": "eslint --config eslint.config.mjs test",
"lint": "npm-run-all -p lint:docs lint:src lint:dist",
"lint": "npm-run-all -p lint:docs lint:examples lint:src lint:dist lint:test",
"node:docs": "node dist/docsgen/index",
"node:docs:watch": "node dist/docsgen/watch",
"test:src": "jest --config jest.config.ts",
"test:types": "tsc -p test",
"test": "npm-run-all -p test:src test:types",
Expand Down

0 comments on commit 16e8326

Please sign in to comment.