@thi.ng/args 2.2.45 → 2.3.0
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/CHANGELOG.md +19 -1
- package/README.md +3 -1
- package/api.d.ts +99 -2
- package/api.js +17 -17
- package/args.js +90 -199
- package/cli.d.ts +3 -0
- package/cli.js +78 -0
- package/coerce.js +45 -37
- package/index.d.ts +1 -0
- package/index.js +1 -0
- package/package.json +17 -8
- package/parse.js +101 -99
- package/usage.js +65 -65
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
-
- **Last updated**: 2023-12-
|
|
3
|
+
- **Last updated**: 2023-12-18T13:41:19Z
|
|
4
4
|
- **Generator**: [thi.ng/monopub](https://thi.ng/monopub)
|
|
5
5
|
|
|
6
6
|
All notable changes to this project will be documented in this file.
|
|
@@ -9,6 +9,24 @@ See [Conventional Commits](https://conventionalcommits.org/) for commit guidelin
|
|
|
9
9
|
**Note:** Unlisted _patch_ versions only involve non-code or otherwise excluded changes
|
|
10
10
|
and/or version bumps of transitive dependencies.
|
|
11
11
|
|
|
12
|
+
## [2.3.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/args@2.3.0) (2023-12-18)
|
|
13
|
+
|
|
14
|
+
#### 🚀 Features
|
|
15
|
+
|
|
16
|
+
- add cliApp() runner ([b2248fa](https://github.com/thi-ng/umbrella/commit/b2248fa))
|
|
17
|
+
- update lifecycle hooks, add NO_COLOR support, add docs ([4a0ebda](https://github.com/thi-ng/umbrella/commit/4a0ebda))
|
|
18
|
+
- add CLIAppConfig pre/post lifecycle hooks
|
|
19
|
+
- update UsageOpts.color handling
|
|
20
|
+
- add `NO_COLOR` env var support in cliApp()
|
|
21
|
+
- add doc strings
|
|
22
|
+
- update deps
|
|
23
|
+
- update cliApp() to support command context extensions ([61d9fb8](https://github.com/thi-ng/umbrella/commit/61d9fb8))
|
|
24
|
+
- update cliApp() error handling ([019e5a1](https://github.com/thi-ng/umbrella/commit/019e5a1))
|
|
25
|
+
- update argv handling in cliApp() ([b1ed768](https://github.com/thi-ng/umbrella/commit/b1ed768))
|
|
26
|
+
- add NO_COLOR aware formatters to CommandCtx ([0e7ddda](https://github.com/thi-ng/umbrella/commit/0e7ddda))
|
|
27
|
+
- update deps
|
|
28
|
+
- update cliApp() to use StreamLogger (target: process.stderr) ([b249295](https://github.com/thi-ng/umbrella/commit/b249295))
|
|
29
|
+
|
|
12
30
|
### [2.2.28](https://github.com/thi-ng/umbrella/tree/@thi.ng/args@2.2.28) (2023-08-04)
|
|
13
31
|
|
|
14
32
|
#### ♻️ Refactoring
|
package/README.md
CHANGED
|
@@ -69,14 +69,16 @@ For Node.js REPL:
|
|
|
69
69
|
const args = await import("@thi.ng/args");
|
|
70
70
|
```
|
|
71
71
|
|
|
72
|
-
Package sizes (brotli'd, pre-treeshake): ESM: 2.
|
|
72
|
+
Package sizes (brotli'd, pre-treeshake): ESM: 2.75 KB
|
|
73
73
|
|
|
74
74
|
## Dependencies
|
|
75
75
|
|
|
76
76
|
- [@thi.ng/api](https://github.com/thi-ng/umbrella/tree/develop/packages/api)
|
|
77
77
|
- [@thi.ng/checks](https://github.com/thi-ng/umbrella/tree/develop/packages/checks)
|
|
78
78
|
- [@thi.ng/errors](https://github.com/thi-ng/umbrella/tree/develop/packages/errors)
|
|
79
|
+
- [@thi.ng/logger](https://github.com/thi-ng/umbrella/tree/develop/packages/logger)
|
|
79
80
|
- [@thi.ng/strings](https://github.com/thi-ng/umbrella/tree/develop/packages/strings)
|
|
81
|
+
- [@thi.ng/text-format](https://github.com/thi-ng/umbrella/tree/develop/packages/text-format)
|
|
80
82
|
|
|
81
83
|
## API
|
|
82
84
|
|
package/api.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import type { Fn, IDeref, IObjectOf } from "@thi.ng/api";
|
|
1
|
+
import type { Fn, Fn2, IDeref, IObjectOf } from "@thi.ng/api";
|
|
2
|
+
import type { ILogger } from "@thi.ng/logger";
|
|
3
|
+
import type { FormatPresets } from "@thi.ng/text-format";
|
|
2
4
|
export interface ArgSpecBase {
|
|
3
5
|
/**
|
|
4
6
|
* Shorthand for given arg/option
|
|
@@ -112,9 +114,13 @@ export interface UsageOpts {
|
|
|
112
114
|
/**
|
|
113
115
|
* If false, ANSI colors will be stripped from output.
|
|
114
116
|
*
|
|
117
|
+
* @remarks
|
|
118
|
+
* When using {@link cliApp}, the default for this value will depend on the
|
|
119
|
+
* `NO_COLOR` env var being set. See https://no-color.org/ for details.
|
|
120
|
+
*
|
|
115
121
|
* @defaultValue true
|
|
116
122
|
*/
|
|
117
|
-
color: Partial<ColorTheme> |
|
|
123
|
+
color: Partial<ColorTheme> | boolean;
|
|
118
124
|
/**
|
|
119
125
|
* If true (default), display argument default values. Nullish or false
|
|
120
126
|
* default values will never be shown.
|
|
@@ -172,4 +178,95 @@ export declare class Tuple<T> implements IDeref<T[]> {
|
|
|
172
178
|
constructor(value: T[]);
|
|
173
179
|
deref(): T[];
|
|
174
180
|
}
|
|
181
|
+
export interface CLIAppConfig<OPTS extends object, CTX extends CommandCtx<OPTS, OPTS> = CommandCtx<OPTS, OPTS>> {
|
|
182
|
+
/**
|
|
183
|
+
* App (CLI command) short name.
|
|
184
|
+
*/
|
|
185
|
+
name: string;
|
|
186
|
+
/**
|
|
187
|
+
* Shared args for all commands
|
|
188
|
+
*/
|
|
189
|
+
opts: Args<OPTS>;
|
|
190
|
+
/**
|
|
191
|
+
* Command spec registry
|
|
192
|
+
*/
|
|
193
|
+
commands: IObjectOf<Command<any, OPTS, CTX>>;
|
|
194
|
+
/**
|
|
195
|
+
* If true, the app will only use the single command entry in
|
|
196
|
+
* {@link CLIAppConfig.commands} and not expect the first CLI args to be a
|
|
197
|
+
* command name.
|
|
198
|
+
*/
|
|
199
|
+
single?: boolean;
|
|
200
|
+
/**
|
|
201
|
+
* Usage options, same as {@link UsageOpts}. Usage will be shown
|
|
202
|
+
* automatically in case of arg parse errors.
|
|
203
|
+
*/
|
|
204
|
+
usage: Partial<UsageOpts>;
|
|
205
|
+
/**
|
|
206
|
+
* Arguments vector to use for arg parsing. If omitted, uses `process.argv`
|
|
207
|
+
*/
|
|
208
|
+
argv?: string[];
|
|
209
|
+
/**
|
|
210
|
+
* {@link CLIAppConfig.argv} index to start parsing from.
|
|
211
|
+
*
|
|
212
|
+
* @defaultValue 2
|
|
213
|
+
*/
|
|
214
|
+
start?: number;
|
|
215
|
+
/**
|
|
216
|
+
* {@link CommandCtx} augmentation handler, i.e. an async function which
|
|
217
|
+
* will be called just before the actual command for additional setup/config
|
|
218
|
+
* purposes. The context object returned will be the one passed to the
|
|
219
|
+
* command.
|
|
220
|
+
*/
|
|
221
|
+
ctx: Fn2<CommandCtx<OPTS, OPTS>, Command<any, OPTS, CTX>, Promise<CTX>>;
|
|
222
|
+
/**
|
|
223
|
+
* Lifecycle hook. Function which will be called just after the actual
|
|
224
|
+
* command handler, e.g. for teardown purposes.
|
|
225
|
+
*/
|
|
226
|
+
post?: Fn2<CTX, Command<any, OPTS, CTX>, Promise<void>>;
|
|
227
|
+
}
|
|
228
|
+
export interface Command<OPTS extends BASE, BASE extends object, CTX extends CommandCtx<OPTS, BASE> = CommandCtx<OPTS, BASE>> {
|
|
229
|
+
/**
|
|
230
|
+
* Command description (short, single line)
|
|
231
|
+
*/
|
|
232
|
+
desc: string;
|
|
233
|
+
/**
|
|
234
|
+
* Command specific CLI arg specs
|
|
235
|
+
*/
|
|
236
|
+
opts: Args<Omit<OPTS, keyof BASE>>;
|
|
237
|
+
/**
|
|
238
|
+
* Number of required rest input value (after all parsed options). Leave
|
|
239
|
+
* unset to allow any number.
|
|
240
|
+
*/
|
|
241
|
+
inputs?: number;
|
|
242
|
+
/**
|
|
243
|
+
* Actual command function/implementation.
|
|
244
|
+
*/
|
|
245
|
+
fn: Fn<CTX, Promise<void>>;
|
|
246
|
+
}
|
|
247
|
+
export interface CommandCtx<OPTS extends BASE, BASE extends object> {
|
|
248
|
+
/**
|
|
249
|
+
* Logger to be used by all commands. By default uses a console logger with
|
|
250
|
+
* log level INFO. Can be customized via {@link CLIAppConfig.pre}.
|
|
251
|
+
*/
|
|
252
|
+
logger: ILogger;
|
|
253
|
+
/**
|
|
254
|
+
* `NO_COLOR`-aware text formatting presets. If color output is NOT disabled
|
|
255
|
+
* via the `NO_COLOR` env var, this defaults to
|
|
256
|
+
* [`PRESET_ANSI16`](https://github.com/thi-ng/umbrella/blob/develop/packages/text-format/README.md),
|
|
257
|
+
* otherwise `PRESET_NONE` (i.e. same API, but ignoring any color requests).
|
|
258
|
+
*
|
|
259
|
+
* See https://no-color.org for context.
|
|
260
|
+
*/
|
|
261
|
+
format: FormatPresets;
|
|
262
|
+
/**
|
|
263
|
+
* Parsed CLI args (according to provided command spec)
|
|
264
|
+
*/
|
|
265
|
+
opts: OPTS;
|
|
266
|
+
/**
|
|
267
|
+
* Array of remaining CLI args (after parsed options). Individual commands
|
|
268
|
+
* can specify the number of items required via {@link Command.inputs}.
|
|
269
|
+
*/
|
|
270
|
+
inputs: string[];
|
|
271
|
+
}
|
|
175
272
|
//# sourceMappingURL=api.d.ts.map
|
package/api.js
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
const DEFAULT_THEME = {
|
|
2
|
+
default: 95,
|
|
3
|
+
hint: 90,
|
|
4
|
+
multi: 90,
|
|
5
|
+
param: 96,
|
|
6
|
+
required: 33
|
|
7
7
|
};
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
16
|
-
deref() {
|
|
17
|
-
return this.value;
|
|
18
|
-
}
|
|
8
|
+
class Tuple {
|
|
9
|
+
constructor(value) {
|
|
10
|
+
this.value = value;
|
|
11
|
+
}
|
|
12
|
+
deref() {
|
|
13
|
+
return this.value;
|
|
14
|
+
}
|
|
19
15
|
}
|
|
16
|
+
export {
|
|
17
|
+
DEFAULT_THEME,
|
|
18
|
+
Tuple
|
|
19
|
+
};
|
package/args.js
CHANGED
|
@@ -1,214 +1,105 @@
|
|
|
1
1
|
import { identity } from "@thi.ng/api/fn";
|
|
2
2
|
import { repeat } from "@thi.ng/strings/repeat";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
coerceFloat,
|
|
5
|
+
coerceFloats,
|
|
6
|
+
coerceHexInt,
|
|
7
|
+
coerceHexInts,
|
|
8
|
+
coerceInt,
|
|
9
|
+
coerceInts,
|
|
10
|
+
coerceJson,
|
|
11
|
+
coerceKV,
|
|
12
|
+
coerceOneOf,
|
|
13
|
+
coerceTuple
|
|
14
|
+
} from "./coerce.js";
|
|
4
15
|
const $single = (coerce, hint) => (spec) => ({
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
16
|
+
coerce,
|
|
17
|
+
hint,
|
|
18
|
+
group: "main",
|
|
19
|
+
...spec
|
|
9
20
|
});
|
|
10
21
|
const $multi = (coerce, hint) => (spec) => ({
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
22
|
+
hint: $hint(hint, spec.delim),
|
|
23
|
+
multi: true,
|
|
24
|
+
coerce,
|
|
25
|
+
group: "main",
|
|
26
|
+
...spec
|
|
16
27
|
});
|
|
17
28
|
const $hint = (hint, delim) => hint + (delim ? `[${delim}..]` : "");
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
*/
|
|
24
|
-
export const flag = (spec) => ({
|
|
25
|
-
flag: true,
|
|
26
|
-
default: false,
|
|
27
|
-
group: "flags",
|
|
28
|
-
...spec,
|
|
29
|
+
const flag = (spec) => ({
|
|
30
|
+
flag: true,
|
|
31
|
+
default: false,
|
|
32
|
+
group: "flags",
|
|
33
|
+
...spec
|
|
29
34
|
});
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
export const strings = $multi(identity, "STR");
|
|
44
|
-
/**
|
|
45
|
-
* Returns a full {@link ArgSpec} for a floating point value arg. The value
|
|
46
|
-
* will be autoatically coerced into a number using {@link coerceFloat}.
|
|
47
|
-
*
|
|
48
|
-
* @param spec -
|
|
49
|
-
*/
|
|
50
|
-
export const float = $single(coerceFloat, "NUM");
|
|
51
|
-
/**
|
|
52
|
-
* Returns a full {@link ArgSpec} for a single hex integer value arg. The value
|
|
53
|
-
* will be autoatically coerced into a number using {@link coerceHexInt}.
|
|
54
|
-
*
|
|
55
|
-
* @param spec -
|
|
56
|
-
*/
|
|
57
|
-
export const hex = $single(coerceHexInt, "HEX");
|
|
58
|
-
/**
|
|
59
|
-
* Returns a full {@link ArgSpec} for a single integer value arg. The value
|
|
60
|
-
* will be autoatically coerced into a number using {@link coerceInt}.
|
|
61
|
-
*
|
|
62
|
-
* @param spec -
|
|
63
|
-
*/
|
|
64
|
-
export const int = $single(coerceInt, "INT");
|
|
65
|
-
/**
|
|
66
|
-
* Multi-arg version of {@link float}. Returns a full {@link ArgSpec} for a
|
|
67
|
-
* multi floating point value arg. This argument can be provided mutiple times
|
|
68
|
-
* with values being coerced into numbers and collected into an array.
|
|
69
|
-
*
|
|
70
|
-
* @param spec -
|
|
71
|
-
*/
|
|
72
|
-
export const floats = $multi(coerceFloats, "NUM");
|
|
73
|
-
/**
|
|
74
|
-
* Multi-arg version of {@link hex}. Returns a full {@link ArgSpec} for a multi
|
|
75
|
-
* hex integer value arg. This argument can be provided mutiple times with
|
|
76
|
-
* values being coerced into numbers and collected into an array.
|
|
77
|
-
*
|
|
78
|
-
* @param spec -
|
|
79
|
-
*/
|
|
80
|
-
export const hexes = $multi(coerceHexInts, "HEX");
|
|
81
|
-
/**
|
|
82
|
-
* Multi-arg version of {@link int}. Returns a full {@link ArgSpec} for a multi
|
|
83
|
-
* integer value arg. This argument can be provided mutiple times with values
|
|
84
|
-
* being coerced into numbers and collected into an array.
|
|
85
|
-
*
|
|
86
|
-
* @param spec -
|
|
87
|
-
*/
|
|
88
|
-
export const ints = $multi(coerceInts, "INT");
|
|
89
|
-
/**
|
|
90
|
-
* Returns full {@link ArgSpec} for a JSON value arg. The raw CLI value string
|
|
91
|
-
* will be automcatically coerced using {@link coerceJson}.
|
|
92
|
-
*
|
|
93
|
-
* @param spec -
|
|
94
|
-
*/
|
|
95
|
-
export const json = (spec) => ({
|
|
96
|
-
coerce: coerceJson,
|
|
97
|
-
hint: "JSON",
|
|
98
|
-
group: "main",
|
|
99
|
-
...spec,
|
|
35
|
+
const string = $single(identity, "STR");
|
|
36
|
+
const strings = $multi(identity, "STR");
|
|
37
|
+
const float = $single(coerceFloat, "NUM");
|
|
38
|
+
const hex = $single(coerceHexInt, "HEX");
|
|
39
|
+
const int = $single(coerceInt, "INT");
|
|
40
|
+
const floats = $multi(coerceFloats, "NUM");
|
|
41
|
+
const hexes = $multi(coerceHexInts, "HEX");
|
|
42
|
+
const ints = $multi(coerceInts, "INT");
|
|
43
|
+
const json = (spec) => ({
|
|
44
|
+
coerce: coerceJson,
|
|
45
|
+
hint: "JSON",
|
|
46
|
+
group: "main",
|
|
47
|
+
...spec
|
|
100
48
|
});
|
|
101
49
|
const $desc = (opts, prefix) => `${prefix ? prefix + ": " : ""}${opts.map((x) => `"${x}"`).join(", ")}`;
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
*/
|
|
109
|
-
export const oneOf = (opts, spec) => ({
|
|
110
|
-
coerce: coerceOneOf(opts),
|
|
111
|
-
hint: "ID",
|
|
112
|
-
group: "main",
|
|
113
|
-
...spec,
|
|
114
|
-
desc: $desc(opts, spec.desc),
|
|
50
|
+
const oneOf = (opts, spec) => ({
|
|
51
|
+
coerce: coerceOneOf(opts),
|
|
52
|
+
hint: "ID",
|
|
53
|
+
group: "main",
|
|
54
|
+
...spec,
|
|
55
|
+
desc: $desc(opts, spec.desc)
|
|
115
56
|
});
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
*/
|
|
124
|
-
export const oneOfMulti = (opts, spec) => ({
|
|
125
|
-
coerce: (xs) => xs.map(coerceOneOf(opts)),
|
|
126
|
-
hint: $hint("ID", spec.delim),
|
|
127
|
-
multi: true,
|
|
128
|
-
group: "main",
|
|
129
|
-
...spec,
|
|
130
|
-
desc: $desc(opts, spec.desc),
|
|
57
|
+
const oneOfMulti = (opts, spec) => ({
|
|
58
|
+
coerce: (xs) => xs.map(coerceOneOf(opts)),
|
|
59
|
+
hint: $hint("ID", spec.delim),
|
|
60
|
+
multi: true,
|
|
61
|
+
group: "main",
|
|
62
|
+
...spec,
|
|
63
|
+
desc: $desc(opts, spec.desc)
|
|
131
64
|
});
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
* are allowed and will receive a `"true"` as their value. However, if `strict`
|
|
139
|
-
* is true, only full KV pairs are allowed.
|
|
140
|
-
*
|
|
141
|
-
* @param spec -
|
|
142
|
-
* @param delim -
|
|
143
|
-
*/
|
|
144
|
-
export const kvPairs = (spec, delim = "=", strict) => ({
|
|
145
|
-
coerce: coerceKV(delim, strict),
|
|
146
|
-
hint: `key${delim}val`,
|
|
147
|
-
multi: true,
|
|
148
|
-
group: "main",
|
|
149
|
-
...spec,
|
|
65
|
+
const kvPairs = (spec, delim = "=", strict) => ({
|
|
66
|
+
coerce: coerceKV(delim, strict),
|
|
67
|
+
hint: `key${delim}val`,
|
|
68
|
+
multi: true,
|
|
69
|
+
group: "main",
|
|
70
|
+
...spec
|
|
150
71
|
});
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
* @param delim -
|
|
158
|
-
* @param strict -
|
|
159
|
-
*/
|
|
160
|
-
export const kvPairsMulti = (spec, delim = "=", strict) => ({
|
|
161
|
-
coerce: coerceKV(delim, strict, true),
|
|
162
|
-
hint: `key${delim}val(s)`,
|
|
163
|
-
multi: true,
|
|
164
|
-
group: "main",
|
|
165
|
-
...spec,
|
|
72
|
+
const kvPairsMulti = (spec, delim = "=", strict) => ({
|
|
73
|
+
coerce: coerceKV(delim, strict, true),
|
|
74
|
+
hint: `key${delim}val(s)`,
|
|
75
|
+
multi: true,
|
|
76
|
+
group: "main",
|
|
77
|
+
...spec
|
|
166
78
|
});
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
*
|
|
173
|
-
* @remarks
|
|
174
|
-
* An error will be thrown if the number of extracted values differs from the
|
|
175
|
-
* specified tuple size or any value coercion fails.
|
|
176
|
-
*
|
|
177
|
-
* @example
|
|
178
|
-
* ```ts
|
|
179
|
-
* parse({ a: tuple(coerceInt, 2, {})}, ["--a", "1,2"])
|
|
180
|
-
* // {
|
|
181
|
-
* // result: { a: Tuple { value: [1, 2] } },
|
|
182
|
-
* // index: 2,
|
|
183
|
-
* // rest: [],
|
|
184
|
-
* // done: true
|
|
185
|
-
* // }
|
|
186
|
-
* ```
|
|
187
|
-
*
|
|
188
|
-
* @param coerce -
|
|
189
|
-
* @param size -
|
|
190
|
-
* @param spec -
|
|
191
|
-
* @param delim -
|
|
192
|
-
*/
|
|
193
|
-
export const tuple = (coerce, size, spec, delim = ",") => ({
|
|
194
|
-
coerce: coerceTuple(coerce, size, delim),
|
|
195
|
-
hint: [...repeat("N", size)].join(delim),
|
|
196
|
-
group: "main",
|
|
197
|
-
...spec,
|
|
79
|
+
const tuple = (coerce, size2, spec, delim = ",") => ({
|
|
80
|
+
coerce: coerceTuple(coerce, size2, delim),
|
|
81
|
+
hint: [...repeat("N", size2)].join(delim),
|
|
82
|
+
group: "main",
|
|
83
|
+
...spec
|
|
198
84
|
});
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
85
|
+
const size = (size2, spec, delim = "x") => tuple(coerceInt, size2, spec, delim);
|
|
86
|
+
const vec = (size2, spec, delim = ",") => tuple(coerceFloat, size2, spec, delim);
|
|
87
|
+
export {
|
|
88
|
+
flag,
|
|
89
|
+
float,
|
|
90
|
+
floats,
|
|
91
|
+
hex,
|
|
92
|
+
hexes,
|
|
93
|
+
int,
|
|
94
|
+
ints,
|
|
95
|
+
json,
|
|
96
|
+
kvPairs,
|
|
97
|
+
kvPairsMulti,
|
|
98
|
+
oneOf,
|
|
99
|
+
oneOfMulti,
|
|
100
|
+
size,
|
|
101
|
+
string,
|
|
102
|
+
strings,
|
|
103
|
+
tuple,
|
|
104
|
+
vec
|
|
105
|
+
};
|
package/cli.d.ts
ADDED
package/cli.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { illegalArgs } from "@thi.ng/errors/illegal-arguments";
|
|
2
|
+
import { StreamLogger } from "@thi.ng/logger/stream";
|
|
3
|
+
import { padRight } from "@thi.ng/strings/pad-right";
|
|
4
|
+
import { PRESET_ANSI16, PRESET_NONE } from "@thi.ng/text-format/presets";
|
|
5
|
+
import { parse } from "./parse.js";
|
|
6
|
+
import { usage } from "./usage.js";
|
|
7
|
+
const cliApp = async (config) => {
|
|
8
|
+
const argv = config.argv || process.argv;
|
|
9
|
+
const isColor = !process.env.NO_COLOR;
|
|
10
|
+
const usageOpts = {
|
|
11
|
+
prefix: "",
|
|
12
|
+
color: isColor,
|
|
13
|
+
...config.usage
|
|
14
|
+
};
|
|
15
|
+
try {
|
|
16
|
+
let cmdID;
|
|
17
|
+
let cmd;
|
|
18
|
+
let start = config.start ?? 2;
|
|
19
|
+
if (config.single) {
|
|
20
|
+
cmdID = Object.keys(config.commands)[0];
|
|
21
|
+
if (!cmdID)
|
|
22
|
+
illegalArgs("no command provided");
|
|
23
|
+
cmd = config.commands[cmdID];
|
|
24
|
+
} else {
|
|
25
|
+
cmdID = argv[start];
|
|
26
|
+
cmd = config.commands[cmdID];
|
|
27
|
+
usageOpts.prefix += __descriptions(config.commands);
|
|
28
|
+
if (!cmd)
|
|
29
|
+
__usageAndExit(config, usageOpts);
|
|
30
|
+
start++;
|
|
31
|
+
}
|
|
32
|
+
let parsed;
|
|
33
|
+
try {
|
|
34
|
+
parsed = parse({ ...config.opts, ...cmd.opts }, argv, {
|
|
35
|
+
showUsage: true,
|
|
36
|
+
usageOpts,
|
|
37
|
+
start
|
|
38
|
+
});
|
|
39
|
+
} catch (_) {
|
|
40
|
+
}
|
|
41
|
+
if (!parsed)
|
|
42
|
+
process.exit(1);
|
|
43
|
+
if (cmd.inputs !== void 0 && cmd.inputs !== parsed.rest.length) {
|
|
44
|
+
process.stderr.write(`expected ${cmd.inputs || 0} input(s)
|
|
45
|
+
`);
|
|
46
|
+
__usageAndExit(config, usageOpts);
|
|
47
|
+
}
|
|
48
|
+
const ctx = await config.ctx(
|
|
49
|
+
{
|
|
50
|
+
logger: new StreamLogger(process.stderr, config.name, "INFO"),
|
|
51
|
+
format: isColor ? PRESET_ANSI16 : PRESET_NONE,
|
|
52
|
+
opts: parsed.result,
|
|
53
|
+
inputs: parsed.rest
|
|
54
|
+
},
|
|
55
|
+
cmd
|
|
56
|
+
);
|
|
57
|
+
await cmd.fn(ctx);
|
|
58
|
+
if (config.post)
|
|
59
|
+
await config.post(ctx, cmd);
|
|
60
|
+
} catch (e) {
|
|
61
|
+
process.stderr.write(e.message + "\n\n");
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
const __usageAndExit = (config, usageOpts) => {
|
|
66
|
+
process.stderr.write(usage(config.opts, usageOpts));
|
|
67
|
+
process.exit(1);
|
|
68
|
+
};
|
|
69
|
+
const __descriptions = (commands) => [
|
|
70
|
+
"\nAvailable commands:\n",
|
|
71
|
+
...Object.keys(commands).map(
|
|
72
|
+
(x) => `${padRight(16)(x)}: ${commands[x].desc}`
|
|
73
|
+
),
|
|
74
|
+
"\n"
|
|
75
|
+
].join("\n");
|
|
76
|
+
export {
|
|
77
|
+
cliApp
|
|
78
|
+
};
|
package/coerce.js
CHANGED
|
@@ -2,42 +2,50 @@ import { isHex } from "@thi.ng/checks/is-hex";
|
|
|
2
2
|
import { isNumericFloat, isNumericInt } from "@thi.ng/checks/is-numeric";
|
|
3
3
|
import { illegalArgs } from "@thi.ng/errors/illegal-arguments";
|
|
4
4
|
import { Tuple } from "./api.js";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
acc[x] = multi ? ["true"] : "true";
|
|
34
|
-
}
|
|
35
|
-
return acc;
|
|
36
|
-
}, {});
|
|
5
|
+
const coerceString = (x) => x;
|
|
6
|
+
const coerceFloat = (x) => isNumericFloat(x) ? parseFloat(x) : illegalArgs(`not a numeric value: ${x}`);
|
|
7
|
+
const coerceFloats = (xs) => xs.map(coerceFloat);
|
|
8
|
+
const coerceHexInt = (x) => isHex(x) ? parseInt(x, 16) : illegalArgs(`not a hex value: ${x}`);
|
|
9
|
+
const coerceHexInts = (xs) => xs.map(coerceHexInt);
|
|
10
|
+
const coerceInt = (x) => isNumericInt(x) ? parseInt(x) : illegalArgs(`not an integer: ${x}`);
|
|
11
|
+
const coerceInts = (xs) => xs.map(coerceInt);
|
|
12
|
+
const coerceJson = (x) => JSON.parse(x);
|
|
13
|
+
const coerceOneOf = (xs) => (x) => xs.includes(x) ? x : illegalArgs(`invalid option: ${x}`);
|
|
14
|
+
function coerceKV(delim = "=", strict = false, multi = false) {
|
|
15
|
+
return (pairs) => pairs.reduce((acc, x) => {
|
|
16
|
+
const idx = x.indexOf(delim);
|
|
17
|
+
strict && idx < 1 && illegalArgs(
|
|
18
|
+
`got '${x}', but expected a 'key${delim}value' pair`
|
|
19
|
+
);
|
|
20
|
+
if (idx > 0) {
|
|
21
|
+
const id = x.substring(0, idx);
|
|
22
|
+
const val = x.substring(idx + 1);
|
|
23
|
+
if (multi) {
|
|
24
|
+
acc[id] ? acc[id].push(val) : acc[id] = [val];
|
|
25
|
+
} else {
|
|
26
|
+
acc[id] = val;
|
|
27
|
+
}
|
|
28
|
+
} else {
|
|
29
|
+
acc[x] = multi ? ["true"] : "true";
|
|
30
|
+
}
|
|
31
|
+
return acc;
|
|
32
|
+
}, {});
|
|
37
33
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
34
|
+
const coerceTuple = (coerce, size, delim = ",") => (src) => {
|
|
35
|
+
const parts = src.split(delim);
|
|
36
|
+
parts.length !== size && illegalArgs(`got '${src}', but expected a tuple of ${size} values`);
|
|
37
|
+
return new Tuple(parts.map(coerce));
|
|
38
|
+
};
|
|
39
|
+
export {
|
|
40
|
+
coerceFloat,
|
|
41
|
+
coerceFloats,
|
|
42
|
+
coerceHexInt,
|
|
43
|
+
coerceHexInts,
|
|
44
|
+
coerceInt,
|
|
45
|
+
coerceInts,
|
|
46
|
+
coerceJson,
|
|
47
|
+
coerceKV,
|
|
48
|
+
coerceOneOf,
|
|
49
|
+
coerceString,
|
|
50
|
+
coerceTuple
|
|
43
51
|
};
|
package/index.d.ts
CHANGED
package/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thi.ng/args",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "Declarative, functional & typechecked CLI argument/options parser, value coercions etc.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "./index.js",
|
|
@@ -24,7 +24,9 @@
|
|
|
24
24
|
"author": "Karsten Schmidt (https://thi.ng)",
|
|
25
25
|
"license": "Apache-2.0",
|
|
26
26
|
"scripts": {
|
|
27
|
-
"build": "yarn
|
|
27
|
+
"build": "yarn build:esbuild && yarn build:decl",
|
|
28
|
+
"build:decl": "tsc --declaration --emitDeclarationOnly",
|
|
29
|
+
"build:esbuild": "esbuild --format=esm --platform=neutral --target=es2022 --tsconfig=tsconfig.json --outdir=. src/**/*.ts",
|
|
28
30
|
"clean": "rimraf --glob '*.js' '*.d.ts' '*.map' doc",
|
|
29
31
|
"doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts",
|
|
30
32
|
"doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose",
|
|
@@ -33,13 +35,16 @@
|
|
|
33
35
|
"test": "bun test"
|
|
34
36
|
},
|
|
35
37
|
"dependencies": {
|
|
36
|
-
"@thi.ng/api": "^8.9.
|
|
37
|
-
"@thi.ng/checks": "^3.4.
|
|
38
|
-
"@thi.ng/errors": "^2.4.
|
|
39
|
-
"@thi.ng/
|
|
38
|
+
"@thi.ng/api": "^8.9.13",
|
|
39
|
+
"@thi.ng/checks": "^3.4.13",
|
|
40
|
+
"@thi.ng/errors": "^2.4.7",
|
|
41
|
+
"@thi.ng/logger": "^2.1.0",
|
|
42
|
+
"@thi.ng/strings": "^3.7.4",
|
|
43
|
+
"@thi.ng/text-format": "^2.0.0"
|
|
40
44
|
},
|
|
41
45
|
"devDependencies": {
|
|
42
46
|
"@microsoft/api-extractor": "^7.38.3",
|
|
47
|
+
"esbuild": "^0.19.8",
|
|
43
48
|
"rimraf": "^5.0.5",
|
|
44
49
|
"tools": "^0.0.1",
|
|
45
50
|
"typedoc": "^0.25.4",
|
|
@@ -55,6 +60,7 @@
|
|
|
55
60
|
"declarative",
|
|
56
61
|
"functional",
|
|
57
62
|
"hex",
|
|
63
|
+
"logger",
|
|
58
64
|
"nodejs",
|
|
59
65
|
"parser",
|
|
60
66
|
"tuple",
|
|
@@ -65,7 +71,7 @@
|
|
|
65
71
|
"access": "public"
|
|
66
72
|
},
|
|
67
73
|
"engines": {
|
|
68
|
-
"node": ">=
|
|
74
|
+
"node": ">=18"
|
|
69
75
|
},
|
|
70
76
|
"files": [
|
|
71
77
|
"./*.js",
|
|
@@ -81,6 +87,9 @@
|
|
|
81
87
|
"./args": {
|
|
82
88
|
"default": "./args.js"
|
|
83
89
|
},
|
|
90
|
+
"./cli": {
|
|
91
|
+
"default": "./cli.js"
|
|
92
|
+
},
|
|
84
93
|
"./coerce": {
|
|
85
94
|
"default": "./coerce.js"
|
|
86
95
|
},
|
|
@@ -94,5 +103,5 @@
|
|
|
94
103
|
"thi.ng": {
|
|
95
104
|
"year": 2018
|
|
96
105
|
},
|
|
97
|
-
"gitHead": "
|
|
106
|
+
"gitHead": "25a42a81fac8603a1e440a7aa8bc343276211ff4\n"
|
|
98
107
|
}
|
package/parse.js
CHANGED
|
@@ -3,118 +3,120 @@ import { defError } from "@thi.ng/errors/deferror";
|
|
|
3
3
|
import { illegalArgs } from "@thi.ng/errors/illegal-arguments";
|
|
4
4
|
import { camel } from "@thi.ng/strings/case";
|
|
5
5
|
import { usage } from "./usage.js";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
throw new ParseError(e.message);
|
|
6
|
+
const ParseError = defError(() => "parse error");
|
|
7
|
+
const parse = (specs, argv, opts) => {
|
|
8
|
+
opts = { start: 2, showUsage: true, help: ["--help", "-h"], ...opts };
|
|
9
|
+
try {
|
|
10
|
+
return parseOpts(specs, argv, opts);
|
|
11
|
+
} catch (e) {
|
|
12
|
+
if (opts.showUsage) {
|
|
13
|
+
console.log(
|
|
14
|
+
e.message + "\n\n" + usage(specs, opts.usageOpts)
|
|
15
|
+
);
|
|
17
16
|
}
|
|
17
|
+
throw new ParseError(e.message);
|
|
18
|
+
}
|
|
18
19
|
};
|
|
19
20
|
const parseOpts = (specs, argv, opts) => {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
i++;
|
|
44
|
-
}
|
|
21
|
+
const aliases = aliasIndex(specs);
|
|
22
|
+
const acc = {};
|
|
23
|
+
let id;
|
|
24
|
+
let spec;
|
|
25
|
+
let i = opts.start;
|
|
26
|
+
for (; i < argv.length; ) {
|
|
27
|
+
const a = argv[i];
|
|
28
|
+
if (!id) {
|
|
29
|
+
if (opts.help.includes(a)) {
|
|
30
|
+
console.log(usage(specs, opts.usageOpts));
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const state = parseKey(specs, aliases, acc, a);
|
|
34
|
+
id = state.id;
|
|
35
|
+
spec = state.spec;
|
|
36
|
+
i = i + ~~(state.state < 2);
|
|
37
|
+
if (state.state)
|
|
38
|
+
break;
|
|
39
|
+
} else {
|
|
40
|
+
if (parseValue(spec, acc, id, a))
|
|
41
|
+
break;
|
|
42
|
+
id = null;
|
|
43
|
+
i++;
|
|
45
44
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
45
|
+
}
|
|
46
|
+
id && illegalArgs(`missing value for: --${id}`);
|
|
47
|
+
return {
|
|
48
|
+
result: processResults(specs, acc),
|
|
49
|
+
index: i,
|
|
50
|
+
rest: argv.slice(i),
|
|
51
|
+
done: i >= argv.length
|
|
52
|
+
};
|
|
53
53
|
};
|
|
54
|
-
const aliasIndex = (specs) => Object.entries(specs).reduce(
|
|
54
|
+
const aliasIndex = (specs) => Object.entries(specs).reduce(
|
|
55
|
+
(acc, [k, v]) => v.alias ? (acc[v.alias] = k, acc) : acc,
|
|
56
|
+
{}
|
|
57
|
+
);
|
|
55
58
|
const parseKey = (specs, aliases, acc, a) => {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
// stop parsing if fn returns false
|
|
74
|
-
if (spec.fn && !spec.fn("true"))
|
|
75
|
-
return { state: 1, spec };
|
|
76
|
-
}
|
|
77
|
-
return { state: 0, id, spec };
|
|
59
|
+
if (a[0] === "-") {
|
|
60
|
+
let id;
|
|
61
|
+
if (a[1] === "-") {
|
|
62
|
+
if (a === "--")
|
|
63
|
+
return { state: 1 };
|
|
64
|
+
id = camel(a.substring(2));
|
|
65
|
+
} else {
|
|
66
|
+
id = aliases[a.substring(1)];
|
|
67
|
+
!id && illegalArgs(`unknown option: ${a}`);
|
|
68
|
+
}
|
|
69
|
+
const spec = specs[id];
|
|
70
|
+
!spec && illegalArgs(id);
|
|
71
|
+
if (spec.flag) {
|
|
72
|
+
acc[id] = true;
|
|
73
|
+
id = void 0;
|
|
74
|
+
if (spec.fn && !spec.fn("true"))
|
|
75
|
+
return { state: 1, spec };
|
|
78
76
|
}
|
|
79
|
-
|
|
80
|
-
|
|
77
|
+
return { state: 0, id, spec };
|
|
78
|
+
}
|
|
79
|
+
return { state: 2 };
|
|
81
80
|
};
|
|
82
81
|
const parseValue = (spec, acc, id, a) => {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
return spec.fn && !spec.fn(a);
|
|
82
|
+
/^-[a-z]/i.test(a) && illegalArgs(`missing value for: --${id}`);
|
|
83
|
+
if (spec.multi) {
|
|
84
|
+
isArray(acc[id]) ? acc[id].push(a) : acc[id] = [a];
|
|
85
|
+
} else {
|
|
86
|
+
acc[id] = a;
|
|
87
|
+
}
|
|
88
|
+
return spec.fn && !spec.fn(a);
|
|
91
89
|
};
|
|
92
90
|
const processResults = (specs, acc) => {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
else if (spec.coerce) {
|
|
105
|
-
coerceValue(spec, acc, id);
|
|
106
|
-
}
|
|
91
|
+
let spec;
|
|
92
|
+
for (let id in specs) {
|
|
93
|
+
spec = specs[id];
|
|
94
|
+
if (acc[id] === void 0) {
|
|
95
|
+
if (spec.default !== void 0) {
|
|
96
|
+
acc[id] = spec.default;
|
|
97
|
+
} else if (spec.optional === false) {
|
|
98
|
+
illegalArgs(`missing arg: --${id}`);
|
|
99
|
+
}
|
|
100
|
+
} else if (spec.coerce) {
|
|
101
|
+
coerceValue(spec, acc, id);
|
|
107
102
|
}
|
|
108
|
-
|
|
103
|
+
}
|
|
104
|
+
return acc;
|
|
109
105
|
};
|
|
110
106
|
const coerceValue = (spec, acc, id) => {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
catch (e) {
|
|
118
|
-
throw new Error(`arg --${id}: ${e.message}`);
|
|
107
|
+
try {
|
|
108
|
+
if (spec.multi && spec.delim) {
|
|
109
|
+
acc[id] = acc[id].reduce(
|
|
110
|
+
(acc2, x) => (acc2.push(...x.split(spec.delim)), acc2),
|
|
111
|
+
[]
|
|
112
|
+
);
|
|
119
113
|
}
|
|
114
|
+
acc[id] = spec.coerce(acc[id]);
|
|
115
|
+
} catch (e) {
|
|
116
|
+
throw new Error(`arg --${id}: ${e.message}`);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
export {
|
|
120
|
+
ParseError,
|
|
121
|
+
parse
|
|
120
122
|
};
|
package/usage.js
CHANGED
|
@@ -1,76 +1,76 @@
|
|
|
1
|
+
import { isPlainObject } from "@thi.ng/checks/is-plain-object";
|
|
1
2
|
import { lengthAnsi } from "@thi.ng/strings/ansi";
|
|
2
3
|
import { capitalize, kebab } from "@thi.ng/strings/case";
|
|
3
4
|
import { padRight } from "@thi.ng/strings/pad-right";
|
|
4
5
|
import { repeat } from "@thi.ng/strings/repeat";
|
|
5
6
|
import { stringify } from "@thi.ng/strings/stringify";
|
|
6
7
|
import { SPLIT_ANSI, wordWrapLines } from "@thi.ng/strings/word-wrap";
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
...
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
8
|
+
import {
|
|
9
|
+
DEFAULT_THEME
|
|
10
|
+
} from "./api.js";
|
|
11
|
+
const usage = (specs, opts = {}) => {
|
|
12
|
+
opts = {
|
|
13
|
+
lineWidth: 80,
|
|
14
|
+
paramWidth: 32,
|
|
15
|
+
showDefaults: true,
|
|
16
|
+
prefix: "",
|
|
17
|
+
suffix: "",
|
|
18
|
+
groups: ["flags", "main"],
|
|
19
|
+
...opts
|
|
20
|
+
};
|
|
21
|
+
const theme = isPlainObject(opts.color) ? { ...DEFAULT_THEME, ...opts.color } : opts.color ? DEFAULT_THEME : {};
|
|
22
|
+
const indent = repeat(" ", opts.paramWidth);
|
|
23
|
+
const format = (ids) => ids.map((id) => argUsage(id, specs[id], opts, theme, indent));
|
|
24
|
+
const sortedIDs = Object.keys(specs).sort();
|
|
25
|
+
const groups = opts.groups ? opts.groups.map(
|
|
26
|
+
(gid) => [
|
|
27
|
+
gid,
|
|
28
|
+
sortedIDs.filter((id) => specs[id].group === gid)
|
|
29
|
+
]
|
|
30
|
+
).filter((g) => !!g[1].length) : [["options", sortedIDs]];
|
|
31
|
+
return [
|
|
32
|
+
...wrap(opts.prefix, opts.lineWidth),
|
|
33
|
+
...groups.map(
|
|
34
|
+
([gid, ids]) => [
|
|
35
|
+
...opts.showGroupNames ? [`${capitalize(gid)}:
|
|
36
|
+
`] : [],
|
|
37
|
+
...format(ids),
|
|
38
|
+
""
|
|
39
|
+
].join("\n")
|
|
40
|
+
),
|
|
41
|
+
...wrap(opts.suffix, opts.lineWidth)
|
|
42
|
+
].join("\n");
|
|
41
43
|
};
|
|
42
44
|
const argUsage = (id, spec, opts, theme, indent) => {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
argDefault(spec, opts, theme);
|
|
54
|
-
return (padRight(opts.paramWidth)(params, lengthAnsi(params)) +
|
|
55
|
-
wrap(body, opts.lineWidth - opts.paramWidth)
|
|
56
|
-
.map((l, i) => (i > 0 ? indent + l : l))
|
|
57
|
-
.join("\n"));
|
|
45
|
+
const hint = argHint(spec, theme);
|
|
46
|
+
const alias = argAlias(spec, theme, hint);
|
|
47
|
+
const name = ansi(`--${kebab(id)}`, theme.param);
|
|
48
|
+
const params = `${alias}${name}${hint}`;
|
|
49
|
+
const isRequired = spec.optional === false && spec.default === void 0;
|
|
50
|
+
const prefixes = [];
|
|
51
|
+
isRequired && prefixes.push("required");
|
|
52
|
+
spec.multi && prefixes.push("multiple");
|
|
53
|
+
const body = argPrefix(prefixes, theme, isRequired) + (spec.desc || "") + argDefault(spec, opts, theme);
|
|
54
|
+
return padRight(opts.paramWidth)(params, lengthAnsi(params)) + wrap(body, opts.lineWidth - opts.paramWidth).map((l, i) => i > 0 ? indent + l : l).join("\n");
|
|
58
55
|
};
|
|
59
56
|
const argHint = (spec, theme) => spec.hint ? ansi(" " + spec.hint, theme.hint) : "";
|
|
60
57
|
const argAlias = (spec, theme, hint) => spec.alias ? `${ansi("-" + spec.alias, theme.param)}${hint}, ` : "";
|
|
61
|
-
const argPrefix = (prefixes, theme, isRequired) => prefixes.length
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
58
|
+
const argPrefix = (prefixes, theme, isRequired) => prefixes.length ? ansi(
|
|
59
|
+
`[${prefixes.join(", ")}] `,
|
|
60
|
+
isRequired ? theme.required : theme.multi
|
|
61
|
+
) : "";
|
|
62
|
+
const argDefault = (spec, opts, theme) => opts.showDefaults && spec.default != null && spec.default !== false ? ansi(
|
|
63
|
+
` (default: ${stringify(true)(
|
|
64
|
+
spec.defaultHint != void 0 ? spec.defaultHint : spec.default
|
|
65
|
+
)})`,
|
|
66
|
+
theme.default
|
|
67
|
+
) : "";
|
|
68
|
+
const ansi = (x, col) => col != null ? `\x1B[${col}m${x}\x1B[0m` : x;
|
|
69
|
+
const wrap = (str, width) => str ? wordWrapLines(str, {
|
|
70
|
+
width,
|
|
71
|
+
splitter: SPLIT_ANSI,
|
|
72
|
+
hard: true
|
|
73
|
+
}) : [];
|
|
74
|
+
export {
|
|
75
|
+
usage
|
|
76
|
+
};
|