cli-kiss 0.1.0 → 0.1.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/Run.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Command, CommandRunner } from "./Command";
1
+ import { Command, CommandFactory } from "./Command";
2
2
  import { ReaderArgs } from "./Reader";
3
3
  import { TypoSupport } from "./Typo";
4
4
  import { usageToStyledLines } from "./Usage";
@@ -9,31 +9,29 @@ export async function runAsCliAndExit<Context>(
9
9
  context: Context,
10
10
  command: Command<Context, void>,
11
11
  application?: {
12
- usageOnError?: boolean | undefined;
13
12
  usageOnHelp?: boolean | undefined;
14
13
  buildVersion?: string | undefined;
15
14
  useColors?: boolean | undefined;
15
+ onExecutionError?: ((error: unknown) => void) | undefined;
16
16
  onLogStdOut?: ((message: string) => void) | undefined; // TODO - this is a problem, deep commands use console
17
17
  onLogStdErr?: ((message: string) => void) | undefined;
18
18
  onExit?: ((code: number) => never) | undefined;
19
- onError?: ((error: unknown) => void) | undefined;
20
19
  },
21
20
  ): Promise<never> {
22
- // TODO - can those flags could be implemented as a chained command ??
23
21
  const readerArgs = new ReaderArgs(cliArgs);
24
- const buildVersion = application?.buildVersion;
25
- if (buildVersion) {
22
+ const usageOnHelp = application?.usageOnHelp ?? true;
23
+ if (usageOnHelp) {
26
24
  readerArgs.registerOption({
27
25
  shorts: [],
28
- longs: ["version"],
26
+ longs: ["help"],
29
27
  valued: false,
30
28
  });
31
29
  }
32
- const usageOnHelp = application?.usageOnHelp ?? true;
33
- if (usageOnHelp) {
30
+ const buildVersion = application?.buildVersion;
31
+ if (buildVersion) {
34
32
  readerArgs.registerOption({
35
33
  shorts: [],
36
- longs: ["help"],
34
+ longs: ["version"],
37
35
  valued: false,
38
36
  });
39
37
  }
@@ -45,54 +43,57 @@ export async function runAsCliAndExit<Context>(
45
43
  longs: ["completion"],
46
44
  });
47
45
  */
48
- const commandRunner = command.createRunnerFromArgs(readerArgs);
46
+ const commandFactory = command.createFactory(readerArgs);
49
47
  while (true) {
50
48
  const positional = readerArgs.consumePositional();
51
49
  if (positional === undefined) {
52
50
  break;
53
51
  }
54
52
  }
53
+ const typoSupport = chooseTypoSupport(application?.useColors);
55
54
  const onLogStdOut = application?.onLogStdOut ?? console.log;
55
+ const onLogStdErr = application?.onLogStdErr ?? console.error;
56
56
  const onExit = application?.onExit ?? process.exit;
57
- if (buildVersion) {
58
- if (readerArgs.getOptionValues("--version" as any).length > 0) {
59
- onLogStdOut([cliName, buildVersion].join(" "));
57
+ if (usageOnHelp) {
58
+ if (readerArgs.getOptionValues("--help" as any).length > 0) {
59
+ onLogStdOut(computeUsageString(cliName, commandFactory, typoSupport));
60
60
  return onExit(0);
61
61
  }
62
62
  }
63
- if (usageOnHelp) {
64
- if (readerArgs.getOptionValues("--help" as any).length > 0) {
65
- const typoSupport = chooseTypoSupport(application?.useColors);
66
- onLogStdOut(computeUsageString(cliName, commandRunner, typoSupport));
63
+ if (buildVersion) {
64
+ if (readerArgs.getOptionValues("--version" as any).length > 0) {
65
+ onLogStdOut([cliName, buildVersion].join(" "));
67
66
  return onExit(0);
68
67
  }
69
68
  }
70
69
  try {
71
- await commandRunner.executeWithContext(context);
72
- return onExit(0);
73
- } catch (error) {
74
- if (application?.onError) {
75
- application.onError(error);
76
- } else {
77
- const onLogStdErr = application?.onLogStdErr ?? console.error;
78
- const typoSupport = chooseTypoSupport(application?.useColors);
79
- if (application?.usageOnError ?? true) {
80
- onLogStdErr(computeUsageString(cliName, commandRunner, typoSupport));
70
+ const commandInstance = commandFactory.createInstance();
71
+ try {
72
+ await commandInstance.executeWithContext(context);
73
+ return onExit(0);
74
+ } catch (error) {
75
+ if (application?.onExecutionError) {
76
+ application.onExecutionError(error);
77
+ } else {
78
+ onLogStdErr(typoSupport.computeStyledErrorMessage(error));
81
79
  }
82
- onLogStdErr(typoSupport.computeStyledErrorMessage(error));
80
+ return onExit(1);
83
81
  }
82
+ } catch (error) {
83
+ onLogStdErr(computeUsageString(cliName, commandFactory, typoSupport));
84
+ onLogStdErr(typoSupport.computeStyledErrorMessage(error));
84
85
  return onExit(1);
85
86
  }
86
87
  }
87
88
 
88
89
  function computeUsageString<Context, Result>(
89
90
  cliName: Lowercase<string>,
90
- commandRunner: CommandRunner<Context, Result>,
91
+ commandFactory: CommandFactory<Context, Result>,
91
92
  typoSupport: TypoSupport,
92
93
  ) {
93
94
  return usageToStyledLines({
94
95
  cliName,
95
- commandUsage: commandRunner.generateUsage(),
96
+ commandUsage: commandFactory.generateUsage(),
96
97
  typoSupport,
97
98
  }).join("\n");
98
99
  }
package/src/lib/Type.ts CHANGED
@@ -122,7 +122,7 @@ export function typeTuple<const Elements extends Array<any>>(
122
122
  () =>
123
123
  new TypoText(
124
124
  new TypoString(elementTypes[index]!.label, typoStyleUserInput),
125
- new TypoString(` at position ${index}`),
125
+ new TypoString(`@${index}`),
126
126
  ),
127
127
  ),
128
128
  ) as Elements;
@@ -147,7 +147,7 @@ export function typeList<Value>(
147
147
  () =>
148
148
  new TypoText(
149
149
  new TypoString(elementType.label, typoStyleUserInput),
150
- new TypoString(` at position ${index}`),
150
+ new TypoString(`@${index}`),
151
151
  ),
152
152
  ),
153
153
  );
package/src/lib/Usage.ts CHANGED
@@ -17,21 +17,6 @@ export function usageToStyledLines(params: {
17
17
 
18
18
  const lines = new Array<string>();
19
19
 
20
- // TODO - description stacking for subcommands ?
21
- lines.push(
22
- textOverview(commandUsage.metadata.description).computeStyledString(
23
- typoSupport,
24
- ),
25
- );
26
- if (commandUsage.metadata.details) {
27
- lines.push(
28
- textSubtleInfo(commandUsage.metadata.details).computeStyledString(
29
- typoSupport,
30
- ),
31
- );
32
- }
33
-
34
- lines.push("");
35
20
  const breadcrumbs = [
36
21
  textUsageTitle("Usage:").computeStyledString(typoSupport),
37
22
  textConstants(cliName).computeStyledString(typoSupport),
@@ -52,20 +37,28 @@ export function usageToStyledLines(params: {
52
37
  );
53
38
  lines.push(breadcrumbs.join(" "));
54
39
 
40
+ lines.push("");
41
+ const infoText = new TypoText();
42
+ infoText.pushString(textUsageIntro(commandUsage.information.description));
43
+ if (commandUsage.information.hint) {
44
+ infoText.pushString(textDelimiter(" "));
45
+ infoText.pushString(textSubtleInfo(`(${commandUsage.information.hint})`));
46
+ }
47
+ lines.push(infoText.computeStyledString(typoSupport));
48
+ if (commandUsage.information.details) {
49
+ const detailsString = textSubtleInfo(commandUsage.information.details);
50
+ lines.push(detailsString.computeStyledString(typoSupport));
51
+ }
52
+
55
53
  if (commandUsage.positionals.length > 0) {
56
54
  lines.push("");
57
55
  lines.push(textBlockTitle("Positionals:").computeStyledString(typoSupport));
58
56
  const typoGrid = new TypoGrid();
59
57
  for (const positionalUsage of commandUsage.positionals) {
60
58
  const typoGridRow = new Array<TypoText>();
61
- typoGridRow.push(new TypoText(textDelimiter()));
59
+ typoGridRow.push(new TypoText(textDelimiter(" ")));
62
60
  typoGridRow.push(new TypoText(textUserInput(positionalUsage.label)));
63
- if (positionalUsage.description) {
64
- typoGridRow.push(new TypoText(textDelimiter()));
65
- typoGridRow.push(
66
- new TypoText(textUsefulInfo(positionalUsage.description)),
67
- );
68
- }
61
+ typoGridRow.push(...createInformationals(positionalUsage));
69
62
  typoGrid.pushRow(typoGridRow);
70
63
  }
71
64
  lines.push(
@@ -77,14 +70,11 @@ export function usageToStyledLines(params: {
77
70
  lines.push("");
78
71
  lines.push(textBlockTitle("Subcommands:").computeStyledString(typoSupport));
79
72
  const typoGrid = new TypoGrid();
80
- for (const subcommand of commandUsage.subcommands) {
73
+ for (const subcommandUsage of commandUsage.subcommands) {
81
74
  const typoGridRow = new Array<TypoText>();
82
- typoGridRow.push(new TypoText(textDelimiter()));
83
- typoGridRow.push(new TypoText(textConstants(subcommand.name)));
84
- if (subcommand.description) {
85
- typoGridRow.push(new TypoText(textDelimiter()));
86
- typoGridRow.push(new TypoText(textUsefulInfo(subcommand.description)));
87
- }
75
+ typoGridRow.push(new TypoText(textDelimiter(" ")));
76
+ typoGridRow.push(new TypoText(textConstants(subcommandUsage.name)));
77
+ typoGridRow.push(...createInformationals(subcommandUsage));
88
78
  typoGrid.pushRow(typoGridRow);
89
79
  }
90
80
  lines.push(
@@ -98,7 +88,7 @@ export function usageToStyledLines(params: {
98
88
  const typoGrid = new TypoGrid();
99
89
  for (const optionUsage of commandUsage.options) {
100
90
  const typoGridRow = new Array<TypoText>();
101
- typoGridRow.push(new TypoText(textDelimiter()));
91
+ typoGridRow.push(new TypoText(textDelimiter(" ")));
102
92
  if (optionUsage.short) {
103
93
  typoGridRow.push(
104
94
  new TypoText(
@@ -125,10 +115,7 @@ export function usageToStyledLines(params: {
125
115
  ),
126
116
  );
127
117
  }
128
- if (optionUsage.description) {
129
- typoGridRow.push(new TypoText(textDelimiter()));
130
- typoGridRow.push(new TypoText(textUsefulInfo(optionUsage.description)));
131
- }
118
+ typoGridRow.push(...createInformationals(optionUsage));
132
119
  typoGrid.pushRow(typoGridRow);
133
120
  }
134
121
  lines.push(
@@ -140,10 +127,33 @@ export function usageToStyledLines(params: {
140
127
  return lines;
141
128
  }
142
129
 
143
- function textOverview(value: string): TypoString {
130
+ function createInformationals(usage: {
131
+ description: string | undefined;
132
+ hint: string | undefined;
133
+ }): Array<TypoText> {
134
+ const informationals = [];
135
+ if (usage.description) {
136
+ informationals.push(textDelimiter(" "));
137
+ informationals.push(textUsefulInfo(usage.description));
138
+ }
139
+ if (usage.hint) {
140
+ informationals.push(textDelimiter(" "));
141
+ informationals.push(textSubtleInfo(`(${usage.hint})`));
142
+ }
143
+ if (informationals.length > 0) {
144
+ return [new TypoText(textDelimiter(" "), ...informationals)];
145
+ }
146
+ return [];
147
+ }
148
+
149
+ function textUsageIntro(value: string): TypoString {
144
150
  return new TypoString(value, { bold: true });
145
151
  }
146
152
 
153
+ function textUsageTitle(value: string): TypoString {
154
+ return new TypoString(value, { fgColor: "darkMagenta", bold: true });
155
+ }
156
+
147
157
  function textUsefulInfo(value: string): TypoString {
148
158
  return new TypoString(value);
149
159
  }
@@ -152,10 +162,6 @@ function textSubtleInfo(value: string): TypoString {
152
162
  return new TypoString(value, { italic: true, dim: true });
153
163
  }
154
164
 
155
- function textUsageTitle(value: string): TypoString {
156
- return new TypoString(value, { fgColor: "darkMagenta", bold: true });
157
- }
158
-
159
165
  function textBlockTitle(value: string): TypoString {
160
166
  return new TypoString(value, { fgColor: "darkGreen", bold: true });
161
167
  }
@@ -168,6 +174,6 @@ function textUserInput(value: string): TypoString {
168
174
  return new TypoString(value, typoStyleUserInput);
169
175
  }
170
176
 
171
- function textDelimiter(value?: string): TypoString {
172
- return new TypoString(value ?? " ");
177
+ function textDelimiter(value: string): TypoString {
178
+ return new TypoString(value);
173
179
  }
@@ -147,6 +147,7 @@ async function executeInterpreted<Context, Result>(
147
147
  command: Command<Context, Result>,
148
148
  ) {
149
149
  const readerArgs = new ReaderArgs(positionals);
150
- const commandRunner = command.createRunnerFromArgs(readerArgs);
151
- return await commandRunner.executeWithContext(context);
150
+ const commandFactory = command.createFactory(readerArgs);
151
+ const commandInstance = commandFactory.createInstance();
152
+ return await commandInstance.executeWithContext(context);
152
153
  }
@@ -94,6 +94,7 @@ const cmd = commandWithSubcommands<string, any, any>(
94
94
  sub2: command(
95
95
  {
96
96
  description: "Subcommand 2 description",
97
+ hint: "Subcommand 2 hint",
97
98
  details: [
98
99
  "Subcommand 2 details.",
99
100
  "Second line of subcommand 2 details.",
@@ -106,6 +107,7 @@ const cmd = commandWithSubcommands<string, any, any>(
106
107
  long: "dudu",
107
108
  type: typeString,
108
109
  default: () => "duduDefault",
110
+ hint: "Dudu option hint",
109
111
  description: "Dudu option description",
110
112
  }),
111
113
  },
@@ -118,6 +120,7 @@ const cmd = commandWithSubcommands<string, any, any>(
118
120
  positionalOptional({
119
121
  label: "OPT-POS",
120
122
  description: "Optional positional string",
123
+ hint: "Optional positional hint",
121
124
  type: typeString,
122
125
  default: () => "42",
123
126
  }),
@@ -148,18 +151,18 @@ it("run", async () => {
148
151
  */
149
152
 
150
153
  expect(usage1).toStrictEqual([
154
+ "{{Usage:}@darkMagenta}+ {{my-cli}@darkCyan}+ {{<POS-1>}@darkBlue}+ {{<POS-2>}@darkBlue}+ {{<SUBCOMMAND>}@darkCyan}+",
155
+ "",
151
156
  "{Root command description}+",
152
157
  "{{Root command details. Second line of root command details.}-}*",
153
158
  "",
154
- "{{Usage:}@darkMagenta}+ {{my-cli}@darkCyan}+ {{<POS-1>}@darkBlue}+ {{<POS-2>}@darkBlue}+ {{<SUBCOMMAND>}@darkCyan}+",
155
- "",
156
159
  "{{Positionals:}@darkGreen}+",
157
160
  " {{<POS-1>}@darkBlue}+ Required positional number 1",
158
161
  " {{<POS-2>}@darkBlue}+ Required positional number 2",
159
162
  "",
160
163
  "{{Subcommands:}@darkGreen}+",
161
164
  " {{sub1}@darkCyan}+ Subcommand 1 description",
162
- " {{sub2}@darkCyan}+ Subcommand 2 description",
165
+ " {{sub2}@darkCyan}+ Subcommand 2 description {{(Subcommand 2 hint)}-}*",
163
166
  "",
164
167
  "{{Options:}@darkGreen}+",
165
168
  " {{-b}@darkCyan}+, {{--boolean-flag}@darkCyan}+{{[=no]}-}* Root boolean-flag description",
@@ -168,11 +171,11 @@ it("run", async () => {
168
171
  "",
169
172
  ]);
170
173
  expect(usage2).toStrictEqual([
174
+ "{{Usage:}@darkMagenta}+ {{my-cli}@darkCyan}+ {{<POS-1>}@darkBlue}+ {{<POS-2>}@darkBlue}+ {{sub1}@darkCyan}+ {{<POS-STRING>}@darkBlue}+",
175
+ "",
171
176
  "{Subcommand 1 description}+",
172
177
  "{{Subcommand 1 details. Second line of subcommand 1 details.}-}*",
173
178
  "",
174
- "{{Usage:}@darkMagenta}+ {{my-cli}@darkCyan}+ {{<POS-1>}@darkBlue}+ {{<POS-2>}@darkBlue}+ {{sub1}@darkCyan}+ {{<POS-STRING>}@darkBlue}+",
175
- "",
176
179
  "{{Positionals:}@darkGreen}+",
177
180
  " {{<POS-1>}@darkBlue}+ Required positional number 1",
178
181
  " {{<POS-2>}@darkBlue}+ Required positional number 2",
@@ -185,23 +188,23 @@ it("run", async () => {
185
188
  "",
186
189
  ]);
187
190
  expect(usage3).toStrictEqual([
188
- "{Subcommand 2 description}+",
189
- "{{Subcommand 2 details. Second line of subcommand 2 details.}-}*",
190
- "",
191
191
  "{{Usage:}@darkMagenta}+ {{my-cli}@darkCyan}+ {{<POS-1>}@darkBlue}+ {{<POS-2>}@darkBlue}+ {{sub2}@darkCyan}+ {{<POS-NUMBER>}@darkBlue}+ {{[OPT-POS]}@darkBlue}+ {{[VARIADIC]...}@darkBlue}+",
192
192
  "",
193
+ "{Subcommand 2 description}+ {{(Subcommand 2 hint)}-}*",
194
+ "{{Subcommand 2 details. Second line of subcommand 2 details.}-}*",
195
+ "",
193
196
  "{{Positionals:}@darkGreen}+",
194
197
  " {{<POS-1>}@darkBlue}+ Required positional number 1",
195
198
  " {{<POS-2>}@darkBlue}+ Required positional number 2",
196
199
  " {{<POS-NUMBER>}@darkBlue}+ Required positional number",
197
- " {{[OPT-POS]}@darkBlue}+ Optional positional string",
200
+ " {{[OPT-POS]}@darkBlue}+ Optional positional string {{(Optional positional hint)}-}*",
198
201
  " {{[VARIADIC]...}@darkBlue}+ Variadic positionals strings",
199
202
  "",
200
203
  "{{Options:}@darkGreen}+",
201
204
  " {{-b}@darkCyan}+, {{--boolean-flag}@darkCyan}+{{[=no]}-}* Root boolean-flag description",
202
205
  " {{-s}@darkCyan}+, {{--string-option}@darkCyan}+ {{<COOL-STUFF>}@darkBlue}+ Root string-option description",
203
206
  " {{--complex-option}@darkCyan}+ {{<NUMBER,STRING[,STRING]...>}@darkBlue}+ Root complex-option description",
204
- " {{--dudu}@darkCyan}+ {{<STRING>}@darkBlue}+ Dudu option description",
207
+ " {{--dudu}@darkCyan}+ {{<STRING>}@darkBlue}+ Dudu option description {{(Dudu option hint)}-}*",
205
208
  "",
206
209
  ]);
207
210
  });
@@ -211,18 +214,10 @@ async function getUsage<Context, Result>(
211
214
  command: Command<Context, Result>,
212
215
  ) {
213
216
  const readerArgs = new ReaderArgs(args);
214
- const commandRunner = command.createRunnerFromArgs(readerArgs);
215
- /*
216
- try {
217
- const interpreterInstance = interpreterFactory.createInterpreterInstance();
218
- console.log(await interpreterInstance.executeWithContext({} as Context));
219
- } catch (error) {
220
- console.error("Error during execution:", error);
221
- }
222
- */
217
+ const commandFactory = command.createFactory(readerArgs);
223
218
  return usageToStyledLines({
224
219
  cliName: "my-cli",
225
- commandUsage: commandRunner.generateUsage(),
220
+ commandUsage: commandFactory.generateUsage(),
226
221
  typoSupport: TypoSupport.mock(),
227
222
  });
228
223
  }