@optique/core 1.2.0-dev.2298 → 1.2.0-dev.2302
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/dist/facade.cjs +47 -8
- package/dist/facade.d.cts +22 -1
- package/dist/facade.d.ts +22 -1
- package/dist/facade.js +47 -8
- package/dist/index.cjs +2 -0
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -2
- package/dist/internal/mode-dispatch.cjs +39 -1
- package/dist/internal/mode-dispatch.d.cts +8 -5
- package/dist/internal/mode-dispatch.d.ts +8 -5
- package/dist/internal/mode-dispatch.js +37 -1
- package/dist/valueparser.cjs +208 -0
- package/dist/valueparser.d.cts +83 -1
- package/dist/valueparser.d.ts +83 -1
- package/dist/valueparser.js +208 -2
- package/package.json +2 -2
- package/skills/optique/SKILL.md +33 -5
package/dist/valueparser.cjs
CHANGED
|
@@ -1,9 +1,34 @@
|
|
|
1
1
|
const require_message = require('./message.cjs');
|
|
2
|
+
const require_mode_dispatch = require('./internal/mode-dispatch.cjs');
|
|
2
3
|
const require_internal_dependency = require('./internal/dependency.cjs');
|
|
3
4
|
const require_suggestion = require('./suggestion.cjs');
|
|
4
5
|
const require_nonempty = require('./nonempty.cjs');
|
|
5
6
|
|
|
6
7
|
//#region src/valueparser.ts
|
|
8
|
+
function transformValueParserResult(result, mapping) {
|
|
9
|
+
if (!result.success) return result;
|
|
10
|
+
const preserveSnapshot = (mapped) => {
|
|
11
|
+
const snapshot = require_internal_dependency.getSnapshottedDefaultDependencyValues(result);
|
|
12
|
+
return snapshot == null ? mapped : require_internal_dependency.snapshotDefaultDependencyValues(mapped, snapshot);
|
|
13
|
+
};
|
|
14
|
+
if (result.deferred) try {
|
|
15
|
+
return preserveSnapshot({
|
|
16
|
+
success: true,
|
|
17
|
+
value: mapping.map(result.value),
|
|
18
|
+
deferred: true
|
|
19
|
+
});
|
|
20
|
+
} catch {
|
|
21
|
+
return preserveSnapshot({
|
|
22
|
+
success: true,
|
|
23
|
+
value: void 0,
|
|
24
|
+
deferred: true
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
return preserveSnapshot({
|
|
28
|
+
success: true,
|
|
29
|
+
value: mapping.map(result.value)
|
|
30
|
+
});
|
|
31
|
+
}
|
|
7
32
|
/**
|
|
8
33
|
* A predicate function that checks if an object is a {@link ValueParser}.
|
|
9
34
|
* @param object The object to check.
|
|
@@ -191,6 +216,187 @@ function choice(choices, options = {}) {
|
|
|
191
216
|
}
|
|
192
217
|
};
|
|
193
218
|
}
|
|
219
|
+
function biject(mapping) {
|
|
220
|
+
if (Array.isArray(mapping)) throw new TypeError("Expected biject mapping to be a non-array object.");
|
|
221
|
+
const keys = [];
|
|
222
|
+
for (const key in mapping) if (Object.prototype.hasOwnProperty.call(mapping, key)) keys.push(key);
|
|
223
|
+
if (keys.length < 1) throw new RangeError("Expected at least one biject entry.");
|
|
224
|
+
const source = choice(keys);
|
|
225
|
+
const forward = /* @__PURE__ */ new Map();
|
|
226
|
+
const reverse = /* @__PURE__ */ new Map();
|
|
227
|
+
for (const key of keys) {
|
|
228
|
+
const value = mapping[key];
|
|
229
|
+
if (reverse.has(value)) throw new RangeError(`Duplicate biject value for key ${JSON.stringify(key)}.`);
|
|
230
|
+
forward.set(key, value);
|
|
231
|
+
reverse.set(value, key);
|
|
232
|
+
}
|
|
233
|
+
const parser = transform(source, {
|
|
234
|
+
map(value) {
|
|
235
|
+
return forward.get(value);
|
|
236
|
+
},
|
|
237
|
+
unmap(value) {
|
|
238
|
+
const key = reverse.get(value);
|
|
239
|
+
if (key !== void 0) return key;
|
|
240
|
+
try {
|
|
241
|
+
return String(value);
|
|
242
|
+
} catch {
|
|
243
|
+
return "";
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
Object.defineProperty(parser, "validate", {
|
|
248
|
+
value(value) {
|
|
249
|
+
if (reverse.has(value)) return {
|
|
250
|
+
success: true,
|
|
251
|
+
value
|
|
252
|
+
};
|
|
253
|
+
let input;
|
|
254
|
+
try {
|
|
255
|
+
input = String(value);
|
|
256
|
+
} catch {
|
|
257
|
+
input = "";
|
|
258
|
+
}
|
|
259
|
+
const result = source.parse(input);
|
|
260
|
+
if (!result.success) return result;
|
|
261
|
+
return {
|
|
262
|
+
success: false,
|
|
263
|
+
error: formatDefaultChoiceError(input, keys)
|
|
264
|
+
};
|
|
265
|
+
},
|
|
266
|
+
configurable: true,
|
|
267
|
+
enumerable: true
|
|
268
|
+
});
|
|
269
|
+
return parser;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Creates a value parser that transforms the result of another value parser.
|
|
273
|
+
*
|
|
274
|
+
* This is useful when an existing value parser already describes the accepted
|
|
275
|
+
* CLI spelling, suggestions, and error messages, but your application wants a
|
|
276
|
+
* different result type. For example, a string `choice()` can be transformed
|
|
277
|
+
* into an internal enum, tagged object, or other domain type.
|
|
278
|
+
*
|
|
279
|
+
* Unlike parser-level `map()`, value parser transformation needs an inverse
|
|
280
|
+
* mapping. The `unmap` function lets the transformed parser format values,
|
|
281
|
+
* validate fallback/default values, and reuse the wrapped parser's metadata
|
|
282
|
+
* without guessing how to serialize the transformed type.
|
|
283
|
+
*
|
|
284
|
+
* Transform functions are synchronous. If the wrapped parser is async, the
|
|
285
|
+
* returned parser is async too, but `map` and `unmap` still run synchronously
|
|
286
|
+
* after the wrapped parser resolves.
|
|
287
|
+
*
|
|
288
|
+
* @template M The execution mode of the wrapped parser.
|
|
289
|
+
* @template T The value type produced by the wrapped parser.
|
|
290
|
+
* @template U The value type produced by the transformed parser.
|
|
291
|
+
* @param parser The value parser to transform.
|
|
292
|
+
* @param mapping Mapping functions between the wrapped and transformed value
|
|
293
|
+
* types.
|
|
294
|
+
* @returns A value parser that accepts the same input as `parser` and produces
|
|
295
|
+
* transformed values.
|
|
296
|
+
* @since 1.2.0
|
|
297
|
+
*/
|
|
298
|
+
function transform(parser, mapping) {
|
|
299
|
+
const normalize = parser.normalize?.bind(parser);
|
|
300
|
+
const suggest = parser.suggest?.bind(parser);
|
|
301
|
+
const transformedChoices = parser.choices == null ? void 0 : Object.freeze(parser.choices.map((choice$1) => mapping.map(choice$1)));
|
|
302
|
+
const transformed = {
|
|
303
|
+
mode: parser.mode,
|
|
304
|
+
metavar: parser.metavar,
|
|
305
|
+
placeholder: void 0,
|
|
306
|
+
...transformedChoices == null ? {} : { choices: transformedChoices },
|
|
307
|
+
parse(input) {
|
|
308
|
+
return require_mode_dispatch.mapModeValue(parser.mode, parser.parse(input), (result) => transformValueParserResult(result, mapping));
|
|
309
|
+
},
|
|
310
|
+
format(value) {
|
|
311
|
+
return parser.format(mapping.unmap(value));
|
|
312
|
+
},
|
|
313
|
+
...normalize == null ? {} : { normalize(value) {
|
|
314
|
+
return mapping.map(normalize(mapping.unmap(value)));
|
|
315
|
+
} },
|
|
316
|
+
...suggest == null ? {} : { suggest(prefix) {
|
|
317
|
+
return suggest(prefix);
|
|
318
|
+
} }
|
|
319
|
+
};
|
|
320
|
+
Object.defineProperty(transformed, "placeholder", {
|
|
321
|
+
get() {
|
|
322
|
+
try {
|
|
323
|
+
return mapping.map(parser.placeholder);
|
|
324
|
+
} catch {
|
|
325
|
+
return void 0;
|
|
326
|
+
}
|
|
327
|
+
},
|
|
328
|
+
configurable: true,
|
|
329
|
+
enumerable: false
|
|
330
|
+
});
|
|
331
|
+
if (typeof parser.validate === "function") {
|
|
332
|
+
const validate = parser.validate.bind(parser);
|
|
333
|
+
Object.defineProperty(transformed, "validate", {
|
|
334
|
+
value(value) {
|
|
335
|
+
const result = validate(mapping.unmap(value));
|
|
336
|
+
return result.success ? {
|
|
337
|
+
success: true,
|
|
338
|
+
value: mapping.map(result.value)
|
|
339
|
+
} : result;
|
|
340
|
+
},
|
|
341
|
+
configurable: true,
|
|
342
|
+
enumerable: true
|
|
343
|
+
});
|
|
344
|
+
} else if (parser.mode === "sync") {
|
|
345
|
+
const syncParser = parser;
|
|
346
|
+
Object.defineProperty(transformed, "validate", {
|
|
347
|
+
value(value) {
|
|
348
|
+
const result = syncParser.parse(syncParser.format(mapping.unmap(value)));
|
|
349
|
+
return result.success ? {
|
|
350
|
+
success: true,
|
|
351
|
+
value: mapping.map(result.value)
|
|
352
|
+
} : result;
|
|
353
|
+
},
|
|
354
|
+
configurable: true,
|
|
355
|
+
enumerable: true
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
if (require_internal_dependency.isDerivedValueParser(parser)) preserveTransformedDerivedMetadata(transformed, parser, mapping);
|
|
359
|
+
return transformed;
|
|
360
|
+
}
|
|
361
|
+
function preserveTransformedDerivedMetadata(transformed, parser, mapping) {
|
|
362
|
+
Object.defineProperties(transformed, {
|
|
363
|
+
[require_internal_dependency.derivedValueParserMarker]: {
|
|
364
|
+
value: true,
|
|
365
|
+
enumerable: true
|
|
366
|
+
},
|
|
367
|
+
[require_internal_dependency.dependencyId]: {
|
|
368
|
+
value: parser[require_internal_dependency.dependencyId],
|
|
369
|
+
enumerable: true
|
|
370
|
+
},
|
|
371
|
+
[require_internal_dependency.parseWithDependency]: {
|
|
372
|
+
value(input, dependencyValue) {
|
|
373
|
+
return require_mode_dispatch.mapMaybePromiseByMode(parser.mode, parser[require_internal_dependency.parseWithDependency](input, dependencyValue), (result) => transformValueParserResult(result, mapping));
|
|
374
|
+
},
|
|
375
|
+
enumerable: true
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
if (require_internal_dependency.dependencyIds in parser && parser[require_internal_dependency.dependencyIds] != null) Object.defineProperty(transformed, require_internal_dependency.dependencyIds, {
|
|
379
|
+
value: parser[require_internal_dependency.dependencyIds],
|
|
380
|
+
enumerable: true
|
|
381
|
+
});
|
|
382
|
+
if (require_internal_dependency.defaultValues in parser && parser[require_internal_dependency.defaultValues] != null) Object.defineProperty(transformed, require_internal_dependency.defaultValues, {
|
|
383
|
+
value: parser[require_internal_dependency.defaultValues],
|
|
384
|
+
enumerable: true
|
|
385
|
+
});
|
|
386
|
+
if (require_internal_dependency.singleDefaultValue in parser && parser[require_internal_dependency.singleDefaultValue] != null) Object.defineProperty(transformed, require_internal_dependency.singleDefaultValue, {
|
|
387
|
+
value: parser[require_internal_dependency.singleDefaultValue],
|
|
388
|
+
enumerable: true
|
|
389
|
+
});
|
|
390
|
+
if (require_internal_dependency.suggestWithDependency in parser && parser[require_internal_dependency.suggestWithDependency] != null) {
|
|
391
|
+
const suggest = parser[require_internal_dependency.suggestWithDependency];
|
|
392
|
+
Object.defineProperty(transformed, require_internal_dependency.suggestWithDependency, {
|
|
393
|
+
value(prefix, dependencyValue) {
|
|
394
|
+
return require_mode_dispatch.wrapIterableForMode(parser.mode, suggest(prefix, dependencyValue));
|
|
395
|
+
},
|
|
396
|
+
enumerable: true
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
}
|
|
194
400
|
/**
|
|
195
401
|
* Validates that an option value, if present, is a boolean.
|
|
196
402
|
* Throws a {@link TypeError} if the value is defined but not a boolean.
|
|
@@ -6336,6 +6542,7 @@ function plainObjectsEqual(a, b) {
|
|
|
6336
6542
|
}
|
|
6337
6543
|
|
|
6338
6544
|
//#endregion
|
|
6545
|
+
exports.biject = biject;
|
|
6339
6546
|
exports.checkBooleanOption = checkBooleanOption;
|
|
6340
6547
|
exports.checkEnumOption = checkEnumOption;
|
|
6341
6548
|
exports.choice = choice;
|
|
@@ -6364,5 +6571,6 @@ exports.portRange = portRange;
|
|
|
6364
6571
|
exports.semVer = semVer;
|
|
6365
6572
|
exports.socketAddress = socketAddress;
|
|
6366
6573
|
exports.string = string;
|
|
6574
|
+
exports.transform = transform;
|
|
6367
6575
|
exports.url = url;
|
|
6368
6576
|
exports.uuid = uuid;
|
package/dist/valueparser.d.cts
CHANGED
|
@@ -327,6 +327,36 @@ interface ChoiceOptionsNumber extends ChoiceOptionsBase {
|
|
|
327
327
|
* {@link ChoiceOptionsNumber} for number choices.
|
|
328
328
|
*/
|
|
329
329
|
type ChoiceOptions = ChoiceOptionsString;
|
|
330
|
+
/**
|
|
331
|
+
* Mapping functions for the {@link transform} value parser combinator.
|
|
332
|
+
*
|
|
333
|
+
* `map` converts values produced by the wrapped parser into the public result
|
|
334
|
+
* type. `unmap` converts public values back to the wrapped parser's type so
|
|
335
|
+
* `format()`, `validate()`, and default-value handling can keep using the
|
|
336
|
+
* wrapped parser's own validation and formatting rules.
|
|
337
|
+
*
|
|
338
|
+
* The two functions should be inverses for the values your CLI accepts:
|
|
339
|
+
* `unmap(map(input))` should return a value accepted by the wrapped parser,
|
|
340
|
+
* and `map(unmap(output))` should preserve valid public values.
|
|
341
|
+
*
|
|
342
|
+
* @template T The value type produced by the wrapped parser.
|
|
343
|
+
* @template U The value type produced by the transformed parser.
|
|
344
|
+
* @since 1.2.0
|
|
345
|
+
*/
|
|
346
|
+
interface TransformMapping<T, U> {
|
|
347
|
+
/**
|
|
348
|
+
* Converts a value produced by the wrapped parser into the transformed
|
|
349
|
+
* parser's public value type.
|
|
350
|
+
*/
|
|
351
|
+
map(value: T): U;
|
|
352
|
+
/**
|
|
353
|
+
* Converts a public transformed value back into the wrapped parser's value
|
|
354
|
+
* type for formatting and fallback validation.
|
|
355
|
+
*/
|
|
356
|
+
unmap(value: U): T;
|
|
357
|
+
}
|
|
358
|
+
type BijectKey<T> = Extract<keyof T, string | number>;
|
|
359
|
+
type BijectValue<T> = T[BijectKey<T>];
|
|
330
360
|
/**
|
|
331
361
|
* A predicate function that checks if an object is a {@link ValueParser}.
|
|
332
362
|
* @param object The object to check.
|
|
@@ -374,6 +404,58 @@ declare function choice<const T extends string>(choices: readonly T[], options?:
|
|
|
374
404
|
* @since 0.9.0
|
|
375
405
|
*/
|
|
376
406
|
declare function choice<const T extends number>(choices: readonly T[], options?: ChoiceOptionsNumber): ValueParser<"sync", T>;
|
|
407
|
+
/**
|
|
408
|
+
* Creates a value parser from a one-to-one mapping of CLI spellings to values.
|
|
409
|
+
*
|
|
410
|
+
* The mapping's string keys are accepted as command-line input, and each key
|
|
411
|
+
* is parsed into its corresponding value. Values must also be unique using
|
|
412
|
+
* the same equality semantics as `Map` keys, so the parser can format a value
|
|
413
|
+
* back to the original key.
|
|
414
|
+
*
|
|
415
|
+
* This is a convenience wrapper around `choice(Object.keys(mapping))` and
|
|
416
|
+
* {@link transform}. It keeps the input-side metadata from `choice()` while
|
|
417
|
+
* exposing the mapped values as the parser result type.
|
|
418
|
+
*
|
|
419
|
+
* @template T The one-to-one mapping from input strings to parsed values.
|
|
420
|
+
* @param mapping A mapping whose own enumerable string keys are valid inputs.
|
|
421
|
+
* @returns A value parser that accepts one of the mapping keys and returns the
|
|
422
|
+
* corresponding value.
|
|
423
|
+
* @throws {TypeError} If `mapping` is an array, or if any key is the empty
|
|
424
|
+
* string.
|
|
425
|
+
* @throws {RangeError} If the mapping has no own enumerable string keys, or if
|
|
426
|
+
* two keys map to the same value according to `Map` key equality.
|
|
427
|
+
* @since 1.2.0
|
|
428
|
+
*/
|
|
429
|
+
declare function biject<const T extends readonly unknown[]>(mapping: T): never;
|
|
430
|
+
declare function biject<const T extends object>(mapping: T): ValueParser<"sync", BijectValue<T>>;
|
|
431
|
+
/**
|
|
432
|
+
* Creates a value parser that transforms the result of another value parser.
|
|
433
|
+
*
|
|
434
|
+
* This is useful when an existing value parser already describes the accepted
|
|
435
|
+
* CLI spelling, suggestions, and error messages, but your application wants a
|
|
436
|
+
* different result type. For example, a string `choice()` can be transformed
|
|
437
|
+
* into an internal enum, tagged object, or other domain type.
|
|
438
|
+
*
|
|
439
|
+
* Unlike parser-level `map()`, value parser transformation needs an inverse
|
|
440
|
+
* mapping. The `unmap` function lets the transformed parser format values,
|
|
441
|
+
* validate fallback/default values, and reuse the wrapped parser's metadata
|
|
442
|
+
* without guessing how to serialize the transformed type.
|
|
443
|
+
*
|
|
444
|
+
* Transform functions are synchronous. If the wrapped parser is async, the
|
|
445
|
+
* returned parser is async too, but `map` and `unmap` still run synchronously
|
|
446
|
+
* after the wrapped parser resolves.
|
|
447
|
+
*
|
|
448
|
+
* @template M The execution mode of the wrapped parser.
|
|
449
|
+
* @template T The value type produced by the wrapped parser.
|
|
450
|
+
* @template U The value type produced by the transformed parser.
|
|
451
|
+
* @param parser The value parser to transform.
|
|
452
|
+
* @param mapping Mapping functions between the wrapped and transformed value
|
|
453
|
+
* types.
|
|
454
|
+
* @returns A value parser that accepts the same input as `parser` and produces
|
|
455
|
+
* transformed values.
|
|
456
|
+
* @since 1.2.0
|
|
457
|
+
*/
|
|
458
|
+
declare function transform<M extends Mode, T, U>(parser: ValueParser<M, T>, mapping: TransformMapping<T, U>): ValueParser<M, U>;
|
|
377
459
|
/**
|
|
378
460
|
* Validates that an option value, if present, is a boolean.
|
|
379
461
|
* Throws a {@link TypeError} if the value is defined but not a boolean.
|
|
@@ -3197,4 +3279,4 @@ declare function firstOf<const TParsers extends readonly [ValueParser<"sync", un
|
|
|
3197
3279
|
*/
|
|
3198
3280
|
declare function firstOf<const TParsers extends readonly ValueParser<"sync", unknown>[]>(parsers: TParsers, options?: FirstOfOptions): ValueParser<"sync", ValueParserValue<TParsers[number]>>;
|
|
3199
3281
|
//#endregion
|
|
3200
|
-
export { ChoiceOptions, ChoiceOptionsBase, ChoiceOptionsNumber, ChoiceOptionsString, CidrOptions, CidrValue, Color, ColorFormat, ColorOptions, CronExpression, CronExpressionForOptions, CronOptions, DeferredMap, DomainOptions, EmailOptions, FileSizeOptions, FileSizeOptionsBigInt, FileSizeOptionsNumber, FileSizeUnit, FirstOfOptions, FloatOptions, HostnameOptions, IntegerOptionsBigInt, IntegerOptionsNumber, IpOptions, Ipv4Options, Ipv6Options, Json, JsonOptions, KeyValueOptions, LocaleOptions, MacAddressOptions, type Mode, type ModeIterable, type ModeValue, type NonEmptyString, PortOptionsBigInt, PortOptionsNumber, PortRangeOptionsBigInt, PortRangeOptionsNumber, PortRangeValueBigInt, PortRangeValueNumber, SemVer, SemVerOptionsObject, SemVerOptionsString, SemVerString, SocketAddressOptions, SocketAddressValue, StringOptions, UrlOptions, Uuid, UuidOptions, ValueParser, ValueParserResult, checkBooleanOption, checkEnumOption, choice, cidr, color, cron, domain, email, ensureNonEmptyString, fileSize, firstOf, float, hostname, integer, ip, ipv4, ipv6, isNonEmptyString, isValueParser, json, keyValue, locale, macAddress, port, portRange, semVer, socketAddress, string, url, uuid };
|
|
3282
|
+
export { ChoiceOptions, ChoiceOptionsBase, ChoiceOptionsNumber, ChoiceOptionsString, CidrOptions, CidrValue, Color, ColorFormat, ColorOptions, CronExpression, CronExpressionForOptions, CronOptions, DeferredMap, DomainOptions, EmailOptions, FileSizeOptions, FileSizeOptionsBigInt, FileSizeOptionsNumber, FileSizeUnit, FirstOfOptions, FloatOptions, HostnameOptions, IntegerOptionsBigInt, IntegerOptionsNumber, IpOptions, Ipv4Options, Ipv6Options, Json, JsonOptions, KeyValueOptions, LocaleOptions, MacAddressOptions, type Mode, type ModeIterable, type ModeValue, type NonEmptyString, PortOptionsBigInt, PortOptionsNumber, PortRangeOptionsBigInt, PortRangeOptionsNumber, PortRangeValueBigInt, PortRangeValueNumber, SemVer, SemVerOptionsObject, SemVerOptionsString, SemVerString, SocketAddressOptions, SocketAddressValue, StringOptions, TransformMapping, UrlOptions, Uuid, UuidOptions, ValueParser, ValueParserResult, biject, checkBooleanOption, checkEnumOption, choice, cidr, color, cron, domain, email, ensureNonEmptyString, fileSize, firstOf, float, hostname, integer, ip, ipv4, ipv6, isNonEmptyString, isValueParser, json, keyValue, locale, macAddress, port, portRange, semVer, socketAddress, string, transform, url, uuid };
|
package/dist/valueparser.d.ts
CHANGED
|
@@ -327,6 +327,36 @@ interface ChoiceOptionsNumber extends ChoiceOptionsBase {
|
|
|
327
327
|
* {@link ChoiceOptionsNumber} for number choices.
|
|
328
328
|
*/
|
|
329
329
|
type ChoiceOptions = ChoiceOptionsString;
|
|
330
|
+
/**
|
|
331
|
+
* Mapping functions for the {@link transform} value parser combinator.
|
|
332
|
+
*
|
|
333
|
+
* `map` converts values produced by the wrapped parser into the public result
|
|
334
|
+
* type. `unmap` converts public values back to the wrapped parser's type so
|
|
335
|
+
* `format()`, `validate()`, and default-value handling can keep using the
|
|
336
|
+
* wrapped parser's own validation and formatting rules.
|
|
337
|
+
*
|
|
338
|
+
* The two functions should be inverses for the values your CLI accepts:
|
|
339
|
+
* `unmap(map(input))` should return a value accepted by the wrapped parser,
|
|
340
|
+
* and `map(unmap(output))` should preserve valid public values.
|
|
341
|
+
*
|
|
342
|
+
* @template T The value type produced by the wrapped parser.
|
|
343
|
+
* @template U The value type produced by the transformed parser.
|
|
344
|
+
* @since 1.2.0
|
|
345
|
+
*/
|
|
346
|
+
interface TransformMapping<T, U> {
|
|
347
|
+
/**
|
|
348
|
+
* Converts a value produced by the wrapped parser into the transformed
|
|
349
|
+
* parser's public value type.
|
|
350
|
+
*/
|
|
351
|
+
map(value: T): U;
|
|
352
|
+
/**
|
|
353
|
+
* Converts a public transformed value back into the wrapped parser's value
|
|
354
|
+
* type for formatting and fallback validation.
|
|
355
|
+
*/
|
|
356
|
+
unmap(value: U): T;
|
|
357
|
+
}
|
|
358
|
+
type BijectKey<T> = Extract<keyof T, string | number>;
|
|
359
|
+
type BijectValue<T> = T[BijectKey<T>];
|
|
330
360
|
/**
|
|
331
361
|
* A predicate function that checks if an object is a {@link ValueParser}.
|
|
332
362
|
* @param object The object to check.
|
|
@@ -374,6 +404,58 @@ declare function choice<const T extends string>(choices: readonly T[], options?:
|
|
|
374
404
|
* @since 0.9.0
|
|
375
405
|
*/
|
|
376
406
|
declare function choice<const T extends number>(choices: readonly T[], options?: ChoiceOptionsNumber): ValueParser<"sync", T>;
|
|
407
|
+
/**
|
|
408
|
+
* Creates a value parser from a one-to-one mapping of CLI spellings to values.
|
|
409
|
+
*
|
|
410
|
+
* The mapping's string keys are accepted as command-line input, and each key
|
|
411
|
+
* is parsed into its corresponding value. Values must also be unique using
|
|
412
|
+
* the same equality semantics as `Map` keys, so the parser can format a value
|
|
413
|
+
* back to the original key.
|
|
414
|
+
*
|
|
415
|
+
* This is a convenience wrapper around `choice(Object.keys(mapping))` and
|
|
416
|
+
* {@link transform}. It keeps the input-side metadata from `choice()` while
|
|
417
|
+
* exposing the mapped values as the parser result type.
|
|
418
|
+
*
|
|
419
|
+
* @template T The one-to-one mapping from input strings to parsed values.
|
|
420
|
+
* @param mapping A mapping whose own enumerable string keys are valid inputs.
|
|
421
|
+
* @returns A value parser that accepts one of the mapping keys and returns the
|
|
422
|
+
* corresponding value.
|
|
423
|
+
* @throws {TypeError} If `mapping` is an array, or if any key is the empty
|
|
424
|
+
* string.
|
|
425
|
+
* @throws {RangeError} If the mapping has no own enumerable string keys, or if
|
|
426
|
+
* two keys map to the same value according to `Map` key equality.
|
|
427
|
+
* @since 1.2.0
|
|
428
|
+
*/
|
|
429
|
+
declare function biject<const T extends readonly unknown[]>(mapping: T): never;
|
|
430
|
+
declare function biject<const T extends object>(mapping: T): ValueParser<"sync", BijectValue<T>>;
|
|
431
|
+
/**
|
|
432
|
+
* Creates a value parser that transforms the result of another value parser.
|
|
433
|
+
*
|
|
434
|
+
* This is useful when an existing value parser already describes the accepted
|
|
435
|
+
* CLI spelling, suggestions, and error messages, but your application wants a
|
|
436
|
+
* different result type. For example, a string `choice()` can be transformed
|
|
437
|
+
* into an internal enum, tagged object, or other domain type.
|
|
438
|
+
*
|
|
439
|
+
* Unlike parser-level `map()`, value parser transformation needs an inverse
|
|
440
|
+
* mapping. The `unmap` function lets the transformed parser format values,
|
|
441
|
+
* validate fallback/default values, and reuse the wrapped parser's metadata
|
|
442
|
+
* without guessing how to serialize the transformed type.
|
|
443
|
+
*
|
|
444
|
+
* Transform functions are synchronous. If the wrapped parser is async, the
|
|
445
|
+
* returned parser is async too, but `map` and `unmap` still run synchronously
|
|
446
|
+
* after the wrapped parser resolves.
|
|
447
|
+
*
|
|
448
|
+
* @template M The execution mode of the wrapped parser.
|
|
449
|
+
* @template T The value type produced by the wrapped parser.
|
|
450
|
+
* @template U The value type produced by the transformed parser.
|
|
451
|
+
* @param parser The value parser to transform.
|
|
452
|
+
* @param mapping Mapping functions between the wrapped and transformed value
|
|
453
|
+
* types.
|
|
454
|
+
* @returns A value parser that accepts the same input as `parser` and produces
|
|
455
|
+
* transformed values.
|
|
456
|
+
* @since 1.2.0
|
|
457
|
+
*/
|
|
458
|
+
declare function transform<M extends Mode, T, U>(parser: ValueParser<M, T>, mapping: TransformMapping<T, U>): ValueParser<M, U>;
|
|
377
459
|
/**
|
|
378
460
|
* Validates that an option value, if present, is a boolean.
|
|
379
461
|
* Throws a {@link TypeError} if the value is defined but not a boolean.
|
|
@@ -3197,4 +3279,4 @@ declare function firstOf<const TParsers extends readonly [ValueParser<"sync", un
|
|
|
3197
3279
|
*/
|
|
3198
3280
|
declare function firstOf<const TParsers extends readonly ValueParser<"sync", unknown>[]>(parsers: TParsers, options?: FirstOfOptions): ValueParser<"sync", ValueParserValue<TParsers[number]>>;
|
|
3199
3281
|
//#endregion
|
|
3200
|
-
export { ChoiceOptions, ChoiceOptionsBase, ChoiceOptionsNumber, ChoiceOptionsString, CidrOptions, CidrValue, Color, ColorFormat, ColorOptions, CronExpression, CronExpressionForOptions, CronOptions, DeferredMap, DomainOptions, EmailOptions, FileSizeOptions, FileSizeOptionsBigInt, FileSizeOptionsNumber, FileSizeUnit, FirstOfOptions, FloatOptions, HostnameOptions, IntegerOptionsBigInt, IntegerOptionsNumber, IpOptions, Ipv4Options, Ipv6Options, Json, JsonOptions, KeyValueOptions, LocaleOptions, MacAddressOptions, type Mode, type ModeIterable, type ModeValue, type NonEmptyString, PortOptionsBigInt, PortOptionsNumber, PortRangeOptionsBigInt, PortRangeOptionsNumber, PortRangeValueBigInt, PortRangeValueNumber, SemVer, SemVerOptionsObject, SemVerOptionsString, SemVerString, SocketAddressOptions, SocketAddressValue, StringOptions, UrlOptions, Uuid, UuidOptions, ValueParser, ValueParserResult, checkBooleanOption, checkEnumOption, choice, cidr, color, cron, domain, email, ensureNonEmptyString, fileSize, firstOf, float, hostname, integer, ip, ipv4, ipv6, isNonEmptyString, isValueParser, json, keyValue, locale, macAddress, port, portRange, semVer, socketAddress, string, url, uuid };
|
|
3282
|
+
export { ChoiceOptions, ChoiceOptionsBase, ChoiceOptionsNumber, ChoiceOptionsString, CidrOptions, CidrValue, Color, ColorFormat, ColorOptions, CronExpression, CronExpressionForOptions, CronOptions, DeferredMap, DomainOptions, EmailOptions, FileSizeOptions, FileSizeOptionsBigInt, FileSizeOptionsNumber, FileSizeUnit, FirstOfOptions, FloatOptions, HostnameOptions, IntegerOptionsBigInt, IntegerOptionsNumber, IpOptions, Ipv4Options, Ipv6Options, Json, JsonOptions, KeyValueOptions, LocaleOptions, MacAddressOptions, type Mode, type ModeIterable, type ModeValue, type NonEmptyString, PortOptionsBigInt, PortOptionsNumber, PortRangeOptionsBigInt, PortRangeOptionsNumber, PortRangeValueBigInt, PortRangeValueNumber, SemVer, SemVerOptionsObject, SemVerOptionsString, SemVerString, SocketAddressOptions, SocketAddressValue, StringOptions, TransformMapping, UrlOptions, Uuid, UuidOptions, ValueParser, ValueParserResult, biject, checkBooleanOption, checkEnumOption, choice, cidr, color, cron, domain, email, ensureNonEmptyString, fileSize, firstOf, float, hostname, integer, ip, ipv4, ipv6, isNonEmptyString, isValueParser, json, keyValue, locale, macAddress, port, portRange, semVer, socketAddress, string, transform, url, uuid };
|
package/dist/valueparser.js
CHANGED
|
@@ -1,9 +1,34 @@
|
|
|
1
1
|
import { cloneMessage, lineBreak, message, metavar, text, valueSet } from "./message.js";
|
|
2
|
-
import {
|
|
2
|
+
import { mapMaybePromiseByMode, mapModeValue, wrapIterableForMode } from "./internal/mode-dispatch.js";
|
|
3
|
+
import { defaultValues, dependencyId, dependencyIds, derivedValueParserMarker, getSnapshottedDefaultDependencyValues, isDerivedValueParser, parseWithDependency, singleDefaultValue, snapshotDefaultDependencyValues, suggestWithDependency } from "./internal/dependency.js";
|
|
3
4
|
import { appendValueHint, appendValueSuggestions, deduplicateSuggestions } from "./suggestion.js";
|
|
4
5
|
import { ensureNonEmptyString, isNonEmptyString } from "./nonempty.js";
|
|
5
6
|
|
|
6
7
|
//#region src/valueparser.ts
|
|
8
|
+
function transformValueParserResult(result, mapping) {
|
|
9
|
+
if (!result.success) return result;
|
|
10
|
+
const preserveSnapshot = (mapped) => {
|
|
11
|
+
const snapshot = getSnapshottedDefaultDependencyValues(result);
|
|
12
|
+
return snapshot == null ? mapped : snapshotDefaultDependencyValues(mapped, snapshot);
|
|
13
|
+
};
|
|
14
|
+
if (result.deferred) try {
|
|
15
|
+
return preserveSnapshot({
|
|
16
|
+
success: true,
|
|
17
|
+
value: mapping.map(result.value),
|
|
18
|
+
deferred: true
|
|
19
|
+
});
|
|
20
|
+
} catch {
|
|
21
|
+
return preserveSnapshot({
|
|
22
|
+
success: true,
|
|
23
|
+
value: void 0,
|
|
24
|
+
deferred: true
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
return preserveSnapshot({
|
|
28
|
+
success: true,
|
|
29
|
+
value: mapping.map(result.value)
|
|
30
|
+
});
|
|
31
|
+
}
|
|
7
32
|
/**
|
|
8
33
|
* A predicate function that checks if an object is a {@link ValueParser}.
|
|
9
34
|
* @param object The object to check.
|
|
@@ -191,6 +216,187 @@ function choice(choices, options = {}) {
|
|
|
191
216
|
}
|
|
192
217
|
};
|
|
193
218
|
}
|
|
219
|
+
function biject(mapping) {
|
|
220
|
+
if (Array.isArray(mapping)) throw new TypeError("Expected biject mapping to be a non-array object.");
|
|
221
|
+
const keys = [];
|
|
222
|
+
for (const key in mapping) if (Object.prototype.hasOwnProperty.call(mapping, key)) keys.push(key);
|
|
223
|
+
if (keys.length < 1) throw new RangeError("Expected at least one biject entry.");
|
|
224
|
+
const source = choice(keys);
|
|
225
|
+
const forward = /* @__PURE__ */ new Map();
|
|
226
|
+
const reverse = /* @__PURE__ */ new Map();
|
|
227
|
+
for (const key of keys) {
|
|
228
|
+
const value = mapping[key];
|
|
229
|
+
if (reverse.has(value)) throw new RangeError(`Duplicate biject value for key ${JSON.stringify(key)}.`);
|
|
230
|
+
forward.set(key, value);
|
|
231
|
+
reverse.set(value, key);
|
|
232
|
+
}
|
|
233
|
+
const parser = transform(source, {
|
|
234
|
+
map(value) {
|
|
235
|
+
return forward.get(value);
|
|
236
|
+
},
|
|
237
|
+
unmap(value) {
|
|
238
|
+
const key = reverse.get(value);
|
|
239
|
+
if (key !== void 0) return key;
|
|
240
|
+
try {
|
|
241
|
+
return String(value);
|
|
242
|
+
} catch {
|
|
243
|
+
return "";
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
Object.defineProperty(parser, "validate", {
|
|
248
|
+
value(value) {
|
|
249
|
+
if (reverse.has(value)) return {
|
|
250
|
+
success: true,
|
|
251
|
+
value
|
|
252
|
+
};
|
|
253
|
+
let input;
|
|
254
|
+
try {
|
|
255
|
+
input = String(value);
|
|
256
|
+
} catch {
|
|
257
|
+
input = "";
|
|
258
|
+
}
|
|
259
|
+
const result = source.parse(input);
|
|
260
|
+
if (!result.success) return result;
|
|
261
|
+
return {
|
|
262
|
+
success: false,
|
|
263
|
+
error: formatDefaultChoiceError(input, keys)
|
|
264
|
+
};
|
|
265
|
+
},
|
|
266
|
+
configurable: true,
|
|
267
|
+
enumerable: true
|
|
268
|
+
});
|
|
269
|
+
return parser;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Creates a value parser that transforms the result of another value parser.
|
|
273
|
+
*
|
|
274
|
+
* This is useful when an existing value parser already describes the accepted
|
|
275
|
+
* CLI spelling, suggestions, and error messages, but your application wants a
|
|
276
|
+
* different result type. For example, a string `choice()` can be transformed
|
|
277
|
+
* into an internal enum, tagged object, or other domain type.
|
|
278
|
+
*
|
|
279
|
+
* Unlike parser-level `map()`, value parser transformation needs an inverse
|
|
280
|
+
* mapping. The `unmap` function lets the transformed parser format values,
|
|
281
|
+
* validate fallback/default values, and reuse the wrapped parser's metadata
|
|
282
|
+
* without guessing how to serialize the transformed type.
|
|
283
|
+
*
|
|
284
|
+
* Transform functions are synchronous. If the wrapped parser is async, the
|
|
285
|
+
* returned parser is async too, but `map` and `unmap` still run synchronously
|
|
286
|
+
* after the wrapped parser resolves.
|
|
287
|
+
*
|
|
288
|
+
* @template M The execution mode of the wrapped parser.
|
|
289
|
+
* @template T The value type produced by the wrapped parser.
|
|
290
|
+
* @template U The value type produced by the transformed parser.
|
|
291
|
+
* @param parser The value parser to transform.
|
|
292
|
+
* @param mapping Mapping functions between the wrapped and transformed value
|
|
293
|
+
* types.
|
|
294
|
+
* @returns A value parser that accepts the same input as `parser` and produces
|
|
295
|
+
* transformed values.
|
|
296
|
+
* @since 1.2.0
|
|
297
|
+
*/
|
|
298
|
+
function transform(parser, mapping) {
|
|
299
|
+
const normalize = parser.normalize?.bind(parser);
|
|
300
|
+
const suggest = parser.suggest?.bind(parser);
|
|
301
|
+
const transformedChoices = parser.choices == null ? void 0 : Object.freeze(parser.choices.map((choice$1) => mapping.map(choice$1)));
|
|
302
|
+
const transformed = {
|
|
303
|
+
mode: parser.mode,
|
|
304
|
+
metavar: parser.metavar,
|
|
305
|
+
placeholder: void 0,
|
|
306
|
+
...transformedChoices == null ? {} : { choices: transformedChoices },
|
|
307
|
+
parse(input) {
|
|
308
|
+
return mapModeValue(parser.mode, parser.parse(input), (result) => transformValueParserResult(result, mapping));
|
|
309
|
+
},
|
|
310
|
+
format(value) {
|
|
311
|
+
return parser.format(mapping.unmap(value));
|
|
312
|
+
},
|
|
313
|
+
...normalize == null ? {} : { normalize(value) {
|
|
314
|
+
return mapping.map(normalize(mapping.unmap(value)));
|
|
315
|
+
} },
|
|
316
|
+
...suggest == null ? {} : { suggest(prefix) {
|
|
317
|
+
return suggest(prefix);
|
|
318
|
+
} }
|
|
319
|
+
};
|
|
320
|
+
Object.defineProperty(transformed, "placeholder", {
|
|
321
|
+
get() {
|
|
322
|
+
try {
|
|
323
|
+
return mapping.map(parser.placeholder);
|
|
324
|
+
} catch {
|
|
325
|
+
return void 0;
|
|
326
|
+
}
|
|
327
|
+
},
|
|
328
|
+
configurable: true,
|
|
329
|
+
enumerable: false
|
|
330
|
+
});
|
|
331
|
+
if (typeof parser.validate === "function") {
|
|
332
|
+
const validate = parser.validate.bind(parser);
|
|
333
|
+
Object.defineProperty(transformed, "validate", {
|
|
334
|
+
value(value) {
|
|
335
|
+
const result = validate(mapping.unmap(value));
|
|
336
|
+
return result.success ? {
|
|
337
|
+
success: true,
|
|
338
|
+
value: mapping.map(result.value)
|
|
339
|
+
} : result;
|
|
340
|
+
},
|
|
341
|
+
configurable: true,
|
|
342
|
+
enumerable: true
|
|
343
|
+
});
|
|
344
|
+
} else if (parser.mode === "sync") {
|
|
345
|
+
const syncParser = parser;
|
|
346
|
+
Object.defineProperty(transformed, "validate", {
|
|
347
|
+
value(value) {
|
|
348
|
+
const result = syncParser.parse(syncParser.format(mapping.unmap(value)));
|
|
349
|
+
return result.success ? {
|
|
350
|
+
success: true,
|
|
351
|
+
value: mapping.map(result.value)
|
|
352
|
+
} : result;
|
|
353
|
+
},
|
|
354
|
+
configurable: true,
|
|
355
|
+
enumerable: true
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
if (isDerivedValueParser(parser)) preserveTransformedDerivedMetadata(transformed, parser, mapping);
|
|
359
|
+
return transformed;
|
|
360
|
+
}
|
|
361
|
+
function preserveTransformedDerivedMetadata(transformed, parser, mapping) {
|
|
362
|
+
Object.defineProperties(transformed, {
|
|
363
|
+
[derivedValueParserMarker]: {
|
|
364
|
+
value: true,
|
|
365
|
+
enumerable: true
|
|
366
|
+
},
|
|
367
|
+
[dependencyId]: {
|
|
368
|
+
value: parser[dependencyId],
|
|
369
|
+
enumerable: true
|
|
370
|
+
},
|
|
371
|
+
[parseWithDependency]: {
|
|
372
|
+
value(input, dependencyValue) {
|
|
373
|
+
return mapMaybePromiseByMode(parser.mode, parser[parseWithDependency](input, dependencyValue), (result) => transformValueParserResult(result, mapping));
|
|
374
|
+
},
|
|
375
|
+
enumerable: true
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
if (dependencyIds in parser && parser[dependencyIds] != null) Object.defineProperty(transformed, dependencyIds, {
|
|
379
|
+
value: parser[dependencyIds],
|
|
380
|
+
enumerable: true
|
|
381
|
+
});
|
|
382
|
+
if (defaultValues in parser && parser[defaultValues] != null) Object.defineProperty(transformed, defaultValues, {
|
|
383
|
+
value: parser[defaultValues],
|
|
384
|
+
enumerable: true
|
|
385
|
+
});
|
|
386
|
+
if (singleDefaultValue in parser && parser[singleDefaultValue] != null) Object.defineProperty(transformed, singleDefaultValue, {
|
|
387
|
+
value: parser[singleDefaultValue],
|
|
388
|
+
enumerable: true
|
|
389
|
+
});
|
|
390
|
+
if (suggestWithDependency in parser && parser[suggestWithDependency] != null) {
|
|
391
|
+
const suggest = parser[suggestWithDependency];
|
|
392
|
+
Object.defineProperty(transformed, suggestWithDependency, {
|
|
393
|
+
value(prefix, dependencyValue) {
|
|
394
|
+
return wrapIterableForMode(parser.mode, suggest(prefix, dependencyValue));
|
|
395
|
+
},
|
|
396
|
+
enumerable: true
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
}
|
|
194
400
|
/**
|
|
195
401
|
* Validates that an option value, if present, is a boolean.
|
|
196
402
|
* Throws a {@link TypeError} if the value is defined but not a boolean.
|
|
@@ -6336,4 +6542,4 @@ function plainObjectsEqual(a, b) {
|
|
|
6336
6542
|
}
|
|
6337
6543
|
|
|
6338
6544
|
//#endregion
|
|
6339
|
-
export { checkBooleanOption, checkEnumOption, choice, cidr, color, cron, domain, email, ensureNonEmptyString, fileSize, firstOf, float, hostname, integer, ip, ipv4, ipv6, isNonEmptyString, isValueParser, json, keyValue, locale, macAddress, port, portRange, semVer, socketAddress, string, url, uuid };
|
|
6545
|
+
export { biject, checkBooleanOption, checkEnumOption, choice, cidr, color, cron, domain, email, ensureNonEmptyString, fileSize, firstOf, float, hostname, integer, ip, ipv4, ipv6, isNonEmptyString, isValueParser, json, keyValue, locale, macAddress, port, portRange, semVer, socketAddress, string, transform, url, uuid };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@optique/core",
|
|
3
|
-
"version": "1.2.0-dev.
|
|
3
|
+
"version": "1.2.0-dev.2302",
|
|
4
4
|
"description": "Type-safe combinatorial command-line interface parser",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"CLI",
|
|
@@ -225,7 +225,7 @@
|
|
|
225
225
|
"fast-check": "^4.7.0",
|
|
226
226
|
"tsdown": "^0.13.0",
|
|
227
227
|
"typescript": "^5.8.3",
|
|
228
|
-
"@optique/env": "1.2.0-dev.
|
|
228
|
+
"@optique/env": "1.2.0-dev.2302+77665cb5"
|
|
229
229
|
},
|
|
230
230
|
"scripts": {
|
|
231
231
|
"build": "tsdown",
|