cli-kiss 0.0.2 → 0.0.4
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 +52 -42
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +1 -1
- package/src/lib/Command.ts +43 -45
- package/src/lib/{Process.ts → Execution.ts} +23 -28
- package/src/lib/Grid.ts +6 -2
- package/src/lib/Run.ts +5 -6
- package/src/lib/Type.ts +27 -2
- package/src/lib/Typo.ts +16 -7
- package/src/lib/Usage.ts +63 -59
- package/tests/{unit.command.run.ts → unit.command.runArgv.ts} +18 -17
- package/tests/unit.command.usage.ts +81 -75
package/src/lib/Command.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { ArgumentUsage } from "./Argument";
|
|
2
|
+
import { Execution } from "./Execution";
|
|
2
3
|
import { OptionUsage } from "./Option";
|
|
3
|
-
import { Process } from "./Process";
|
|
4
4
|
import { ReaderTokenizer } from "./Reader";
|
|
5
5
|
|
|
6
6
|
export type Command<Context, Result> = {
|
|
7
|
-
|
|
7
|
+
getDescription(): string | undefined;
|
|
8
8
|
prepareRunner(
|
|
9
9
|
readerTokenizer: ReaderTokenizer,
|
|
10
10
|
): CommandRunner<Context, Result>;
|
|
@@ -17,54 +17,52 @@ export type CommandRunner<Context, Result> = {
|
|
|
17
17
|
|
|
18
18
|
export type CommandUsage = {
|
|
19
19
|
breadcrumbs: Array<CommandUsageBreadcrumb>;
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
description: string;
|
|
21
|
+
details: Array<string> | undefined;
|
|
22
22
|
options: Array<OptionUsage>;
|
|
23
23
|
arguments: Array<ArgumentUsage>;
|
|
24
|
-
subcommands: Array<{ name: string;
|
|
24
|
+
subcommands: Array<{ name: string; description: string | undefined }>;
|
|
25
25
|
};
|
|
26
26
|
|
|
27
|
-
export type CommandUsageBreadcrumb = {
|
|
28
|
-
kind: "command" | "argument";
|
|
29
|
-
value: string;
|
|
30
|
-
};
|
|
27
|
+
export type CommandUsageBreadcrumb = { argument: string } | { command: string };
|
|
31
28
|
|
|
32
29
|
export function command<Context, Result>(
|
|
33
30
|
metadata: {
|
|
34
|
-
|
|
35
|
-
|
|
31
|
+
description: string;
|
|
32
|
+
details?: Array<string>;
|
|
33
|
+
// TODO - examples ?
|
|
36
34
|
},
|
|
37
|
-
|
|
35
|
+
execution: Execution<Context, Result>,
|
|
38
36
|
): Command<Context, Result> {
|
|
39
37
|
return {
|
|
40
|
-
|
|
41
|
-
return metadata.
|
|
38
|
+
getDescription() {
|
|
39
|
+
return metadata.description;
|
|
42
40
|
},
|
|
43
41
|
prepareRunner(readerTokenizer: ReaderTokenizer) {
|
|
44
42
|
function computeUsage(): CommandUsage {
|
|
45
|
-
const
|
|
43
|
+
const executionUsage = execution.computeUsage();
|
|
46
44
|
return {
|
|
47
|
-
breadcrumbs:
|
|
45
|
+
breadcrumbs: executionUsage.arguments.map((argument) =>
|
|
48
46
|
breadcrumbArgument(argument.label),
|
|
49
47
|
),
|
|
50
|
-
title: metadata.title,
|
|
51
48
|
description: metadata.description,
|
|
52
|
-
|
|
53
|
-
|
|
49
|
+
details: metadata.details,
|
|
50
|
+
options: executionUsage.options,
|
|
51
|
+
arguments: executionUsage.arguments,
|
|
54
52
|
subcommands: [],
|
|
55
53
|
};
|
|
56
54
|
}
|
|
57
55
|
try {
|
|
58
|
-
const
|
|
56
|
+
const executionResolver = execution.prepareResolver(readerTokenizer);
|
|
59
57
|
const lastPositional = readerTokenizer.consumePositional();
|
|
60
58
|
if (lastPositional !== undefined) {
|
|
61
59
|
throw Error(`Unprocessed positional: ${lastPositional}`);
|
|
62
60
|
}
|
|
63
|
-
const
|
|
61
|
+
const executionCallback = executionResolver();
|
|
64
62
|
return {
|
|
65
63
|
computeUsage,
|
|
66
64
|
async execute(context: Context) {
|
|
67
|
-
return await
|
|
65
|
+
return await executionCallback(context);
|
|
68
66
|
},
|
|
69
67
|
};
|
|
70
68
|
} catch (error) {
|
|
@@ -81,19 +79,19 @@ export function command<Context, Result>(
|
|
|
81
79
|
|
|
82
80
|
export function commandWithSubcommands<Context, Payload, Result>(
|
|
83
81
|
metadata: {
|
|
84
|
-
|
|
85
|
-
|
|
82
|
+
description: string;
|
|
83
|
+
details?: Array<string>;
|
|
86
84
|
},
|
|
87
|
-
|
|
85
|
+
execution: Execution<Context, Payload>,
|
|
88
86
|
subcommands: { [subcommand: Lowercase<string>]: Command<Payload, Result> },
|
|
89
87
|
): Command<Context, Result> {
|
|
90
88
|
return {
|
|
91
|
-
|
|
92
|
-
return metadata.
|
|
89
|
+
getDescription() {
|
|
90
|
+
return metadata.description;
|
|
93
91
|
},
|
|
94
92
|
prepareRunner(readerTokenizer: ReaderTokenizer) {
|
|
95
93
|
try {
|
|
96
|
-
const
|
|
94
|
+
const executionResolver = execution.prepareResolver(readerTokenizer);
|
|
97
95
|
const subcommandName = readerTokenizer.consumePositional();
|
|
98
96
|
if (subcommandName === undefined) {
|
|
99
97
|
throw new Error("Expected a subcommand");
|
|
@@ -104,46 +102,46 @@ export function commandWithSubcommands<Context, Payload, Result>(
|
|
|
104
102
|
throw new Error(`Unknown subcommand: ${subcommandName}`);
|
|
105
103
|
}
|
|
106
104
|
const subcommandRunner = subcommandInput.prepareRunner(readerTokenizer);
|
|
107
|
-
const
|
|
105
|
+
const executionCallback = executionResolver();
|
|
108
106
|
return {
|
|
109
107
|
computeUsage() {
|
|
110
|
-
const
|
|
108
|
+
const executionUsage = execution.computeUsage();
|
|
111
109
|
const subcommandUsage = subcommandRunner.computeUsage();
|
|
112
110
|
return {
|
|
113
|
-
breadcrumbs:
|
|
111
|
+
breadcrumbs: executionUsage.arguments
|
|
114
112
|
.map((argument) => breadcrumbArgument(argument.label))
|
|
115
113
|
.concat([breadcrumbCommand(subcommandName)])
|
|
116
114
|
.concat(subcommandUsage.breadcrumbs),
|
|
117
|
-
title: subcommandUsage.title,
|
|
118
115
|
description: subcommandUsage.description,
|
|
119
|
-
|
|
120
|
-
|
|
116
|
+
details: subcommandUsage.details,
|
|
117
|
+
options: executionUsage.options.concat(subcommandUsage.options),
|
|
118
|
+
arguments: executionUsage.arguments.concat(
|
|
121
119
|
subcommandUsage.arguments,
|
|
122
120
|
),
|
|
123
121
|
subcommands: subcommandUsage.subcommands,
|
|
124
122
|
};
|
|
125
123
|
},
|
|
126
124
|
async execute(context: Context) {
|
|
127
|
-
const payload = await
|
|
125
|
+
const payload = await executionCallback(context);
|
|
128
126
|
return await subcommandRunner.execute(payload);
|
|
129
127
|
},
|
|
130
128
|
};
|
|
131
129
|
} catch (error) {
|
|
132
130
|
return {
|
|
133
131
|
computeUsage() {
|
|
134
|
-
const
|
|
132
|
+
const executionUsage = execution.computeUsage();
|
|
135
133
|
return {
|
|
136
|
-
breadcrumbs:
|
|
134
|
+
breadcrumbs: executionUsage.arguments
|
|
137
135
|
.map((argument) => breadcrumbArgument(argument.label))
|
|
138
136
|
.concat([breadcrumbCommand("<SUBCOMMAND>")]),
|
|
139
|
-
title: metadata.title,
|
|
140
137
|
description: metadata.description,
|
|
141
|
-
|
|
142
|
-
|
|
138
|
+
details: metadata.details,
|
|
139
|
+
options: executionUsage.options,
|
|
140
|
+
arguments: executionUsage.arguments,
|
|
143
141
|
subcommands: Object.entries(subcommands).map(
|
|
144
142
|
([name, subcommand]) => ({
|
|
145
143
|
name,
|
|
146
|
-
|
|
144
|
+
description: subcommand.getDescription(),
|
|
147
145
|
}),
|
|
148
146
|
),
|
|
149
147
|
};
|
|
@@ -157,10 +155,10 @@ export function commandWithSubcommands<Context, Payload, Result>(
|
|
|
157
155
|
};
|
|
158
156
|
}
|
|
159
157
|
|
|
160
|
-
function breadcrumbArgument(
|
|
161
|
-
return {
|
|
158
|
+
function breadcrumbArgument(value: string): CommandUsageBreadcrumb {
|
|
159
|
+
return { argument: value };
|
|
162
160
|
}
|
|
163
161
|
|
|
164
|
-
function breadcrumbCommand(
|
|
165
|
-
return {
|
|
162
|
+
function breadcrumbCommand(value: string): CommandUsageBreadcrumb {
|
|
163
|
+
return { command: value };
|
|
166
164
|
}
|
|
@@ -2,48 +2,45 @@ import { Argument, ArgumentUsage } from "./Argument";
|
|
|
2
2
|
import { Option, OptionUsage } from "./Option";
|
|
3
3
|
import { ReaderTokenizer } from "./Reader";
|
|
4
4
|
|
|
5
|
-
export type
|
|
6
|
-
computeUsage():
|
|
5
|
+
export type Execution<Context, Result> = {
|
|
6
|
+
computeUsage(): ExecutionUsage;
|
|
7
7
|
prepareResolver(
|
|
8
8
|
readerTokenizer: ReaderTokenizer,
|
|
9
|
-
):
|
|
9
|
+
): ExecutionResolver<Context, Result>;
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
-
export type
|
|
12
|
+
export type ExecutionResolver<Context, Result> = () => ExecutionCallback<
|
|
13
13
|
Context,
|
|
14
14
|
Result
|
|
15
15
|
>;
|
|
16
16
|
|
|
17
|
-
export type
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
export type ExecutionCallback<Context, Result> = (
|
|
18
|
+
context: Context,
|
|
19
|
+
) => Promise<Result>;
|
|
20
20
|
|
|
21
|
-
export type
|
|
21
|
+
export type ExecutionUsage = {
|
|
22
22
|
options: Array<OptionUsage>;
|
|
23
23
|
arguments: Array<ArgumentUsage>;
|
|
24
24
|
};
|
|
25
25
|
|
|
26
|
-
export function
|
|
26
|
+
export function execution<
|
|
27
27
|
Context,
|
|
28
28
|
Result,
|
|
29
|
-
Options extends { [option: string]:
|
|
30
|
-
const Arguments extends Array<
|
|
29
|
+
Options extends { [option: string]: any },
|
|
30
|
+
const Arguments extends Array<any>,
|
|
31
31
|
>(
|
|
32
|
-
inputs: {
|
|
32
|
+
inputs: {
|
|
33
|
+
options: { [K in keyof Options]: Option<Options[K]> };
|
|
34
|
+
arguments: { [K in keyof Arguments]: Argument<Arguments[K]> };
|
|
35
|
+
},
|
|
33
36
|
handler: (
|
|
34
37
|
context: Context,
|
|
35
38
|
inputs: {
|
|
36
|
-
options:
|
|
37
|
-
|
|
38
|
-
ReturnType<Options[K]["prepareConsumer"]>
|
|
39
|
-
>;
|
|
40
|
-
};
|
|
41
|
-
arguments: {
|
|
42
|
-
[K in keyof Arguments]: ReturnType<Arguments[K]["consumeValue"]>;
|
|
43
|
-
};
|
|
39
|
+
options: Options;
|
|
40
|
+
arguments: Arguments;
|
|
44
41
|
},
|
|
45
42
|
) => Promise<Result>,
|
|
46
|
-
):
|
|
43
|
+
): Execution<Context, Result> {
|
|
47
44
|
return {
|
|
48
45
|
computeUsage() {
|
|
49
46
|
const optionsUsage = new Array<OptionUsage>();
|
|
@@ -73,13 +70,11 @@ export function process<
|
|
|
73
70
|
for (const optionKey in optionsConsumers) {
|
|
74
71
|
optionsValues[optionKey] = optionsConsumers[optionKey]!();
|
|
75
72
|
}
|
|
76
|
-
return {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
});
|
|
82
|
-
},
|
|
73
|
+
return async (context: Context) => {
|
|
74
|
+
return await handler(context, {
|
|
75
|
+
options: optionsValues,
|
|
76
|
+
arguments: argumentsValues,
|
|
77
|
+
});
|
|
83
78
|
};
|
|
84
79
|
};
|
|
85
80
|
},
|
package/src/lib/Grid.ts
CHANGED
|
@@ -4,7 +4,11 @@ export type Grid = Array<GridRow>;
|
|
|
4
4
|
export type GridRow = Array<GridCell>;
|
|
5
5
|
export type GridCell = Array<TypoText>;
|
|
6
6
|
|
|
7
|
-
export function gridToPrintableLines(
|
|
7
|
+
export function gridToPrintableLines(
|
|
8
|
+
grid: Grid,
|
|
9
|
+
typoSupport: TypoSupport,
|
|
10
|
+
delimiter: string = "",
|
|
11
|
+
): Array<string> {
|
|
8
12
|
const lines = new Array<string>();
|
|
9
13
|
const gridWidths = new Array<number>();
|
|
10
14
|
for (const gridRow of grid) {
|
|
@@ -42,7 +46,7 @@ export function gridToPrintableLines(grid: Grid, typoSupport: TypoSupport) {
|
|
|
42
46
|
lineColumns.push(parts.join(""));
|
|
43
47
|
}
|
|
44
48
|
}
|
|
45
|
-
lines.push(lineColumns.join(
|
|
49
|
+
lines.push(lineColumns.join(delimiter));
|
|
46
50
|
}
|
|
47
51
|
return lines;
|
|
48
52
|
}
|
package/src/lib/Run.ts
CHANGED
|
@@ -3,18 +3,17 @@ import { ReaderTokenizer } from "./Reader";
|
|
|
3
3
|
import { typoInferSupport } from "./Typo";
|
|
4
4
|
import { usageToPrintableLines } from "./Usage";
|
|
5
5
|
|
|
6
|
-
export async function
|
|
7
|
-
|
|
6
|
+
export async function runCommand<Context, Result>(
|
|
7
|
+
cliName: Lowercase<string>,
|
|
8
|
+
cliArgs: Array<string>,
|
|
8
9
|
context: Context,
|
|
9
10
|
command: Command<Context, Result>,
|
|
10
11
|
cliInfo?: {
|
|
11
|
-
name?: Lowercase<string>;
|
|
12
12
|
version?: string;
|
|
13
13
|
helpOnError?: boolean;
|
|
14
14
|
},
|
|
15
15
|
): Promise<Result> {
|
|
16
|
-
const
|
|
17
|
-
const readerTokenizer = new ReaderTokenizer(argv.slice(2));
|
|
16
|
+
const readerTokenizer = new ReaderTokenizer(cliArgs);
|
|
18
17
|
if (cliInfo?.version) {
|
|
19
18
|
readerTokenizer.registerFlag({
|
|
20
19
|
key: "version",
|
|
@@ -65,7 +64,7 @@ export async function runWithArgv<Context, Result>(
|
|
|
65
64
|
}).join("\n"),
|
|
66
65
|
);
|
|
67
66
|
}
|
|
68
|
-
console.error(error);
|
|
67
|
+
console.error(error); // TODO - better, prettier errors
|
|
69
68
|
process.exit(1);
|
|
70
69
|
}
|
|
71
70
|
} catch (error) {
|
package/src/lib/Type.ts
CHANGED
|
@@ -48,10 +48,35 @@ export const typeBigInt: Type<bigint> = {
|
|
|
48
48
|
},
|
|
49
49
|
};
|
|
50
50
|
|
|
51
|
-
export function
|
|
51
|
+
export function typeCommaTuple<
|
|
52
|
+
const Elements extends Array<any>,
|
|
53
|
+
>(elementTypes: {
|
|
54
|
+
[K in keyof Elements]: Type<Elements[K]>;
|
|
55
|
+
}): Type<Elements> {
|
|
56
|
+
return {
|
|
57
|
+
label: elementTypes
|
|
58
|
+
.map((elementType) => elementType.label)
|
|
59
|
+
.join(",") as Uppercase<string>,
|
|
60
|
+
decoder(value: string) {
|
|
61
|
+
const parts = value.split(",", elementTypes.length);
|
|
62
|
+
if (parts.length !== elementTypes.length) {
|
|
63
|
+
throw new Error(
|
|
64
|
+
`Invalid tuple value: ${value}, expected ${elementTypes.length} parts`,
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
return parts.map((part, index) =>
|
|
68
|
+
elementTypes[index]!.decoder(part),
|
|
69
|
+
) as Elements;
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function typeCommaList<Value>(
|
|
75
|
+
elementType: Type<Value>,
|
|
76
|
+
): Type<Array<Value>> {
|
|
52
77
|
return {
|
|
53
78
|
label:
|
|
54
|
-
`${elementType.label}[
|
|
79
|
+
`${elementType.label}[,${elementType.label}...]` as Uppercase<string>,
|
|
55
80
|
decoder(value: string) {
|
|
56
81
|
return value.split(",").map(elementType.decoder);
|
|
57
82
|
},
|
package/src/lib/Typo.ts
CHANGED
|
@@ -63,11 +63,20 @@ export function typoInferSupport(): TypoSupport {
|
|
|
63
63
|
const resetCode = "\x1b[0m";
|
|
64
64
|
const boldCode = "\x1b[1m";
|
|
65
65
|
const colorCodes = {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
66
|
+
darkBlack: "\x1b[30m",
|
|
67
|
+
darkRed: "\x1b[31m",
|
|
68
|
+
darkGreen: "\x1b[32m",
|
|
69
|
+
darkYellow: "\x1b[33m",
|
|
70
|
+
darkBlue: "\x1b[34m",
|
|
71
|
+
darkMagenta: "\x1b[35m",
|
|
72
|
+
darkCyan: "\x1b[36m",
|
|
73
|
+
darkWhite: "\x1b[37m",
|
|
74
|
+
brightBlack: "\x1b[90m",
|
|
75
|
+
brightRed: "\x1b[91m",
|
|
76
|
+
brightGreen: "\x1b[92m",
|
|
77
|
+
brightYellow: "\x1b[93m",
|
|
78
|
+
brightBlue: "\x1b[94m",
|
|
79
|
+
brightMagenta: "\x1b[95m",
|
|
80
|
+
brightCyan: "\x1b[96m",
|
|
81
|
+
brightWhite: "\x1b[97m",
|
|
73
82
|
};
|
package/src/lib/Usage.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { GridCell, GridRow, gridToPrintableLines } from "./Grid";
|
|
|
3
3
|
import { TypoSupport, TypoText, typoPrintableString } from "./Typo";
|
|
4
4
|
|
|
5
5
|
export function usageToPrintableLines(params: {
|
|
6
|
-
cliName: string
|
|
6
|
+
cliName: Lowercase<string>;
|
|
7
7
|
commandUsage: CommandUsage;
|
|
8
8
|
typoSupport: TypoSupport;
|
|
9
9
|
}) {
|
|
@@ -11,42 +11,61 @@ export function usageToPrintableLines(params: {
|
|
|
11
11
|
|
|
12
12
|
const lines = new Array<string>();
|
|
13
13
|
|
|
14
|
-
lines.push(
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
lines.push(
|
|
15
|
+
typoPrintableString(typoSupport, textDescription(commandUsage.description)),
|
|
16
|
+
);
|
|
17
|
+
if (commandUsage.details) {
|
|
18
|
+
for (const detailLine of commandUsage.details) {
|
|
19
|
+
lines.push(typoPrintableString(typoSupport, textDetails(detailLine)));
|
|
19
20
|
}
|
|
20
21
|
}
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
typoPrintableString(typoSupport, textName(cliName)),
|
|
23
|
+
lines.push("");
|
|
24
|
+
lines.push(typoPrintableString(typoSupport, textCategory("Usage:")));
|
|
25
|
+
const breadcrumbs = [
|
|
26
|
+
" ",
|
|
27
|
+
typoPrintableString(typoSupport, textFixed(cliName)),
|
|
28
28
|
].concat(
|
|
29
29
|
commandUsage.breadcrumbs.map((breadcrumb) => {
|
|
30
|
-
if (
|
|
31
|
-
return typoPrintableString(typoSupport,
|
|
30
|
+
if ("argument" in breadcrumb) {
|
|
31
|
+
return typoPrintableString(typoSupport, textInput(breadcrumb.argument));
|
|
32
|
+
}
|
|
33
|
+
if ("command" in breadcrumb) {
|
|
34
|
+
return typoPrintableString(typoSupport, textFixed(breadcrumb.command));
|
|
32
35
|
}
|
|
33
|
-
|
|
36
|
+
throw new Error(`Unknown breadcrumb: ${JSON.stringify(breadcrumb)}`);
|
|
34
37
|
}),
|
|
35
38
|
);
|
|
36
|
-
lines.push("");
|
|
37
|
-
lines.push(`${breadcrumbPrefix} ${breadcrumbsItems.join(" ")}`);
|
|
39
|
+
lines.push(breadcrumbs.join(" "));
|
|
38
40
|
|
|
39
41
|
if (commandUsage.arguments.length > 0) {
|
|
40
42
|
lines.push("");
|
|
41
|
-
lines.push(typoPrintableString(typoSupport,
|
|
43
|
+
lines.push(typoPrintableString(typoSupport, textCategory("Arguments:")));
|
|
42
44
|
const grid = new Array<GridRow>();
|
|
43
45
|
for (const argumentUsage of commandUsage.arguments) {
|
|
44
46
|
const gridRow = new Array<GridCell>();
|
|
45
|
-
gridRow.push([]);
|
|
46
|
-
gridRow.push([
|
|
47
|
-
gridRow.push([]);
|
|
47
|
+
gridRow.push([textDelimiter()]);
|
|
48
|
+
gridRow.push([textInput(argumentUsage.label)]);
|
|
48
49
|
if (argumentUsage.description) {
|
|
49
|
-
gridRow.push([
|
|
50
|
+
gridRow.push([textDelimiter()]);
|
|
51
|
+
gridRow.push([textDescription(argumentUsage.description)]);
|
|
52
|
+
}
|
|
53
|
+
grid.push(gridRow);
|
|
54
|
+
}
|
|
55
|
+
lines.push(...gridToPrintableLines(grid, typoSupport));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (commandUsage.subcommands.length > 0) {
|
|
59
|
+
lines.push("");
|
|
60
|
+
lines.push(typoPrintableString(typoSupport, textCategory("Subcommands:")));
|
|
61
|
+
const grid = new Array<GridRow>();
|
|
62
|
+
for (const subcommand of commandUsage.subcommands) {
|
|
63
|
+
const gridRow = new Array<GridCell>();
|
|
64
|
+
gridRow.push([textDelimiter()]);
|
|
65
|
+
gridRow.push([textFixed(subcommand.name)]);
|
|
66
|
+
if (subcommand.description) {
|
|
67
|
+
gridRow.push([textDelimiter()]);
|
|
68
|
+
gridRow.push([textDescription(subcommand.description)]);
|
|
50
69
|
}
|
|
51
70
|
grid.push(gridRow);
|
|
52
71
|
}
|
|
@@ -55,72 +74,57 @@ export function usageToPrintableLines(params: {
|
|
|
55
74
|
|
|
56
75
|
if (commandUsage.options.length > 0) {
|
|
57
76
|
lines.push("");
|
|
58
|
-
lines.push(typoPrintableString(typoSupport,
|
|
77
|
+
lines.push(typoPrintableString(typoSupport, textCategory("Options:")));
|
|
59
78
|
const grid = new Array<GridRow>();
|
|
60
79
|
for (const optionUsage of commandUsage.options) {
|
|
61
80
|
const gridRow = new Array<GridCell>();
|
|
62
|
-
gridRow.push([]);
|
|
81
|
+
gridRow.push([textDelimiter()]);
|
|
63
82
|
if (optionUsage.short) {
|
|
64
|
-
gridRow.push([
|
|
83
|
+
gridRow.push([textFixed(`-${optionUsage.short}`), { value: ", " }]);
|
|
65
84
|
} else {
|
|
66
85
|
gridRow.push([]);
|
|
67
86
|
}
|
|
68
87
|
if (optionUsage.label) {
|
|
69
88
|
gridRow.push([
|
|
70
|
-
|
|
71
|
-
|
|
89
|
+
textFixed(`--${optionUsage.long} `),
|
|
90
|
+
textInput(optionUsage.label),
|
|
72
91
|
]);
|
|
73
92
|
} else {
|
|
74
|
-
gridRow.push([
|
|
75
|
-
textName(`--${optionUsage.long}`),
|
|
76
|
-
{ value: "[=yes|no]", color: "grey" },
|
|
77
|
-
]);
|
|
93
|
+
gridRow.push([textFixed(`--${optionUsage.long}`)]);
|
|
78
94
|
}
|
|
79
|
-
gridRow.push([]);
|
|
80
95
|
if (optionUsage.description) {
|
|
81
|
-
gridRow.push([
|
|
96
|
+
gridRow.push([textDelimiter()]);
|
|
97
|
+
gridRow.push([textDescription(optionUsage.description)]);
|
|
82
98
|
}
|
|
83
99
|
grid.push(gridRow);
|
|
84
100
|
}
|
|
85
101
|
lines.push(...gridToPrintableLines(grid, typoSupport));
|
|
86
102
|
}
|
|
87
103
|
|
|
88
|
-
if (commandUsage.subcommands.length > 0) {
|
|
89
|
-
lines.push("");
|
|
90
|
-
lines.push(typoPrintableString(typoSupport, textTitle("Subcommands:")));
|
|
91
|
-
const grid = new Array<GridRow>();
|
|
92
|
-
for (const subcommand of commandUsage.subcommands) {
|
|
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)]);
|
|
99
|
-
}
|
|
100
|
-
grid.push(gridRow);
|
|
101
|
-
}
|
|
102
|
-
lines.push(...gridToPrintableLines(grid, typoSupport));
|
|
103
|
-
}
|
|
104
104
|
lines.push("");
|
|
105
105
|
return lines;
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
function
|
|
109
|
-
return { value: text, color: "
|
|
108
|
+
function textCategory(text: string): TypoText {
|
|
109
|
+
return { value: text, color: "brightGreen", bold: true };
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
-
function
|
|
113
|
-
return { value: text,
|
|
112
|
+
function textDescription(text: string): TypoText {
|
|
113
|
+
return { value: text, bold: true };
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
function
|
|
117
|
-
return { value: text,
|
|
116
|
+
function textDetails(text: string): TypoText {
|
|
117
|
+
return { value: text, color: "brightBlack" };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function textFixed(text: string): TypoText {
|
|
121
|
+
return { value: text, color: "brightCyan", bold: true };
|
|
118
122
|
}
|
|
119
123
|
|
|
120
|
-
function
|
|
121
|
-
return { value: text, color: "
|
|
124
|
+
function textInput(text: string): TypoText {
|
|
125
|
+
return { value: text, color: "brightBlue" };
|
|
122
126
|
}
|
|
123
127
|
|
|
124
|
-
function
|
|
125
|
-
return { value:
|
|
128
|
+
function textDelimiter(): TypoText {
|
|
129
|
+
return { value: " " };
|
|
126
130
|
}
|
|
@@ -5,18 +5,19 @@ import {
|
|
|
5
5
|
argumentVariadics,
|
|
6
6
|
command,
|
|
7
7
|
commandWithSubcommands,
|
|
8
|
+
execution,
|
|
8
9
|
optionFlag,
|
|
9
10
|
optionRepeatable,
|
|
10
11
|
optionSingleValue,
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
runCommand,
|
|
13
|
+
typeCommaList,
|
|
13
14
|
typeNumber,
|
|
14
15
|
typeString,
|
|
15
16
|
} from "../src";
|
|
16
17
|
|
|
17
18
|
const cmd = commandWithSubcommands<string, any, any>(
|
|
18
|
-
{
|
|
19
|
-
|
|
19
|
+
{ description: "Root command description" },
|
|
20
|
+
execution(
|
|
20
21
|
{
|
|
21
22
|
options: {
|
|
22
23
|
booleanFlag: optionFlag({ long: "boolean-flag", default: () => false }),
|
|
@@ -27,7 +28,7 @@ const cmd = commandWithSubcommands<string, any, any>(
|
|
|
27
28
|
}),
|
|
28
29
|
numberOption: optionRepeatable({
|
|
29
30
|
long: "number-option",
|
|
30
|
-
type: typeNumber,
|
|
31
|
+
type: typeCommaList(typeNumber),
|
|
31
32
|
}),
|
|
32
33
|
},
|
|
33
34
|
arguments: [
|
|
@@ -41,8 +42,8 @@ const cmd = commandWithSubcommands<string, any, any>(
|
|
|
41
42
|
),
|
|
42
43
|
{
|
|
43
44
|
sub1: command(
|
|
44
|
-
{
|
|
45
|
-
|
|
45
|
+
{ description: "Subcommand 1 description" },
|
|
46
|
+
execution(
|
|
46
47
|
{
|
|
47
48
|
options: {},
|
|
48
49
|
arguments: [argumentRequired({ type: typeString })],
|
|
@@ -53,8 +54,8 @@ const cmd = commandWithSubcommands<string, any, any>(
|
|
|
53
54
|
),
|
|
54
55
|
),
|
|
55
56
|
sub2: command(
|
|
56
|
-
{
|
|
57
|
-
|
|
57
|
+
{ description: "Subcommand 2 description" },
|
|
58
|
+
execution(
|
|
58
59
|
{
|
|
59
60
|
options: {},
|
|
60
61
|
arguments: [
|
|
@@ -72,8 +73,9 @@ const cmd = commandWithSubcommands<string, any, any>(
|
|
|
72
73
|
);
|
|
73
74
|
|
|
74
75
|
it("run", async () => {
|
|
75
|
-
const res1 = await
|
|
76
|
-
|
|
76
|
+
const res1 = await runCommand(
|
|
77
|
+
"script",
|
|
78
|
+
["50", "51", "sub1", "final"],
|
|
77
79
|
"Run Context Input",
|
|
78
80
|
cmd,
|
|
79
81
|
);
|
|
@@ -97,18 +99,17 @@ it("run", async () => {
|
|
|
97
99
|
at: "sub1",
|
|
98
100
|
});
|
|
99
101
|
|
|
100
|
-
const res2 = await
|
|
102
|
+
const res2 = await runCommand(
|
|
103
|
+
"script",
|
|
101
104
|
[
|
|
102
|
-
"node",
|
|
103
|
-
"script",
|
|
104
105
|
"40",
|
|
105
106
|
"41",
|
|
106
107
|
"sub2",
|
|
107
108
|
"--string-option=hello",
|
|
108
109
|
"--number-option",
|
|
109
|
-
"123",
|
|
110
|
+
"123.1,123.2",
|
|
110
111
|
"--number-option",
|
|
111
|
-
"
|
|
112
|
+
"123.3",
|
|
112
113
|
"88.88",
|
|
113
114
|
"a,b",
|
|
114
115
|
"final",
|
|
@@ -124,7 +125,7 @@ it("run", async () => {
|
|
|
124
125
|
options: {
|
|
125
126
|
booleanFlag: true,
|
|
126
127
|
stringOption: "hello",
|
|
127
|
-
numberOption: [123,
|
|
128
|
+
numberOption: [[123.1, 123.2], [123.3]],
|
|
128
129
|
},
|
|
129
130
|
arguments: [40, 41],
|
|
130
131
|
},
|