Skip to content

Commit 4c3023f

Browse files
committed
Refactor doc/generator to fully automate command reference generation
1 parent 6b45362 commit 4c3023f

File tree

8 files changed

+395
-178
lines changed

8 files changed

+395
-178
lines changed

.vscode/launch.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,5 +162,13 @@
162162
"args": [ "platform" , "backend", "stripes.config.js"],
163163
"cwd": "${workspaceFolder}/../platform-core"
164164
},
165+
{
166+
"type": "node",
167+
"request": "launch",
168+
"name": "Doc Generator",
169+
"program": "${workspaceFolder}/lib/doc/generator.js",
170+
"args": [],
171+
"cwd": "${workspaceFolder}"
172+
}
165173
]
166174
}

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Stripes CLI
22

3-
Copyright (C) 2017-2018 The Open Library Foundation
3+
Copyright (C) 2017-2019 The Open Library Foundation
44

55
This software is distributed under the terms of the Apache License,
66
Version 2.0. See the file "[LICENSE](LICENSE)" for more information.

doc/commands-template.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Stripes CLI Commands
2+
3+
Version <%= version =>
4+
5+
This following command documentation is generated from the CLI's own built-in help. Run any command with the `--help` option to view the latest help for your currently installed CLI. To regenerate this file, run `yarn docs`.
6+
7+
* [Common options](#common-options)
8+
<%= toc =%>
9+
* [`completion` command](#completion-command)
10+
11+
12+
## Common options
13+
14+
The following options are available for all commands:
15+
16+
Option | Description | Type | Notes
17+
---|---|---|---
18+
`--help` | Show help | boolean |
19+
`--version` | Show version number | boolean |
20+
`--interactive` | Enable interactive input (use --no-interactive to disable) | boolean | default: true
21+
22+
Examples:
23+
24+
Show help for the build command:
25+
```
26+
$ stripes build --help
27+
```
28+
29+
Disable interactive option
30+
```
31+
$ stripes app create "Hello World" --no-interactive
32+
```
33+
34+
<%= commands =%>
35+
36+
## `completion` command
37+
38+
Generate a bash completion script. Follow instructions included with the script for adding it to your bash profile.
39+
40+
Usage:
41+
```
42+
$ stripes completion
43+
```

doc/generator.js

Lines changed: 0 additions & 176 deletions
This file was deleted.

lib/doc/commands-to-markdown.js

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/*
2+
Generates CLI command reference in markdown format using command data obtained from yargs-help-parser
3+
*/
4+
5+
const logger = require('../cli/logger')('docs');
6+
7+
8+
// Exclude these commands from the markdown
9+
const skipCommands = [
10+
'stripes', // root not needed
11+
'stripes new', // deprecated
12+
'stripes completion', // internal to yargs, does not render help
13+
];
14+
15+
// Exclude these options that apply to all commands
16+
const skipOptions = [
17+
'--help',
18+
'--version',
19+
'--interactive',
20+
];
21+
22+
// Returns command in the document
23+
function commandTitle(command) {
24+
const wip = '(work in progress)';
25+
return `\`${command.name}\` command${command.description.includes(wip) ? ` ${wip}` : ''}`;
26+
}
27+
28+
// Returns link to a command in the document
29+
function markdownCommandLink(command, displayName) {
30+
return `[${displayName}](#${commandTitle(command).replace(/[`()]/g, '').replace(/\s/g, '-')})`;
31+
}
32+
33+
// Returns a command heading prefixed with appropriate number of '#'
34+
function markdownCommandSummary(command, depth) {
35+
let level = depth;
36+
let headingLevel = '#';
37+
while (level--) {
38+
headingLevel += '#';
39+
}
40+
let summary = `\n${headingLevel} ${commandTitle(command)}\n\n`;
41+
summary += `${command.description}\n\n`;
42+
summary += 'Usage:\n```\n';
43+
summary += `$ ${command.usage}\n`;
44+
summary += '```\n';
45+
return summary;
46+
}
47+
48+
// Returns bulleted list of sub-commands with links
49+
function markdownSubCommands(subCommands) {
50+
if (!subCommands.length) {
51+
return '';
52+
}
53+
let section = 'Sub-commands:\n';
54+
subCommands.forEach(sub => {
55+
const displayName = `\`${sub.fullName}\``;
56+
section += `* ${markdownCommandLink(sub, displayName)}\n`;
57+
});
58+
return section;
59+
}
60+
61+
// Returns formatted table of options (also used for positionals)
62+
function markdownOptionTable(displayName, optionsData) {
63+
const options = optionsData.filter(opt => !skipOptions.includes(opt.name));
64+
if (!options.length) {
65+
return '';
66+
}
67+
let optionsTable = `${displayName} | Description | Type | Notes\n`;
68+
optionsTable += '---|---|---|---\n';
69+
options.forEach((option) => {
70+
const description = option.stdin ? option.description.replace('(stdin)', '') : option.description;
71+
const notes = [
72+
option.required ? '(*)' : '',
73+
option.default ? `default: ${option.default}` : '',
74+
option.choices ? `choices: ${option.choices}` : '',
75+
option.stdin ? 'supports stdin' : '',
76+
];
77+
optionsTable += `\`${option.name}\` | ${description} | ${option.type} | ${notes.filter(note => note.length > 0).join(' ')}\n`;
78+
});
79+
return optionsTable;
80+
}
81+
82+
// Returns code blocks for command's "Examples:" section
83+
function markdownExamples(examples) {
84+
if (!examples.length) {
85+
return '';
86+
}
87+
let exampleSection = 'Examples:\n\n';
88+
examples.forEach((example) => {
89+
exampleSection += `${example.description}:\n`;
90+
exampleSection += '```\n';
91+
exampleSection += `$ ${example.usage}\n`;
92+
exampleSection += '```\n';
93+
});
94+
return exampleSection;
95+
}
96+
97+
// Defines overall structure and returns markdown for a single command
98+
function markdownCommand(command, depth) {
99+
logger.log(`generate markdown for ${command.name}`);
100+
const sections = [
101+
markdownCommandSummary(command, depth),
102+
markdownSubCommands(command.subCommands),
103+
markdownOptionTable('Positional', command.positionals),
104+
markdownOptionTable('Option', command.options),
105+
markdownExamples(command.examples),
106+
];
107+
return sections.filter(section => section.length > 0).join('\n');
108+
}
109+
110+
// Recursively returns markdown for all commands in the tree
111+
function generateMarkdown(command, depth = 1) {
112+
const skip = skipCommands.includes(command.fullName);
113+
let markdown = skip ? '' : markdownCommand(command, depth);
114+
command.subCommands.forEach(subCommand => {
115+
markdown += generateMarkdown(subCommand, skip ? depth : depth + 1);
116+
});
117+
return markdown;
118+
}
119+
120+
// Recursively returns markdown TOC for all commands in the tree
121+
function generateToc(command, depth = 0) {
122+
const skip = skipCommands.includes(command.fullName);
123+
let level = depth;
124+
let tocIndent = '';
125+
while (level--) {
126+
tocIndent += ' ';
127+
}
128+
const displayName = commandTitle(command);
129+
let toc = skip ? '' : `${tocIndent}* ${markdownCommandLink(command, displayName)}\n`;
130+
command.subCommands.forEach(subCommand => {
131+
toc += generateToc(subCommand, skip ? depth : depth + 1);
132+
});
133+
return toc;
134+
}
135+
136+
module.exports = {
137+
generateToc: (command) => generateToc(command).trim(),
138+
generateMarkdown,
139+
};

0 commit comments

Comments
 (0)