clyops-doc 0.0.0-stage → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +97 -2
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +150 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +37 -0
- package/dist/model.d.ts +129 -0
- package/dist/model.d.ts.map +1 -0
- package/dist/model.js +280 -0
- package/dist/render.d.ts +23 -0
- package/dist/render.d.ts.map +1 -0
- package/dist/render.js +165 -0
- package/package.json +50 -3
- package/src/cli.ts +142 -0
- package/src/index.ts +48 -0
- package/src/model.ts +355 -0
- package/src/render.ts +187 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Dan VerWeire
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,98 @@
|
|
|
1
|
-
#
|
|
1
|
+
# clyops-doc
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Documentation from any [clyops](https://github.com/wankdanker/clyops) tool's `--help-json-schema`: a
|
|
4
|
+
man page, Markdown or a standalone HTML page. It only reads the schema, so it works for a tool in
|
|
5
|
+
any of the nine languages, compiled ones included, and needs nothing from the tool itself.
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm install -g clyops-doc
|
|
9
|
+
|
|
10
|
+
mytool --help-json-schema | clyops-doc > mytool.1 # man page (the default)
|
|
11
|
+
mytool --help-json-schema | clyops-doc --format markdown > mytool.md
|
|
12
|
+
mytool --help-json-schema | clyops-doc --format html > mytool.html
|
|
13
|
+
man -l mytool.1
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
`clyops-doc` is a clyops tool too, so it documents itself:
|
|
17
|
+
`clyops-doc --help-json-schema | clyops-doc | man -l -`.
|
|
18
|
+
|
|
19
|
+
| Option | |
|
|
20
|
+
| --- | --- |
|
|
21
|
+
| `-f, --format` | `man` (default), `markdown` or `html` |
|
|
22
|
+
| `-n, --name` | How the tool is run, default the schema's name; for a dispatcher path, `--name "mytool media to-pcm"` |
|
|
23
|
+
| `-s, --section` | Manual section for the man page header (default `1`) |
|
|
24
|
+
| `-V, --version` | Version shown in the header |
|
|
25
|
+
| `-d, --date` | Date shown in the header, YYYY-MM-DD (default today, or `SOURCE_DATE_EPOCH` for reproducible builds) |
|
|
26
|
+
| `--schema FILE` | Read the schema from a file instead of stdin |
|
|
27
|
+
| `-r, --root DIR` | Document every tool in a directory (or dispatcher definition), with an index page (needs `--out-dir` or `--prefix`) |
|
|
28
|
+
| `-o, --out-dir DIR` | Write page files into `DIR` (created if missing) instead of to stdout |
|
|
29
|
+
| `-p, --prefix PREFIX` | Install man pages into `PREFIX/share/man/manN`, and warn if `man` won't find them |
|
|
30
|
+
| `--split` | A page per command, git-style (needs `--out-dir` or `--prefix`) |
|
|
31
|
+
| `-x, --execute` | Write the files; without it, `--out-dir` and `--prefix` only list them |
|
|
32
|
+
|
|
33
|
+
Each option can also be set as `CLYOPS_DOC_<OPTION>`. Input that isn't a clyops schema (`"clyops": 1`)
|
|
34
|
+
is rejected with exit status 1.
|
|
35
|
+
|
|
36
|
+
## A page per command
|
|
37
|
+
|
|
38
|
+
```sh
|
|
39
|
+
mytool --help-json-schema | clyops-doc --split --out-dir man1 # lists the files
|
|
40
|
+
mytool --help-json-schema | clyops-doc --split --out-dir man1 --execute # writes them
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
`mytool.1` gets the program's own options and a list of the commands, and each runnable command gets
|
|
44
|
+
its own page, `mytool-db-migrate.1`, with the options it inherits and the nearest declared effects,
|
|
45
|
+
stdin, stdout and roles. They refer to each other under SEE ALSO, as links in Markdown and HTML
|
|
46
|
+
(`.md`, `.html`). A program without commands is one page. Files that already say the same are left
|
|
47
|
+
alone, so a rerun is a no-op. Because it can write files, clyops-doc declares the `idempotent` and
|
|
48
|
+
`destructive` effects, although nothing is written without `--execute`.
|
|
49
|
+
|
|
50
|
+
## A whole directory of tools
|
|
51
|
+
|
|
52
|
+
```sh
|
|
53
|
+
clyops-doc --root ~/mytool/scripts --name mytool --prefix ~/.local # lists the pages
|
|
54
|
+
clyops-doc --root ~/mytool/scripts --name mytool --prefix ~/.local --execute # installs them
|
|
55
|
+
man mytool-media-to-pcm
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`--root` finds tools the way `clyops-dispatch`, `clyops-api` and `clyops-mcp` do (subdirectories
|
|
59
|
+
become command words, `.clyops` files give descriptions and ignores, nested dispatchers are
|
|
60
|
+
followed), asks each for `--help-json-schema`, and writes a page per tool, or with `--split` per
|
|
61
|
+
command, plus an index page named after the dispatcher (`--name`, default the directory's name)
|
|
62
|
+
that lists them all. Executables that aren't clyops tools, or that fail to give a schema, are
|
|
63
|
+
skipped with a warning.
|
|
64
|
+
|
|
65
|
+
`--prefix` writes into `PREFIX/share/man/man1` (`man8` with `--section 8`) and checks `manpath`
|
|
66
|
+
(or `MANPATH`), saying how to add the directory when `man` won't look there.
|
|
67
|
+
|
|
68
|
+
## What goes where
|
|
69
|
+
|
|
70
|
+
| Section | From the schema |
|
|
71
|
+
| --- | --- |
|
|
72
|
+
| NAME, SYNOPSIS | the name, the first sentence of `description`, and a usage line per command, as help prints it |
|
|
73
|
+
| DESCRIPTION | `description` |
|
|
74
|
+
| ARGUMENTS, OPTIONS | each argument and option with its default, accepted values ([spec §5](https://github.com/wankdanker/clyops/blob/main/spec/SPEC.md#5-validation-rules)), whether it repeats or is a secret, and its environment variable |
|
|
75
|
+
| COMMANDS | for a program with commands, a subsection per command with the options it inherits and the nearest declared effects, stdin, stdout and roles |
|
|
76
|
+
| STREAMS | `stdin`, `stdout` and [roles](https://github.com/wankdanker/clyops/blob/main/spec/SPEC.md#14-roles-on-file-descriptors) with their types |
|
|
77
|
+
| NOTES | effects, option relationships and required commands |
|
|
78
|
+
| ENVIRONMENT | each option's variable, the precedence rule, `CLYOPS_FDS` when there are roles, `CLYOPS_SILENT`, `CLYOPS_MAX_WIDTH` and `NO_COLOR` |
|
|
79
|
+
| FILES | the config file: the option and variable naming it, its default, the line format and prefixes |
|
|
80
|
+
| EXIT STATUS | 0, 1, and the tool's own |
|
|
81
|
+
| EXAMPLES | `epilog` |
|
|
82
|
+
| SEE ALSO | with `--split`, the other pages |
|
|
83
|
+
|
|
84
|
+
Man pages use the plain `man(7)` macros, which groff and mandoc both render; text is escaped so a
|
|
85
|
+
line can't start a request and option dashes are `\-`, so they copy and paste. The HTML page is
|
|
86
|
+
self-contained, with every value escaped.
|
|
87
|
+
|
|
88
|
+
## As a library
|
|
89
|
+
|
|
90
|
+
```js
|
|
91
|
+
import { buildDoc, document, pages, renderMarkdown } from 'clyops-doc';
|
|
92
|
+
|
|
93
|
+
const page = document(schema, 'man', { name: 'mytool', version: '1.2.0' });
|
|
94
|
+
const md = renderMarkdown(buildDoc(schema)); // or build the document once and render it several ways
|
|
95
|
+
for (const { file, content } of pages(schema, 'man')) { /* mytool.1, mytool-db-migrate.1, ... */ }
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
ESM only, Node 20+.
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// clyops-doc: a clyops tool's --help-json-schema on stdin, as a man page, Markdown or HTML.
|
|
3
|
+
import { Cli, die, info, warn } from 'clyops';
|
|
4
|
+
import { commands, discover, loadSchema } from 'clyops-tools';
|
|
5
|
+
import { execFileSync } from 'node:child_process';
|
|
6
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
7
|
+
import { delimiter, join, resolve } from 'node:path';
|
|
8
|
+
import { document, extension, pages } from './index.js';
|
|
9
|
+
import { defaultDate, firstSentence, pageName, toolsetDoc } from './model.js';
|
|
10
|
+
import { render } from './render.js';
|
|
11
|
+
const cli = new Cli({ name: 'clyops-doc' });
|
|
12
|
+
cli.setDescription('Render a clyops tool\'s --help-json-schema as documentation: a man page, Markdown or HTML. It reads the schema on stdin, so it works with any clyops tool, compiled ones included.');
|
|
13
|
+
cli.setEpilog('Examples:\n mytool --help-json-schema | clyops-doc > mytool.1\n mytool --help-json-schema | clyops-doc --format markdown > mytool.md\n clyops-doc --schema schema.json --name "mytool media to-pcm" --format html > to-pcm.html\n clyops-doc --help-json-schema | clyops-doc | man -l -\n mytool --help-json-schema | clyops-doc --split --out-dir man1 --execute\n clyops-doc --root ~/mytool/scripts --name mytool --prefix ~/.local --execute');
|
|
14
|
+
// It writes files only with --out-dir or --prefix and --execute, and then only its own pages.
|
|
15
|
+
cli.setEffects('idempotent', 'destructive');
|
|
16
|
+
cli.setStdin('The tool\'s --help-json-schema output (unless --schema or --root is given)', 'application/json');
|
|
17
|
+
// One declared type per program: what it writes is text/troff, text/markdown or text/html by --format.
|
|
18
|
+
cli.setStdout('The documentation as --format says: text/troff, text/markdown or text/html; with --out-dir or --prefix, the files written (or, without --execute, that would be), one per line', 'text/plain');
|
|
19
|
+
cli.opt('CLYOPS_DOC_FORMAT', 'format', 'f', 'man', 'Output format', 'Output', 'choice:man,markdown,html');
|
|
20
|
+
cli.opt('CLYOPS_DOC_NAME', 'name', 'n', 'optional', 'How the tool is run (default: the schema\'s name), e.g. "mytool media to-pcm"; with --root, the dispatcher\'s name (default: the directory\'s)', 'Output');
|
|
21
|
+
cli.opt('CLYOPS_DOC_SECTION', 'section', 's', '1', 'Manual section for the man page header', 'Output', 'regex:^[1-9][a-z]*$');
|
|
22
|
+
cli.opt('CLYOPS_DOC_VERSION', 'version', 'V', 'optional', 'Version shown in the header', 'Output');
|
|
23
|
+
cli.opt('CLYOPS_DOC_DATE', 'date', 'd', 'optional', 'Date shown in the header (default: today, or SOURCE_DATE_EPOCH)', 'Output', 'date:YYYY-MM-DD');
|
|
24
|
+
cli.opt('CLYOPS_DOC_SCHEMA', 'schema', '', 'optional', 'Read the schema from this file instead of stdin', 'Input', 'file:readable');
|
|
25
|
+
cli.opt('CLYOPS_DOC_ROOT', 'root', 'r', 'optional', 'Document every tool in this directory (or dispatcher definition), found as clyops-dispatch, clyops-api and clyops-mcp find them, with an index page', 'Input', 'path');
|
|
26
|
+
cli.opt('CLYOPS_DOC_OUT_DIR', 'out-dir', 'o', 'optional', 'Write page files into this directory (created if missing) instead of to stdout', 'Files', 'path');
|
|
27
|
+
cli.opt('CLYOPS_DOC_PREFIX', 'prefix', 'p', 'optional', 'Install man pages into PREFIX/share/man/manSECTION and check that man finds them', 'Files', 'path');
|
|
28
|
+
cli.opt('CLYOPS_DOC_SPLIT', 'split', '', 'flag', 'A page per command, git-style: mytool.1 lists them, mytool-db-migrate.1 documents one', 'Files');
|
|
29
|
+
cli.opt('CLYOPS_DOC_EXECUTE', 'execute', 'x', 'flag', 'Write the files; without it, only list them', 'Files');
|
|
30
|
+
cli.exclusive('schema', 'root');
|
|
31
|
+
cli.exclusive('out-dir', 'prefix');
|
|
32
|
+
const args = cli.run();
|
|
33
|
+
const format = args.CLYOPS_DOC_FORMAT;
|
|
34
|
+
const opts = {
|
|
35
|
+
name: args.CLYOPS_DOC_NAME ?? undefined,
|
|
36
|
+
section: args.CLYOPS_DOC_SECTION,
|
|
37
|
+
version: args.CLYOPS_DOC_VERSION ?? undefined,
|
|
38
|
+
date: args.CLYOPS_DOC_DATE ?? defaultDate(),
|
|
39
|
+
};
|
|
40
|
+
const root = args.CLYOPS_DOC_ROOT;
|
|
41
|
+
const prefix = args.CLYOPS_DOC_PREFIX;
|
|
42
|
+
const split = args.CLYOPS_DOC_SPLIT;
|
|
43
|
+
const execute = args.CLYOPS_DOC_EXECUTE;
|
|
44
|
+
const manDir = prefix === null ? null : join(prefix, 'share', 'man');
|
|
45
|
+
const outDir = args.CLYOPS_DOC_OUT_DIR ?? (manDir === null ? null : join(manDir, `man${opts.section}`));
|
|
46
|
+
if (prefix !== null && format !== 'man')
|
|
47
|
+
die(1, '--prefix installs man pages; use --out-dir for --format %s', format);
|
|
48
|
+
if (outDir === null) {
|
|
49
|
+
// Several pages, or writing them, need somewhere to go.
|
|
50
|
+
const needs = root !== null ? 'root' : split ? 'split' : execute ? 'execute' : null;
|
|
51
|
+
if (needs)
|
|
52
|
+
die(1, '--%s needs --out-dir or --prefix', needs);
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
if (root !== null)
|
|
56
|
+
writePages(await toolsetPages(root));
|
|
57
|
+
else {
|
|
58
|
+
const schema = readSchema();
|
|
59
|
+
if (outDir === null)
|
|
60
|
+
process.stdout.write(document(schema, format, opts));
|
|
61
|
+
else
|
|
62
|
+
writePages(split ? pages(schema, format, opts) : [{ file: `${pageName(opts.name || schema.script)}.${extension(format, opts.section)}`, content: document(schema, format, opts) }]);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
die(1, '%s', err.message);
|
|
67
|
+
}
|
|
68
|
+
function readSchema() {
|
|
69
|
+
try {
|
|
70
|
+
return JSON.parse(readFileSync(args.CLYOPS_DOC_SCHEMA ?? 0, 'utf8'));
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
return die(1, 'Could not read the schema: %s', err.message);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// --root: a page (or, with --split, pages) per tool, and an index page named after the dispatcher.
|
|
77
|
+
async function toolsetPages(dir) {
|
|
78
|
+
const group = discover(dir, { name: opts.name });
|
|
79
|
+
const ext = extension(format, opts.section);
|
|
80
|
+
const out = [];
|
|
81
|
+
const tools = [];
|
|
82
|
+
for (const cmd of commands(group)) {
|
|
83
|
+
if (cmd.kind !== 'tool') {
|
|
84
|
+
warn('Skipping %s: not a clyops tool', cmd.file);
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
let schema;
|
|
88
|
+
try {
|
|
89
|
+
schema = await loadSchema(cmd.file);
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
warn('Skipping %s', err.message);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
const name = [group.name, ...cmd.words].join(' ');
|
|
96
|
+
out.push(...(split
|
|
97
|
+
? pages(schema, format, { ...opts, name })
|
|
98
|
+
: [{ file: `${pageName(name)}.${ext}`, content: document(schema, format, { ...opts, name, page: pageName(name) }) }]));
|
|
99
|
+
tools.push({ words: cmd.words, description: firstSentence(schema.description) });
|
|
100
|
+
}
|
|
101
|
+
if (!tools.length)
|
|
102
|
+
throw new Error(`no clyops tools in ${dir}`);
|
|
103
|
+
const all = [{ file: `${pageName(group.name)}.${ext}`, content: render(toolsetDoc(group.name, group.description, tools, opts), format) }, ...out];
|
|
104
|
+
const seen = new Set();
|
|
105
|
+
for (const p of all) {
|
|
106
|
+
if (seen.has(p.file))
|
|
107
|
+
throw new Error(`two pages would be named ${p.file}`);
|
|
108
|
+
seen.add(p.file);
|
|
109
|
+
}
|
|
110
|
+
return all;
|
|
111
|
+
}
|
|
112
|
+
// Each page into the directory, leaving files that already say the same alone.
|
|
113
|
+
function writePages(all) {
|
|
114
|
+
const dir = outDir;
|
|
115
|
+
let replaced = 0;
|
|
116
|
+
let unchanged = 0;
|
|
117
|
+
if (execute)
|
|
118
|
+
mkdirSync(dir, { recursive: true });
|
|
119
|
+
for (const page of all) {
|
|
120
|
+
const path = join(dir, page.file);
|
|
121
|
+
const before = existsSync(path) ? readFileSync(path, 'utf8') : null;
|
|
122
|
+
if (before === page.content)
|
|
123
|
+
unchanged++;
|
|
124
|
+
else {
|
|
125
|
+
if (before !== null)
|
|
126
|
+
replaced++;
|
|
127
|
+
if (execute)
|
|
128
|
+
writeFileSync(path, page.content);
|
|
129
|
+
}
|
|
130
|
+
process.stdout.write(`${path}\n`);
|
|
131
|
+
}
|
|
132
|
+
const counts = `${all.length - unchanged} page(s)${replaced ? ` (replacing ${replaced})` : ''}${unchanged ? `; ${unchanged} already up to date` : ''}`;
|
|
133
|
+
info(execute ? `Wrote ${counts} in ${dir}.` : `Would write ${counts} in ${dir}; add --execute to write them.`);
|
|
134
|
+
if (manDir !== null)
|
|
135
|
+
checkManPath(manDir);
|
|
136
|
+
}
|
|
137
|
+
// --prefix: say so when man won't look in PREFIX/share/man.
|
|
138
|
+
function checkManPath(dir) {
|
|
139
|
+
let paths = process.env.MANPATH ?? '';
|
|
140
|
+
try {
|
|
141
|
+
paths = execFileSync('manpath', [], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], timeout: 5000 }).trim();
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
// No manpath(1): MANPATH is all there is to go on.
|
|
145
|
+
}
|
|
146
|
+
const want = resolve(dir);
|
|
147
|
+
if (!paths.split(delimiter).some((p) => p && resolve(p) === want)) {
|
|
148
|
+
warn('man won\'t find these pages: %s is not on its search path. Add it with: export MANPATH="%s%s$(manpath)"', want, want, delimiter);
|
|
149
|
+
}
|
|
150
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export * from './model.js';
|
|
2
|
+
export * from './render.js';
|
|
3
|
+
import type { Schema } from 'clyops-schema';
|
|
4
|
+
import { type DocOptions } from './model.js';
|
|
5
|
+
import { type Format } from './render.js';
|
|
6
|
+
/** A schema's documentation in `format` (default man). Throws for input that isn't a clyops schema. */
|
|
7
|
+
export declare function document(schema: Schema, format?: Format, opts?: DocOptions): string;
|
|
8
|
+
/** One file of a split document. */
|
|
9
|
+
export interface Page {
|
|
10
|
+
/** File name, e.g. `mytool-db-migrate.1`, `mytool.md`. */
|
|
11
|
+
file: string;
|
|
12
|
+
/** The page in its format. */
|
|
13
|
+
content: string;
|
|
14
|
+
}
|
|
15
|
+
/** A page's file extension: the manual section for man, else the format's. */
|
|
16
|
+
export declare function extension(format: Format, section?: string): string;
|
|
17
|
+
/**
|
|
18
|
+
* The documentation as a page per runnable command, git-style: `mytool.1` lists the commands and
|
|
19
|
+
* each has its own, e.g. `mytool-db-migrate.1`, with what it inherits. A program without commands
|
|
20
|
+
* is one page. Throws for input that isn't a clyops schema, or for two commands with one page name.
|
|
21
|
+
*/
|
|
22
|
+
export declare function pages(schema: Schema, format?: Format, opts?: DocOptions): Page[];
|
|
23
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAE5B,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAA0C,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AACrF,OAAO,EAAU,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AAElD,uGAAuG;AACvG,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,GAAE,MAAc,EAAE,IAAI,GAAE,UAAe,GAAG,MAAM,CAE9F;AAED,oCAAoC;AACpC,MAAM,WAAW,IAAI;IACnB,0DAA0D;IAC1D,IAAI,EAAE,MAAM,CAAC;IACb,8BAA8B;IAC9B,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,8EAA8E;AAC9E,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,SAAM,GAAG,MAAM,CAE/D;AAED;;;;GAIG;AACH,wBAAgB,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,GAAE,MAAc,EAAE,IAAI,GAAE,UAAe,GAAG,IAAI,EAAE,CAgB3F"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// clyops-doc: documentation (man, Markdown, HTML) from a clyops tool's --help-json-schema.
|
|
2
|
+
export * from './model.js';
|
|
3
|
+
export * from './render.js';
|
|
4
|
+
import { buildDoc, leafSchema, leaves, pageName } from './model.js';
|
|
5
|
+
import { render } from './render.js';
|
|
6
|
+
/** A schema's documentation in `format` (default man). Throws for input that isn't a clyops schema. */
|
|
7
|
+
export function document(schema, format = 'man', opts = {}) {
|
|
8
|
+
return render(buildDoc(schema, opts), format);
|
|
9
|
+
}
|
|
10
|
+
/** A page's file extension: the manual section for man, else the format's. */
|
|
11
|
+
export function extension(format, section = '1') {
|
|
12
|
+
return format === 'man' ? section : format === 'markdown' ? 'md' : 'html';
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* The documentation as a page per runnable command, git-style: `mytool.1` lists the commands and
|
|
16
|
+
* each has its own, e.g. `mytool-db-migrate.1`, with what it inherits. A program without commands
|
|
17
|
+
* is one page. Throws for input that isn't a clyops schema, or for two commands with one page name.
|
|
18
|
+
*/
|
|
19
|
+
export function pages(schema, format = 'man', opts = {}) {
|
|
20
|
+
const name = opts.name || schema?.script || '';
|
|
21
|
+
const top = buildDoc(schema, { ...opts, split: true, page: pageName(name) });
|
|
22
|
+
const ext = extension(format, top.section);
|
|
23
|
+
const out = [{ file: `${top.page}.${ext}`, content: render(top, format) }];
|
|
24
|
+
const all = leaves(schema);
|
|
25
|
+
if (all.length === 1 && all[0].words.length === 0)
|
|
26
|
+
return out;
|
|
27
|
+
for (const leaf of all) {
|
|
28
|
+
const page = pageName(name, leaf.words);
|
|
29
|
+
const doc = buildDoc(leafSchema(schema, leaf), { ...opts, split: false, name: [name, ...leaf.words].join(' '), page });
|
|
30
|
+
doc.sections.push({ title: 'SEE ALSO', blocks: [{ kind: 'refs', items: [{ page: pageName(name), section: top.section }] }] });
|
|
31
|
+
const file = `${page}.${ext}`;
|
|
32
|
+
if (out.some((p) => p.file === file))
|
|
33
|
+
throw new Error(`two pages would be named ${file}`);
|
|
34
|
+
out.push({ file, content: render(doc, format) });
|
|
35
|
+
}
|
|
36
|
+
return out;
|
|
37
|
+
}
|
package/dist/model.d.ts
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import type { Schema, SchemaArgument, SchemaOption, SchemaRole, SchemaStream } from 'clyops-schema';
|
|
2
|
+
/** One piece of a section. Terms are option names, commands and the like: code in every format. */
|
|
3
|
+
export type Block = {
|
|
4
|
+
kind: 'p';
|
|
5
|
+
text: string;
|
|
6
|
+
} | {
|
|
7
|
+
kind: 'pre';
|
|
8
|
+
text: string;
|
|
9
|
+
} | {
|
|
10
|
+
kind: 'dl';
|
|
11
|
+
items: {
|
|
12
|
+
term: string;
|
|
13
|
+
text: string;
|
|
14
|
+
}[];
|
|
15
|
+
} | {
|
|
16
|
+
kind: 'h';
|
|
17
|
+
text: string;
|
|
18
|
+
} | {
|
|
19
|
+
kind: 'refs';
|
|
20
|
+
items: {
|
|
21
|
+
page: string;
|
|
22
|
+
section: string;
|
|
23
|
+
}[];
|
|
24
|
+
};
|
|
25
|
+
/** A top-level section, e.g. OPTIONS. */
|
|
26
|
+
export interface Section {
|
|
27
|
+
/** Upper-case title, as in man pages: NAME, SYNOPSIS, OPTIONS. */
|
|
28
|
+
title: string;
|
|
29
|
+
/** The section's content, in order. */
|
|
30
|
+
blocks: Block[];
|
|
31
|
+
}
|
|
32
|
+
/** The document for one program: its header and sections. */
|
|
33
|
+
export interface Doc {
|
|
34
|
+
/** How the program is run, e.g. `mytool` or `mytool media to-pcm`. */
|
|
35
|
+
name: string;
|
|
36
|
+
/** The page's name in the header and NAME line: `name`, or a split page's, e.g. `mytool-db-migrate`. */
|
|
37
|
+
page: string;
|
|
38
|
+
/** Manual section, e.g. `1`. */
|
|
39
|
+
section: string;
|
|
40
|
+
/** Version shown in the header, or empty. */
|
|
41
|
+
version: string;
|
|
42
|
+
/** Date shown in the header (YYYY-MM-DD). */
|
|
43
|
+
date: string;
|
|
44
|
+
/** The first line of the program's description. */
|
|
45
|
+
summary: string;
|
|
46
|
+
/** The sections, in order. */
|
|
47
|
+
sections: Section[];
|
|
48
|
+
}
|
|
49
|
+
/** What a renderer needs to know beyond the schema. */
|
|
50
|
+
export interface DocOptions {
|
|
51
|
+
/** How the program is run (default: the schema's `script`). */
|
|
52
|
+
name?: string;
|
|
53
|
+
/** Manual section (default `1`). */
|
|
54
|
+
section?: string;
|
|
55
|
+
/** Version for the header. */
|
|
56
|
+
version?: string;
|
|
57
|
+
/** Date for the header, YYYY-MM-DD (default: today). */
|
|
58
|
+
date?: string;
|
|
59
|
+
/** The page's name (default: `name`); see `Doc.page`. */
|
|
60
|
+
page?: string;
|
|
61
|
+
/** Commands get pages of their own (see `pages`): list them, with a SEE ALSO, instead of documenting each. */
|
|
62
|
+
split?: boolean;
|
|
63
|
+
}
|
|
64
|
+
/** A runnable level: a program without commands, or a leaf command, with what it inherits. */
|
|
65
|
+
export interface Leaf {
|
|
66
|
+
/** The command words after the program name, e.g. ['db', 'migrate']; empty for the program. */
|
|
67
|
+
words: string[];
|
|
68
|
+
/** The level's own description. */
|
|
69
|
+
description: string;
|
|
70
|
+
/** Text after its help, e.g. examples. */
|
|
71
|
+
epilog: string;
|
|
72
|
+
/** Positional arguments. */
|
|
73
|
+
arguments: SchemaArgument[];
|
|
74
|
+
/** Its own options, then those it inherits, nearest first. */
|
|
75
|
+
options: SchemaOption[];
|
|
76
|
+
/** The nearest declared effects. */
|
|
77
|
+
effects: string[];
|
|
78
|
+
/** The nearest declared stdin. */
|
|
79
|
+
stdin: SchemaStream | null;
|
|
80
|
+
/** The nearest declared stdout. */
|
|
81
|
+
stdout: SchemaStream | null;
|
|
82
|
+
/** Its roles (spec section 14), the nearest declaration of each name winning. */
|
|
83
|
+
roles: SchemaRole[];
|
|
84
|
+
/** Option relationships from the program down. */
|
|
85
|
+
constraints: {
|
|
86
|
+
type: string;
|
|
87
|
+
options: string[];
|
|
88
|
+
}[];
|
|
89
|
+
/** Required commands from the program down. */
|
|
90
|
+
requiredCommands: {
|
|
91
|
+
command: string;
|
|
92
|
+
description: string;
|
|
93
|
+
installHint: string;
|
|
94
|
+
}[];
|
|
95
|
+
}
|
|
96
|
+
/** Every runnable level of a schema, the way clyops-tools expands commands (spec section 1.7). */
|
|
97
|
+
export declare function leaves(schema: Schema): Leaf[];
|
|
98
|
+
/** The usage line, as help prints it (spec section 7). */
|
|
99
|
+
export declare function synopsis(name: string, leaf: Leaf): string;
|
|
100
|
+
/** An option's label: `-s, --long=<value>`, or `--long` for a flag. */
|
|
101
|
+
export declare function optionLabel(o: SchemaOption): string;
|
|
102
|
+
/** An option's description with its notes, as help annotates it (spec section 7). */
|
|
103
|
+
export declare function optionText(o: SchemaOption): string;
|
|
104
|
+
/** The first sentence of a description (its first line at most), for the NAME line. */
|
|
105
|
+
export declare function firstSentence(text: string): string;
|
|
106
|
+
/** Today in UTC, or SOURCE_DATE_EPOCH's date for reproducible builds. */
|
|
107
|
+
export declare function defaultDate(env?: NodeJS.ProcessEnv): string;
|
|
108
|
+
/**
|
|
109
|
+
* The document for a schema. Throws for input that isn't a clyops schema (`"clyops": 1`, with
|
|
110
|
+
* description, options and arguments).
|
|
111
|
+
*/
|
|
112
|
+
export declare function buildDoc(schema: Schema, opts?: DocOptions): Doc;
|
|
113
|
+
/** A page's name: the invocation's words joined by hyphens, e.g. `mytool-db-migrate`. */
|
|
114
|
+
export declare function pageName(name: string, words?: string[]): string;
|
|
115
|
+
/** A runnable command as a program of its own, with everything it inherits, for a page of its own. */
|
|
116
|
+
export declare function leafSchema(schema: Schema, leaf: Leaf): Schema;
|
|
117
|
+
/** One tool of a toolset, for its index page (see `toolsetDoc`). */
|
|
118
|
+
export interface ToolEntry {
|
|
119
|
+
/** The words after the toolset's name, e.g. ['media', 'to-pcm']. */
|
|
120
|
+
words: string[];
|
|
121
|
+
/** The tool's description. */
|
|
122
|
+
description: string;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* The index page of a directory of tools (`--root`): what the dispatcher `name` runs, each tool with
|
|
126
|
+
* a reference to its own page.
|
|
127
|
+
*/
|
|
128
|
+
export declare function toolsetDoc(name: string, description: string, tools: ToolEntry[], opts?: DocOptions): Doc;
|
|
129
|
+
//# sourceMappingURL=model.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"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"}
|