Skip to content

Commit 4dbfd05

Browse files
authored
feat: add man-page generator (#125)
* feat: add mandoc generator * fixup! Resolve suggestions + Fixup trimming
1 parent f9dfcd5 commit 4dbfd05

File tree

7 files changed

+487
-5
lines changed

7 files changed

+487
-5
lines changed

.github/workflows/codespell.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,4 @@ jobs:
1818
with:
1919
ignore_words_list: crate,raison
2020
exclude_file: .gitignore
21-
skip: package-lock.json
21+
skip: package-lock.json, ./src/generators/mandoc/template.1

README.md

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,7 @@ Options:
3838
-i, --input [patterns...] Specify input file patterns using glob syntax
3939
-o, --output <path> Specify the relative or absolute output directory
4040
-v, --version <semver> Specify the target version of Node.js, semver compliant (default: "v22.6.0")
41-
-c, --changelog <url> Specify the path (file: or https://) to the CHANGELOG.md file (default:
42-
"https://raw.githubusercontent.com/nodejs/node/HEAD/CHANGELOG.md")
43-
-t, --target [mode...] Set the processing target modes (choices: "json-simple", "legacy-html",
44-
"legacy-html-all")
41+
-c, --changelog <url> Specify the path (file: or https://) to the CHANGELOG.md file (default: "https://raw.githubusercontent.com/nodejs/node/HEAD/CHANGELOG.md")
42+
-t, --target [mode...] Set the processing target modes (choices: "json-simple", "legacy-html", "legacy-html-all", "man-page")
4543
-h, --help display help for command
4644
```

src/generators/index.mjs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
import jsonSimple from './json-simple/index.mjs';
44
import legacyHtml from './legacy-html/index.mjs';
55
import legacyHtmlAll from './legacy-html-all/index.mjs';
6+
import manPage from './man-page/index.mjs';
67

78
export default {
89
'json-simple': jsonSimple,
910
'legacy-html': legacyHtml,
1011
'legacy-html-all': legacyHtmlAll,
12+
'man-page': manPage,
1113
};

src/generators/man-page/index.mjs

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
'use strict';
2+
3+
import { writeFile, readFile } from 'node:fs/promises';
4+
import { join } from 'node:path';
5+
6+
import {
7+
convertOptionToMandoc,
8+
convertEnvVarToMandoc,
9+
} from './utils/converter.mjs';
10+
11+
/**
12+
* This generator generates a man page version of the CLI.md file.
13+
* See https://man.openbsd.org/mdoc.7 for the formatting.
14+
*
15+
* @typedef {Array<ApiDocMetadataEntry>} Input
16+
*
17+
* @type {import('../types.d.ts').GeneratorMetadata<Input, string>}
18+
*/
19+
export default {
20+
name: 'man-page',
21+
22+
version: '1.0.0',
23+
24+
description: 'Generates the Node.js man-page.',
25+
26+
dependsOn: 'ast',
27+
28+
async generate(input, options) {
29+
// Filter to only 'cli'.
30+
const components = input.filter(({ api }) => api === 'cli');
31+
if (!components.length) {
32+
throw new Error('CLI.md not found');
33+
}
34+
35+
// Find the appropriate headers
36+
const optionsStart = components.findIndex(({ slug }) => slug === 'options');
37+
const environmentStart = components.findIndex(
38+
({ slug }) => slug === 'environment-variables-1'
39+
);
40+
// The first header that is <3 in depth after environmentStart
41+
const environmentEnd = components.findIndex(
42+
({ heading }, index) => heading.depth < 3 && index > environmentStart
43+
);
44+
45+
const output = {
46+
// Extract the CLI options.
47+
options: extractMandoc(
48+
components,
49+
optionsStart + 1,
50+
environmentStart,
51+
convertOptionToMandoc
52+
),
53+
// Extract the environment variables.
54+
env: extractMandoc(
55+
components,
56+
environmentStart + 1,
57+
environmentEnd,
58+
convertEnvVarToMandoc
59+
),
60+
};
61+
62+
const template = await readFile(
63+
join(import.meta.dirname, 'template.1'),
64+
'utf-8'
65+
);
66+
67+
const filledTemplate = template
68+
.replace('__OPTIONS__', output.options)
69+
.replace('__ENVIRONMENT__', output.env);
70+
71+
await writeFile(options.output, filledTemplate);
72+
},
73+
};
74+
75+
function extractMandoc(components, start, end, convert) {
76+
return components
77+
.slice(start, end)
78+
.filter(({ heading }) => heading.depth === 3)
79+
.map(convert)
80+
.join('');
81+
}

src/generators/man-page/template.1

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
.\"
2+
.\" This file is automatically generated by api-docs-tooling.
3+
.\" Do not edit this file directly. Please make changes to CLI.md
4+
.\" and then regenerate this file.
5+
.\"
6+
.\" For generation instructions using api-docs-tooling, see:
7+
.\" https://github.com/nodejs/api-docs-tooling
8+
.\"
9+
.\"======================================================================
10+
.Dd $Mdocdate$
11+
.Dt NODE 1
12+
.
13+
.Sh NAME
14+
.Nm node
15+
.Nd server-side JavaScript runtime
16+
.
17+
.Sh SYNOPSIS
18+
.Nm node
19+
.Op Ar options
20+
.Op Ar v8 options
21+
.Op Ar <program-entry-point> | Fl e Ar string | Fl -
22+
.Op Ar arguments ...
23+
.
24+
.Nm node
25+
.Cm inspect,
26+
.Op Ar <program-entry-point> | Fl e Ar string | Ar <host>:<port>
27+
.Ar ...
28+
.
29+
.Nm node
30+
.Op Fl -v8-options
31+
.
32+
.Sh DESCRIPTION
33+
Node.js is a set of libraries for JavaScript which allows it to be used outside of the browser.
34+
It is primarily focused on creating simple, easy-to-build network clients and servers.
35+
.Pp
36+
Execute
37+
.Nm
38+
without arguments to start a REPL.
39+
.
40+
.Sh OPTIONS
41+
.Bl -tag -width 6n
42+
__OPTIONS__
43+
.El
44+
.
45+
.Sh ENVIRONMENT
46+
.Bl -tag -width 6n
47+
__ENVIRONMENT__
48+
.El
49+
.
50+
.Sh BUGS
51+
Bugs are tracked in GitHub Issues:
52+
.Sy https://github.com/nodejs/node/issues
53+
.
54+
.Sh COPYRIGHT
55+
Copyright Node.js contributors.
56+
Node.js is available under the MIT license.
57+
.
58+
.Pp
59+
Node.js also includes external libraries that are available under a variety of licenses.
60+
See
61+
.Sy https://github.com/nodejs/node/blob/HEAD/LICENSE
62+
for the full license text.
63+
.
64+
.Sh SEE ALSO
65+
Website:
66+
.Sy https://nodejs.org/
67+
.
68+
.Pp
69+
Documentation:
70+
.Sy https://nodejs.org/api/
71+
.
72+
.Pp
73+
GitHub repository and issue tracker:
74+
.Sy https://github.com/nodejs/node
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
/**
2+
* Converts an Abstract Syntax Tree (AST) node to the Mandoc format for Unix manual pages.
3+
* This function processes the node recursively, converting each supported node type
4+
* to its corresponding Mandoc markup representation. Unsupported node types will be ignored.
5+
*
6+
* @param {import("mdast").Node} node - The AST node to be converted to Mandoc format.
7+
* @param {boolean} [isListItem=false] - Indicates if the current node is a list item.
8+
* This parameter is used to correctly format list elements in Mandoc.
9+
* @returns {string} The Mandoc formatted string representing the given node and its children.
10+
*/
11+
export function convertNodeToMandoc(node, isListItem = false) {
12+
const convertChildren = (sep = '', ili = false) =>
13+
node.children.map(child => convertNodeToMandoc(child, ili)).join(sep);
14+
const escapeText = () => node.value.replace(/\\/g, '\\\\');
15+
16+
switch (node.type) {
17+
case 'root':
18+
// Process the root node by converting all children and separating them with new lines.
19+
return convertChildren('\n');
20+
21+
case 'heading':
22+
// Convert to a Mandoc heading section (.Sh).
23+
return `.Sh ${convertChildren()}`;
24+
25+
case 'link':
26+
case 'paragraph':
27+
case 'listItem':
28+
// Convert to Mandoc paragraph or list item.
29+
// .It denotes a list item in Mandoc, added only if the node is a list item.
30+
return `${isListItem && node.type === 'listItem' ? '.It\n' : ''}${convertChildren()}`;
31+
32+
case 'text':
33+
// Escape any special characters in plain text content.
34+
return escapeText();
35+
36+
case 'inlineCode':
37+
// Format inline code using Mandoc's bold markup (\\fB ... \\fR).
38+
return `\\fB${escapeText()}\\fR`;
39+
40+
case 'strong':
41+
// Format inline code + strong using Mandoc's bold markup (\\fB ... \\fR).
42+
return `\\fB${convertChildren()}\\fR`;
43+
44+
case 'code':
45+
// Format code blocks as literal text using .Bd -literal and .Ed for start and end.
46+
return `.Bd -literal\n${escapeText()}\n.Ed`;
47+
48+
case 'list':
49+
// Convert to a bullet list in Mandoc, starting with .Bl -bullet and ending with .El.
50+
return `.Bl -bullet\n${convertChildren('\n', true)}\n.El`;
51+
52+
case 'emphasis':
53+
// Format emphasized text in Mandoc using italic markup (\\fI ... \\fR).
54+
return `\\fI${convertChildren()}\\fR`;
55+
56+
default:
57+
// Ignore `html`, `blockquote`, etc.
58+
return '';
59+
}
60+
}
61+
62+
/**
63+
* Converts a command-line flag to its Mandoc representation.
64+
* This function splits the flag into its name and optional value (if present),
65+
* formatting them appropriately for Mandoc manual pages.
66+
*
67+
* @param {string} flag - The command-line flag to be formatted. It may include a value
68+
* specified with either an equals sign (=) or a space.
69+
* @returns {string} The Mandoc formatted representation of the flag and its value.
70+
*/
71+
export function flagValueToMandoc(flag) {
72+
// The separator is '=' or ' '.
73+
let sep = flag.match(/[= ]/)?.[0];
74+
75+
if (sep == null) {
76+
// This flag does not have a default value.
77+
return '';
78+
}
79+
80+
// Split the flag into the name and value based on the separator ('=' or space).
81+
const value = flag.split(sep)[1];
82+
83+
// If there is no value, return an empty string.
84+
if (!value) {
85+
return '';
86+
}
87+
88+
// Determine the prefix based on the separator type.
89+
const prefix = sep === ' ' ? '' : ' Ns = Ns';
90+
91+
// Combine prefix and formatted value.
92+
return `${prefix} Ar ${value.replace(/\]$/, '')}`;
93+
}
94+
95+
const formatFlag = flag =>
96+
// 'Fl' denotes a flag, followed by an optional 'Ar' (argument).
97+
`Fl ${flag.split(/[= ]/)[0].slice(1)}${flagValueToMandoc(flag)}`;
98+
99+
/**
100+
* Converts an API option metadata entry into the Mandoc format.
101+
* This function formats command-line options, including flags and descriptions,
102+
* for display in Unix manual pages using Mandoc.
103+
*
104+
* @param {ApiDocMetadataEntry} element - The metadata entry containing details about the API option.
105+
* @returns {string} The Mandoc formatted string representing the API option, including flags and content.
106+
*/
107+
export function convertOptionToMandoc(element) {
108+
// Format the option flags by splitting them, removing backticks, and converting each flag.
109+
const formattedFlags = element.heading.data.text
110+
.replace(/`/g, '')
111+
.split(', ')
112+
.map(formatFlag)
113+
.join(' , ')
114+
.trim();
115+
116+
// Remove the header itself.
117+
element.content.children.shift();
118+
119+
// Return the formatted flags and content, separated by Mandoc markers.
120+
return `.It ${formattedFlags}\n${convertNodeToMandoc(element.content).trim()}\n.\n`;
121+
}
122+
123+
/**
124+
* Converts an API environment variable metadata entry into the Mandoc format.
125+
* This function formats environment variables for Unix manual pages, converting
126+
* the variable name and value, along with any associated descriptions, into Mandoc.
127+
*
128+
* @param {ApiDocMetadataEntry} element - The metadata entry containing details about the environment variable.
129+
* @returns {string} The Mandoc formatted representation of the environment variable and its content.
130+
*/
131+
export function convertEnvVarToMandoc(element) {
132+
// Split the environment variable into name and optional value.
133+
const [varName, varValue] = element.heading.data.text
134+
.replace(/`/g, '')
135+
.split('=');
136+
137+
// Format the variable value if present.
138+
const formattedValue = varValue ? ` Ar ${varValue}` : '';
139+
140+
// Remove the header itself.
141+
element.content.children.shift();
142+
143+
// Return the formatted environment variable and content, using Mandoc's .It (List item) and .Ev (Env Var) macros.
144+
return `.It Ev ${varName}${formattedValue}\n${convertNodeToMandoc(element.content)}\n.\n`;
145+
}

0 commit comments

Comments
 (0)