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/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
- lines.push("");
10
- lines.push(commandUsage.description);
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(`Usage: ${cliName} ${commandUsage.breadcrumbs.join(" ")}`);
37
+ lines.push(`${breadcrumbPrefix} ${breadcrumbsItems.join(" ")}`);
38
+
14
39
  if (commandUsage.arguments.length > 0) {
15
40
  lines.push("");
16
- lines.push("Arguments:");
17
- lines.push("");
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 columns = new Array<string>();
21
- columns.push("");
22
- columns.push(argumentUsage.label);
23
- columns.push("");
44
+ const gridRow = new Array<GridCell>();
45
+ gridRow.push([]);
46
+ gridRow.push([textValue(argumentUsage.label)]);
47
+ gridRow.push([]);
24
48
  if (argumentUsage.description) {
25
- columns.push(argumentUsage.description);
49
+ gridRow.push([textDesc(argumentUsage.description)]);
26
50
  }
27
- rows.push(columns);
51
+ grid.push(gridRow);
28
52
  }
29
- pushGrid(lines, rows);
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
- lines.push("");
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 columns = new Array<string>();
38
- columns.push("");
61
+ const gridRow = new Array<GridCell>();
62
+ gridRow.push([]);
39
63
  if (optionUsage.short) {
40
- columns.push(`-${optionUsage.short},`);
64
+ gridRow.push([textName(`-${optionUsage.short}`), { value: "," }]);
41
65
  } else {
42
- columns.push("");
66
+ gridRow.push([]);
43
67
  }
44
68
  if (optionUsage.label) {
45
- columns.push(`--${optionUsage.long} ${optionUsage.label}`);
69
+ gridRow.push([
70
+ textName(`--${optionUsage.long} `),
71
+ textValue(optionUsage.label),
72
+ ]);
46
73
  } else {
47
- columns.push(`--${optionUsage.long}`);
74
+ gridRow.push([
75
+ textName(`--${optionUsage.long}`),
76
+ { value: "[=yes|no]", color: "grey" },
77
+ ]);
48
78
  }
49
- columns.push("");
79
+ gridRow.push([]);
50
80
  if (optionUsage.description) {
51
- columns.push(optionUsage.description);
81
+ gridRow.push([textDesc(optionUsage.description)]);
52
82
  }
53
- rows.push(columns);
83
+ grid.push(gridRow);
54
84
  }
55
- pushGrid(lines, rows);
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
- lines.push("");
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 columns = new Array<string>();
64
- columns.push("");
65
- columns.push(subcommand.name);
66
- columns.push("");
67
- if (subcommand.description) {
68
- columns.push(subcommand.description);
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
- rows.push(columns);
100
+ grid.push(gridRow);
71
101
  }
72
- pushGrid(lines, rows);
102
+ lines.push(...gridToPrintableLines(grid, typoSupport));
73
103
  }
74
104
  lines.push("");
75
- return lines.join("\n");
105
+ return lines;
76
106
  }
77
107
 
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
- }
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
  }
@@ -18,8 +18,8 @@ it("run", async () => {
18
18
  "-cd=4.1",
19
19
  "-ef5.1",
20
20
  "positional-2",
21
- "-gh=false",
22
- "-ij=true",
21
+ "-gh=FALSE",
22
+ "-ij=TRUE",
23
23
  "-b",
24
24
  "3.2",
25
25
  "-d=4.2",
@@ -8,15 +8,15 @@ import {
8
8
  optionFlag,
9
9
  optionRepeatable,
10
10
  optionSingleValue,
11
- processor,
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 description",
19
- processor(
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 description",
45
- processor(
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 description",
57
- processor(
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
- processor,
12
+ process,
13
13
  ReaderTokenizer,
14
14
  typeNumber,
15
15
  typeString,
16
16
  } from "../src";
17
- import { usageFormatter } from "../src/lib/Usage";
17
+ import { usageToPrintableLines } from "../src/lib/Usage";
18
18
 
19
19
  const cmd = commandWithSubcommands<string, any, any>(
20
- "Root command description",
21
- processor(
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: "COOL_STUFF",
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: "POSITIONAL-1",
52
+ label: "POS-1",
47
53
  description: "First positional argument",
48
54
  type: typeNumber,
49
55
  }),
50
56
  argumentRequired({
51
- label: "POSITIONAL-2",
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
- "Subcommand 1 description",
64
- processor(
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
- "Subcommand 2 description",
82
- processor(
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-POSITIONAL",
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-POSITIONALS",
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 res0 = getUsage([], cmd);
121
- console.log(res0);
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 res1 = getUsage(["50", "51", "sub1", "final"], cmd);
124
- console.log(res1);
125
- //expect(res1).toStrictEqual({});
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 res2 = getUsage(
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
- console.log(res2);
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 usageFormatter("my-cli", commandRunner.computeUsage());
229
+ return usageToPrintableLines({
230
+ cliName: "my-cli",
231
+ commandUsage: commandRunner.computeUsage(),
232
+ typoSupport: "mock",
233
+ });
153
234
  }