copy over old code

This commit is contained in:
James Brumond 2023-05-13 19:26:25 -07:00
parent b2fb73fd2a
commit e243b0dcd9
Signed by: james
GPG Key ID: E8F2FC44BAA3357A
7 changed files with 1061 additions and 463 deletions

38
package-lock.json generated
View File

@ -9,14 +9,17 @@
"version": "0.1.0",
"dependencies": {
"glob": "^10.2.2",
"json-schema": "^0.4.0",
"luxon": "^3.3.0",
"mustache": "^4.2.0",
"word-wrap": "^1.2.3",
"yaml": "^2.2.2"
},
"bin": {
"docs2website": "bin/docs2website"
},
"devDependencies": {
"@types/json-schema": "^7.0.11",
"@types/luxon": "^3.1.0",
"@types/mustache": "^4.2.2",
"@types/node": "^18.16.3",
@ -48,6 +51,12 @@
"node": ">=14"
}
},
"node_modules/@types/json-schema": {
"version": "7.0.11",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
"integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
"dev": true
},
"node_modules/@types/luxon": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.3.0.tgz",
@ -206,6 +215,11 @@
"@pkgjs/parseargs": "^0.11.0"
}
},
"node_modules/json-schema": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz",
"integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="
},
"node_modules/lru-cache": {
"version": "9.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-9.1.1.tgz",
@ -420,6 +434,14 @@
"node": ">= 8"
}
},
"node_modules/word-wrap": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
"integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/wrap-ansi": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
@ -533,6 +555,12 @@
"integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
"optional": true
},
"@types/json-schema": {
"version": "7.0.11",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
"integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
"dev": true
},
"@types/luxon": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.3.0.tgz",
@ -647,6 +675,11 @@
"@pkgjs/parseargs": "^0.11.0"
}
},
"json-schema": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz",
"integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="
},
"lru-cache": {
"version": "9.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-9.1.1.tgz",
@ -784,6 +817,11 @@
"isexe": "^2.0.0"
}
},
"word-wrap": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
"integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ=="
},
"wrap-ansi": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",

View File

@ -13,6 +13,7 @@
},
"main": "./build/index.js",
"devDependencies": {
"@types/json-schema": "^7.0.11",
"@types/luxon": "^3.1.0",
"@types/mustache": "^4.2.2",
"@types/node": "^18.16.3",
@ -20,8 +21,10 @@
},
"dependencies": {
"glob": "^10.2.2",
"json-schema": "^0.4.0",
"luxon": "^3.3.0",
"mustache": "^4.2.0",
"word-wrap": "^1.2.3",
"yaml": "^2.2.2"
}
}

79
src/detect-version.ts Normal file
View File

@ -0,0 +1,79 @@
import { JSONSchema4, JSONSchema6, JSONSchema7 } from 'json-schema';
const v4_schemas = new Set([
'http://json-schema.org/schema#',
'https://json-schema.org/schema#',
'http://json-schema.org/schema',
'https://json-schema.org/schema',
'http://json-schema.org/hyper-schema#',
'https://json-schema.org/hyper-schema#',
'http://json-schema.org/hyper-schema',
'https://json-schema.org/hyper-schema',
'http://json-schema.org/draft-04/schema#',
'https://json-schema.org/draft-04/schema#',
'http://json-schema.org/draft-04/schema',
'https://json-schema.org/draft-04/schema',
'http://json-schema.org/draft-04/hyper-schema#',
'https://json-schema.org/draft-04/hyper-schema#',
'http://json-schema.org/draft-04/hyper-schema',
'https://json-schema.org/draft-04/hyper-schema',
'http://json-schema.org/draft-03/schema#',
'https://json-schema.org/draft-03/schema#',
'http://json-schema.org/draft-03/schema',
'https://json-schema.org/draft-03/schema',
'http://json-schema.org/draft-03/hyper-schema#',
'https://json-schema.org/draft-03/hyper-schema#',
'http://json-schema.org/draft-03/hyper-schema',
'https://json-schema.org/draft-03/hyper-schema',
])
export function is_json_schema_draft4(data: unknown) : data is JSONSchema4 {
return v4_schemas.has(data?.['$schema']);
}
const v6_schemas = new Set([
'http://json-schema.org/schema#',
'https://json-schema.org/schema#',
'http://json-schema.org/schema',
'https://json-schema.org/schema',
'http://json-schema.org/hyper-schema#',
'https://json-schema.org/hyper-schema#',
'http://json-schema.org/hyper-schema',
'https://json-schema.org/hyper-schema',
'http://json-schema.org/draft-06/schema#',
'https://json-schema.org/draft-06/schema#',
'http://json-schema.org/draft-06/schema',
'https://json-schema.org/draft-06/schema',
'http://json-schema.org/draft-06/hyper-schema#',
'https://json-schema.org/draft-06/hyper-schema#',
'http://json-schema.org/draft-06/hyper-schema',
'https://json-schema.org/draft-06/hyper-schema',
]);
export function is_json_schema_draft6(data: unknown) : data is JSONSchema6 {
return v6_schemas.has(data?.['$schema']);
}
const v7_schemas = new Set([
'http://json-schema.org/schema#',
'https://json-schema.org/schema#',
'http://json-schema.org/schema',
'https://json-schema.org/schema',
'http://json-schema.org/hyper-schema#',
'https://json-schema.org/hyper-schema#',
'http://json-schema.org/hyper-schema',
'https://json-schema.org/hyper-schema',
'http://json-schema.org/draft-07/schema#',
'https://json-schema.org/draft-07/schema#',
'http://json-schema.org/draft-07/schema',
'https://json-schema.org/draft-07/schema',
'http://json-schema.org/draft-07/hyper-schema#',
'https://json-schema.org/draft-07/hyper-schema#',
'http://json-schema.org/draft-07/hyper-schema',
'https://json-schema.org/draft-07/hyper-schema',
]);
export function is_json_schema_draft7(data: unknown) : data is JSONSchema7 {
return v7_schemas.has(data?.['$schema']);
}

File diff suppressed because it is too large Load Diff

110
src/json-pointer.ts Normal file
View File

@ -0,0 +1,110 @@
// see: https://www.rfc-editor.org/rfc/rfc6901#section-3
export function escape_for_json_pointer(str: string, escape_for_markdown = true) {
return str
.replace(/~/g, escape_for_markdown ? '\\~0' : '~0')
.replace(/\//g, escape_for_markdown ? '\\~1' : '~1');
}
export function unescape_for_json_pointer(str: string) {
return str.replace(/~1/g, '/').replace(/~0/g, '~');
}
export interface ResolvedPointer {
value: any;
found: boolean;
stack: {
parent: any;
field: string;
}[];
}
export function resolve_json_pointer(root: object, json_pointer: string) : ResolvedPointer {
if (json_pointer === '' || json_pointer === '/') {
return {
value: root,
found: true,
stack: [ ],
};
}
if (! json_pointer.startsWith('/')) {
throw new Error('invalid JSON pointer');
}
if (json_pointer.includes('//')) {
throw new Error('invalid JSON pointer');
}
const fields = json_pointer.split('/').slice(1);
if (json_pointer.endsWith('/')) {
fields.pop();
}
const resolved: ResolvedPointer = {
value: null,
found: true,
stack: [ ],
};
let current = root;
for (let field of fields) {
field = unescape_for_json_pointer(field);
resolved.stack.push({ parent: current, field });
if (! (field in current)) {
resolved.found = false;
break;
}
current = current[field];
}
if (resolved.found) {
resolved.value = current;
}
return resolved;
}
export function jsonptr(...steps: string[]) {
return new JsonPointer(steps);
}
export function jsonptr_str(...steps: string[]) {
return (new JsonPointer(steps)).as_str();
}
export function jsonptr_md_str(...steps: string[]) {
return (new JsonPointer(steps)).as_md_str();
}
export class JsonPointer {
public steps: string[];
public md_steps: string[];
private _str: string;
private _md_str: string;
constructor(steps: string[] = [ ]) {
this.steps = steps.map((step) => escape_for_json_pointer(step, false));
this.md_steps = steps.map((step) => escape_for_json_pointer(step, true));
}
public as_str() {
return this._str = (this._str || '/' + this.steps.join('/'));
}
public as_md_str() {
return this._md_str = (this._md_str || '/' + this.md_steps.join('/'));
}
public step_down(next: string) {
const new_pointer = new JsonPointer();
new_pointer.steps.push(...this.steps, escape_for_json_pointer(next, false));
new_pointer.md_steps.push(...this.md_steps, escape_for_json_pointer(next, true));
return new_pointer;
}
}

317
src/markdown-builder.ts Normal file
View File

@ -0,0 +1,317 @@
import { JsonPointer } from './json-pointer';
import { SectionIds } from './section-ids';
export interface MarkdownBuilderOptions {
numbered_headings?: boolean | {
prefix?: string;
};
}
export type Content = string | (() => string);
export type HeadingDepth = 1 | 2 | 3 | 4 | 5 | 6;
const max_section_depth = 5;
export interface TableOfContentsItem {
id: string | JsonPointer;
label: string;
depth: HeadingDepth;
}
export interface AttrsOptions {
id?: string | JsonPointer;
classes?: string[];
attrs?: Record<string, string | JsonPointer>;
}
export interface HeadingOptions extends AttrsOptions {
/** If set to true, this heading will not show up in the table of contents */
no_table_of_contents?: boolean;
}
export interface LinkOptions extends AttrsOptions {
text?: string;
}
export interface CodeBlockOptions {
lang?: string;
caption?: string;
extra_depth?: number;
}
export class MarkdownBuilder {
private ids?: SectionIds;
private contents: Content[] = [ ];
private toc_items: TableOfContentsItem[] = [ ];
private remaining_section_depth = max_section_depth;
constructor(
private readonly options: MarkdownBuilderOptions = { }
) {
if (options.numbered_headings) {
if (typeof options.numbered_headings === 'object') {
this.ids = new SectionIds(options.numbered_headings.prefix || 'section-');
}
}
}
public as_str() {
return this.contents.map((content) => {
return typeof content === 'function' ? content() : content;
}).join('');
}
// =====
public raw(raw: string) {
this.contents.push(raw);
}
public text(text: string) {
// todo: escape text
this.contents.push(text);
}
// ===== Block =====
public p(text: string) {
return this.raw(text + '\n\n');
}
public h1(text: string, opts: HeadingOptions = { }) {
return this.heading(1, text, opts);
}
public h2(text: string, opts: HeadingOptions = { }) {
return this.heading(2, text, opts);
}
public h3(text: string, opts: HeadingOptions = { }) {
return this.heading(3, text, opts);
}
public h4(text: string, opts: HeadingOptions = { }) {
return this.heading(4, text, opts);
}
public h5(text: string, opts: HeadingOptions = { }) {
return this.heading(5, text, opts);
}
public h6(text: string, opts: HeadingOptions = { }) {
return this.heading(6, text, opts);
}
public heading(level: 1 | 2 | 3 | 4 | 5 | 6, text: string, { id, classes, no_table_of_contents }: HeadingOptions) {
let label = text;
if (! id && this.ids) {
let sec: string;
({ id, sec } = this.ids.next(level, text));
label = `${sec}. ${label}`;
}
let rendered = `${'#'.repeat(level)} ${text}${this.attrs_md({ id, classes })}\n\n`;
if (! no_table_of_contents) {
this.toc_items.push({
id,
depth: level,
label: text,
});
}
this.raw(rendered);
}
public hr() {
this.raw('---\n\n');
}
// ===== Open Blocks (closed separately) =====
public section(opts: AttrsOptions = { }) {
const depth = this.remaining_section_depth--;
if (! depth) {
this.remaining_section_depth++;
throw new Error(`max section depth (${max_section_depth}) exceeded`);
}
const fence = '!'.repeat(depth + 5);
const attrs = this.attrs_md(opts);
this.raw(`${fence}${attrs}\n`);
let closed = false;
return () => {
if (closed) {
throw new Error('attempted to close section twice');
}
closed = true;
this.raw(fence + '\n\n');
this.remaining_section_depth++;
};
}
public code_block(opts: CodeBlockOptions = { }) {
const fence = '`'.repeat(3 + (opts.extra_depth || 0));
const caption = opts.caption ? ' ' + JSON.stringify(opts.caption) : '';
this.raw(`${fence}${opts.lang || 'plain'}${caption}\n`);
let closed = false;
return () => {
if (closed) {
throw new Error('attempted to close code block twice');
}
closed = true;
this.raw(fence + '\n\n');
};
}
// ===== Inline =====
public code(text: string) {
return `\`${text.replace(/`/g, '\\`')}\``;
}
public math(text: string) {
return `$${text}$`;
}
public link(url: string, opts: LinkOptions = { }) {
if (! opts.text) {
return `<${url}>`;
}
const attrs = this.attrs_md(opts);
if (! attrs) {
return `[${opts.text}](${url})`;
}
// todo: more links
}
public em(text: string) {
return `_${text.replace(/_/g, '\\_')}_`;
}
// ===== Complex =====
public cite() {
// todo: citations/footnotes
}
public table_of_contents() {
return () => {
return this.toc_items.map(({ id, depth, label }) => {
return ' '.repeat(depth - 1) + `[${label}](#${id})`;
}).join('\n') + '\n';
};
}
public dl(opts: AttrsOptions = { }) {
const attrs = this.attrs_html(opts);
this.raw(`<dl${attrs}>\n`);
let closed = false;
return () => {
if (closed) {
throw new Error('attempted to close <dl> twice');
}
closed = true;
this.raw('</dl>\n\n');
};
}
public dt(text: string, opts: AttrsOptions = { }) {
const attrs = this.attrs_html(opts);
this.raw(`<dt${attrs}>${text}</dt>`);
}
public dd(text: string, opts: AttrsOptions = { }) {
const attrs = this.attrs_html(opts);
this.raw(`<dd${attrs}>${text}</dd>\n`);
}
// =====
private attrs_md(raw_attrs: AttrsOptions) {
const attrs: string[] = [ ];
if (raw_attrs.id) {
attrs.push('#' + md_str(raw_attrs.id));
}
if (raw_attrs.classes) {
for (const classname of raw_attrs.classes) {
attrs.push('.' + classname);
}
}
if (raw_attrs.attrs) {
for (const [name, value] of Object.entries(raw_attrs.attrs)) {
attrs.push(`:${name}=${value}`);
}
}
return attrs.length ? ` {${attrs.join(' ')}}` : '';
}
private attrs_html(raw_attrs: AttrsOptions) {
const attrs: string[] = [ ];
if (raw_attrs.id) {
attrs.push(`id="${str(raw_attrs.id)}"`);
}
if (raw_attrs.classes) {
attrs.push(`class="${raw_attrs.classes.join(' ')}"`);
}
if (raw_attrs.attrs) {
for (const [name, value] of Object.entries(raw_attrs.attrs)) {
attrs.push(`${name}="${value}"`);
}
}
return attrs.length ? ` ${attrs.join(' ')}` : '';
}
}
function md_str(str: string | JsonPointer) : string {
if (str instanceof JsonPointer) {
return str.as_md_str();
}
return str;
}
function str(str: string | JsonPointer) : string {
if (str instanceof JsonPointer) {
return str.as_str();
}
return str;
}

25
src/section-ids.ts Normal file
View File

@ -0,0 +1,25 @@
export const char_section = '§';
export class SectionIds {
public current: number[] = [ ];
public contents: { id: string, label: string }[] = [ ];
constructor(
public prefix = ''
) { }
public next(depth: number, label: string) {
while (depth > this.current.length) {
this.current.push(0);
}
this.current.length = depth;
this.current[depth - 1]++;
const sec = this.current.join('.');
const id = this.prefix + sec;
this.contents.push({ id, label });
return { id, sec };
}
}