import hl from "highlight.js/lib/core"; import hl_typescript from "highlight.js/lib/languages/typescript"; import {marked} from "marked"; import fs from "node:fs"; import {parseArgs} from "node:util"; hl.registerLanguage("typescript", hl_typescript); let {positionals, values} = parseArgs({ allowPositionals: true, options: { component: { type: "string", multiple: true, }, system: { type: "string", multiple: true, }, library: { type: "string", multiple: true, }, utility: { type: "string", multiple: true, }, }, }); let source_ts = positionals.shift(); let content = fs.readFileSync(source_ts); let lines = content.toString().split("\n"); let source_gh = "https://github.com/piesku/goodluck/blob/main/" + source_ts.slice(3); class Section { constructor(docs, code) { this.docs = docs; this.code = code; } } class Line { constructor(lineno, text) { this.lineno = lineno; this.value = text; } } let sections = []; let in_comment = false; let in_indent = false; let code = []; let docs = []; let lineno = 0; for (let line of lines) { lineno++; if (line.startsWith("/**")) { in_comment = true; } else if (in_comment && line.startsWith(" *")) { let lineobj = new Line(lineno, line.slice(3)); docs.push(lineobj); } else if (in_comment && line.startsWith("*/")) { in_comment = false; } else if (line === "" && !in_indent) { let section = new Section(docs, code); sections.push(section); code = []; docs = []; } else { in_comment = false; if (/^\s/.test(line)) { in_indent = true; } else { in_indent = false; } let lineobj = new Line(lineno, line); code.push(lineobj); } } //console.log(JSON.stringify(sections, null, 4)); marked.setOptions({ renderer: new marked.Renderer(), highlight: function (code, lang) { const language = hl.getLanguage(lang) ? lang : "typescript"; return hl.highlight(code, {language}).value; }, langPrefix: "hljs language-", // highlight.js css expects a top-level 'hljs' class. pedantic: false, gfm: true, breaks: false, sanitize: false, smartLists: true, smartypants: false, xhtml: false, }); marked.use({ extensions: [ { name: "definition_list", level: "block", start(src) { // Hint to Marked.js to stop and check for a match return src.match(/@(param|returns)/)?.index; }, tokenizer(src, tokens) { if (src.startsWith("@param") || src.startsWith("@returns")) { let token = { // Token to generate type: "definition_list", // Should match "name" above raw: src, // Text to consume from the source tokens: [], // Array where child inline tokens will be generated }; // Queue this data to be processed for inline tokens this.lexer.inline(src.trim(), token.tokens); return token; } }, renderer(token) { return `
${this.parser.parseInline(token.tokens)}\n
`; }, }, { name: "definition_item", level: "inline", start(src) { // Hint to Marked.js to stop and check for a match return src.match(/@(param|returns)/)?.index; }, tokenizer(src, tokens) { // Regex for the complete token, anchored to string start let rule = /^@(?:(param) ([^ ]*)(?: -)?|(returns)) ([^]*?)(?:\n(?=@)|$)/; let match = rule.exec(src); if (match) { return { // Token to generate type: "definition_item", // Should match "name" above raw: match[0], // Text to consume from the source tag: match[1] || match[3], // The tag: param or returns dt: match[2], dd: this.lexer.inlineTokens(match[4].trim()), }; } }, renderer(token) { return `
${token.dt ? `${token.dt}` : ""} ${token.tag}
${this.parser.parseInline(token.dd)}
`; }, // Child tokens to be visited by walkTokens childTokens: ["dt", "dd"], }, ], }); function render_docs(section) { let content = section.docs.map((line) => line.value).join("\n"); if (content.length > 0) { return `
${marked(content)}
`; } return ""; } function render_code(section) { let content = section.code.map((line) => line.value).join("\n"); if (content.length > 0) { return `
${hl.highlight(content, {language: "typescript"}).value}
`; } return ""; } function render_link(filename_html) { let filename_ts = filename_html.replace(".html", ".ts"); let nice_name = filename_html.replace(/^(com|lib)_/, "").replace(/.html$/, ""); if (source_ts.includes(filename_ts)) { return nice_name; } return `${nice_name}`; } let first_section = sections[0]; if (first_section.code.length === 0) { // The first section is module-wide docs. sections.shift(); } else { first_section = null; } console.log(`
Goodluck / API Reference
${first_section ? render_docs(first_section) : ""}
`);