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.
@@ -5,24 +5,29 @@ import { ReaderArgs } from "./Reader";
5
5
  import { TypoError, TypoString, typoStyleConstants, TypoText } from "./Typo";
6
6
 
7
7
  export type Command<Context, Result> = {
8
- getDescription(): string | undefined;
9
- createRunnerFromArgs(readerArgs: ReaderArgs): CommandRunner<Context, Result>;
8
+ getInformation(): CommandInformation;
9
+ createFactory(readerArgs: ReaderArgs): CommandFactory<Context, Result>;
10
10
  };
11
11
 
12
- export type CommandRunner<Context, Result> = {
12
+ export type CommandFactory<Context, Result> = {
13
13
  generateUsage(): CommandUsage;
14
+ createInstance(): CommandInstance<Context, Result>;
15
+ };
16
+
17
+ export type CommandInstance<Context, Result> = {
14
18
  executeWithContext(context: Context): Promise<Result>;
15
19
  };
16
20
 
17
- export type CommandMetadata = {
21
+ export type CommandInformation = {
18
22
  description: string;
23
+ hint?: string;
19
24
  details?: string;
20
25
  // TODO - printable examples ?
21
26
  };
22
27
 
23
28
  export type CommandUsage = {
24
- metadata: CommandMetadata;
25
29
  breadcrumbs: Array<CommandUsageBreadcrumb>;
30
+ information: CommandInformation;
26
31
  positionals: Array<PositionalUsage>;
27
32
  subcommands: Array<CommandUsageSubcommand>;
28
33
  options: Array<OptionUsage>;
@@ -35,45 +40,51 @@ export type CommandUsageBreadcrumb =
35
40
  export type CommandUsageSubcommand = {
36
41
  name: string;
37
42
  description: string | undefined;
43
+ hint: string | undefined;
38
44
  };
39
45
 
40
46
  export function command<Context, Result>(
41
- metadata: CommandMetadata,
47
+ information: CommandInformation,
42
48
  operation: Operation<Context, Result>,
43
49
  ): Command<Context, Result> {
44
50
  return {
45
- getDescription() {
46
- return metadata.description;
51
+ getInformation() {
52
+ return information;
47
53
  },
48
- createRunnerFromArgs(readerArgs: ReaderArgs) {
54
+ createFactory(readerArgs: ReaderArgs) {
49
55
  function generateUsage(): CommandUsage {
50
56
  const operationUsage = operation.generateUsage();
51
57
  return {
52
- metadata,
53
58
  breadcrumbs: operationUsage.positionals.map((positional) =>
54
59
  breadcrumbPositional(positional.label),
55
60
  ),
61
+ information: information,
56
62
  positionals: operationUsage.positionals,
57
63
  subcommands: [],
58
64
  options: operationUsage.options,
59
65
  };
60
66
  }
61
67
  try {
62
- const operationRunner = operation.createRunnerFromArgs(readerArgs);
68
+ const operationFactory = operation.createFactory(readerArgs);
63
69
  const endPositional = readerArgs.consumePositional();
64
70
  if (endPositional !== undefined) {
65
71
  throw Error(`Unexpected argument: "${endPositional}"`);
66
72
  }
67
73
  return {
68
74
  generateUsage,
69
- async executeWithContext(context: Context) {
70
- return operationRunner.executeWithContext(context);
75
+ createInstance() {
76
+ const operationInstance = operationFactory.createInstance();
77
+ return {
78
+ async executeWithContext(context: Context) {
79
+ return await operationInstance.executeWithContext(context);
80
+ },
81
+ };
71
82
  },
72
83
  };
73
84
  } catch (error) {
74
85
  return {
75
86
  generateUsage,
76
- async executeWithContext() {
87
+ createInstance() {
77
88
  throw error;
78
89
  },
79
90
  };
@@ -83,23 +94,23 @@ export function command<Context, Result>(
83
94
  }
84
95
 
85
96
  export function commandWithSubcommands<Context, Payload, Result>(
86
- metadata: CommandMetadata,
97
+ information: CommandInformation,
87
98
  operation: Operation<Context, Payload>,
88
99
  subcommands: { [subcommand: Lowercase<string>]: Command<Payload, Result> },
89
100
  ): Command<Context, Result> {
90
101
  return {
91
- getDescription() {
92
- return metadata.description;
102
+ getInformation() {
103
+ return information;
93
104
  },
94
- createRunnerFromArgs(readerArgs: ReaderArgs) {
105
+ createFactory(readerArgs: ReaderArgs) {
95
106
  try {
96
- const operationRunner = operation.createRunnerFromArgs(readerArgs);
107
+ const operationFactory = operation.createFactory(readerArgs);
97
108
  const subcommandName = readerArgs.consumePositional();
98
109
  if (subcommandName === undefined) {
99
110
  throw new TypoError(
100
111
  new TypoText(
101
- new TypoString(`Missing required positional argument: `),
102
- new TypoString(`SUBCOMMAND`, typoStyleConstants),
112
+ new TypoString(`<SUBCOMMAND>`, typoStyleConstants),
113
+ new TypoString(`: Is required, but was not provided`),
103
114
  ),
104
115
  );
105
116
  }
@@ -108,24 +119,22 @@ export function commandWithSubcommands<Context, Payload, Result>(
108
119
  if (subcommandInput === undefined) {
109
120
  throw new TypoError(
110
121
  new TypoText(
111
- new TypoString(`Invalid value for positional argument: `),
112
- new TypoString(`SUBCOMMAND`, typoStyleConstants),
113
- new TypoString(`: "${subcommandName}"`),
122
+ new TypoString(`<SUBCOMMAND>`, typoStyleConstants),
123
+ new TypoString(`: Invalid value: "${subcommandName}"`),
114
124
  ),
115
125
  );
116
126
  }
117
- const subcommandRunner =
118
- subcommandInput.createRunnerFromArgs(readerArgs);
127
+ const subcommandFactory = subcommandInput.createFactory(readerArgs);
119
128
  return {
120
129
  generateUsage() {
121
130
  const operationUsage = operation.generateUsage();
122
- const subcommandUsage = subcommandRunner.generateUsage();
131
+ const subcommandUsage = subcommandFactory.generateUsage();
123
132
  return {
124
- metadata: subcommandUsage.metadata,
125
133
  breadcrumbs: operationUsage.positionals
126
134
  .map((positional) => breadcrumbPositional(positional.label))
127
135
  .concat([breadcrumbCommand(subcommandName)])
128
136
  .concat(subcommandUsage.breadcrumbs),
137
+ information: subcommandUsage.information,
129
138
  positionals: operationUsage.positionals.concat(
130
139
  subcommandUsage.positionals,
131
140
  ),
@@ -133,10 +142,16 @@ export function commandWithSubcommands<Context, Payload, Result>(
133
142
  options: operationUsage.options.concat(subcommandUsage.options),
134
143
  };
135
144
  },
136
- async executeWithContext(context: Context) {
137
- return await subcommandRunner.executeWithContext(
138
- await operationRunner.executeWithContext(context),
139
- );
145
+ createInstance() {
146
+ const operationInstance = operationFactory.createInstance();
147
+ const subcommandInstance = subcommandFactory.createInstance();
148
+ return {
149
+ async executeWithContext(context: Context) {
150
+ return await subcommandInstance.executeWithContext(
151
+ await operationInstance.executeWithContext(context),
152
+ );
153
+ },
154
+ };
140
155
  },
141
156
  };
142
157
  } catch (error) {
@@ -144,21 +159,23 @@ export function commandWithSubcommands<Context, Payload, Result>(
144
159
  generateUsage() {
145
160
  const operationUsage = operation.generateUsage();
146
161
  return {
147
- metadata,
148
162
  breadcrumbs: operationUsage.positionals
149
163
  .map((positional) => breadcrumbPositional(positional.label))
150
164
  .concat([breadcrumbCommand("<SUBCOMMAND>")]),
165
+ information: information,
151
166
  positionals: operationUsage.positionals,
152
- subcommands: Object.entries(subcommands).map(
153
- ([name, subcommand]) => ({
154
- name,
155
- description: subcommand.getDescription(),
156
- }),
157
- ),
167
+ subcommands: Object.entries(subcommands).map((subcommand) => {
168
+ const metadata = subcommand[1].getInformation();
169
+ return {
170
+ name: subcommand[0],
171
+ description: metadata.description,
172
+ hint: metadata.hint,
173
+ };
174
+ }),
158
175
  options: operationUsage.options,
159
176
  };
160
177
  },
161
- async executeWithContext() {
178
+ createInstance() {
162
179
  throw error;
163
180
  },
164
181
  };
@@ -168,23 +185,23 @@ export function commandWithSubcommands<Context, Payload, Result>(
168
185
  }
169
186
 
170
187
  export function commandChained<Context, Payload, Result>(
171
- metadata: CommandMetadata,
188
+ metadata: CommandInformation,
172
189
  operation: Operation<Context, Payload>,
173
190
  nextCommand: Command<Payload, Result>,
174
191
  ): Command<Context, Result> {
175
192
  return {
176
- getDescription() {
177
- return metadata.description;
193
+ getInformation() {
194
+ return metadata;
178
195
  },
179
- createRunnerFromArgs(readerArgs: ReaderArgs) {
180
- const operationRunner = operation.createRunnerFromArgs(readerArgs);
181
- const nextCommandRunner = nextCommand.createRunnerFromArgs(readerArgs);
196
+ createFactory(readerArgs: ReaderArgs) {
197
+ const operationFactory = operation.createFactory(readerArgs);
198
+ const nextCommandFactory = nextCommand.createFactory(readerArgs);
182
199
  return {
183
200
  generateUsage() {
184
201
  const operationUsage = operation.generateUsage();
185
- const nextCommandUsage = nextCommandRunner.generateUsage();
202
+ const nextCommandUsage = nextCommandFactory.generateUsage();
186
203
  return {
187
- metadata: nextCommandUsage.metadata,
204
+ information: nextCommandUsage.information,
188
205
  breadcrumbs: operationUsage.positionals
189
206
  .map((positional) => breadcrumbPositional(positional.label))
190
207
  .concat(nextCommandUsage.breadcrumbs),
@@ -195,10 +212,16 @@ export function commandChained<Context, Payload, Result>(
195
212
  options: operationUsage.options.concat(nextCommandUsage.options),
196
213
  };
197
214
  },
198
- async executeWithContext(context: Context) {
199
- return await nextCommandRunner.executeWithContext(
200
- await operationRunner.executeWithContext(context),
201
- );
215
+ createInstance() {
216
+ const operationInstance = operationFactory.createInstance();
217
+ const nextCommandInstance = nextCommandFactory.createInstance();
218
+ return {
219
+ async executeWithContext(context: Context) {
220
+ return await nextCommandInstance.executeWithContext(
221
+ await operationInstance.executeWithContext(context),
222
+ );
223
+ },
224
+ };
202
225
  },
203
226
  };
204
227
  },
@@ -4,10 +4,14 @@ import { ReaderArgs } from "./Reader";
4
4
 
5
5
  export type Operation<Input, Output> = {
6
6
  generateUsage(): OperationUsage;
7
- createRunnerFromArgs(readerArgs: ReaderArgs): OperationRunner<Input, Output>;
7
+ createFactory(readerArgs: ReaderArgs): OperationFactory<Input, Output>;
8
8
  };
9
9
 
10
- export type OperationRunner<Input, Output> = {
10
+ export type OperationFactory<Input, Output> = {
11
+ createInstance(): OperationInstance<Input, Output>;
12
+ };
13
+
14
+ export type OperationInstance<Input, Output> = {
11
15
  executeWithContext(input: Input): Promise<Output>;
12
16
  };
13
17
 
@@ -46,7 +50,7 @@ export function operation<
46
50
  }
47
51
  return { options: optionsUsage, positionals: positionalsUsage };
48
52
  },
49
- createRunnerFromArgs(readerArgs: ReaderArgs) {
53
+ createFactory(readerArgs: ReaderArgs) {
50
54
  const optionsGetters: any = {};
51
55
  for (const optionKey in inputs.options) {
52
56
  const optionInput = inputs.options[optionKey]!;
@@ -57,15 +61,19 @@ export function operation<
57
61
  positionalsValues.push(positionalInput.consumePositionals(readerArgs));
58
62
  }
59
63
  return {
60
- executeWithContext(context: Context) {
64
+ createInstance() {
61
65
  const optionsValues: any = {};
62
66
  for (const optionKey in optionsGetters) {
63
67
  optionsValues[optionKey] = optionsGetters[optionKey]!.getValue();
64
68
  }
65
- return handler(context, {
66
- options: optionsValues,
67
- positionals: positionalsValues,
68
- });
69
+ return {
70
+ executeWithContext(context: Context) {
71
+ return handler(context, {
72
+ options: optionsValues,
73
+ positionals: positionalsValues,
74
+ });
75
+ },
76
+ };
69
77
  },
70
78
  };
71
79
  },
package/src/lib/Option.ts CHANGED
@@ -15,10 +15,10 @@ export type Option<Value> = {
15
15
 
16
16
  export type OptionUsage = {
17
17
  description: string | undefined;
18
+ hint: string | undefined;
18
19
  long: Lowercase<string>; // TODO - better type for long option names ?
19
20
  short: string | undefined;
20
21
  label: Uppercase<string> | undefined;
21
- // TODO - default value for usage ? but it can be dynamic, so maybe not
22
22
  };
23
23
 
24
24
  export type OptionGetter<Value> = {
@@ -29,13 +29,16 @@ export function optionFlag(definition: {
29
29
  long: Lowercase<string>;
30
30
  short?: string;
31
31
  description?: string;
32
+ hint?: string;
32
33
  aliases?: { longs?: Array<Lowercase<string>>; shorts?: Array<string> };
33
34
  default?: () => boolean;
34
35
  }): Option<boolean> {
36
+ const label = `<${typeBoolean.label}>` as Uppercase<string>;
35
37
  return {
36
38
  generateUsage() {
37
39
  return {
38
40
  description: definition.description,
41
+ hint: definition.hint,
39
42
  long: definition.long,
40
43
  short: definition.short,
41
44
  label: undefined,
@@ -52,9 +55,8 @@ export function optionFlag(definition: {
52
55
  if (optionValues.length > 1) {
53
56
  throw new TypoError(
54
57
  new TypoText(
55
- new TypoString(`Option value for: `),
56
58
  new TypoString(`--${definition.long}`, typoStyleConstants),
57
- new TypoString(`: must not be set multiple times`),
59
+ new TypoString(`: Must not be set multiple times`),
58
60
  ),
59
61
  );
60
62
  }
@@ -66,8 +68,8 @@ export function optionFlag(definition: {
66
68
  } catch (error) {
67
69
  throw new TypoError(
68
70
  new TypoText(
69
- new TypoString(`Failed to compute default value for: `),
70
71
  new TypoString(`--${definition.long}`, typoStyleConstants),
72
+ new TypoString(`: Failed to compute default value`),
71
73
  ),
72
74
  error,
73
75
  );
@@ -80,7 +82,7 @@ export function optionFlag(definition: {
80
82
  new TypoText(
81
83
  new TypoString(`--${definition.long}`, typoStyleConstants),
82
84
  new TypoString(`: `),
83
- new TypoString(typeBoolean.label, typoStyleUserInput),
85
+ new TypoString(label, typoStyleUserInput),
84
86
  ),
85
87
  );
86
88
  },
@@ -93,19 +95,21 @@ export function optionSingleValue<Value>(definition: {
93
95
  long: Lowercase<string>;
94
96
  short?: string;
95
97
  description?: string;
98
+ hint?: string;
96
99
  aliases?: { longs?: Array<Lowercase<string>>; shorts?: Array<string> };
97
100
  label?: Uppercase<string>;
98
101
  type: Type<Value>;
99
102
  default: () => Value;
100
103
  }): Option<Value> {
101
- const label = definition.label ?? definition.type.label;
104
+ const label = `<${definition.label ?? definition.type.label}>`;
102
105
  return {
103
106
  generateUsage() {
104
107
  return {
105
108
  description: definition.description,
109
+ hint: definition.hint,
106
110
  long: definition.long,
107
111
  short: definition.short,
108
- label: `<${label}>` as Uppercase<string>,
112
+ label: label as Uppercase<string>,
109
113
  };
110
114
  },
111
115
  createGetter(readerOptions: ReaderOptions) {
@@ -119,9 +123,8 @@ export function optionSingleValue<Value>(definition: {
119
123
  if (optionValues.length > 1) {
120
124
  throw new TypoError(
121
125
  new TypoText(
122
- new TypoString(`Option value for: `),
123
126
  new TypoString(`--${definition.long}`, typoStyleConstants),
124
- new TypoString(`: must not be set multiple times`),
127
+ new TypoString(`: Must not be set multiple times`),
125
128
  ),
126
129
  );
127
130
  }
@@ -132,8 +135,8 @@ export function optionSingleValue<Value>(definition: {
132
135
  } catch (error) {
133
136
  throw new TypoError(
134
137
  new TypoText(
135
- new TypoString(`Failed to compute default value for: `),
136
138
  new TypoString(`--${definition.long}`, typoStyleConstants),
139
+ new TypoString(`: Failed to compute default value`),
137
140
  ),
138
141
  error,
139
142
  );
@@ -159,19 +162,21 @@ export function optionRepeatable<Value>(definition: {
159
162
  long: Lowercase<string>;
160
163
  short?: string;
161
164
  description?: string;
165
+ hint?: string;
162
166
  aliases?: { longs?: Array<Lowercase<string>>; shorts?: Array<string> };
163
167
  label?: Uppercase<string>;
164
168
  type: Type<Value>;
165
169
  }): Option<Array<Value>> {
166
- const label = definition.label ?? definition.type.label;
170
+ const label = `<${definition.label ?? definition.type.label}>`;
167
171
  return {
168
172
  generateUsage() {
169
173
  // TODO - showcase that it can be repeated ?
170
174
  return {
171
175
  description: definition.description,
176
+ hint: definition.hint,
172
177
  long: definition.long,
173
178
  short: definition.short,
174
- label: `<${label}>` as Uppercase<string>,
179
+ label: label as Uppercase<string>,
175
180
  };
176
181
  },
177
182
  createGetter(readerOptions: ReaderOptions) {
@@ -9,20 +9,23 @@ export type Positional<Value> = {
9
9
 
10
10
  export type PositionalUsage = {
11
11
  description: string | undefined;
12
+ hint: string | undefined;
12
13
  label: Uppercase<string>;
13
14
  };
14
15
 
15
16
  export function positionalRequired<Value>(definition: {
16
17
  description?: string;
18
+ hint?: string;
17
19
  label?: Uppercase<string>;
18
20
  type: Type<Value>;
19
21
  }): Positional<Value> {
20
- const label = definition.label ?? definition.type.label;
22
+ const label = `<${definition.label ?? definition.type.label}>`;
21
23
  return {
22
24
  generateUsage() {
23
25
  return {
24
26
  description: definition.description,
25
- label: `<${label}>` as Uppercase<string>,
27
+ hint: definition.hint,
28
+ label: label as Uppercase<string>,
26
29
  };
27
30
  },
28
31
  consumePositionals(readerPositionals: ReaderPositionals) {
@@ -30,8 +33,8 @@ export function positionalRequired<Value>(definition: {
30
33
  if (positional === undefined) {
31
34
  throw new TypoError(
32
35
  new TypoText(
33
- new TypoString(`Missing required positional argument: `),
34
36
  new TypoString(label, typoStyleUserInput),
37
+ new TypoString(`: Is required, but was not provided`),
35
38
  ),
36
39
  );
37
40
  }
@@ -46,16 +49,18 @@ export function positionalRequired<Value>(definition: {
46
49
 
47
50
  export function positionalOptional<Value>(definition: {
48
51
  description?: string;
52
+ hint?: string;
49
53
  label?: Uppercase<string>;
50
54
  type: Type<Value>;
51
55
  default: () => Value;
52
56
  }): Positional<Value> {
53
- const label = definition.label ?? definition.type.label;
57
+ const label = `[${definition.label ?? definition.type.label}]`;
54
58
  return {
55
59
  generateUsage() {
56
60
  return {
57
61
  description: definition.description,
58
- label: `[${label}]` as Uppercase<string>,
62
+ hint: definition.hint,
63
+ label: label as Uppercase<string>,
59
64
  };
60
65
  },
61
66
  consumePositionals(readerPositionals: ReaderPositionals) {
@@ -66,8 +71,8 @@ export function positionalOptional<Value>(definition: {
66
71
  } catch (error) {
67
72
  throw new TypoError(
68
73
  new TypoText(
69
- new TypoString(`Failed to compute default value for: `),
70
74
  new TypoString(label, typoStyleUserInput),
75
+ new TypoString(`: Failed to compute default value`),
71
76
  ),
72
77
  error,
73
78
  );
@@ -85,15 +90,17 @@ export function positionalOptional<Value>(definition: {
85
90
  export function positionalVariadics<Value>(definition: {
86
91
  endDelimiter?: string;
87
92
  description?: string;
93
+ hint?: string;
88
94
  label?: Uppercase<string>;
89
95
  type: Type<Value>;
90
96
  }): Positional<Array<Value>> {
91
- const label = definition.label ?? definition.type.label;
97
+ const label = `[${definition.label ?? definition.type.label}]`;
92
98
  return {
93
99
  generateUsage() {
94
100
  return {
95
101
  description: definition.description,
96
- label: (`[${label}]...` +
102
+ hint: definition.hint,
103
+ label: (`${label}...` +
97
104
  (definition.endDelimiter
98
105
  ? `["${definition.endDelimiter}"]`
99
106
  : "")) as Uppercase<string>,
package/src/lib/Reader.ts CHANGED
@@ -146,8 +146,8 @@ export class ReaderArgs {
146
146
  }
147
147
  throw new TypoError(
148
148
  new TypoText(
149
- new TypoString(`Unknown option: `),
150
149
  new TypoString(`-${arg.slice(shortIndexStart)}`, typoStyleConstants),
150
+ new TypoString(`: Unexpected unknown option`),
151
151
  ),
152
152
  );
153
153
  }
@@ -169,8 +169,8 @@ export class ReaderArgs {
169
169
  }
170
170
  throw new TypoError(
171
171
  new TypoText(
172
- new TypoString(`Unknown option: `),
173
172
  new TypoString(constant, typoStyleConstants),
173
+ new TypoString(`: Unexpected unknown option`),
174
174
  ),
175
175
  );
176
176
  }
@@ -202,7 +202,6 @@ export class ReaderArgs {
202
202
  if (arg === null) {
203
203
  throw new TypoError(
204
204
  new TypoText(
205
- new TypoString(`Option parsing: `),
206
205
  new TypoString(constant, typoStyleConstants),
207
206
  new TypoString(`: requires a value, but got end of input`),
208
207
  ),
@@ -211,7 +210,6 @@ export class ReaderArgs {
211
210
  if (this.#parsedDouble) {
212
211
  throw new TypoError(
213
212
  new TypoText(
214
- new TypoString(`Option parsing: `),
215
213
  new TypoString(constant, typoStyleConstants),
216
214
  new TypoString(`: requires a value before "--"`),
217
215
  ),
@@ -221,7 +219,6 @@ export class ReaderArgs {
221
219
  if (arg.startsWith("-")) {
222
220
  throw new TypoError(
223
221
  new TypoText(
224
- new TypoString(`Option parsing: `),
225
222
  new TypoString(constant, typoStyleConstants),
226
223
  new TypoString(`: requires a value, but got: "${arg}"`),
227
224
  ),