Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compiled example docs + watch mode #190

Merged
merged 2 commits into from
May 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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*/i;
const EXAMPLE_END_REGX = /^\/\/\s+end\s*/i;

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", { recursive: true }, (_eventType, filename) => {
if (filename?.endsWith(".md") || filename?.endsWith(".ts")) {
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
Loading