copy over old code
This commit is contained in:
parent
b2fb73fd2a
commit
e243b0dcd9
38
package-lock.json
generated
38
package-lock.json
generated
@ -9,14 +9,17 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"glob": "^10.2.2",
|
"glob": "^10.2.2",
|
||||||
|
"json-schema": "^0.4.0",
|
||||||
"luxon": "^3.3.0",
|
"luxon": "^3.3.0",
|
||||||
"mustache": "^4.2.0",
|
"mustache": "^4.2.0",
|
||||||
|
"word-wrap": "^1.2.3",
|
||||||
"yaml": "^2.2.2"
|
"yaml": "^2.2.2"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"docs2website": "bin/docs2website"
|
"docs2website": "bin/docs2website"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/json-schema": "^7.0.11",
|
||||||
"@types/luxon": "^3.1.0",
|
"@types/luxon": "^3.1.0",
|
||||||
"@types/mustache": "^4.2.2",
|
"@types/mustache": "^4.2.2",
|
||||||
"@types/node": "^18.16.3",
|
"@types/node": "^18.16.3",
|
||||||
@ -48,6 +51,12 @@
|
|||||||
"node": ">=14"
|
"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": {
|
"node_modules/@types/luxon": {
|
||||||
"version": "3.3.0",
|
"version": "3.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.3.0.tgz",
|
||||||
@ -206,6 +215,11 @@
|
|||||||
"@pkgjs/parseargs": "^0.11.0"
|
"@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": {
|
"node_modules/lru-cache": {
|
||||||
"version": "9.1.1",
|
"version": "9.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-9.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-9.1.1.tgz",
|
||||||
@ -420,6 +434,14 @@
|
|||||||
"node": ">= 8"
|
"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": {
|
"node_modules/wrap-ansi": {
|
||||||
"version": "8.1.0",
|
"version": "8.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
|
||||||
@ -533,6 +555,12 @@
|
|||||||
"integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
|
"integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
|
||||||
"optional": true
|
"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": {
|
"@types/luxon": {
|
||||||
"version": "3.3.0",
|
"version": "3.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.3.0.tgz",
|
||||||
@ -647,6 +675,11 @@
|
|||||||
"@pkgjs/parseargs": "^0.11.0"
|
"@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": {
|
"lru-cache": {
|
||||||
"version": "9.1.1",
|
"version": "9.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-9.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-9.1.1.tgz",
|
||||||
@ -784,6 +817,11 @@
|
|||||||
"isexe": "^2.0.0"
|
"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": {
|
"wrap-ansi": {
|
||||||
"version": "8.1.0",
|
"version": "8.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
},
|
},
|
||||||
"main": "./build/index.js",
|
"main": "./build/index.js",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/json-schema": "^7.0.11",
|
||||||
"@types/luxon": "^3.1.0",
|
"@types/luxon": "^3.1.0",
|
||||||
"@types/mustache": "^4.2.2",
|
"@types/mustache": "^4.2.2",
|
||||||
"@types/node": "^18.16.3",
|
"@types/node": "^18.16.3",
|
||||||
@ -20,8 +21,10 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"glob": "^10.2.2",
|
"glob": "^10.2.2",
|
||||||
|
"json-schema": "^0.4.0",
|
||||||
"luxon": "^3.3.0",
|
"luxon": "^3.3.0",
|
||||||
"mustache": "^4.2.0",
|
"mustache": "^4.2.0",
|
||||||
|
"word-wrap": "^1.2.3",
|
||||||
"yaml": "^2.2.2"
|
"yaml": "^2.2.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
79
src/detect-version.ts
Normal file
79
src/detect-version.ts
Normal 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']);
|
||||||
|
}
|
1132
src/index.ts
1132
src/index.ts
File diff suppressed because it is too large
Load Diff
110
src/json-pointer.ts
Normal file
110
src/json-pointer.ts
Normal 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
317
src/markdown-builder.ts
Normal 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
25
src/section-ids.ts
Normal 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 };
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user