From 6f5c34a611919e702f3f0bc2a2c9c1118a391052 Mon Sep 17 00:00:00 2001 From: RedYetiDev Date: Mon, 14 Oct 2024 12:39:44 -0400 Subject: [PATCH] feat: add mandoc generator --- .github/workflows/codespell.yml | 2 +- README.md | 5 +- src/generators/index.mjs | 2 + src/generators/mandoc/index.mjs | 59 ++++++++++++++++++++ src/generators/mandoc/template.1 | 65 +++++++++++++++++++++++ src/generators/mandoc/utils/converter.mjs | 63 ++++++++++++++++++++++ 6 files changed, 192 insertions(+), 4 deletions(-) create mode 100644 src/generators/mandoc/index.mjs create mode 100644 src/generators/mandoc/template.1 create mode 100644 src/generators/mandoc/utils/converter.mjs diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml index d6188e3..8345185 100644 --- a/.github/workflows/codespell.yml +++ b/.github/workflows/codespell.yml @@ -18,4 +18,4 @@ jobs: with: ignore_words_list: crate,raison exclude_file: .gitignore - skip: package-lock.json + skip: package-lock.json,./src/generators/mandoc/template.1 diff --git a/README.md b/README.md index 7b45655..60d9ce7 100644 --- a/README.md +++ b/README.md @@ -37,10 +37,9 @@ CLI tool to generate API documentation of a Node.js project. Options: -i, --input [patterns...] Specify input file patterns using glob syntax -o, --output Specify the relative or absolute output directory - -v, --version Specify the target version of Node.js, semver compliant (default: "v22.6.0") + -v, --version Specify the target version of Node.js, semver compliant (default: "v22.9.0") -c, --changelog Specify the path (file: or https://) to the CHANGELOG.md file (default: "https://raw.githubusercontent.com/nodejs/node/HEAD/CHANGELOG.md") - -t, --target [mode...] Set the processing target modes (choices: "json-simple", "legacy-html", - "legacy-html-all") + -t, --target [mode...] Set the processing target modes (choices: "json-simple", "legacy-html", "legacy-html-all", "mandoc") -h, --help display help for command ``` diff --git a/src/generators/index.mjs b/src/generators/index.mjs index 5b916d8..c6131e3 100644 --- a/src/generators/index.mjs +++ b/src/generators/index.mjs @@ -3,9 +3,11 @@ import jsonSimple from './json-simple/index.mjs'; import legacyHtml from './legacy-html/index.mjs'; import legacyHtmlAll from './legacy-html-all/index.mjs'; +import mandoc from './mandoc/index.mjs'; export default { 'json-simple': jsonSimple, 'legacy-html': legacyHtml, 'legacy-html-all': legacyHtmlAll, + mandoc: mandoc, }; diff --git a/src/generators/mandoc/index.mjs b/src/generators/mandoc/index.mjs new file mode 100644 index 0000000..981cc3b --- /dev/null +++ b/src/generators/mandoc/index.mjs @@ -0,0 +1,59 @@ +'use strict'; + +import { optionToMandoc, envToMandoc } from './utils/converter.mjs'; +import { writeFile, readFile } from 'node:fs/promises'; +import { join } from 'node:path'; + +/** + * This generator generates a mandoc version of the API docs + * + * @typedef {Array} Input + * + * @type {import('../types.d.ts').GeneratorMetadata} + */ +export default { + name: 'mandoc', + + version: '1.0.0', + + description: 'Generates the `node.1` file.', + + dependsOn: 'ast', + + async generate(input, options) { + // Find the appropriate headers + const optionsStart = input.findIndex(({ slug }) => slug === 'options'); + const environmentStart = input.findIndex( + ({ slug }) => slug === 'environment-variables-1' + ); + + // Generate the option mandoc + let optionsOutput = ''; + for (let i = optionsStart + 1; i < environmentStart; i++) { + const el = input[i]; + if (el.heading?.depth === 3) { + optionsOutput += optionToMandoc(el); + } + } + + // Generate the environment mandoc + let envOutput = ''; + for (let i = environmentStart + 1; i < input.length; i++) { + const el = input[i]; + if (el.heading?.depth === 3) { + envOutput += envToMandoc(el); + } + if (el.heading?.depth < 3) break; + } + + const apiTemplate = await readFile( + join(import.meta.dirname, 'template.1'), + 'utf-8' + ); + const template = apiTemplate + .replace('__OPTIONS__', optionsOutput) + .replace('__ENVIRONMENT__', envOutput); + + return await writeFile(options.output, template); + }, +}; diff --git a/src/generators/mandoc/template.1 b/src/generators/mandoc/template.1 new file mode 100644 index 0000000..fef94d3 --- /dev/null +++ b/src/generators/mandoc/template.1 @@ -0,0 +1,65 @@ +.Dd $Mdocdate$ +.Dt NODE 1 +. +.Sh NAME +.Nm node +.Nd server-side JavaScript runtime +. +.Sh SYNOPSIS +.Nm node +.Op Ar options +.Op Ar v8 options +.Op Ar | Fl e Ar string | Fl - +.Op Ar arguments ... +. +.Nm node +.Cm inspect, +.Op Ar | Fl e Ar string | Ar : +.Ar ... +. +.Nm node +.Op Fl -v8-options +. +.Sh DESCRIPTION +Node.js is a set of libraries for JavaScript which allows it to be used outside of the browser. +It is primarily focused on creating simple, easy-to-build network clients and servers. +.Pp +Execute +.Nm +without arguments to start a REPL. +. +.Sh OPTIONS +.Bl -tag -width 6n +__OPTIONS__ +.El +. +.Sh ENVIRONMENT +.Bl -tag -width 6n +__ENVIRONMENT__ +.El +. +.Sh BUGS +Bugs are tracked in GitHub Issues: +.Sy https://github.com/nodejs/node/issues +. +.Sh COPYRIGHT +Copyright Node.js contributors. +Node.js is available under the MIT license. +. +.Pp +Node.js also includes external libraries that are available under a variety of licenses. +See +.Sy https://github.com/nodejs/node/blob/HEAD/LICENSE +for the full license text. +. +.Sh SEE ALSO +Website: +.Sy https://nodejs.org/ +. +.Pp +Documentation: +.Sy https://nodejs.org/api/ +. +.Pp +GitHub repository and issue tracker: +.Sy https://github.com/nodejs/node \ No newline at end of file diff --git a/src/generators/mandoc/utils/converter.mjs b/src/generators/mandoc/utils/converter.mjs new file mode 100644 index 0000000..97beeed --- /dev/null +++ b/src/generators/mandoc/utils/converter.mjs @@ -0,0 +1,63 @@ +function toMandoc(node, opts) { + const mapChildren = () => node.children.map(toMandoc).join(''); + const value = () => node.value.replace(/\\/g, '\\\\'); + + switch (node.type) { + case 'root': + return node.children.map(toMandoc).join('\n'); + case 'heading': + return `.Sh ${mapChildren()}`; + case 'link': + case 'paragraph': + case 'listItem': + return `${opts.fromListParent ? '.It\n' : ''}${mapChildren()}`; + case 'text': + return value(); + case 'inlineCode': + return `\\fB${value()}\\fR`; // Bold formatting + case 'blockquote': + return ''; // Ignore stability + case 'code': + return `.Bd -literal\n${value()}\n.Ed`; + case 'list': + return `.Bl -bullet\n${node.children.map(child => toMandoc(child, { fromListParent: true })).join('\n')}\n.El\n.\n`; + case 'emphasis': + return `\\fI${mapChildren()}\\fR`; // Italic formatting + default: + return ''; + } +} + +function flagToMandoc(flag) { + const [, value] = flag.split(/[= ]/); + const separator = flag.match(/[= ]/)?.[0]; + return separator === ' ' || !value + ? '' + : ` Ns ${separator} Ns Ar ${value.replace(/\]$/, '')}`; +} + +export function optionToMandoc(element) { + const formatFlag = flag => { + const name = flag.split(/\[?[= ]/)[0].slice(1); + return `Fl ${name}${flagToMandoc(flag)}`; + }; + + const formattedFlags = element.heading.data.text + .replace(/`/g, '') + .split(', ') + .map(formatFlag) + .join(' , '); + + element.content.children.shift(); // Remove the first child + return `.It ${formattedFlags.trim()}\n${toMandoc(element.content)}\n.\n`; +} + +export function envToMandoc(element) { + const [varName, varValue] = element.heading.data.text + .replace(/`/g, '') + .split('='); + const value = varValue ? ` Ar ${varValue}` : ''; + + element.content.children.shift(); // Remove the first child + return `.It Ev ${varName}${value}\n${toMandoc(element.content)}\n.\n`; +}