@thi.ng/args 2.10.2 → 3.1.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # Change Log
2
2
 
3
- - **Last updated**: 2025-09-25T11:10:32Z
3
+ - **Last updated**: 2025-09-26T13:50:05Z
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.
@@ -11,6 +11,34 @@ See [Conventional Commits](https://conventionalcommits.org/) for commit guidelin
11
11
  **Note:** Unlisted _patch_ versions only involve non-code or otherwise excluded changes
12
12
  and/or version bumps of transitive dependencies.
13
13
 
14
+ ## [3.1.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/args@3.1.0) (2025-09-26)
15
+
16
+ #### 🚀 Features
17
+
18
+ - add ARG_TYPES index ([5f68489](https://github.com/thi-ng/umbrella/commit/5f68489))
19
+
20
+ #### ♻️ Refactoring
21
+
22
+ - update factory fns to only take single arg (spec) ([2766125](https://github.com/thi-ng/umbrella/commit/2766125))
23
+ - update `oneOf`, `oneOfMulti`, `tuple`, `size`, `vec`
24
+ - fix `required`-handling in `ARG_OUT_DIR` & `ARG_OUT_FILE` presets
25
+ - fix code example for `tuple()`
26
+ - update tests
27
+
28
+ # [3.0.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/args@3.0.0) (2025-09-26)
29
+
30
+ #### 🛑 Breaking changes
31
+
32
+ - update arg specs & arg factory fns, simplify types ([7ad3efb](https://github.com/thi-ng/umbrella/commit/7ad3efb))
33
+ - BREAKING CHANGES: update arg specs & arg factory fns, simplify types
34
+ - add `type` field in all arg specs
35
+ - add/update arg-related types
36
+ - update required arg handling: `optional: false` => `required: true`
37
+ - update delimiter handling (move into arg specs)
38
+ - update `tuple()` arg order
39
+ - remove obsolete coercion fns (`coerceFloats()` etc.)
40
+ - update tests
41
+
14
42
  ## [2.10.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/args@2.10.0) (2025-09-04)
15
43
 
16
44
  #### 🚀 Features
package/README.md CHANGED
@@ -19,6 +19,7 @@
19
19
  - [Re-usable argument presets](#re-usable-argument-presets)
20
20
  - [CLI app framework](#cli-app-framework)
21
21
  - [Status](#status)
22
+ - [Breaking changes in 3.0.0](#breaking-changes-in-300)
22
23
  - [Installation](#installation)
23
24
  - [Dependencies](#dependencies)
24
25
  - [Projects using this package](#projects-using-this-package)
@@ -97,6 +98,19 @@ section](#projects-using-this-package) in this readme.
97
98
 
98
99
  [Search or submit any issues for this package](https://github.com/thi-ng/umbrella/issues?q=%5Bargs%5D+in%3Atitle)
99
100
 
101
+ ### Breaking changes in 3.0.0
102
+
103
+ - Required arguments are now to be specified using either `required: true` or
104
+ given a `default` value
105
+ - All factory functions now only accept a single arg spec, with any type-specific
106
+ options moved into the spec, for example:
107
+ - old: `oneOf(["a","b","c"], {...})`
108
+ - new: `oneOf({ opts: ["a","b","c"], ...})`
109
+ - old: `tuple(identity, 3, {...})`
110
+ - new: `tuple({ size: 3, coerce: identity, ...})`
111
+ - Where applicable, `delim`iters are now to be included in the arg spec (rather
112
+ than given as separate function arg)
113
+
100
114
  ## Installation
101
115
 
102
116
  ```bash
@@ -115,7 +129,7 @@ For Node.js REPL:
115
129
  const args = await import("@thi.ng/args");
116
130
  ```
117
131
 
118
- Package sizes (brotli'd, pre-treeshake): ESM: 3.31 KB
132
+ Package sizes (brotli'd, pre-treeshake): ESM: 3.47 KB
119
133
 
120
134
  ## Dependencies
121
135
 
@@ -191,42 +205,51 @@ const specs: Args<TestArgs> = {
191
205
  hint: "PATH",
192
206
  desc: "Config file path (CLI args always take precedence over those settings)",
193
207
  }),
208
+
194
209
  // boolean flag (default: false)
195
210
  force: flag({
196
211
  alias: "f",
197
212
  desc: "Force operation",
198
- // side effect & predicate
199
- // parsing only continues if fn returns true
213
+ // side effect and/or validation
214
+ // parsing only continues if function returns true
200
215
  fn: (_) => (console.log("force mode enabled"), true),
201
216
  }),
217
+
202
218
  // hex int value
203
219
  bg: hex({
204
220
  desc: "Background color",
205
- // mandatory args require a `default` value and/or `optional: false`
221
+ // mandatory args require a `default` value and/or `required: true`
206
222
  default: 0xffffff,
207
223
  defaultHint: "ffffff",
208
224
  }),
225
+
209
226
  // enum value (mandatory)
210
- type: oneOf(["png", "jpg", "gif", "tiff"], {
227
+ type: oneOf({
211
228
  alias: "t",
212
229
  desc: "Image type",
213
- // mandatory args require a `default` value and/or `optional: false`
214
- optional: false,
230
+ opts: ["png", "jpg", "gif", "tiff"],
231
+ // mandatory args require a `default` value and/or `required: true`
232
+ required: true,
215
233
  }),
234
+
216
235
  // fixed size numeric tuple w/ `x` as delimiter
217
- // size: tuple(coerceInt, 2, { hint: "WxH", desc: "Target size" }, "x"),
218
- // syntax sugar for above:
219
- size: size(2, { hint: "WxH", desc: "Target size" }),
236
+ size: size({ size: 2, hint: "WxH", desc: "Target size", delim: "x" }),
237
+ // syntax sugar for:
238
+ // size: tuple(2, coerceInt, { hint: "WxH", desc: "Target size" }, "x"),
239
+
220
240
  // another version for tuples of floating point values
221
- // pos: tuple(coerceFloat, 2, { desc: "Lat/Lon" }, ","),
222
- pos: vec(2, { desc: "Lat/Lon coordinates" }),
241
+ pos: vec({ size: 2, desc: "Lat/Lon coordinates", hint: "LAT,LON" }),
242
+ // syntax sugar for:
243
+ // pos: tuple(2, coerceFloat, { desc: "Lat/Lon" }),
244
+
223
245
  // JSON string arg
224
246
  xtra: json({
225
247
  alias: "x",
226
248
  desc: "Extra options",
227
249
  group: "extra",
228
250
  }),
229
- // key-value pairs parsed into an object
251
+
252
+ // key-value pairs parsed into an object (multiple allowed)
230
253
  define: kvPairs({
231
254
  alias: "D",
232
255
  desc: "Define dict entry",
@@ -382,7 +405,7 @@ const HELLO: Command<HelloOpts, CommonOpts> = {
382
405
  name: string({
383
406
  alias: "n",
384
407
  desc: "Name for greeting",
385
- optional: false,
408
+ required: true,
386
409
  }),
387
410
  },
388
411
  // this command does not accept any inputs
package/api.d.ts CHANGED
@@ -1,7 +1,11 @@
1
- import type { Fn, Fn2, IDeref, IObjectOf } from "@thi.ng/api";
1
+ import type { Fn, Fn2, IDeref, IObjectOf, Predicate } from "@thi.ng/api";
2
2
  import type { ILogger } from "@thi.ng/logger";
3
3
  import type { FormatPresets } from "@thi.ng/text-format";
4
4
  export interface ArgSpecBase {
5
+ /**
6
+ * Unique arg type ID
7
+ */
8
+ type: string;
5
9
  /**
6
10
  * Shorthand for given arg/option
7
11
  */
@@ -31,44 +35,82 @@ export interface ArgSpecBase {
31
35
  */
32
36
  group?: string;
33
37
  }
34
- export type ArgSpecRestrict<T> = undefined extends T ? {} : {
35
- optional: false;
38
+ export type ArgSpec<T> = ArgSpecBase & ArgSpecRequired<T>;
39
+ export type ArgSpecRequired<T> = undefined extends T ? {
40
+ default?: T;
41
+ } : {
42
+ required: true;
36
43
  } | {
37
44
  default: T;
38
45
  };
39
- export type ArgSpec<T> = ArgSpecBase & ArgSpecRestrict<T>;
46
+ export type Args<T extends object> = {
47
+ [id in keyof T]: ArgSpec<T[id]>;
48
+ };
49
+ /**
50
+ * Partial arg spec given to various argument factory functions.
51
+ */
52
+ export type ArgDef = Omit<ArgSpecBase, "type">;
53
+ /**
54
+ * Partial arg spec (for required arguments) given to various argument factory
55
+ * functions.
56
+ */
57
+ export type ArgDefRequired<T> = ArgDef & ({
58
+ default: T;
59
+ } | {
60
+ required: true;
61
+ });
62
+ /**
63
+ * @internal
64
+ */
40
65
  export type ArgSpecExt = ArgSpec<any> & {
66
+ /**
67
+ * Value coercion fn.
68
+ */
41
69
  coerce?: Fn<any, any>;
70
+ /**
71
+ * Delimiter to split values (only used for `multi` args and if `split=false`)
72
+ */
42
73
  delim?: string;
74
+ /**
75
+ * Default value
76
+ */
43
77
  default?: any;
44
- flag?: boolean;
45
- fn?: Fn<string, boolean>;
78
+ /**
79
+ * User defined validation fn (can be used for side effects too).
80
+ */
81
+ fn?: Predicate<string>;
82
+ /**
83
+ * Indicator flag for args accepting multiple values
84
+ */
46
85
  multi?: boolean;
47
- optional?: any;
48
- };
49
- export type Args<T extends IObjectOf<any>> = {
50
- [id in keyof T]: boolean extends T[id] ? ArgSpec<T[id]> & {
51
- flag: true;
52
- } : any[] extends T[id] ? ArgSpec<T[id]> & {
53
- coerce: Fn<string[], Exclude<T[id], undefined>>;
54
- multi: true;
55
- delim?: string;
56
- } : KVDict extends T[id] ? ArgSpec<T[id]> & {
57
- coerce: Fn<string[], Exclude<T[id], undefined>>;
58
- multi: true;
59
- } : KVMultiDict extends T[id] ? ArgSpec<T[id]> & {
60
- coerce: Fn<string[], Exclude<T[id], undefined>>;
61
- multi: true;
62
- } : ArgSpec<T[id]> & {
63
- coerce: Fn<string, Exclude<T[id], undefined>>;
64
- };
86
+ /**
87
+ * Indicator flag for required args.
88
+ */
89
+ required?: true;
90
+ /**
91
+ * Unless false, collected values are split via `delim` prior to calling
92
+ * coercion function. Only used for `multi` specs.
93
+ */
94
+ split?: boolean;
65
95
  };
66
96
  export type KVDict = IObjectOf<string>;
67
97
  export type KVMultiDict = IObjectOf<string[]>;
68
98
  export interface ParseResult<T> {
99
+ /**
100
+ * Parsed arguments
101
+ */
69
102
  result: T;
103
+ /**
104
+ * `process.argv` index (+1) where parsing stopped
105
+ */
70
106
  index: number;
107
+ /**
108
+ * If true, all elements of `process.argv` have been consumed
109
+ */
71
110
  done: boolean;
111
+ /**
112
+ * Remaining unparsed elements of `process.argv` (if any)
113
+ */
72
114
  rest: string[];
73
115
  }
74
116
  export interface ParseOpts {
package/args.d.ts CHANGED
@@ -1,13 +1,13 @@
1
1
  import { type Fn } from "@thi.ng/api/fn";
2
- import type { ArgSpec, KVDict, KVMultiDict, Tuple } from "./api.js";
2
+ import type { ArgDef, ArgDefRequired, ArgSpec, KVDict, KVMultiDict, Tuple } from "./api.js";
3
3
  /**
4
4
  * Returns a full {@link ArgSpec} for a boolean flag. The mere presence of this
5
5
  * arg will enable the flag.
6
6
  *
7
7
  * @param spec -
8
8
  */
9
- export declare const flag: <S extends Partial<ArgSpec<boolean>>>(spec: S) => S & {
10
- flag: true;
9
+ export declare const flag: <S extends ArgDef>(spec: S) => S & {
10
+ type: "flag";
11
11
  default: boolean;
12
12
  group: string;
13
13
  };
@@ -16,7 +16,8 @@ export declare const flag: <S extends Partial<ArgSpec<boolean>>>(spec: S) => S &
16
16
  *
17
17
  * @param spec -
18
18
  */
19
- export declare const string: <S extends Partial<ArgSpec<string>>>(spec: S) => S & {
19
+ export declare const string: <S extends ArgDef | ArgDefRequired<string>>(spec: S) => S & {
20
+ type: "string";
20
21
  coerce: Fn<string, string>;
21
22
  hint: string;
22
23
  group: string;
@@ -28,13 +29,15 @@ export declare const string: <S extends Partial<ArgSpec<string>>>(spec: S) => S
28
29
  *
29
30
  * @param spec -
30
31
  */
31
- export declare const strings: <S extends Partial<ArgSpec<string[]> & {
32
- delim: string;
33
- }>>(spec: S) => S & {
34
- coerce: Fn<string[], string[]>;
32
+ export declare const strings: <S extends ArgDef | ArgDefRequired<unknown[]>>(spec: S & {
33
+ delim?: string;
34
+ }) => S & {
35
+ type: "strings";
36
+ coerce: Fn<string[], unknown[]>;
35
37
  hint: string;
36
- multi: true;
37
38
  group: string;
39
+ delim: string;
40
+ multi: true;
38
41
  };
39
42
  /**
40
43
  * Returns a full {@link ArgSpec} for a floating point value arg. The value
@@ -42,21 +45,28 @@ export declare const strings: <S extends Partial<ArgSpec<string[]> & {
42
45
  *
43
46
  * @param spec -
44
47
  */
45
- export declare const float: <S extends Partial<ArgSpec<number>>>(spec: S) => S & {
48
+ export declare const float: <S extends ArgDef | ArgDefRequired<number>>(spec: S) => S & {
49
+ type: "float";
46
50
  coerce: Fn<string, number>;
47
51
  hint: string;
48
52
  group: string;
49
53
  };
50
54
  /**
51
- * Returns a full {@link ArgSpec} for a single hex integer value arg. The value
52
- * will be autoatically coerced into a number using {@link coerceHexInt}.
55
+ * Multi-arg version of {@link float}. Returns a full {@link ArgSpec} for a
56
+ * multi floating point value arg. This argument can be provided mutiple times
57
+ * with values being coerced into numbers and collected into an array.
53
58
  *
54
59
  * @param spec -
55
60
  */
56
- export declare const hex: <S extends Partial<ArgSpec<number>>>(spec: S) => S & {
57
- coerce: Fn<string, number>;
61
+ export declare const floats: <S extends ArgDef | ArgDefRequired<unknown[]>>(spec: S & {
62
+ delim?: string;
63
+ }) => S & {
64
+ type: "floats";
65
+ coerce: Fn<string[], unknown[]>;
58
66
  hint: string;
59
67
  group: string;
68
+ delim: string;
69
+ multi: true;
60
70
  };
61
71
  /**
62
72
  * Returns a full {@link ArgSpec} for a single integer value arg. The value
@@ -64,66 +74,57 @@ export declare const hex: <S extends Partial<ArgSpec<number>>>(spec: S) => S & {
64
74
  *
65
75
  * @param spec -
66
76
  */
67
- export declare const int: <S extends Partial<ArgSpec<number>>>(spec: S) => S & {
77
+ export declare const int: <S extends ArgDef | ArgDefRequired<number>>(spec: S) => S & {
78
+ type: "int";
68
79
  coerce: Fn<string, number>;
69
80
  hint: string;
70
81
  group: string;
71
82
  };
72
83
  /**
73
- * Multi-arg version of {@link float}. Returns a full {@link ArgSpec} for a
74
- * multi floating point value arg. This argument can be provided mutiple times
75
- * with values being coerced into numbers and collected into an array.
84
+ * Multi-arg version of {@link int}. Returns a full {@link ArgSpec} for a multi
85
+ * integer value arg. This argument can be provided mutiple times with values
86
+ * being coerced into numbers and collected into an array.
76
87
  *
77
88
  * @param spec -
78
89
  */
79
- export declare const floats: <S extends Partial<ArgSpec<number[]> & {
80
- delim: string;
81
- }>>(spec: S) => S & {
82
- coerce: Fn<string[], number[]>;
90
+ export declare const ints: <S extends ArgDef | ArgDefRequired<unknown[]>>(spec: S & {
91
+ delim?: string;
92
+ }) => S & {
93
+ type: "ints";
94
+ coerce: Fn<string[], unknown[]>;
83
95
  hint: string;
84
- multi: true;
85
96
  group: string;
86
- };
87
- /**
88
- * Multi-arg version of {@link hex}. Returns a full {@link ArgSpec} for a multi
89
- * hex integer value arg. This argument can be provided mutiple times with
90
- * values being coerced into numbers and collected into an array.
91
- *
92
- * @param spec -
93
- */
94
- export declare const hexes: <S extends Partial<ArgSpec<number[]> & {
95
97
  delim: string;
96
- }>>(spec: S) => S & {
97
- coerce: Fn<string[], number[]>;
98
- hint: string;
99
98
  multi: true;
100
- group: string;
101
99
  };
102
100
  /**
103
- * Multi-arg version of {@link int}. Returns a full {@link ArgSpec} for a multi
104
- * integer value arg. This argument can be provided mutiple times with values
105
- * being coerced into numbers and collected into an array.
101
+ * Returns a full {@link ArgSpec} for a single hex integer value arg. The value
102
+ * will be autoatically coerced into a number using {@link coerceHexInt}.
106
103
  *
107
104
  * @param spec -
108
105
  */
109
- export declare const ints: <S extends Partial<ArgSpec<number[]> & {
110
- delim: string;
111
- }>>(spec: S) => S & {
112
- coerce: Fn<string[], number[]>;
106
+ export declare const hex: <S extends ArgDef | ArgDefRequired<number>>(spec: S) => S & {
107
+ type: "hex";
108
+ coerce: Fn<string, number>;
113
109
  hint: string;
114
- multi: true;
115
110
  group: string;
116
111
  };
117
112
  /**
118
- * Returns full {@link ArgSpec} for a JSON value arg. The raw CLI value string
119
- * will be automcatically coerced using {@link coerceJson}.
113
+ * Multi-arg version of {@link hex}. Returns a full {@link ArgSpec} for a multi
114
+ * hex integer value arg. This argument can be provided mutiple times with
115
+ * values being coerced into numbers and collected into an array.
120
116
  *
121
117
  * @param spec -
122
118
  */
123
- export declare const json: <T, S extends Partial<ArgSpec<T>>>(spec: S) => S & {
124
- coerce: Fn<string, T>;
119
+ export declare const hexes: <S extends ArgDef | ArgDefRequired<unknown[]>>(spec: S & {
120
+ delim?: string;
121
+ }) => S & {
122
+ type: "hexes";
123
+ coerce: Fn<string[], unknown[]>;
125
124
  hint: string;
126
125
  group: string;
126
+ delim: string;
127
+ multi: true;
127
128
  };
128
129
  /**
129
130
  * Returns full {@link ArgSpec} for an enum-like string value arg. The raw CLI
@@ -132,12 +133,15 @@ export declare const json: <T, S extends Partial<ArgSpec<T>>>(spec: S) => S & {
132
133
  * @param opts -
133
134
  * @param spec -
134
135
  */
135
- export declare const oneOf: <K extends string, S extends Partial<ArgSpec<K>>>(opts: readonly K[], spec: S) => S & {
136
+ export declare const oneOf: <K extends string, S extends ArgDef | ArgDefRequired<K>>(spec: S & {
137
+ opts: readonly K[];
138
+ }) => S & {
139
+ type: "oneOf";
136
140
  coerce: Fn<string, K>;
141
+ desc: string;
137
142
  hint: string;
138
143
  group: string;
139
- } & {
140
- desc: string;
144
+ opts: readonly K[];
141
145
  };
142
146
  /**
143
147
  * Multi-arg version of {@link oneOf}. Returns full {@link ArgSpec} for multiple
@@ -147,15 +151,18 @@ export declare const oneOf: <K extends string, S extends Partial<ArgSpec<K>>>(op
147
151
  * @param opts -
148
152
  * @param spec -
149
153
  */
150
- export declare const oneOfMulti: <K extends string, S extends Partial<ArgSpec<K[]> & {
151
- delim: string;
152
- }>>(opts: readonly K[], spec: S) => S & {
153
- coerce: Fn<string[], K[]>;
154
+ export declare const oneOfMulti: <K extends string, S extends ArgDef | ArgDefRequired<K>>(spec: S & {
155
+ opts: readonly K[];
156
+ delim?: string;
157
+ }) => S & {
158
+ type: "oneOfMulti";
159
+ coerce: Fn<string, K>;
160
+ desc: string;
154
161
  hint: string;
155
- multi: true;
156
162
  group: string;
157
- } & {
158
- desc: string;
163
+ multi: true;
164
+ opts: readonly K[];
165
+ delim?: string;
159
166
  };
160
167
  /**
161
168
  * Returns a full {@link ArgSpec} for multiple `key=value` pair args, coerced
@@ -167,13 +174,19 @@ export declare const oneOfMulti: <K extends string, S extends Partial<ArgSpec<K[
167
174
  * is true, only full KV pairs are allowed.
168
175
  *
169
176
  * @param spec -
170
- * @param delim -
171
177
  */
172
- export declare const kvPairs: <S extends Partial<ArgSpec<KVDict>>>(spec: S, delim?: string, strict?: boolean) => S & {
178
+ export declare const kvPairs: <S extends ArgDef | ArgDefRequired<KVDict>>(spec: S & {
179
+ delim?: string;
180
+ strict?: boolean;
181
+ }) => S & {
182
+ type: "kvPairs";
173
183
  coerce: Fn<string[], KVDict>;
174
184
  hint: string;
175
- multi: true;
176
185
  group: string;
186
+ multi: true;
187
+ kvDelim?: string;
188
+ strict?: boolean;
189
+ split: false;
177
190
  };
178
191
  /**
179
192
  * Like {@link kvPairs}, but coerces KV pairs into a result {@link KVMultiDict}
@@ -181,20 +194,24 @@ export declare const kvPairs: <S extends Partial<ArgSpec<KVDict>>>(spec: S, deli
181
194
  * into arrays).
182
195
  *
183
196
  * @param spec -
184
- * @param delim -
185
- * @param strict -
186
197
  */
187
- export declare const kvPairsMulti: <S extends Partial<ArgSpec<KVMultiDict>>>(spec: S, delim?: string, strict?: boolean) => S & {
198
+ export declare const kvPairsMulti: <S extends ArgDef | ArgDefRequired<KVMultiDict>>(spec: S & {
199
+ delim?: string;
200
+ strict?: boolean;
201
+ }) => S & {
202
+ type: "kvPairsMulti";
188
203
  coerce: Fn<string[], KVMultiDict>;
189
204
  hint: string;
190
- multi: true;
191
205
  group: string;
206
+ multi: true;
207
+ delim?: string;
208
+ strict?: boolean;
192
209
  };
193
210
  /**
194
211
  * Returns a full {@link ArgSpec} for a fixed `size` tuple extracted from a
195
- * single value string. The individual values are delimited by `delim` and will
196
- * be coerced into their target type via `coerce`. The result tuple will be
197
- * wrapped in a {@link Tuple} instance.
212
+ * single value string. The individual values are delimited by `delim` (default:
213
+ * `,`) and will be coerced into their target type via `coerce`. The result
214
+ * tuple will be wrapped in a {@link Tuple} instance.
198
215
  *
199
216
  * @remarks
200
217
  * An error will be thrown if the number of extracted values differs from the
@@ -202,10 +219,18 @@ export declare const kvPairsMulti: <S extends Partial<ArgSpec<KVMultiDict>>>(spe
202
219
  *
203
220
  * @example
204
221
  * ```ts tangle:../export/tuple.ts
205
- * import { coerceInt, parse, tuple } from "@thi.ng/args";
222
+ * import { coerceInt, parse, tuple, type Tuple } from "@thi.ng/args";
223
+ *
224
+ * interface A {
225
+ * a?: Tuple<number>;
226
+ * }
206
227
  *
207
228
  * console.log(
208
- * parse({ a: tuple(coerceInt, 2, {})}, ["--a", "1,2"])
229
+ * parse<A>(
230
+ * { a: tuple({ size: 2, coerce: coerceInt }) },
231
+ * ["--a", "1,2"],
232
+ * { start: 0 }
233
+ * )
209
234
  * );
210
235
  * // {
211
236
  * // result: { a: Tuple { value: [1, 2] } },
@@ -215,40 +240,78 @@ export declare const kvPairsMulti: <S extends Partial<ArgSpec<KVMultiDict>>>(spe
215
240
  * // }
216
241
  * ```
217
242
  *
218
- * @param coerce -
219
- * @param size -
220
243
  * @param spec -
221
- * @param delim -
222
244
  */
223
- export declare const tuple: <T, S extends Partial<ArgSpec<Tuple<T>>>>(coerce: Fn<string, T>, size: number, spec: S, delim?: string) => S & {
245
+ export declare const tuple: <T, S extends ArgDef | ArgDefRequired<Tuple<T>>>(spec: S & {
246
+ size: number;
247
+ coerce: Fn<string, T>;
248
+ delim?: string;
249
+ }) => S & {
250
+ type: "tuple";
224
251
  coerce: Fn<string, Tuple<T>>;
225
252
  hint: string;
226
253
  group: string;
254
+ size: number;
255
+ delim?: string;
227
256
  };
228
257
  /**
229
- * Syntax sugar for `tuple(coerceInt, size, {...}, delim)`.
258
+ * Syntax sugar for `tuple(size, coerceInt, {...})`. See {@link tuple} for
259
+ * further details.
230
260
  *
231
- * @param size -
232
261
  * @param spec -
233
- * @param delim -
234
262
  */
235
- export declare const size: <S extends Partial<ArgSpec<Tuple<number>>>>(size: number, spec: S, delim?: string) => S & {
263
+ export declare const size: <S extends ArgDef | ArgDefRequired<Tuple<number>>>(spec: S & {
264
+ size: number;
265
+ delim?: string;
266
+ }) => S & {
267
+ coerce: (x: string) => number;
268
+ size: number;
269
+ delim?: string;
270
+ } & {
271
+ type: "tuple";
236
272
  coerce: Fn<string, Tuple<number>>;
237
273
  hint: string;
238
274
  group: string;
275
+ size: number;
276
+ delim?: string;
239
277
  };
240
278
  /**
241
- * Syntax sugar for `tuple(coerceFloat, size, {...}, delim)`.
279
+ * Syntax sugar for `tuple(size, coerceFloat, {...})`. See {@link tuple} for
280
+ * further details.
242
281
  *
243
- * @param size -
244
282
  * @param spec -
245
- * @param delim -
246
283
  */
247
- export declare const vec: <S extends Partial<ArgSpec<Tuple<number>>>>(size: number, spec: S, delim?: string) => S & {
284
+ export declare const vec: <S extends ArgDef | ArgDefRequired<Tuple<number>>>(spec: S & {
285
+ size: number;
286
+ delim?: string;
287
+ }) => S & {
288
+ coerce: (x: string) => number;
289
+ size: number;
290
+ delim?: string;
291
+ } & {
292
+ type: "tuple";
248
293
  coerce: Fn<string, Tuple<number>>;
249
294
  hint: string;
250
295
  group: string;
296
+ size: number;
297
+ delim?: string;
251
298
  };
299
+ /**
300
+ * Returns full {@link ArgSpec} for a JSON value arg. The raw CLI value string
301
+ * will be automcatically coerced using {@link coerceJson}.
302
+ *
303
+ * @param spec -
304
+ */
305
+ export declare const json: <T, S extends ArgDef | ArgDefRequired<T>>(spec: S) => S & {
306
+ type: "json";
307
+ coerce: Fn<string, T>;
308
+ hint: string;
309
+ group: string;
310
+ };
311
+ /**
312
+ * Index which maps arg type IDs to their factory functions
313
+ */
314
+ export declare const ARG_TYPES: Record<string, Fn<any, ArgSpec<any>>>;
252
315
  /**
253
316
  * Re-usable preset arg spec for a `--dry-run` flag.
254
317
  */
@@ -256,7 +319,7 @@ export declare const ARG_DRY_RUN: {
256
319
  dryRun: {
257
320
  desc: string;
258
321
  } & {
259
- flag: true;
322
+ type: "flag";
260
323
  default: boolean;
261
324
  group: string;
262
325
  };
@@ -269,7 +332,7 @@ export declare const ARG_QUIET: {
269
332
  alias: string;
270
333
  desc: string;
271
334
  } & {
272
- flag: true;
335
+ type: "flag";
273
336
  default: boolean;
274
337
  group: string;
275
338
  };
@@ -282,7 +345,7 @@ export declare const ARG_VERBOSE: {
282
345
  alias: string;
283
346
  desc: string;
284
347
  } & {
285
- flag: true;
348
+ type: "flag";
286
349
  default: boolean;
287
350
  group: string;
288
351
  };
package/args.js CHANGED
@@ -2,88 +2,118 @@ import { identity } from "@thi.ng/api/fn";
2
2
  import { repeat } from "@thi.ng/strings/repeat";
3
3
  import {
4
4
  coerceFloat,
5
- coerceFloats,
6
5
  coerceHexInt,
7
- coerceHexInts,
8
6
  coerceInt,
9
- coerceInts,
10
7
  coerceJson,
11
8
  coerceKV,
12
9
  coerceOneOf,
13
10
  coerceTuple
14
11
  } from "./coerce.js";
15
- const $single = (coerce, hint) => (spec) => ({
12
+ const __desc = (opts, prefix) => `${prefix ? prefix + ": " : ""}${opts.map((x) => `"${x}"`).join(", ")}`;
13
+ const __hint = (hint, delim) => hint + (delim ? `[${delim}..]` : "");
14
+ const defSingle = (type, coerce, hint) => (spec) => ({
15
+ type,
16
16
  coerce,
17
17
  hint,
18
18
  group: "main",
19
19
  ...spec
20
20
  });
21
- const $multi = (coerce, hint) => (spec) => ({
22
- hint: $hint(hint, spec.delim),
23
- multi: true,
24
- coerce,
21
+ const defMulti = (type, coerce, hint, delim = ",") => (spec) => ({
22
+ type,
23
+ delim,
24
+ hint: __hint(hint, spec.delim ?? delim),
25
+ coerce: (x) => x.map(coerce),
25
26
  group: "main",
27
+ multi: true,
26
28
  ...spec
27
29
  });
28
- const $hint = (hint, delim) => hint + (delim ? `[${delim}..]` : "");
29
30
  const flag = (spec) => ({
30
- flag: true,
31
- default: false,
31
+ type: "flag",
32
32
  group: "flags",
33
+ default: false,
33
34
  ...spec
34
35
  });
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
48
- });
49
- const $desc = (opts, prefix) => `${prefix ? prefix + ": " : ""}${opts.map((x) => `"${x}"`).join(", ")}`;
50
- const oneOf = (opts, spec) => ({
51
- coerce: coerceOneOf(opts),
52
- hint: "ID",
53
- group: "main",
36
+ const string = defSingle("string", identity, "STR");
37
+ const strings = defMulti("strings", identity, "STR");
38
+ const float = defSingle("float", coerceFloat, "NUM");
39
+ const floats = defMulti("floats", coerceFloat, "NUM");
40
+ const int = defSingle("int", coerceInt, "INT");
41
+ const ints = defMulti("ints", coerceInt, "INT");
42
+ const hex = defSingle("hex", coerceHexInt, "HEX");
43
+ const hexes = defMulti("hexes", coerceHexInt, "HEX");
44
+ const oneOf = (spec) => ({
54
45
  ...spec,
55
- desc: $desc(opts, spec.desc)
46
+ type: "oneOf",
47
+ coerce: coerceOneOf(spec.opts),
48
+ hint: spec.hint ?? "ID",
49
+ group: spec.group ?? "main",
50
+ desc: __desc(spec.opts, spec.desc)
56
51
  });
57
- const oneOfMulti = (opts, spec) => ({
58
- coerce: (values) => values.map(coerceOneOf(opts)),
59
- hint: $hint("ID", spec.delim),
60
- multi: true,
61
- group: "main",
52
+ const oneOfMulti = (spec) => ({
62
53
  ...spec,
63
- desc: $desc(opts, spec.desc)
54
+ type: "oneOfMulti",
55
+ coerce: coerceOneOf(spec.opts),
56
+ hint: spec.hint ?? __hint("ID", spec.delim),
57
+ group: spec.group ?? "main",
58
+ desc: __desc(spec.opts, spec.desc),
59
+ multi: true
64
60
  });
65
- const kvPairs = (spec, delim = "=", strict) => ({
66
- coerce: coerceKV(delim, strict),
67
- hint: `key${delim}val`,
68
- multi: true,
61
+ const kvPairs = (spec) => {
62
+ if (!spec.delim) spec.delim = "=";
63
+ return {
64
+ type: "kvPairs",
65
+ coerce: coerceKV(spec.delim, spec.strict, false),
66
+ hint: `key${spec.delim}val`,
67
+ group: "main",
68
+ multi: true,
69
+ split: false,
70
+ ...spec
71
+ };
72
+ };
73
+ const kvPairsMulti = (spec) => ({
74
+ type: "kvPairsMulti",
75
+ coerce: coerceKV(spec.delim, spec.strict, true),
76
+ hint: `key${spec.delim}val`,
69
77
  group: "main",
70
- ...spec
71
- });
72
- const kvPairsMulti = (spec, delim = "=", strict) => ({
73
- coerce: coerceKV(delim, strict, true),
74
- hint: `key${delim}val(s)`,
75
78
  multi: true,
76
- group: "main",
77
79
  ...spec
78
80
  });
79
- const tuple = (coerce, size2, spec, delim = ",") => ({
80
- coerce: coerceTuple(coerce, size2, delim),
81
- hint: [...repeat("N", size2)].join(delim),
81
+ const tuple = (spec) => {
82
+ if (!spec.delim) spec.delim = ",";
83
+ return {
84
+ ...spec,
85
+ type: "tuple",
86
+ coerce: coerceTuple(spec.coerce, spec.size, spec.delim),
87
+ hint: spec.hint ?? [...repeat("N", spec.size)].join(spec.delim),
88
+ group: spec.group ?? "main"
89
+ };
90
+ };
91
+ const size = (spec) => tuple({ ...spec, coerce: coerceInt });
92
+ const vec = (spec) => tuple({ ...spec, coerce: coerceFloat });
93
+ const json = (spec) => ({
94
+ type: "json",
95
+ coerce: coerceJson,
96
+ hint: "JSON",
82
97
  group: "main",
83
98
  ...spec
84
99
  });
85
- const size = (size2, spec, delim = "x") => tuple(coerceInt, size2, spec, delim);
86
- const vec = (size2, spec, delim = ",") => tuple(coerceFloat, size2, spec, delim);
100
+ const ARG_TYPES = {
101
+ flag,
102
+ float,
103
+ floats,
104
+ hex,
105
+ hexes,
106
+ int,
107
+ ints,
108
+ json,
109
+ kvPairs,
110
+ kvPairsMulti,
111
+ oneOf,
112
+ oneOfMulti,
113
+ string,
114
+ strings,
115
+ tuple
116
+ };
87
117
  const ARG_DRY_RUN = {
88
118
  dryRun: flag({
89
119
  desc: "Dry run (no changes applied)"
@@ -107,7 +137,7 @@ const ARG_OUT_DIR = (defaultVal, desc) => ({
107
137
  desc: "Output directory" + (desc ?? ""),
108
138
  hint: "PATH",
109
139
  default: defaultVal,
110
- optional: !!defaultVal
140
+ required: !!defaultVal
111
141
  })
112
142
  });
113
143
  const ARG_OUT_FILE = (defaultVal, desc) => ({
@@ -116,7 +146,7 @@ const ARG_OUT_FILE = (defaultVal, desc) => ({
116
146
  desc: "Output file" + (desc ?? ""),
117
147
  hint: "PATH",
118
148
  default: defaultVal,
119
- optional: !!defaultVal
149
+ required: !!defaultVal
120
150
  })
121
151
  });
122
152
  export {
@@ -124,6 +154,7 @@ export {
124
154
  ARG_OUT_DIR,
125
155
  ARG_OUT_FILE,
126
156
  ARG_QUIET,
157
+ ARG_TYPES,
127
158
  ARG_VERBOSE,
128
159
  flag,
129
160
  float,
package/coerce.d.ts CHANGED
@@ -2,11 +2,8 @@ import type { Fn } from "@thi.ng/api";
2
2
  import { Tuple, type KVDict, type KVMultiDict } from "./api.js";
3
3
  export declare const coerceString: (x: string) => string;
4
4
  export declare const coerceFloat: (x: string) => number;
5
- export declare const coerceFloats: (values: string[]) => number[];
6
5
  export declare const coerceHexInt: (x: string) => number;
7
- export declare const coerceHexInts: (values: string[]) => number[];
8
6
  export declare const coerceInt: (x: string) => number;
9
- export declare const coerceInts: (values: string[]) => number[];
10
7
  export declare const coerceJson: <T>(x: string) => T;
11
8
  export declare const coerceOneOf: <K extends string>(values: readonly K[]) => (x: string) => K;
12
9
  export declare function coerceKV(delim?: string, strict?: boolean, multi?: false): Fn<string[], KVDict>;
package/coerce.js CHANGED
@@ -4,11 +4,8 @@ import { illegalArgs } from "@thi.ng/errors/illegal-arguments";
4
4
  import { Tuple } from "./api.js";
5
5
  const coerceString = (x) => x;
6
6
  const coerceFloat = (x) => isNumericFloat(x) ? parseFloat(x) : illegalArgs(`not a numeric value: ${x}`);
7
- const coerceFloats = (values) => values.map(coerceFloat);
8
7
  const coerceHexInt = (x) => isHex(x) ? parseInt(x, 16) : illegalArgs(`not a hex value: ${x}`);
9
- const coerceHexInts = (values) => values.map(coerceHexInt);
10
8
  const coerceInt = (x) => isNumericInt(x) ? parseInt(x) : illegalArgs(`not an integer: ${x}`);
11
- const coerceInts = (values) => values.map(coerceInt);
12
9
  const coerceJson = (x) => JSON.parse(x);
13
10
  const coerceOneOf = (values) => (x) => values.includes(x) ? x : illegalArgs(`invalid option: ${x}`);
14
11
  function coerceKV(delim = "=", strict = false, multi = false) {
@@ -38,11 +35,8 @@ const coerceTuple = (coerce, size, delim = ",") => (src) => {
38
35
  };
39
36
  export {
40
37
  coerceFloat,
41
- coerceFloats,
42
38
  coerceHexInt,
43
- coerceHexInts,
44
39
  coerceInt,
45
- coerceInts,
46
40
  coerceJson,
47
41
  coerceKV,
48
42
  coerceOneOf,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thi.ng/args",
3
- "version": "2.10.2",
3
+ "version": "3.1.0",
4
4
  "description": "Declarative, functional CLI argument/options parser, app framework, arg value coercions, multi/sub-commands, usage generation, error handling etc.",
5
5
  "type": "module",
6
6
  "module": "./index.js",
@@ -110,5 +110,5 @@
110
110
  "tag": "cli",
111
111
  "year": 2018
112
112
  },
113
- "gitHead": "3c3531350eec56011982583c694aeb1a2e0d0ff4\n"
113
+ "gitHead": "d86306c5375d19fa95bc900e433d097226516b1e\n"
114
114
  }
package/parse.js CHANGED
@@ -58,25 +58,23 @@ const __aliasIndex = (specs) => Object.entries(specs).reduce(
58
58
  {}
59
59
  );
60
60
  const __parseKey = (specs, aliases, acc, a) => {
61
- if (a[0] === "-") {
62
- let id;
63
- if (a[1] === "-") {
64
- if (a === "--") return { state: 1 };
65
- id = camel(a.substring(2));
66
- } else {
67
- id = aliases[a.substring(1)];
68
- !id && illegalArgs(`unknown option: ${a}`);
69
- }
70
- const spec = specs[id];
71
- !spec && illegalArgs(id);
72
- if (spec.flag) {
73
- acc[id] = true;
74
- id = void 0;
75
- if (spec.fn && !spec.fn("true")) return { state: 1, spec };
76
- }
77
- return { state: 0, id, spec };
61
+ if (a[0] !== "-") return { state: 2 };
62
+ let id;
63
+ if (a[1] === "-") {
64
+ if (a.length === 2) return { state: 1 };
65
+ id = camel(a.substring(2));
66
+ } else {
67
+ id = aliases[a.substring(1)];
68
+ !id && illegalArgs(`unknown option: ${a}`);
69
+ }
70
+ const spec = specs[id];
71
+ !spec && illegalArgs(id);
72
+ if (spec.type === "flag") {
73
+ acc[id] = true;
74
+ id = void 0;
75
+ if (spec.fn && !spec.fn("true")) return { state: 1, spec };
78
76
  }
79
- return { state: 2 };
77
+ return { state: 0, id, spec };
80
78
  };
81
79
  const __parseValue = (spec, acc, id, a) => {
82
80
  if (spec.multi) {
@@ -93,7 +91,7 @@ const __processResults = (specs, acc) => {
93
91
  if (acc[id] === void 0) {
94
92
  if (spec.default !== void 0) {
95
93
  acc[id] = spec.default;
96
- } else if (spec.optional === false) {
94
+ } else if (spec.required) {
97
95
  illegalArgs(`missing arg: --${kebab(id)}`);
98
96
  }
99
97
  } else if (spec.coerce) {
@@ -104,7 +102,7 @@ const __processResults = (specs, acc) => {
104
102
  };
105
103
  const __coerceValue = (spec, acc, id) => {
106
104
  try {
107
- if (spec.multi && spec.delim) {
105
+ if (spec.multi && spec.delim && spec.split !== false) {
108
106
  acc[id] = acc[id].reduce(
109
107
  (acc2, x) => (acc2.push(...x.split(spec.delim)), acc2),
110
108
  []
package/usage.js CHANGED
@@ -48,7 +48,7 @@ const __argUsage = (id, spec, opts, theme, indent) => {
48
48
  const alias = __argAlias(spec, theme, hint);
49
49
  const name = __ansi(`--${kebab(id)}`, theme.param);
50
50
  const params = `${alias}${name}${hint}`;
51
- const isRequired = spec.optional === false && spec.default === void 0;
51
+ const isRequired = !!spec.required && spec.default === void 0;
52
52
  const prefixes = [];
53
53
  isRequired && prefixes.push("required");
54
54
  spec.multi && prefixes.push("multiple");