Skip to content

Commit

Permalink
feat: add mandoc generator
Browse files Browse the repository at this point in the history
  • Loading branch information
RedYetiDev committed Oct 16, 2024
1 parent f88ed7a commit e535c13
Show file tree
Hide file tree
Showing 7 changed files with 461 additions and 4 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/codespell.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <path> Specify the relative or absolute output directory
-v, --version <semver> Specify the target version of Node.js, semver compliant (default: "v22.6.0")
-v, --version <semver> Specify the target version of Node.js, semver compliant (default: "v22.9.0")
-c, --changelog <url> 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
```
2 changes: 2 additions & 0 deletions src/generators/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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 manPage from './man-page/index.mjs';

export default {
'json-simple': jsonSimple,
'legacy-html': legacyHtml,
'legacy-html-all': legacyHtmlAll,
'man-page': manPage,
};
68 changes: 68 additions & 0 deletions src/generators/man-page/index.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
'use strict';

import { writeFile, readFile } from 'node:fs/promises';
import { join } from 'node:path';

import {
convertOptionToMandoc,
convertEnvVarToMandoc,
} from './utils/converter.mjs';

/**
* This generator generates a man page version of the CLI.md file.
* See https://man.openbsd.org/mdoc.7 for the formatting.
*
* @typedef {Array<ApiDocMetadataEntry>} Input
*
* @type {import('../types.d.ts').GeneratorMetadata<Input, string>}
*/
export default {
name: 'man-page',

version: '1.0.0',

description: 'Generates the Node.js man-page.',

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'
);

if (optionsStart + environmentStart <= 0) {
throw new Error('Could not find headers');
}

// Generate the option mandoc
let optionsOutput = '';
for (let i = optionsStart + 1; i < environmentStart; i++) {
const el = input[i];
if (el.heading.depth === 3) {
optionsOutput += convertOptionToMandoc(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 += convertEnvVarToMandoc(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);

await writeFile(options.output, template);
},
};
74 changes: 74 additions & 0 deletions src/generators/man-page/template.1
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
.\"
.\" This file is automatically generated by api-docs-tooling.
.\" Do not edit this file directly. Please make changes to CLI.md
.\" and then regenerate this file.
.\"
.\" For generation instructions using api-docs-tooling, see:
.\" https://github.com/nodejs/api-docs-tooling
.\"
.\"======================================================================
.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 <program-entry-point> | Fl e Ar string | Fl -
.Op Ar arguments ...
.
.Nm node
.Cm inspect,
.Op Ar <program-entry-point> | Fl e Ar string | Ar <host>:<port>
.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
132 changes: 132 additions & 0 deletions src/generators/man-page/utils/converter.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/**
* Converts an Abstract Syntax Tree (AST) node to the Mandoc format for Unix manual pages.
* This function processes the node recursively, converting each supported node type
* to its corresponding Mandoc markup representation. Unsupported node types will be ignored.
*
* @param {import("mdast").Node} node - The AST node to be converted to Mandoc format.
* @param {boolean} [isListItem=false] - Indicates if the current node is a list item.
* This parameter is used to correctly format list elements in Mandoc.
* @returns {string} The Mandoc formatted string representing the given node and its children.
*/
export function convertNodeToMandoc(node, isListItem = false) {
const convertChildren = (sep = '', ili = false) =>
node.children.map(child => convertNodeToMandoc(child, ili)).join(sep);
const escapeText = () => node.value.replace(/\\/g, '\\\\');

switch (node.type) {
case 'root':
// Process the root node by converting all children and separating them with new lines.
return convertChildren('\n');

case 'heading':
// Convert to a Mandoc heading section (.Sh).
return `.Sh ${convertChildren()}`;

case 'link':
case 'paragraph':
case 'listItem':
// Convert to Mandoc paragraph or list item.
// .It denotes a list item in Mandoc, added only if the node is a list item.
return `${isListItem && node.type === 'listItem' ? '.It\n' : ''}${convertChildren()}`;

case 'text':
// Escape any special characters in plain text content.
return escapeText();

case 'inlineCode':
// Format inline code using Mandoc's bold markup (\\fB ... \\fR).
return `\\fB${escapeText()}\\fR`;

case 'strong':
// Format inline code + strong using Mandoc's bold markup (\\fB ... \\fR).
return `\\fB${convertChildren()}\\fR`;

case 'code':
// Format code blocks as literal text using .Bd -literal and .Ed for start and end.
return `.Bd -literal\n${escapeText()}\n.Ed`;

case 'list':
// Convert to a bullet list in Mandoc, starting with .Bl -bullet and ending with .El.
return `.Bl -bullet\n${convertChildren('\n', true)}\n.El`;

case 'emphasis':
// Format emphasized text in Mandoc using italic markup (\\fI ... \\fR).
return `\\fI${convertChildren()}\\fR`;

default:
// Ignore `html`, `blockquote`, etc.
return '';
}
}

/**
* Converts a command-line flag to its Mandoc representation.
* This function splits the flag into its name and optional value (if present),
* formatting them appropriately for Mandoc manual pages.
*
* @param {string} flag - The command-line flag to be formatted. It may include a value
* specified with either an equals sign (=) or a space.
* @returns {string} The Mandoc formatted representation of the flag and its value.
*/
export function flagValueToMandoc(flag) {
// The seperator is '=' or ' '.
const sep = flag.match(/[= ]/)?.[0];
if (sep == null) return '';
// Split the flag into the name and value based on = or space delimiter.
const value = flag.split(sep)[1];
// Format the value using Ns and Ar macros for Mandoc, if present.
// If the seperator is ' ', it'll become ''.
return value
? `${sep === ' ' ? '' : ' Ns = Ns'} Ar ${value.replace(/\]$/, '')}`
: '';
}

/**
* Converts an API option metadata entry into the Mandoc format.
* This function formats command-line options, including flags and descriptions,
* for display in Unix manual pages using Mandoc.
*
* @param {ApiDocMetadataEntry} element - The metadata entry containing details about the API option.
* @returns {string} The Mandoc formatted string representing the API option, including flags and content.
*/
export function convertOptionToMandoc(element) {
// Format the option flags by splitting them, removing backticks, and converting each flag.
const formattedFlags = element.heading.data.text
.replace(/`/g, '')
.split(', ')
.map(
// 'Fl' denotes a flag
flag => `Fl ${flag.split(/[= ]/)[0].slice(1)}${flagValueToMandoc(flag)}`
)
.join(' , ');

// Remove the header itself.
element.content.children.shift();

// Return the formatted flags and content, separated by Mandoc markers.
return `.It ${formattedFlags.trim()}\n${convertNodeToMandoc(element.content)}\n.\n`;
}

/**
* Converts an API environment variable metadata entry into the Mandoc format.
* This function formats environment variables for Unix manual pages, converting
* the variable name and value, along with any associated descriptions, into Mandoc.
*
* @param {ApiDocMetadataEntry} element - The metadata entry containing details about the environment variable.
* @returns {string} The Mandoc formatted representation of the environment variable and its content.
*/
export function convertEnvVarToMandoc(element) {
// Split the environment variable into name and optional value.
const [varName, varValue] = element.heading.data.text
.replace(/`/g, '')
.split('=');

// Format the variable value if present.
const formattedValue = varValue ? ` Ar ${varValue}` : '';

// Remove the header itself.
element.content.children.shift();

// Return the formatted environment variable and content, using Mandoc's .It (List item) and .Ev (Env Var) macros.
return `.It Ev ${varName}${formattedValue}\n${convertNodeToMandoc(element.content)}\n.\n`;
}
Loading

0 comments on commit e535c13

Please sign in to comment.