@thi.ng/args 2.10.1 → 3.0.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-26T11:53:06Z
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,20 @@ 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.0.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/args@3.0.0) (2025-09-26)
15
+
16
+ #### 🛑 Breaking changes
17
+
18
+ - update arg specs & arg factory fns, simplify types ([7ad3efb](https://github.com/thi-ng/umbrella/commit/7ad3efb))
19
+ - BREAKING CHANGES: update arg specs & arg factory fns, simplify types
20
+ - add `type` field in all arg specs
21
+ - add/update arg-related types
22
+ - update required arg handling: `optional: false` => `required: true`
23
+ - update delimiter handling (move into arg specs)
24
+ - update `tuple()` arg order
25
+ - remove obsolete coercion fns (`coerceFloats()` etc.)
26
+ - update tests
27
+
14
28
  ## [2.10.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/args@2.10.0) (2025-09-04)
15
29
 
16
30
  #### 🚀 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,15 @@ 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
+ - Tuple argument order has been swapped (to be more aligned with `size` and
106
+ `vec`) to: `tuple(size, coerce, {...})`
107
+ - Where applicable, `delim`iters are now to be included in the arg spec (rather
108
+ than given as separate function arg)
109
+
100
110
  ## Installation
101
111
 
102
112
  ```bash
@@ -115,7 +125,7 @@ For Node.js REPL:
115
125
  const args = await import("@thi.ng/args");
116
126
  ```
117
127
 
118
- Package sizes (brotli'd, pre-treeshake): ESM: 3.31 KB
128
+ Package sizes (brotli'd, pre-treeshake): ESM: 3.39 KB
119
129
 
120
130
  ## Dependencies
121
131
 
@@ -191,42 +201,50 @@ const specs: Args<TestArgs> = {
191
201
  hint: "PATH",
192
202
  desc: "Config file path (CLI args always take precedence over those settings)",
193
203
  }),
204
+
194
205
  // boolean flag (default: false)
195
206
  force: flag({
196
207
  alias: "f",
197
208
  desc: "Force operation",
198
- // side effect & predicate
199
- // parsing only continues if fn returns true
209
+ // side effect and/or validation
210
+ // parsing only continues if function returns true
200
211
  fn: (_) => (console.log("force mode enabled"), true),
201
212
  }),
213
+
202
214
  // hex int value
203
215
  bg: hex({
204
216
  desc: "Background color",
205
- // mandatory args require a `default` value and/or `optional: false`
217
+ // mandatory args require a `default` value and/or `required: true`
206
218
  default: 0xffffff,
207
219
  defaultHint: "ffffff",
208
220
  }),
221
+
209
222
  // enum value (mandatory)
210
223
  type: oneOf(["png", "jpg", "gif", "tiff"], {
211
224
  alias: "t",
212
225
  desc: "Image type",
213
- // mandatory args require a `default` value and/or `optional: false`
214
- optional: false,
226
+ // mandatory args require a `default` value and/or `required: true`
227
+ required: true,
215
228
  }),
229
+
216
230
  // 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" }),
231
+ size: size(2, { hint: "WxH", desc: "Target size", delim: "x" }),
232
+ // syntax sugar for:
233
+ // size: tuple(2, coerceInt, { hint: "WxH", desc: "Target size" }, "x"),
234
+
220
235
  // another version for tuples of floating point values
221
- // pos: tuple(coerceFloat, 2, { desc: "Lat/Lon" }, ","),
222
- pos: vec(2, { desc: "Lat/Lon coordinates" }),
236
+ pos: vec(2, { desc: "Lat/Lon coordinates", hint: "LAT,LON" }),
237
+ // syntax sugar for:
238
+ // pos: tuple(2, coerceFloat, { desc: "Lat/Lon" }),
239
+
223
240
  // JSON string arg
224
241
  xtra: json({
225
242
  alias: "x",
226
243
  desc: "Extra options",
227
244
  group: "extra",
228
245
  }),
229
- // key-value pairs parsed into an object
246
+
247
+ // key-value pairs parsed into an object (multiple allowed)
230
248
  define: kvPairs({
231
249
  alias: "D",
232
250
  desc: "Define dict entry",
@@ -382,7 +400,7 @@ const HELLO: Command<HelloOpts, CommonOpts> = {
382
400
  name: string({
383
401
  alias: "n",
384
402
  desc: "Name for greeting",
385
- optional: false,
403
+ required: true,
386
404
  }),
387
405
  },
388
406
  // 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, 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,13 @@ 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>>(opts: readonly K[], spec: S) => S & {
137
+ type: "oneOf";
136
138
  coerce: Fn<string, K>;
139
+ desc: string;
137
140
  hint: string;
138
141
  group: string;
139
- } & {
140
- desc: string;
142
+ opts: readonly K[];
141
143
  };
142
144
  /**
143
145
  * Multi-arg version of {@link oneOf}. Returns full {@link ArgSpec} for multiple
@@ -147,15 +149,17 @@ export declare const oneOf: <K extends string, S extends Partial<ArgSpec<K>>>(op
147
149
  * @param opts -
148
150
  * @param spec -
149
151
  */
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[]>;
152
+ export declare const oneOfMulti: <K extends string, S extends ArgDef | ArgDefRequired<K>>(opts: readonly K[], spec: S & {
153
+ delim?: string;
154
+ }) => S & {
155
+ type: "oneOfMulti";
156
+ coerce: Fn<string, K>;
157
+ desc: string;
154
158
  hint: string;
155
- multi: true;
156
159
  group: string;
157
- } & {
158
- desc: string;
160
+ multi: true;
161
+ opts: readonly K[];
162
+ delim?: string;
159
163
  };
160
164
  /**
161
165
  * Returns a full {@link ArgSpec} for multiple `key=value` pair args, coerced
@@ -167,13 +171,19 @@ export declare const oneOfMulti: <K extends string, S extends Partial<ArgSpec<K[
167
171
  * is true, only full KV pairs are allowed.
168
172
  *
169
173
  * @param spec -
170
- * @param delim -
171
174
  */
172
- export declare const kvPairs: <S extends Partial<ArgSpec<KVDict>>>(spec: S, delim?: string, strict?: boolean) => S & {
175
+ export declare const kvPairs: <S extends ArgDef | ArgDefRequired<KVDict>>(spec: S & {
176
+ delim?: string;
177
+ strict?: boolean;
178
+ }) => S & {
179
+ type: "kvPairs";
173
180
  coerce: Fn<string[], KVDict>;
174
181
  hint: string;
175
- multi: true;
176
182
  group: string;
183
+ multi: true;
184
+ kvDelim?: string;
185
+ strict?: boolean;
186
+ split: false;
177
187
  };
178
188
  /**
179
189
  * Like {@link kvPairs}, but coerces KV pairs into a result {@link KVMultiDict}
@@ -181,20 +191,24 @@ export declare const kvPairs: <S extends Partial<ArgSpec<KVDict>>>(spec: S, deli
181
191
  * into arrays).
182
192
  *
183
193
  * @param spec -
184
- * @param delim -
185
- * @param strict -
186
194
  */
187
- export declare const kvPairsMulti: <S extends Partial<ArgSpec<KVMultiDict>>>(spec: S, delim?: string, strict?: boolean) => S & {
195
+ export declare const kvPairsMulti: <S extends ArgDef | ArgDefRequired<KVMultiDict>>(spec: S & {
196
+ delim?: string;
197
+ strict?: boolean;
198
+ }) => S & {
199
+ type: "kvPairsMulti";
188
200
  coerce: Fn<string[], KVMultiDict>;
189
201
  hint: string;
190
- multi: true;
191
202
  group: string;
203
+ multi: true;
204
+ delim?: string;
205
+ strict?: boolean;
192
206
  };
193
207
  /**
194
208
  * 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.
209
+ * single value string. The individual values are delimited by `delim` (default:
210
+ * `,`) and will be coerced into their target type via `coerce`. The result
211
+ * tuple will be wrapped in a {@link Tuple} instance.
198
212
  *
199
213
  * @remarks
200
214
  * An error will be thrown if the number of extracted values differs from the
@@ -205,7 +219,7 @@ export declare const kvPairsMulti: <S extends Partial<ArgSpec<KVMultiDict>>>(spe
205
219
  * import { coerceInt, parse, tuple } from "@thi.ng/args";
206
220
  *
207
221
  * console.log(
208
- * parse({ a: tuple(coerceInt, 2, {})}, ["--a", "1,2"])
222
+ * parse({ a: tuple(2, coerceInt, {})}, ["--a", "1,2"])
209
223
  * );
210
224
  * // {
211
225
  * // result: { a: Tuple { value: [1, 2] } },
@@ -215,39 +229,65 @@ export declare const kvPairsMulti: <S extends Partial<ArgSpec<KVMultiDict>>>(spe
215
229
  * // }
216
230
  * ```
217
231
  *
218
- * @param coerce -
219
232
  * @param size -
233
+ * @param coerce -
220
234
  * @param spec -
221
- * @param delim -
222
235
  */
223
- export declare const tuple: <T, S extends Partial<ArgSpec<Tuple<T>>>>(coerce: Fn<string, T>, size: number, spec: S, delim?: string) => S & {
236
+ export declare const tuple: <T, S extends ArgDef | ArgDefRequired<Tuple<T>>>(size: number, coerce: Fn<string, T>, spec: S & {
237
+ delim?: string;
238
+ }) => S & {
239
+ type: "tuple";
224
240
  coerce: Fn<string, Tuple<T>>;
225
241
  hint: string;
226
242
  group: string;
243
+ size: number;
244
+ delim?: string;
227
245
  };
228
246
  /**
229
- * Syntax sugar for `tuple(coerceInt, size, {...}, delim)`.
247
+ * Syntax sugar for `tuple(size, coerceInt, {...})`. See {@link tuple} for
248
+ * further details.
230
249
  *
231
250
  * @param size -
232
251
  * @param spec -
233
- * @param delim -
234
252
  */
235
- export declare const size: <S extends Partial<ArgSpec<Tuple<number>>>>(size: number, spec: S, delim?: string) => S & {
253
+ export declare const size: <S extends ArgDef | ArgDefRequired<Tuple<number>>>(size: number, spec: S & {
254
+ delim?: string;
255
+ }) => S & {
256
+ type: "tuple";
236
257
  coerce: Fn<string, Tuple<number>>;
237
258
  hint: string;
238
259
  group: string;
260
+ size: number;
261
+ delim?: string;
239
262
  };
240
263
  /**
241
- * Syntax sugar for `tuple(coerceFloat, size, {...}, delim)`.
264
+ * Syntax sugar for `tuple(size, coerceFloat, {...})`. See {@link tuple} for
265
+ * further details.
242
266
  *
243
267
  * @param size -
244
268
  * @param spec -
245
- * @param delim -
246
269
  */
247
- export declare const vec: <S extends Partial<ArgSpec<Tuple<number>>>>(size: number, spec: S, delim?: string) => S & {
270
+ export declare const vec: <S extends ArgDef | ArgDefRequired<Tuple<number>>>(size: number, spec: S & {
271
+ delim?: string;
272
+ }) => S & {
273
+ type: "tuple";
248
274
  coerce: Fn<string, Tuple<number>>;
249
275
  hint: string;
250
276
  group: string;
277
+ size: number;
278
+ delim?: string;
279
+ };
280
+ /**
281
+ * Returns full {@link ArgSpec} for a JSON value arg. The raw CLI value string
282
+ * will be automcatically coerced using {@link coerceJson}.
283
+ *
284
+ * @param spec -
285
+ */
286
+ export declare const json: <T, S extends ArgDef | ArgDefRequired<T>>(spec: S) => S & {
287
+ type: "json";
288
+ coerce: Fn<string, T>;
289
+ hint: string;
290
+ group: string;
251
291
  };
252
292
  /**
253
293
  * Re-usable preset arg spec for a `--dry-run` flag.
@@ -256,7 +296,7 @@ export declare const ARG_DRY_RUN: {
256
296
  dryRun: {
257
297
  desc: string;
258
298
  } & {
259
- flag: true;
299
+ type: "flag";
260
300
  default: boolean;
261
301
  group: string;
262
302
  };
@@ -269,7 +309,7 @@ export declare const ARG_QUIET: {
269
309
  alias: string;
270
310
  desc: string;
271
311
  } & {
272
- flag: true;
312
+ type: "flag";
273
313
  default: boolean;
274
314
  group: string;
275
315
  };
@@ -282,7 +322,7 @@ export declare const ARG_VERBOSE: {
282
322
  alias: string;
283
323
  desc: string;
284
324
  } & {
285
- flag: true;
325
+ type: "flag";
286
326
  default: boolean;
287
327
  group: string;
288
328
  };
package/args.js CHANGED
@@ -2,88 +2,104 @@ 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(", ")}`;
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");
50
44
  const oneOf = (opts, spec) => ({
51
- coerce: coerceOneOf(opts),
52
- hint: "ID",
53
- group: "main",
54
45
  ...spec,
55
- desc: $desc(opts, spec.desc)
46
+ type: "oneOf",
47
+ coerce: coerceOneOf(opts),
48
+ hint: spec.hint ?? "ID",
49
+ desc: __desc(opts, spec.desc),
50
+ group: spec.group ?? "main",
51
+ opts
56
52
  });
57
53
  const oneOfMulti = (opts, spec) => ({
58
- coerce: (values) => values.map(coerceOneOf(opts)),
59
- hint: $hint("ID", spec.delim),
60
- multi: true,
61
- group: "main",
62
54
  ...spec,
63
- desc: $desc(opts, spec.desc)
64
- });
65
- const kvPairs = (spec, delim = "=", strict) => ({
66
- coerce: coerceKV(delim, strict),
67
- hint: `key${delim}val`,
55
+ type: "oneOfMulti",
56
+ coerce: coerceOneOf(opts),
57
+ hint: spec.hint ?? __hint("ID", spec.delim),
58
+ desc: __desc(opts, spec.desc),
59
+ group: spec.group ?? "main",
68
60
  multi: true,
69
- group: "main",
70
- ...spec
61
+ opts
71
62
  });
72
- const kvPairsMulti = (spec, delim = "=", strict) => ({
73
- coerce: coerceKV(delim, strict, true),
74
- hint: `key${delim}val(s)`,
75
- multi: true,
63
+ const kvPairs = (spec) => {
64
+ if (!spec.delim) spec.delim = "=";
65
+ return {
66
+ type: "kvPairs",
67
+ coerce: coerceKV(spec.delim, spec.strict, false),
68
+ hint: `key${spec.delim}val`,
69
+ group: "main",
70
+ multi: true,
71
+ split: false,
72
+ ...spec
73
+ };
74
+ };
75
+ const kvPairsMulti = (spec) => ({
76
+ type: "kvPairsMulti",
77
+ coerce: coerceKV(spec.delim, spec.strict, true),
78
+ hint: `key${spec.delim}val`,
76
79
  group: "main",
80
+ multi: true,
77
81
  ...spec
78
82
  });
79
- const tuple = (coerce, size2, spec, delim = ",") => ({
80
- coerce: coerceTuple(coerce, size2, delim),
81
- hint: [...repeat("N", size2)].join(delim),
83
+ const tuple = (size2, coerce, spec) => {
84
+ if (!spec.delim) spec.delim = ",";
85
+ return {
86
+ type: "tuple",
87
+ hint: [...repeat("N", size2)].join(spec.delim),
88
+ coerce: coerceTuple(coerce, size2, spec.delim),
89
+ group: "main",
90
+ size: size2,
91
+ ...spec
92
+ };
93
+ };
94
+ const size = (size2, spec) => tuple(size2, coerceInt, spec);
95
+ const vec = (size2, spec) => tuple(size2, coerceInt, spec);
96
+ const json = (spec) => ({
97
+ type: "json",
98
+ coerce: coerceJson,
99
+ hint: "JSON",
82
100
  group: "main",
83
101
  ...spec
84
102
  });
85
- const size = (size2, spec, delim = "x") => tuple(coerceInt, size2, spec, delim);
86
- const vec = (size2, spec, delim = ",") => tuple(coerceFloat, size2, spec, delim);
87
103
  const ARG_DRY_RUN = {
88
104
  dryRun: flag({
89
105
  desc: "Dry run (no changes applied)"
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.1",
3
+ "version": "3.0.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",
@@ -42,7 +42,7 @@
42
42
  "@thi.ng/api": "^8.12.2",
43
43
  "@thi.ng/checks": "^3.7.18",
44
44
  "@thi.ng/errors": "^2.5.42",
45
- "@thi.ng/logger": "^3.1.17",
45
+ "@thi.ng/logger": "^3.2.0",
46
46
  "@thi.ng/strings": "^3.9.22",
47
47
  "@thi.ng/text-format": "^2.2.41"
48
48
  },
@@ -110,5 +110,5 @@
110
110
  "tag": "cli",
111
111
  "year": 2018
112
112
  },
113
- "gitHead": "b7ede4f099767e0175ea8e09257208f73970b220\n"
113
+ "gitHead": "f3fa7a4798132f2faf9eb1ef12d99b9ca2148ec3\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");