cli-kiss 0.0.1 → 0.0.2
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/dist/index.d.ts +56 -16
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +4 -1
- package/src/lib/Command.ts +62 -42
- package/src/lib/Grid.ts +56 -0
- package/src/lib/Option.ts +1 -1
- package/src/lib/{Processor.ts → Process.ts} +8 -8
- package/src/lib/Reader.ts +17 -11
- package/src/lib/Run.ts +26 -9
- package/src/lib/Typo.ts +73 -0
- package/src/lib/Usage.ts +90 -68
- package/tests/unit.Reader.commons.ts +2 -2
- package/tests/unit.command.run.ts +7 -7
- package/tests/unit.command.usage.ts +102 -21
package/src/lib/Usage.ts
CHANGED
|
@@ -1,104 +1,126 @@
|
|
|
1
1
|
import { CommandUsage } from "./Command";
|
|
2
|
+
import { GridCell, GridRow, gridToPrintableLines } from "./Grid";
|
|
3
|
+
import { TypoSupport, TypoText, typoPrintableString } from "./Typo";
|
|
4
|
+
|
|
5
|
+
export function usageToPrintableLines(params: {
|
|
6
|
+
cliName: string;
|
|
7
|
+
commandUsage: CommandUsage;
|
|
8
|
+
typoSupport: TypoSupport;
|
|
9
|
+
}) {
|
|
10
|
+
const { cliName, commandUsage, typoSupport } = params;
|
|
2
11
|
|
|
3
|
-
export function usageFormatter(
|
|
4
|
-
cliName: string,
|
|
5
|
-
commandUsage: CommandUsage,
|
|
6
|
-
): string {
|
|
7
12
|
const lines = new Array<string>();
|
|
13
|
+
|
|
14
|
+
lines.push("");
|
|
15
|
+
lines.push(typoPrintableString(typoSupport, textDesc(commandUsage.title)));
|
|
8
16
|
if (commandUsage.description) {
|
|
9
|
-
|
|
10
|
-
|
|
17
|
+
for (const descriptionLine of commandUsage.description) {
|
|
18
|
+
lines.push(typoPrintableString(typoSupport, textSubs(descriptionLine)));
|
|
19
|
+
}
|
|
11
20
|
}
|
|
21
|
+
|
|
22
|
+
const breadcrumbPrefix = typoPrintableString(
|
|
23
|
+
typoSupport,
|
|
24
|
+
textTitle("Usage:"),
|
|
25
|
+
);
|
|
26
|
+
const breadcrumbsItems = [
|
|
27
|
+
typoPrintableString(typoSupport, textName(cliName)),
|
|
28
|
+
].concat(
|
|
29
|
+
commandUsage.breadcrumbs.map((breadcrumb) => {
|
|
30
|
+
if (breadcrumb.kind === "argument") {
|
|
31
|
+
return typoPrintableString(typoSupport, textValue(breadcrumb.value));
|
|
32
|
+
}
|
|
33
|
+
return typoPrintableString(typoSupport, textName(breadcrumb.value));
|
|
34
|
+
}),
|
|
35
|
+
);
|
|
12
36
|
lines.push("");
|
|
13
|
-
lines.push(
|
|
37
|
+
lines.push(`${breadcrumbPrefix} ${breadcrumbsItems.join(" ")}`);
|
|
38
|
+
|
|
14
39
|
if (commandUsage.arguments.length > 0) {
|
|
15
40
|
lines.push("");
|
|
16
|
-
lines.push("Arguments:");
|
|
17
|
-
|
|
18
|
-
const rows = new Array<Array<string>>();
|
|
41
|
+
lines.push(typoPrintableString(typoSupport, textTitle("Arguments:")));
|
|
42
|
+
const grid = new Array<GridRow>();
|
|
19
43
|
for (const argumentUsage of commandUsage.arguments) {
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
44
|
+
const gridRow = new Array<GridCell>();
|
|
45
|
+
gridRow.push([]);
|
|
46
|
+
gridRow.push([textValue(argumentUsage.label)]);
|
|
47
|
+
gridRow.push([]);
|
|
24
48
|
if (argumentUsage.description) {
|
|
25
|
-
|
|
49
|
+
gridRow.push([textDesc(argumentUsage.description)]);
|
|
26
50
|
}
|
|
27
|
-
|
|
51
|
+
grid.push(gridRow);
|
|
28
52
|
}
|
|
29
|
-
|
|
53
|
+
lines.push(...gridToPrintableLines(grid, typoSupport));
|
|
30
54
|
}
|
|
55
|
+
|
|
31
56
|
if (commandUsage.options.length > 0) {
|
|
32
57
|
lines.push("");
|
|
33
|
-
lines.push("Options:");
|
|
34
|
-
|
|
35
|
-
const rows = new Array<Array<string>>();
|
|
58
|
+
lines.push(typoPrintableString(typoSupport, textTitle("Options:")));
|
|
59
|
+
const grid = new Array<GridRow>();
|
|
36
60
|
for (const optionUsage of commandUsage.options) {
|
|
37
|
-
const
|
|
38
|
-
|
|
61
|
+
const gridRow = new Array<GridCell>();
|
|
62
|
+
gridRow.push([]);
|
|
39
63
|
if (optionUsage.short) {
|
|
40
|
-
|
|
64
|
+
gridRow.push([textName(`-${optionUsage.short}`), { value: "," }]);
|
|
41
65
|
} else {
|
|
42
|
-
|
|
66
|
+
gridRow.push([]);
|
|
43
67
|
}
|
|
44
68
|
if (optionUsage.label) {
|
|
45
|
-
|
|
69
|
+
gridRow.push([
|
|
70
|
+
textName(`--${optionUsage.long} `),
|
|
71
|
+
textValue(optionUsage.label),
|
|
72
|
+
]);
|
|
46
73
|
} else {
|
|
47
|
-
|
|
74
|
+
gridRow.push([
|
|
75
|
+
textName(`--${optionUsage.long}`),
|
|
76
|
+
{ value: "[=yes|no]", color: "grey" },
|
|
77
|
+
]);
|
|
48
78
|
}
|
|
49
|
-
|
|
79
|
+
gridRow.push([]);
|
|
50
80
|
if (optionUsage.description) {
|
|
51
|
-
|
|
81
|
+
gridRow.push([textDesc(optionUsage.description)]);
|
|
52
82
|
}
|
|
53
|
-
|
|
83
|
+
grid.push(gridRow);
|
|
54
84
|
}
|
|
55
|
-
|
|
85
|
+
lines.push(...gridToPrintableLines(grid, typoSupport));
|
|
56
86
|
}
|
|
87
|
+
|
|
57
88
|
if (commandUsage.subcommands.length > 0) {
|
|
58
89
|
lines.push("");
|
|
59
|
-
lines.push("Subcommands:");
|
|
60
|
-
|
|
61
|
-
const rows = new Array<Array<string>>();
|
|
90
|
+
lines.push(typoPrintableString(typoSupport, textTitle("Subcommands:")));
|
|
91
|
+
const grid = new Array<GridRow>();
|
|
62
92
|
for (const subcommand of commandUsage.subcommands) {
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
if (subcommand.
|
|
68
|
-
|
|
93
|
+
const gridRow = new Array<GridCell>();
|
|
94
|
+
gridRow.push([]);
|
|
95
|
+
gridRow.push([textName(subcommand.name)]);
|
|
96
|
+
gridRow.push([]);
|
|
97
|
+
if (subcommand.title) {
|
|
98
|
+
gridRow.push([textDesc(subcommand.title)]);
|
|
69
99
|
}
|
|
70
|
-
|
|
100
|
+
grid.push(gridRow);
|
|
71
101
|
}
|
|
72
|
-
|
|
102
|
+
lines.push(...gridToPrintableLines(grid, typoSupport));
|
|
73
103
|
}
|
|
74
104
|
lines.push("");
|
|
75
|
-
return lines
|
|
105
|
+
return lines;
|
|
76
106
|
}
|
|
77
107
|
|
|
78
|
-
function
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const padding = " ".repeat(widths[columnIndex]! - cell.length);
|
|
97
|
-
cells.push(cell + padding);
|
|
98
|
-
} else {
|
|
99
|
-
cells.push(cell);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
lines.push(cells.join(" "));
|
|
103
|
-
}
|
|
108
|
+
function textTitle(text: string): TypoText {
|
|
109
|
+
return { value: text, color: "green", bold: true };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function textSubs(text: string): TypoText {
|
|
113
|
+
return { value: text, color: "grey" };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function textDesc(text: string): TypoText {
|
|
117
|
+
return { value: text, bold: true };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function textName(text: string): TypoText {
|
|
121
|
+
return { value: text, color: "cyan", bold: true };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function textValue(text: string): TypoText {
|
|
125
|
+
return { value: text, color: "cyan" };
|
|
104
126
|
}
|
|
@@ -8,15 +8,15 @@ import {
|
|
|
8
8
|
optionFlag,
|
|
9
9
|
optionRepeatable,
|
|
10
10
|
optionSingleValue,
|
|
11
|
-
|
|
11
|
+
process,
|
|
12
12
|
runWithArgv,
|
|
13
13
|
typeNumber,
|
|
14
14
|
typeString,
|
|
15
15
|
} from "../src";
|
|
16
16
|
|
|
17
17
|
const cmd = commandWithSubcommands<string, any, any>(
|
|
18
|
-
"Root command
|
|
19
|
-
|
|
18
|
+
{ title: "Root command title" },
|
|
19
|
+
process(
|
|
20
20
|
{
|
|
21
21
|
options: {
|
|
22
22
|
booleanFlag: optionFlag({ long: "boolean-flag", default: () => false }),
|
|
@@ -41,8 +41,8 @@ const cmd = commandWithSubcommands<string, any, any>(
|
|
|
41
41
|
),
|
|
42
42
|
{
|
|
43
43
|
sub1: command(
|
|
44
|
-
"Subcommand 1
|
|
45
|
-
|
|
44
|
+
{ title: "Subcommand 1 title" },
|
|
45
|
+
process(
|
|
46
46
|
{
|
|
47
47
|
options: {},
|
|
48
48
|
arguments: [argumentRequired({ type: typeString })],
|
|
@@ -53,8 +53,8 @@ const cmd = commandWithSubcommands<string, any, any>(
|
|
|
53
53
|
),
|
|
54
54
|
),
|
|
55
55
|
sub2: command(
|
|
56
|
-
"Subcommand 2
|
|
57
|
-
|
|
56
|
+
{ title: "Subcommand 2 title" },
|
|
57
|
+
process(
|
|
58
58
|
{
|
|
59
59
|
options: {},
|
|
60
60
|
arguments: [
|
|
@@ -9,16 +9,22 @@ import {
|
|
|
9
9
|
optionFlag,
|
|
10
10
|
optionRepeatable,
|
|
11
11
|
optionSingleValue,
|
|
12
|
-
|
|
12
|
+
process,
|
|
13
13
|
ReaderTokenizer,
|
|
14
14
|
typeNumber,
|
|
15
15
|
typeString,
|
|
16
16
|
} from "../src";
|
|
17
|
-
import {
|
|
17
|
+
import { usageToPrintableLines } from "../src/lib/Usage";
|
|
18
18
|
|
|
19
19
|
const cmd = commandWithSubcommands<string, any, any>(
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
{
|
|
21
|
+
title: "Root command title",
|
|
22
|
+
description: [
|
|
23
|
+
"Root command description",
|
|
24
|
+
"Second line of root command description",
|
|
25
|
+
],
|
|
26
|
+
},
|
|
27
|
+
process(
|
|
22
28
|
{
|
|
23
29
|
options: {
|
|
24
30
|
booleanFlag: optionFlag({
|
|
@@ -31,7 +37,7 @@ const cmd = commandWithSubcommands<string, any, any>(
|
|
|
31
37
|
long: "string-option",
|
|
32
38
|
type: typeString,
|
|
33
39
|
default: () => undefined,
|
|
34
|
-
label: "
|
|
40
|
+
label: "COOL-STUFF",
|
|
35
41
|
description: "Root string-option description",
|
|
36
42
|
}),
|
|
37
43
|
numberOption: optionRepeatable({
|
|
@@ -43,12 +49,12 @@ const cmd = commandWithSubcommands<string, any, any>(
|
|
|
43
49
|
},
|
|
44
50
|
arguments: [
|
|
45
51
|
argumentRequired({
|
|
46
|
-
label: "
|
|
52
|
+
label: "POS-1",
|
|
47
53
|
description: "First positional argument",
|
|
48
54
|
type: typeNumber,
|
|
49
55
|
}),
|
|
50
56
|
argumentRequired({
|
|
51
|
-
label: "
|
|
57
|
+
label: "POS-2",
|
|
52
58
|
description: "Second positional argument",
|
|
53
59
|
type: typeNumber,
|
|
54
60
|
}),
|
|
@@ -60,8 +66,14 @@ const cmd = commandWithSubcommands<string, any, any>(
|
|
|
60
66
|
),
|
|
61
67
|
{
|
|
62
68
|
sub1: command(
|
|
63
|
-
|
|
64
|
-
|
|
69
|
+
{
|
|
70
|
+
title: "Subcommand 1 title",
|
|
71
|
+
description: [
|
|
72
|
+
"Subcommand 1 description",
|
|
73
|
+
"Second line of subcommand 1 description",
|
|
74
|
+
],
|
|
75
|
+
},
|
|
76
|
+
process(
|
|
65
77
|
{
|
|
66
78
|
options: {},
|
|
67
79
|
arguments: [
|
|
@@ -78,8 +90,14 @@ const cmd = commandWithSubcommands<string, any, any>(
|
|
|
78
90
|
),
|
|
79
91
|
),
|
|
80
92
|
sub2: command(
|
|
81
|
-
|
|
82
|
-
|
|
93
|
+
{
|
|
94
|
+
title: "Subcommand 2 title",
|
|
95
|
+
description: [
|
|
96
|
+
"Subcommand 2 description",
|
|
97
|
+
"Second line of subcommand 2 description",
|
|
98
|
+
],
|
|
99
|
+
},
|
|
100
|
+
process(
|
|
83
101
|
{
|
|
84
102
|
options: {
|
|
85
103
|
duduValue: optionSingleValue({
|
|
@@ -96,13 +114,13 @@ const cmd = commandWithSubcommands<string, any, any>(
|
|
|
96
114
|
type: typeNumber,
|
|
97
115
|
}),
|
|
98
116
|
argumentOptional({
|
|
99
|
-
label: "OPT-
|
|
117
|
+
label: "OPT-POS",
|
|
100
118
|
description: "Optional positional argument",
|
|
101
119
|
type: typeString,
|
|
102
120
|
default: () => "42",
|
|
103
121
|
}),
|
|
104
122
|
argumentVariadics({
|
|
105
|
-
label: "VARIADIC-
|
|
123
|
+
label: "VARIADIC-POSS",
|
|
106
124
|
description: "Variadic positional arguments",
|
|
107
125
|
type: typeString,
|
|
108
126
|
}),
|
|
@@ -117,14 +135,52 @@ const cmd = commandWithSubcommands<string, any, any>(
|
|
|
117
135
|
);
|
|
118
136
|
|
|
119
137
|
it("run", async () => {
|
|
120
|
-
const
|
|
121
|
-
|
|
138
|
+
const usage1 = getUsage([], cmd);
|
|
139
|
+
expect(usage1).toStrictEqual([
|
|
140
|
+
"",
|
|
141
|
+
"{Root command title}+",
|
|
142
|
+
"{Root command description}@grey",
|
|
143
|
+
"{Second line of root command description}@grey",
|
|
144
|
+
"",
|
|
145
|
+
"{Usage:}@green+ {my-cli}@cyan+ {<POS-1>}@cyan {<POS-2>}@cyan {<SUBCOMMAND>}@cyan+",
|
|
146
|
+
"",
|
|
147
|
+
"{Arguments:}@green+",
|
|
148
|
+
" {<POS-1>}@cyan {First positional argument}+",
|
|
149
|
+
" {<POS-2>}@cyan {Second positional argument}+",
|
|
150
|
+
"",
|
|
151
|
+
"{Options:}@green+",
|
|
152
|
+
" {-b}@cyan+{,} {--boolean-flag}@cyan+{[=yes|no]}@grey {Root boolean-flag description}+",
|
|
153
|
+
" {-s}@cyan+{,} {--string-option }@cyan+{<COOL-STUFF>}@cyan {Root string-option description}+",
|
|
154
|
+
" {-n}@cyan+{,} {--number-option }@cyan+{<NUMBER>}@cyan {Root number-option description}+",
|
|
155
|
+
"",
|
|
156
|
+
"{Subcommands:}@green+",
|
|
157
|
+
" {sub1}@cyan+ {Subcommand 1 title}+",
|
|
158
|
+
" {sub2}@cyan+ {Subcommand 2 title}+",
|
|
159
|
+
"",
|
|
160
|
+
]);
|
|
122
161
|
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
162
|
+
const usage2 = getUsage(["50", "51", "sub1", "final"], cmd);
|
|
163
|
+
expect(usage2).toStrictEqual([
|
|
164
|
+
"",
|
|
165
|
+
"{Subcommand 1 title}+",
|
|
166
|
+
"{Subcommand 1 description}@grey",
|
|
167
|
+
"{Second line of subcommand 1 description}@grey",
|
|
168
|
+
"",
|
|
169
|
+
"{Usage:}@green+ {my-cli}@cyan+ {<POS-1>}@cyan {<POS-2>}@cyan {sub1}@cyan+ {<POS-STRING>}@cyan",
|
|
170
|
+
"",
|
|
171
|
+
"{Arguments:}@green+",
|
|
172
|
+
" {<POS-1>}@cyan {First positional argument}+",
|
|
173
|
+
" {<POS-2>}@cyan {Second positional argument}+",
|
|
174
|
+
" {<POS-STRING>}@cyan {Positional string argument}+",
|
|
175
|
+
"",
|
|
176
|
+
"{Options:}@green+",
|
|
177
|
+
" {-b}@cyan+{,} {--boolean-flag}@cyan+{[=yes|no]}@grey {Root boolean-flag description}+",
|
|
178
|
+
" {-s}@cyan+{,} {--string-option }@cyan+{<COOL-STUFF>}@cyan {Root string-option description}+",
|
|
179
|
+
" {-n}@cyan+{,} {--number-option }@cyan+{<NUMBER>}@cyan {Root number-option description}+",
|
|
180
|
+
"",
|
|
181
|
+
]);
|
|
126
182
|
|
|
127
|
-
const
|
|
183
|
+
const usage3 = getUsage(
|
|
128
184
|
[
|
|
129
185
|
"40",
|
|
130
186
|
"41",
|
|
@@ -141,7 +197,28 @@ it("run", async () => {
|
|
|
141
197
|
],
|
|
142
198
|
cmd,
|
|
143
199
|
);
|
|
144
|
-
|
|
200
|
+
expect(usage3).toStrictEqual([
|
|
201
|
+
"",
|
|
202
|
+
"{Subcommand 2 title}+",
|
|
203
|
+
"{Subcommand 2 description}@grey",
|
|
204
|
+
"{Second line of subcommand 2 description}@grey",
|
|
205
|
+
"",
|
|
206
|
+
"{Usage:}@green+ {my-cli}@cyan+ {<POS-1>}@cyan {<POS-2>}@cyan {sub2}@cyan+ {<POS-NUMBER>}@cyan {[OPT-POS]}@cyan {[VARIADIC-POSS...]}@cyan",
|
|
207
|
+
"",
|
|
208
|
+
"{Arguments:}@green+",
|
|
209
|
+
" {<POS-1>}@cyan {First positional argument}+",
|
|
210
|
+
" {<POS-2>}@cyan {Second positional argument}+",
|
|
211
|
+
" {<POS-NUMBER>}@cyan {Positional number argument}+",
|
|
212
|
+
" {[OPT-POS]}@cyan {Optional positional argument}+",
|
|
213
|
+
" {[VARIADIC-POSS...]}@cyan {Variadic positional arguments}+",
|
|
214
|
+
"",
|
|
215
|
+
"{Options:}@green+",
|
|
216
|
+
" {-b}@cyan+{,} {--boolean-flag}@cyan+{[=yes|no]}@grey {Root boolean-flag description}+",
|
|
217
|
+
" {-s}@cyan+{,} {--string-option }@cyan+{<COOL-STUFF>}@cyan {Root string-option description}+",
|
|
218
|
+
" {-n}@cyan+{,} {--number-option }@cyan+{<NUMBER>}@cyan {Root number-option description}+",
|
|
219
|
+
" {--dudu }@cyan+{<STRING>}@cyan {Dudu option description}+",
|
|
220
|
+
"",
|
|
221
|
+
]);
|
|
145
222
|
});
|
|
146
223
|
|
|
147
224
|
function getUsage<Context, Result>(
|
|
@@ -149,5 +226,9 @@ function getUsage<Context, Result>(
|
|
|
149
226
|
command: Command<Context, Result>,
|
|
150
227
|
) {
|
|
151
228
|
const commandRunner = command.prepareRunner(new ReaderTokenizer(argv));
|
|
152
|
-
return
|
|
229
|
+
return usageToPrintableLines({
|
|
230
|
+
cliName: "my-cli",
|
|
231
|
+
commandUsage: commandRunner.computeUsage(),
|
|
232
|
+
typoSupport: "mock",
|
|
233
|
+
});
|
|
153
234
|
}
|