Skip to content

Commit 83f0c99

Browse files
committed
Add comprehensive Decimal documentation and cookbook
- Add documentation build system with Markdown support - Create cookbook with practical Decimal usage examples - Add why-decimal.md explaining the motivation for Decimal - Add data-model.md explaining the technical design - Add decimal.md with API reference - Update package.json with documentation scripts - Add .gitignore entries for generated docs
1 parent ecfa75d commit 83f0c99

16 files changed

+2380
-3889
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
/node_modules/
2+
docs/out/

docs/.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
node_modules/
2+
out/
3+
*.log
4+
.DS_Store

docs/buildDocs.js

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
const fs = require('fs').promises;
2+
const { marked } = require('marked');
3+
const { mkdirp } = require('mkdirp');
4+
const path = require('path');
5+
const Prism = require('prismjs');
6+
const loadLanguages = require('prismjs/components/');
7+
8+
// Load additional languages (javascript is included by default)
9+
loadLanguages(['javascript', 'js', 'bash', 'typescript']);
10+
11+
const encoding = 'utf-8';
12+
13+
// Configure marked options
14+
marked.use({
15+
mangle: false,
16+
headerIds: true,
17+
headerPrefix: '',
18+
});
19+
20+
// Create a custom extension that processes links
21+
const linkExtension = {
22+
name: 'mdToHtml',
23+
level: 'inline',
24+
start(src) { return src.match(/\[/)?.index; },
25+
tokenizer(src, tokens) {
26+
const rule = /^\[([^\]]+)\]\(([^)]+\.md)([^)]*)\)/;
27+
const match = rule.exec(src);
28+
if (match) {
29+
return {
30+
type: 'mdToHtml',
31+
raw: match[0],
32+
text: match[1],
33+
href: match[2].replace(/\.md$/, '.html'),
34+
title: match[3]
35+
};
36+
}
37+
},
38+
renderer(token) {
39+
const titleAttr = token.title ? ` title="${token.title}"` : '';
40+
return `<a href="${token.href}"${titleAttr}>${token.text}</a>`;
41+
}
42+
};
43+
44+
// Custom code renderer for syntax highlighting
45+
const codeRenderer = {
46+
name: 'codeRenderer',
47+
renderer: {
48+
code(token) {
49+
const code = token.text || '';
50+
const language = token.lang || '';
51+
52+
if (!language) {
53+
// No language specified - escape the code
54+
const escaped = code.replace(/[&<>"']/g, (char) => {
55+
const escapes = {
56+
'&': '&amp;',
57+
'<': '&lt;',
58+
'>': '&gt;',
59+
'"': '&quot;',
60+
"'": '&#39;'
61+
};
62+
return escapes[char];
63+
});
64+
return `<pre><code>${escaped}</code></pre>\n`;
65+
}
66+
67+
// Use Prism for syntax highlighting if language is supported
68+
const lang = language.toLowerCase();
69+
// Map common aliases
70+
const langMap = {
71+
'js': 'javascript',
72+
'javascript': 'javascript',
73+
'ts': 'typescript',
74+
'typescript': 'typescript',
75+
'bash': 'bash',
76+
'sh': 'bash'
77+
};
78+
const mappedLang = langMap[lang] || lang;
79+
80+
if (Prism.languages[mappedLang]) {
81+
const highlighted = Prism.highlight(code, Prism.languages[mappedLang], mappedLang);
82+
return `<pre class="language-${lang}"><code class="language-${lang}">${highlighted}</code></pre>\n`;
83+
}
84+
85+
// Fallback for unsupported languages - escape the code
86+
const escaped = code.replace(/[&<>"']/g, (char) => {
87+
const escapes = {
88+
'&': '&amp;',
89+
'<': '&lt;',
90+
'>': '&gt;',
91+
'"': '&quot;',
92+
"'": '&#39;'
93+
};
94+
return escapes[char];
95+
});
96+
return `<pre class="language-${lang}"><code class="language-${lang}">${escaped}</code></pre>\n`;
97+
}
98+
}
99+
};
100+
101+
// Register the extensions
102+
marked.use({ extensions: [linkExtension], renderer: codeRenderer.renderer });
103+
104+
// Override the heading renderer using the proper API
105+
const headingRenderer = {
106+
name: 'headingRenderer',
107+
renderer: {
108+
heading(token) {
109+
// Extract text content from the token
110+
let text = '';
111+
if (token.tokens && token.tokens.length > 0) {
112+
text = token.tokens.map(t => t.text || t.raw || '').join('');
113+
} else if (token.text) {
114+
text = token.text;
115+
}
116+
117+
const level = token.depth || 1;
118+
119+
// Check for explicit ID syntax {#id}
120+
let id = '';
121+
let displayText = text;
122+
const idMatch = text.match(/^(.+?)\s*\{#(.+?)\}$/);
123+
124+
if (idMatch) {
125+
displayText = idMatch[1].trim();
126+
id = idMatch[2];
127+
} else {
128+
// Generate ID from text
129+
id = text.toLowerCase().replace(/[^\w]+/g, '-').replace(/^-+|-+$/g, '');
130+
}
131+
132+
return `<h${level} id="${id}"><a class="heading-link" href="#${id}"></a>${displayText}</h${level}>\n`;
133+
}
134+
}
135+
};
136+
137+
marked.use(headingRenderer);
138+
139+
// Also use a walkTokens function to transform .md links in regular links
140+
marked.use({
141+
walkTokens(token) {
142+
if (token.type === 'link' && token.href && token.href.endsWith('.md')) {
143+
token.href = token.href.replace(/\.md$/, '.html');
144+
}
145+
}
146+
});
147+
148+
async function processMarkdownFile(markdownFile, outputDir, head, tail) {
149+
console.log(`Processing ${markdownFile}...`);
150+
151+
// Read markdown content
152+
let content = await fs.readFile(markdownFile, { encoding });
153+
154+
// Parse markdown
155+
let html = marked(content);
156+
157+
// Get output filename
158+
const baseName = path.basename(markdownFile, '.md');
159+
const outputFile = path.join(outputDir, `${baseName}.html`);
160+
161+
// Combine with header and footer
162+
const fullHtml = head + html + tail;
163+
164+
// Write output file
165+
await fs.writeFile(outputFile, fullHtml, { encoding });
166+
console.log(` → ${outputFile}`);
167+
}
168+
169+
async function build() {
170+
try {
171+
console.log('Building Decimal documentation...\n');
172+
173+
// Create output directory
174+
const outputDir = path.join(__dirname, 'out');
175+
await mkdirp(outputDir);
176+
177+
// Read header and footer templates
178+
const head = await fs.readFile(path.join(__dirname, 'head.html.part'), { encoding });
179+
const tail = await fs.readFile(path.join(__dirname, 'tail.html.part'), { encoding });
180+
181+
// Get all markdown files
182+
const files = await fs.readdir(__dirname);
183+
const mdFiles = files.filter(f => f.endsWith('.md'));
184+
185+
// Process each markdown file
186+
for (const file of mdFiles) {
187+
await processMarkdownFile(path.join(__dirname, file), outputDir, head, tail);
188+
}
189+
190+
// Copy prism.css - using okaidia theme for vibrant syntax highlighting
191+
const prismCss = path.join(__dirname, '..', 'node_modules/prismjs/themes/prism-okaidia.css');
192+
const prismDest = path.join(outputDir, 'prism.css');
193+
await fs.copyFile(prismCss, prismDest);
194+
console.log(` → ${prismDest}`);
195+
196+
// Build and copy proposal-decimal polyfill as browser bundle
197+
const { execSync } = require('child_process');
198+
const polyfillSrc = path.join(__dirname, '..', 'node_modules/proposal-decimal/src/Decimal.mjs');
199+
const polyfillDest = path.join(outputDir, 'decimal-polyfill.js');
200+
201+
// Build IIFE bundle that exposes DecimalPolyfill globally
202+
execSync(`npx esbuild "${polyfillSrc}" --bundle --format=iife --global-name=DecimalPolyfill --outfile="${polyfillDest}"`, {
203+
cwd: __dirname,
204+
stdio: 'inherit'
205+
});
206+
console.log(` → ${polyfillDest}`);
207+
208+
// Also copy the original ES module
209+
const polyfillMjsDest = path.join(outputDir, 'decimal-polyfill.mjs');
210+
await fs.copyFile(polyfillSrc, polyfillMjsDest);
211+
console.log(` → ${polyfillMjsDest}`);
212+
213+
console.log('\nBuild complete!');
214+
} catch (error) {
215+
console.error('Build failed:', error);
216+
process.exit(1);
217+
}
218+
}
219+
220+
// Run the build
221+
build();

0 commit comments

Comments
 (0)