cli-kiss 0.0.1

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.
@@ -0,0 +1,104 @@
1
+ import { CommandUsage } from "./Command";
2
+
3
+ export function usageFormatter(
4
+ cliName: string,
5
+ commandUsage: CommandUsage,
6
+ ): string {
7
+ const lines = new Array<string>();
8
+ if (commandUsage.description) {
9
+ lines.push("");
10
+ lines.push(commandUsage.description);
11
+ }
12
+ lines.push("");
13
+ lines.push(`Usage: ${cliName} ${commandUsage.breadcrumbs.join(" ")}`);
14
+ if (commandUsage.arguments.length > 0) {
15
+ lines.push("");
16
+ lines.push("Arguments:");
17
+ lines.push("");
18
+ const rows = new Array<Array<string>>();
19
+ for (const argumentUsage of commandUsage.arguments) {
20
+ const columns = new Array<string>();
21
+ columns.push("");
22
+ columns.push(argumentUsage.label);
23
+ columns.push("");
24
+ if (argumentUsage.description) {
25
+ columns.push(argumentUsage.description);
26
+ }
27
+ rows.push(columns);
28
+ }
29
+ pushGrid(lines, rows);
30
+ }
31
+ if (commandUsage.options.length > 0) {
32
+ lines.push("");
33
+ lines.push("Options:");
34
+ lines.push("");
35
+ const rows = new Array<Array<string>>();
36
+ for (const optionUsage of commandUsage.options) {
37
+ const columns = new Array<string>();
38
+ columns.push("");
39
+ if (optionUsage.short) {
40
+ columns.push(`-${optionUsage.short},`);
41
+ } else {
42
+ columns.push("");
43
+ }
44
+ if (optionUsage.label) {
45
+ columns.push(`--${optionUsage.long} ${optionUsage.label}`);
46
+ } else {
47
+ columns.push(`--${optionUsage.long}`);
48
+ }
49
+ columns.push("");
50
+ if (optionUsage.description) {
51
+ columns.push(optionUsage.description);
52
+ }
53
+ rows.push(columns);
54
+ }
55
+ pushGrid(lines, rows);
56
+ }
57
+ if (commandUsage.subcommands.length > 0) {
58
+ lines.push("");
59
+ lines.push("Subcommands:");
60
+ lines.push("");
61
+ const rows = new Array<Array<string>>();
62
+ for (const subcommand of commandUsage.subcommands) {
63
+ const columns = new Array<string>();
64
+ columns.push("");
65
+ columns.push(subcommand.name);
66
+ columns.push("");
67
+ if (subcommand.description) {
68
+ columns.push(subcommand.description);
69
+ }
70
+ rows.push(columns);
71
+ }
72
+ pushGrid(lines, rows);
73
+ }
74
+ lines.push("");
75
+ return lines.join("\n");
76
+ }
77
+
78
+ function pushGrid(lines: Array<string>, rows: Array<Array<string>>) {
79
+ const widths = new Array<number>();
80
+ for (const row of rows) {
81
+ for (let columnIndex = 0; columnIndex < row.length; columnIndex++) {
82
+ const cell = row[columnIndex]!;
83
+ if (
84
+ widths[columnIndex] === undefined ||
85
+ cell.length > widths[columnIndex]!
86
+ ) {
87
+ widths[columnIndex] = cell.length;
88
+ }
89
+ }
90
+ }
91
+ for (const row of rows) {
92
+ const cells = new Array<string>();
93
+ for (let columnIndex = 0; columnIndex < row.length; columnIndex++) {
94
+ const cell = row[columnIndex]!;
95
+ if (columnIndex < row.length - 1) {
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
+ }
104
+ }
@@ -0,0 +1,46 @@
1
+ import { expect, it } from "@jest/globals";
2
+ import { ReaderTokenizer } from "../src";
3
+
4
+ it("run", async () => {
5
+ const readerTokenizer = new ReaderTokenizer([
6
+ "--option=1.1",
7
+ "--option-alias1=1.2",
8
+ "--option-alias2",
9
+ "1.3",
10
+ "-pts=1.4",
11
+ "-o",
12
+ "1.5",
13
+ "--flag-alias",
14
+ "-fa2",
15
+ ]);
16
+
17
+ readerTokenizer.registerOption({
18
+ key: "option",
19
+ longs: ["option", "option-alias1", "option-alias2"],
20
+ shorts: ["pts", "o"],
21
+ });
22
+
23
+ readerTokenizer.registerFlag({
24
+ key: "flag1",
25
+ longs: ["flag1", "flag-alias"],
26
+ shorts: [],
27
+ });
28
+ readerTokenizer.registerFlag({
29
+ key: "flag2",
30
+ longs: ["flag2"],
31
+ shorts: ["fa2"],
32
+ });
33
+
34
+ expect(readerTokenizer.consumePositional()).toStrictEqual(undefined);
35
+
36
+ expect(readerTokenizer.consumeOption("option")).toStrictEqual([
37
+ "1.1",
38
+ "1.2",
39
+ "1.3",
40
+ "1.4",
41
+ "1.5",
42
+ ]);
43
+
44
+ expect(readerTokenizer.consumeFlag("flag1")).toStrictEqual(true);
45
+ expect(readerTokenizer.consumeFlag("flag2")).toStrictEqual(true);
46
+ });
@@ -0,0 +1,124 @@
1
+ import { expect, it } from "@jest/globals";
2
+ import { ReaderTokenizer } from "../src";
3
+
4
+ it("run", async () => {
5
+ const stream = new ReaderTokenizer([
6
+ "positional-0",
7
+ "--flag-normal",
8
+ "--flag-positive=true",
9
+ "--flag-negative=false",
10
+ "positional-1",
11
+ "--option-split",
12
+ "1.1",
13
+ "--option-split",
14
+ "1.2",
15
+ "--option-join=2",
16
+ "-ab",
17
+ "3.1",
18
+ "-cd=4.1",
19
+ "-ef5.1",
20
+ "positional-2",
21
+ "-gh=false",
22
+ "-ij=true",
23
+ "-b",
24
+ "3.2",
25
+ "-d=4.2",
26
+ "-f5.2",
27
+ "positional-3",
28
+ "--",
29
+ "--not-a-flag",
30
+ "-mn",
31
+ "--",
32
+ "positional-4",
33
+ ]);
34
+
35
+ expect(stream.consumePositional()).toStrictEqual("positional-0");
36
+
37
+ stream.registerFlag({
38
+ key: "flag-normal",
39
+ longs: ["flag-normal"],
40
+ shorts: [],
41
+ });
42
+ stream.registerFlag({
43
+ key: "flag-positive",
44
+ longs: ["flag-positive"],
45
+ shorts: [],
46
+ });
47
+ stream.registerFlag({
48
+ key: "flag-negative",
49
+ longs: ["flag-negative"],
50
+ shorts: [],
51
+ });
52
+ stream.registerFlag({
53
+ key: "flag-unset",
54
+ longs: ["flag-unset"],
55
+ shorts: [],
56
+ });
57
+
58
+ expect(stream.consumePositional()).toStrictEqual("positional-1");
59
+
60
+ stream.registerOption({
61
+ key: "option-split",
62
+ longs: ["option-split"],
63
+ shorts: [],
64
+ });
65
+ stream.registerOption({
66
+ key: "option-join",
67
+ longs: ["option-join"],
68
+ shorts: [],
69
+ });
70
+ stream.registerOption({
71
+ key: "option-unset",
72
+ longs: ["option-unset"],
73
+ shorts: [],
74
+ });
75
+
76
+ stream.registerFlag({ key: "a", longs: [], shorts: ["a"] });
77
+ stream.registerOption({ key: "b", longs: [], shorts: ["b"] });
78
+
79
+ stream.registerFlag({ key: "c", longs: [], shorts: ["c"] });
80
+ stream.registerOption({ key: "d", longs: [], shorts: ["d"] });
81
+
82
+ stream.registerFlag({ key: "e", longs: [], shorts: ["e"] });
83
+ stream.registerOption({ key: "f", longs: [], shorts: ["f"] });
84
+
85
+ expect(stream.consumePositional()).toStrictEqual("positional-2");
86
+
87
+ stream.registerFlag({ key: "g", longs: [], shorts: ["g"] });
88
+ stream.registerFlag({ key: "h", longs: [], shorts: ["h"] });
89
+
90
+ stream.registerFlag({ key: "i", longs: [], shorts: ["i"] });
91
+ stream.registerFlag({ key: "j", longs: [], shorts: ["j"] });
92
+
93
+ expect(stream.consumePositional()).toStrictEqual("positional-3");
94
+
95
+ expect(stream.consumePositional()).toStrictEqual("--not-a-flag");
96
+ expect(stream.consumePositional()).toStrictEqual("-mn");
97
+ expect(stream.consumePositional()).toStrictEqual("--");
98
+ expect(stream.consumePositional()).toStrictEqual("positional-4");
99
+ expect(stream.consumePositional()).toStrictEqual(undefined);
100
+
101
+ expect(stream.consumeFlag("flag-normal")).toStrictEqual(true);
102
+ expect(stream.consumeFlag("flag-positive")).toStrictEqual(true);
103
+ expect(stream.consumeFlag("flag-negative")).toStrictEqual(false);
104
+ expect(stream.consumeFlag("flag-unset")).toStrictEqual(undefined);
105
+
106
+ expect(stream.consumeOption("option-unset")).toStrictEqual([]);
107
+ expect(stream.consumeOption("option-split")).toStrictEqual(["1.1", "1.2"]);
108
+ expect(stream.consumeOption("option-join")).toStrictEqual(["2"]);
109
+
110
+ expect(stream.consumeFlag("a")).toStrictEqual(true);
111
+ expect(stream.consumeOption("b")).toStrictEqual(["3.1", "3.2"]);
112
+
113
+ expect(stream.consumeFlag("c")).toStrictEqual(true);
114
+ expect(stream.consumeOption("d")).toStrictEqual(["4.1", "4.2"]);
115
+
116
+ expect(stream.consumeFlag("e")).toStrictEqual(true);
117
+ expect(stream.consumeOption("f")).toStrictEqual(["5.1", "5.2"]);
118
+
119
+ expect(stream.consumeFlag("g")).toStrictEqual(true);
120
+ expect(stream.consumeFlag("h")).toStrictEqual(false);
121
+
122
+ expect(stream.consumeFlag("i")).toStrictEqual(true);
123
+ expect(stream.consumeFlag("j")).toStrictEqual(true);
124
+ });
@@ -0,0 +1,86 @@
1
+ import { expect, it } from "@jest/globals";
2
+ import { ReaderTokenizer } from "../src";
3
+
4
+ it("run", async () => {
5
+ const stream = new ReaderTokenizer([
6
+ "positional-0",
7
+ "-aasof-normal",
8
+ "-bbsof-positive=true",
9
+ "-ccsof-negative=false",
10
+ "positional-1",
11
+ "-ddsov-split",
12
+ "1.1",
13
+ "-eesov-split",
14
+ "1.2",
15
+ "-ffsov-join=2",
16
+ "positional-2",
17
+ ]);
18
+
19
+ expect(stream.consumePositional()).toStrictEqual("positional-0");
20
+
21
+ stream.registerFlag({
22
+ key: "sof-normal",
23
+ shorts: ["sof-normal"],
24
+ longs: [],
25
+ });
26
+ stream.registerFlag({
27
+ key: "sof-positive",
28
+ longs: [],
29
+ shorts: ["sof-positive"],
30
+ });
31
+ stream.registerFlag({
32
+ key: "sof-negative",
33
+ longs: [],
34
+ shorts: ["sof-negative"],
35
+ });
36
+ stream.registerFlag({
37
+ key: "sof-unset",
38
+ longs: [],
39
+ shorts: ["sof-unset"],
40
+ });
41
+
42
+ stream.registerFlag({ key: "aa", longs: [], shorts: ["aa"] });
43
+ stream.registerFlag({ key: "bb", longs: [], shorts: ["bb"] });
44
+ stream.registerFlag({ key: "cc", longs: [], shorts: ["cc"] });
45
+
46
+ expect(stream.consumePositional()).toStrictEqual("positional-1");
47
+
48
+ stream.registerOption({
49
+ key: "sov-split",
50
+ longs: [],
51
+ shorts: ["sov-split"],
52
+ });
53
+ stream.registerOption({
54
+ key: "sov-join",
55
+ longs: [],
56
+ shorts: ["sov-join"],
57
+ });
58
+ stream.registerOption({
59
+ key: "sov-unset",
60
+ longs: [],
61
+ shorts: ["sov-unset"],
62
+ });
63
+
64
+ stream.registerFlag({ key: "dd", longs: [], shorts: ["dd"] });
65
+ stream.registerFlag({ key: "ee", longs: [], shorts: ["ee"] });
66
+ stream.registerFlag({ key: "ff", longs: [], shorts: ["ff"] });
67
+
68
+ expect(stream.consumePositional()).toStrictEqual("positional-2");
69
+
70
+ expect(stream.consumeFlag("sof-normal")).toStrictEqual(true);
71
+ expect(stream.consumeFlag("sof-positive")).toStrictEqual(true);
72
+ expect(stream.consumeFlag("sof-negative")).toStrictEqual(false);
73
+ expect(stream.consumeFlag("sof-unset")).toStrictEqual(undefined);
74
+
75
+ expect(stream.consumeFlag("aa")).toStrictEqual(true);
76
+ expect(stream.consumeFlag("bb")).toStrictEqual(true);
77
+ expect(stream.consumeFlag("cc")).toStrictEqual(true);
78
+
79
+ expect(stream.consumeOption("sov-unset")).toStrictEqual([]);
80
+ expect(stream.consumeOption("sov-split")).toStrictEqual(["1.1", "1.2"]);
81
+ expect(stream.consumeOption("sov-join")).toStrictEqual(["2"]);
82
+
83
+ expect(stream.consumeFlag("dd")).toStrictEqual(true);
84
+ expect(stream.consumeFlag("ee")).toStrictEqual(true);
85
+ expect(stream.consumeFlag("ff")).toStrictEqual(true);
86
+ });
@@ -0,0 +1,139 @@
1
+ import { expect, it } from "@jest/globals";
2
+ import {
3
+ argumentOptional,
4
+ argumentRequired,
5
+ argumentVariadics,
6
+ command,
7
+ commandWithSubcommands,
8
+ optionFlag,
9
+ optionRepeatable,
10
+ optionSingleValue,
11
+ processor,
12
+ runWithArgv,
13
+ typeNumber,
14
+ typeString,
15
+ } from "../src";
16
+
17
+ const cmd = commandWithSubcommands<string, any, any>(
18
+ "Root command description",
19
+ processor(
20
+ {
21
+ options: {
22
+ booleanFlag: optionFlag({ long: "boolean-flag", default: () => false }),
23
+ stringOption: optionSingleValue({
24
+ long: "string-option",
25
+ type: typeString,
26
+ default: () => undefined,
27
+ }),
28
+ numberOption: optionRepeatable({
29
+ long: "number-option",
30
+ type: typeNumber,
31
+ }),
32
+ },
33
+ arguments: [
34
+ argumentRequired({ type: typeNumber }),
35
+ argumentRequired({ type: typeNumber }),
36
+ ],
37
+ },
38
+ async (context, inputs) => {
39
+ return { at: "root", context, inputs };
40
+ },
41
+ ),
42
+ {
43
+ sub1: command(
44
+ "Subcommand 1 description",
45
+ processor(
46
+ {
47
+ options: {},
48
+ arguments: [argumentRequired({ type: typeString })],
49
+ },
50
+ async (context, inputs) => {
51
+ return { at: "sub1", context, inputs };
52
+ },
53
+ ),
54
+ ),
55
+ sub2: command(
56
+ "Subcommand 2 description",
57
+ processor(
58
+ {
59
+ options: {},
60
+ arguments: [
61
+ argumentRequired({ type: typeNumber }),
62
+ argumentOptional({ type: typeString, default: () => "42" }),
63
+ argumentVariadics({ type: typeString }),
64
+ ],
65
+ },
66
+ async (context, inputs) => {
67
+ return { at: "sub2", context, inputs };
68
+ },
69
+ ),
70
+ ),
71
+ },
72
+ );
73
+
74
+ it("run", async () => {
75
+ const res1 = await runWithArgv(
76
+ ["node", "script", "50", "51", "sub1", "final"],
77
+ "Run Context Input",
78
+ cmd,
79
+ );
80
+ expect(res1).toStrictEqual({
81
+ context: {
82
+ context: "Run Context Input",
83
+ inputs: {
84
+ options: {
85
+ booleanFlag: false,
86
+ stringOption: undefined,
87
+ numberOption: [],
88
+ },
89
+ arguments: [50, 51],
90
+ },
91
+ at: "root",
92
+ },
93
+ inputs: {
94
+ options: {},
95
+ arguments: ["final"],
96
+ },
97
+ at: "sub1",
98
+ });
99
+
100
+ const res2 = await runWithArgv(
101
+ [
102
+ "node",
103
+ "script",
104
+ "40",
105
+ "41",
106
+ "sub2",
107
+ "--string-option=hello",
108
+ "--number-option",
109
+ "123",
110
+ "--number-option",
111
+ "1234",
112
+ "88.88",
113
+ "a,b",
114
+ "final",
115
+ "--boolean-flag",
116
+ ],
117
+ "Run Context Input",
118
+ cmd,
119
+ );
120
+ expect(res2).toStrictEqual({
121
+ context: {
122
+ context: "Run Context Input",
123
+ inputs: {
124
+ options: {
125
+ booleanFlag: true,
126
+ stringOption: "hello",
127
+ numberOption: [123, 1234],
128
+ },
129
+ arguments: [40, 41],
130
+ },
131
+ at: "root",
132
+ },
133
+ inputs: {
134
+ options: {},
135
+ arguments: [88.88, "a,b", ["final"]],
136
+ },
137
+ at: "sub2",
138
+ });
139
+ });
@@ -0,0 +1,153 @@
1
+ import { it } from "@jest/globals";
2
+ import {
3
+ argumentOptional,
4
+ argumentRequired,
5
+ argumentVariadics,
6
+ Command,
7
+ command,
8
+ commandWithSubcommands,
9
+ optionFlag,
10
+ optionRepeatable,
11
+ optionSingleValue,
12
+ processor,
13
+ ReaderTokenizer,
14
+ typeNumber,
15
+ typeString,
16
+ } from "../src";
17
+ import { usageFormatter } from "../src/lib/Usage";
18
+
19
+ const cmd = commandWithSubcommands<string, any, any>(
20
+ "Root command description",
21
+ processor(
22
+ {
23
+ options: {
24
+ booleanFlag: optionFlag({
25
+ short: "b",
26
+ long: "boolean-flag",
27
+ description: "Root boolean-flag description",
28
+ }),
29
+ stringOption: optionSingleValue({
30
+ short: "s",
31
+ long: "string-option",
32
+ type: typeString,
33
+ default: () => undefined,
34
+ label: "COOL_STUFF",
35
+ description: "Root string-option description",
36
+ }),
37
+ numberOption: optionRepeatable({
38
+ short: "n",
39
+ long: "number-option",
40
+ type: typeNumber,
41
+ description: "Root number-option description",
42
+ }),
43
+ },
44
+ arguments: [
45
+ argumentRequired({
46
+ label: "POSITIONAL-1",
47
+ description: "First positional argument",
48
+ type: typeNumber,
49
+ }),
50
+ argumentRequired({
51
+ label: "POSITIONAL-2",
52
+ description: "Second positional argument",
53
+ type: typeNumber,
54
+ }),
55
+ ],
56
+ },
57
+ async (context, inputs) => {
58
+ return { at: "root", context, inputs };
59
+ },
60
+ ),
61
+ {
62
+ sub1: command(
63
+ "Subcommand 1 description",
64
+ processor(
65
+ {
66
+ options: {},
67
+ arguments: [
68
+ argumentRequired({
69
+ label: "POS-STRING",
70
+ description: "Positional string argument",
71
+ type: typeString,
72
+ }),
73
+ ],
74
+ },
75
+ async (context, inputs) => {
76
+ return { at: "sub1", context, inputs };
77
+ },
78
+ ),
79
+ ),
80
+ sub2: command(
81
+ "Subcommand 2 description",
82
+ processor(
83
+ {
84
+ options: {
85
+ duduValue: optionSingleValue({
86
+ long: "dudu",
87
+ type: typeString,
88
+ default: () => "duduDefault",
89
+ description: "Dudu option description",
90
+ }),
91
+ },
92
+ arguments: [
93
+ argumentRequired({
94
+ label: "POS-NUMBER",
95
+ description: "Positional number argument",
96
+ type: typeNumber,
97
+ }),
98
+ argumentOptional({
99
+ label: "OPT-POSITIONAL",
100
+ description: "Optional positional argument",
101
+ type: typeString,
102
+ default: () => "42",
103
+ }),
104
+ argumentVariadics({
105
+ label: "VARIADIC-POSITIONALS",
106
+ description: "Variadic positional arguments",
107
+ type: typeString,
108
+ }),
109
+ ],
110
+ },
111
+ async (context, inputs) => {
112
+ return { at: "sub2", context, inputs };
113
+ },
114
+ ),
115
+ ),
116
+ },
117
+ );
118
+
119
+ it("run", async () => {
120
+ const res0 = getUsage([], cmd);
121
+ console.log(res0);
122
+
123
+ const res1 = getUsage(["50", "51", "sub1", "final"], cmd);
124
+ console.log(res1);
125
+ //expect(res1).toStrictEqual({});
126
+
127
+ const res2 = getUsage(
128
+ [
129
+ "40",
130
+ "41",
131
+ "sub2",
132
+ "--string-option=hello",
133
+ "--number-option",
134
+ "123",
135
+ "--number-option",
136
+ "1234",
137
+ "88.88",
138
+ "a,b",
139
+ "final",
140
+ "--boolean-flag",
141
+ ],
142
+ cmd,
143
+ );
144
+ console.log(res2);
145
+ });
146
+
147
+ function getUsage<Context, Result>(
148
+ argv: Array<string>,
149
+ command: Command<Context, Result>,
150
+ ) {
151
+ const commandRunner = command.prepareRunner(new ReaderTokenizer(argv));
152
+ return usageFormatter("my-cli", commandRunner.computeUsage());
153
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "outDir": "dist",
7
+ "declaration": true,
8
+ "resolveJsonModule": true,
9
+ "esModuleInterop": true,
10
+ "strict": true,
11
+ "alwaysStrict": true,
12
+ "noImplicitOverride": true,
13
+ "noImplicitAny": true,
14
+ "noImplicitThis": true,
15
+ "noImplicitReturns": true,
16
+ "noPropertyAccessFromIndexSignature": true,
17
+ "noUnusedLocals": true,
18
+ "noUnusedParameters": true,
19
+ "noFallthroughCasesInSwitch": true,
20
+ "noUncheckedIndexedAccess": true,
21
+ "exactOptionalPropertyTypes": true,
22
+ "useUnknownInCatchVariables": true,
23
+ "erasableSyntaxOnly": true,
24
+ "forceConsistentCasingInFileNames": true
25
+ },
26
+ "include": ["src", "tests"]
27
+ }