From da1061aaa77ce8b5d8bea7cc81ffd8ba9cd57414 Mon Sep 17 00:00:00 2001 From: Francesco Bigiarini Date: Mon, 27 Jul 2026 17:40:57 +0200 Subject: [PATCH 1/2] New HTML page blocks Signed-off-by: Francesco Bigiarini --- src/site/footer.html | 877 ++++++++++++++++++++++ src/site/index.html | 884 +---------------------- utils/generate/commands/examples.ts | 23 + utils/generate/commands/generate-site.ts | 81 ++- utils/generate/functions.ts | 9 +- utils/generate/header.ts | 4 +- utils/generate/html-function.ts | 30 + utils/generate/package.json | 1 + 8 files changed, 989 insertions(+), 920 deletions(-) create mode 100644 src/site/footer.html create mode 100644 utils/generate/commands/examples.ts diff --git a/src/site/footer.html b/src/site/footer.html new file mode 100644 index 0000000..25db47f --- /dev/null +++ b/src/site/footer.html @@ -0,0 +1,877 @@ + + + diff --git a/src/site/index.html b/src/site/index.html index c631d74..ee0404c 100644 --- a/src/site/index.html +++ b/src/site/index.html @@ -7,7 +7,7 @@ - Mojibake — Unicode text processing in C, without the baggage + [TITLE_HERE] @@ -57,7 +57,7 @@

- + [SIDEBAR_HERE] @@ -115,886 +115,10 @@

Try every function in your browser

-
+
[FUNCTIONS_HERE]
-
-

Mojibake - Unicode text processing for C.

-

Source on GitHub - MIT License

-
- - + [FOOTER_HERE] diff --git a/utils/generate/commands/examples.ts b/utils/generate/commands/examples.ts new file mode 100644 index 0000000..3b80df7 --- /dev/null +++ b/utils/generate/commands/examples.ts @@ -0,0 +1,23 @@ +import functions from '../functions'; +import { CFunction } from '../html-function'; + +function generateExample(name: string, args: string[], after?: string): string { + const fn = functions.find(f => f.name === name); + + if(!fn) { + throw new Error(`Function ${name} not found`); + } + + const htmlFn = new CFunction(fn); + + /*return `
${
+        hljs.highlight(htmlFn.functionCallHTML(args, after), { language: 'c' }).value
+    }
`;*/ + return htmlFn.functionCallHTML(args, after); +} + +function generateExamples(): void { + console.log(generateExample('mjb_codepoint_info', ['0x022A', '&character'])); +} + +generateExamples(); diff --git a/utils/generate/commands/generate-site.ts b/utils/generate/commands/generate-site.ts index ad73ff0..adb5373 100644 --- a/utils/generate/commands/generate-site.ts +++ b/utils/generate/commands/generate-site.ts @@ -10,7 +10,7 @@ import http from 'http'; import markdownit from 'markdown-it'; import { basename, extname, join, relative } from 'path'; import { Section } from '../functions'; -import { cfns } from '../html-function'; +import { cfns, CFunction } from '../html-function'; import { getVersion, substituteBlock, substituteText } from '../utils'; const SOURCE_DIR = '../../src/site'; @@ -72,6 +72,12 @@ const API_SECTIONS = [ title: 'Emoji', description: 'Inspect emoji sequences and properties.' }, + { + section: Section.IDNA, + id: 'idna', + title: 'IDNA', + description: 'Handle Internationalized Domain Names in Applications (IDNA).' + }, { section: Section.DisplayWidth, id: 'display-width', @@ -163,46 +169,23 @@ ${functionsHTML} }).join('\n'); } -function processIndexHtml() { - console.log('Processing index.html...'); - - let fileContent = readFileSync(`${SOURCE_DIR}/index.html`, 'utf-8'); - const functs = getFunctions(); - const functionNames = new Set(functs.map(fn => fn.getName())); - - fileContent = substituteBlock(fileContent, - '', - '\n' + formatAPINavigation(functs) + '\n '); - - fileContent = substituteBlock(fileContent, +function getFooterContent(functs: CFunction[]): string { + let footerContent = readFileSync(`${SOURCE_DIR}/footer.html`, 'utf-8'); + footerContent = substituteBlock(footerContent, 'const functions = {', '};', functs.map(fn => `"${fn.getName()}": ${fn.formatJSON()}`).join(',\n')); - fileContent = substituteBlock(fileContent, - '
', - '
', - '\n' + formatAPISections(functs, functionNames) + '\n '); - - fileContent = substituteBlock(fileContent, + footerContent = substituteBlock(footerContent, "// On click events\n", - " ", + "", functs.filter(fn => fn.isWASM()).map(fn => ' ' + fn.formatEventListener()).join('\n') + "\n" ); - const version = getVersion(); - const fileName = `mojibake-amalgamation-${version.major}${version.minor}${version.revision}.zip`; - const baseURL = `https://github.com/zaerl/mojibake/releases/download/v${version.version}/`; - - fileContent = substituteText(fileContent, '[AM_HREF]', baseURL + fileName); - fileContent = substituteText(fileContent, '[AM_NAME]', fileName); - fileContent = substituteText(fileContent, '[VERSION]', version.version); - - let readme = readFileSync('../../README.md', 'utf-8'); - readme = substituteBlock(readme, 'CONFORMANCE_REQUIREMENTS.md)\n', '## Feature highlights', ''); - readme = substituteBlock(readme, 'CONFORMANCE_REQUIREMENTS.md)\n', '## Feature highlights', ''); + return footerContent; +} +function getHeaderContent(): string { const md = markdownit({ highlight: function (str, lang) { if(lang && hljs.getLanguage(lang)) { @@ -216,8 +199,38 @@ function processIndexHtml() { } }).use(require('markdown-it-footnote')); - const header = md.render(readme.slice(readme.indexOf("You don't need"), readme.indexOf('## Thanks'))); - fileContent = substituteText(fileContent, '[HEADER_HERE]', header); + let readme = readFileSync('../../README.md', 'utf-8'); + readme = substituteBlock(readme, 'CONFORMANCE_REQUIREMENTS.md)\n', '## Feature highlights', ''); + + return md.render(readme.slice(readme.indexOf("You don't need"), readme.indexOf('## Thanks'))); +} + +function processIndexHtml() { + console.log('Processing index.html...'); + + let fileContent = readFileSync(`${SOURCE_DIR}/index.html`, 'utf-8'); + const functs = getFunctions(); + const functionNames = new Set(functs.map(fn => fn.getName())); + + const version = getVersion(); + const fileName = `mojibake-amalgamation-${version.major}${version.minor}${version.revision}.zip`; + const baseURL = `https://github.com/zaerl/mojibake/releases/download/v${version.version}/`; + + const vars = { + 'TITLE_HERE': 'Mojibake — Unicode text processing in C, without the baggage', + 'SIDEBAR_HERE': '', + 'FUNCTIONS_HERE': '\n' + formatAPISections(functs, functionNames) + '\n ', + 'FOOTER_HERE': getFooterContent(functs), + 'AM_HREF': baseURL + fileName, + 'AM_NAME': fileName, + 'VERSION': version.version, + 'HEADER_HERE': getHeaderContent() + } + + for(const [key, value] of Object.entries(vars)) { + fileContent = substituteText(fileContent, `[${key}]`, value); + } writeFileSync(`${BUILD_DIR}/index.html`, fileContent); console.log('index.html processed successfully'); diff --git a/utils/generate/functions.ts b/utils/generate/functions.ts index 925d9ee..cd8daf5 100644 --- a/utils/generate/functions.ts +++ b/utils/generate/functions.ts @@ -35,6 +35,7 @@ export enum Section { Segmentation, Bidirectional, Emoji, + IDNA, DisplayWidth, HangulLanguage, Utility @@ -584,7 +585,7 @@ printf("NFKC casefold payload (no terminator): %.*s", (int)output_size, output); result() ], wasm: true, - section: Section.TextTransformation, + section: Section.IDNA, exampleFeature: 'MJB_FEATURE_IDNA', details: 'Apply UTS #46 nontransitional processing with STD3 ASCII, hyphen, joiner, bidi, ' + 'label-length, and domain-length checks enabled. Non-ASCII labels are encoded with ' + @@ -653,7 +654,7 @@ mjb_result_free(&result);`, } ], wasm: false, - section: Section.TextTransformation, + section: Section.IDNA, details: 'Apply the same strict nontransitional profile as `mjb_idna_to_ascii`. Set `output` ' + 'to NULL to query the required byte count. No bytes are written if capacity is ' + 'insufficient. Processing uses temporary allocations even during a size query. If ' + @@ -698,7 +699,7 @@ mjb_result_free(&result);`, result() ], wasm: true, - section: Section.TextTransformation, + section: Section.IDNA, exampleFeature: 'MJB_FEATURE_IDNA', details: 'Apply UTS #46 nontransitional processing and decode valid `xn--` labels. The ' + 'function returns its best-effort converted string even when `info->errors` records a ' + @@ -766,7 +767,7 @@ mjb_result_free(&result);`, } ], wasm: false, - section: Section.TextTransformation, + section: Section.IDNA, details: 'Apply the same strict nontransitional profile as `mjb_idna_to_unicode`. Set ' + '`output` to NULL to query the required byte count. No bytes are written if capacity is ' + 'insufficient. Processing uses temporary allocations even during a size query. If ' + diff --git a/utils/generate/header.ts b/utils/generate/header.ts index 661c02b..06cf24a 100644 --- a/utils/generate/header.ts +++ b/utils/generate/header.ts @@ -125,7 +125,7 @@ function getBidiMirroringInfo(pairs: BidiMirroringPair[]) { export function generateHeader(blocks: Block[], categories: string[], properties: Property[], bidiBrackets: BidiBracket[], bidiMirroring: BidiMirroringPair[]) { let fileContent = readFileSync('../../src/unicode.h', 'utf-8'); let fileWASMContent = readFileSync('../../src/api/unicode.ts', 'utf-8'); - let fileHTMLContent = readFileSync('../../src/site/index.html', 'utf-8'); + let fileHTMLContent = readFileSync('../../src/site/footer.html', 'utf-8'); // Fill unicode.h mjb_block. fileContent = substituteBlock(fileContent, "typedef enum mjb_block {\n", "\n} mjb_block;", getBlockEnumNames(blocks)); @@ -219,5 +219,5 @@ export function generateHeader(blocks: Block[], categories: string[], properties writeFileSync('../../src/bidi.c', fileContent); writeFileSync('../../src/api/unicode.ts', fileWASMContent); - writeFileSync('../../src/site/index.html', fileHTMLContent); + writeFileSync('../../src/site/footer.html', fileHTMLContent); } diff --git a/utils/generate/html-function.ts b/utils/generate/html-function.ts index 9e7c4e5..5945320 100644 --- a/utils/generate/html-function.ts +++ b/utils/generate/html-function.ts @@ -216,6 +216,36 @@ export class CFunction implements MojibakeFunction { `; } + private functionCall(args: string[], after?: string): string { + const is_status = this.ret === 'mjb_status'; + + const prefix = is_status ? 'if(' : ''; + const suffix = is_status ? ` != MJB_OK) {\n return 1;\n }` : ';'; + + return `#include +#include + +int main(int argc, char *const argv[]) { + ${prefix}${this.getName()}(${args.join(', ')})${suffix} + + return 0; +}`; + } + + public functionCallHTML(args: string[], after?: string): string { + const code = this.functionCall(args, after); + + return `
+
+
${hljs.highlight(code, { language: 'c' }).value}
+
+
+
${this.isWASM() ? this.formInputHTML() : '' }
+
+
+
`; + } + private static escapeHTML(value: string): string { return value.replace(/&/g, '&') .replace(/ Date: Mon, 27 Jul 2026 22:13:04 +0200 Subject: [PATCH 2/2] Add new HTML generator Signed-off-by: Francesco Bigiarini --- API.md | 8 +- Makefile | 4 +- src/site/index.html | 12 +- utils/generate/commands/generate-site.ts | 141 ++++++++++++++++++----- utils/generate/utils.ts | 7 +- 5 files changed, 128 insertions(+), 44 deletions(-) diff --git a/API.md b/API.md index 37b3ae6..163da86 100644 --- a/API.md +++ b/API.md @@ -1,15 +1,15 @@ # API -Welcome to the Unicode world. Before starting, you can find a demo site where you can find the API -documentation and test the functions by using WASM here: -[https://mojibake.zaerl.com](https://mojibake.zaerl.com) +You can find a demo site where you can find the API documentation and test the functions by using +WASM here: [https://mojibake.zaerl.com](https://mojibake.zaerl.com) > [!NOTE] > When we refer to "Unicode" or "Unicode 18" we refer to Unicode 18 **beta** ## Unicode glossary -You will need to have a basic glossary to understand the terminology. +Welcome to the Unicode world. Before starting, you will need to have a basic glossary to understand +the terminology. A **Code Point** (`typedef uint32_t mjb_codepoint` in the code) in Unicode is an integer from 0 to 0x10FFFF. It's used to identify a **Character** in the list. For example `U+20A0`, the € currency. diff --git a/Makefile b/Makefile index acbd957..f113ff8 100644 --- a/Makefile +++ b/Makefile @@ -136,7 +136,7 @@ generate-locale: generate-unicode-tables: @cd ./utils/generate && npm run generate -- unicode-tables -generate-site: src/site/index.html +generate-site: src/site/index.html src/site/footer.html @cd ./utils/generate && npm run generate-site sync-api-wasm: build-wasm @@ -147,7 +147,7 @@ build-api: sync-api-wasm @cd ./src/api && npm run build # Watch site files -watch-site: build-api src/site/index.html +watch-site: build-api src/site/index.html src/site/footer.html @cd ./utils/generate && npm run watch-site # Watch API files diff --git a/src/site/index.html b/src/site/index.html index ee0404c..f78ea98 100644 --- a/src/site/index.html +++ b/src/site/index.html @@ -5,8 +5,7 @@ - + [TITLE_HERE] @@ -26,7 +25,8 @@ v[VERSION]