@optique/core 1.0.0-dev.1547 → 1.0.0-dev.1555

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.
@@ -2144,7 +2144,48 @@ function macAddress(options) {
2144
2144
  const hyphenRegex = /^([0-9a-fA-F]{1,2})-([0-9a-fA-F]{1,2})-([0-9a-fA-F]{1,2})-([0-9a-fA-F]{1,2})-([0-9a-fA-F]{1,2})-([0-9a-fA-F]{1,2})$/;
2145
2145
  const dotRegex = /^([0-9a-fA-F]{4})\.([0-9a-fA-F]{4})\.([0-9a-fA-F]{4})$/;
2146
2146
  const noneRegex = /^([0-9a-fA-F]{12})$/;
2147
- return {
2147
+ function joinOctets(octets, sep) {
2148
+ let formatted = octets;
2149
+ if (caseOption === "upper") formatted = octets.map((o) => o.toUpperCase());
2150
+ else if (caseOption === "lower") formatted = octets.map((o) => o.toLowerCase());
2151
+ if (sep === ".") return [
2152
+ formatted[0] + formatted[1],
2153
+ formatted[2] + formatted[3],
2154
+ formatted[4] + formatted[5]
2155
+ ].join(".");
2156
+ if (sep === "none") return formatted.join("");
2157
+ return formatted.join(sep);
2158
+ }
2159
+ function normalizeMac(value) {
2160
+ if (typeof value !== "string") return metavar;
2161
+ let octets;
2162
+ let detectedSep;
2163
+ if (value.includes(":")) {
2164
+ octets = value.split(":");
2165
+ detectedSep = ":";
2166
+ } else if (value.includes("-")) {
2167
+ octets = value.split("-");
2168
+ detectedSep = "-";
2169
+ } else if (value.includes(".")) {
2170
+ const groups = value.split(".");
2171
+ if (groups.length !== 3 || !groups.every((g) => /^[0-9a-fA-F]{4}$/.test(g))) return value;
2172
+ octets = groups.flatMap((g) => [g.slice(0, 2), g.slice(2)]);
2173
+ detectedSep = ".";
2174
+ } else {
2175
+ if (value.length !== 12) return value;
2176
+ octets = [];
2177
+ for (let i = 0; i < value.length; i += 2) octets.push(value.slice(i, i + 2));
2178
+ detectedSep = "none";
2179
+ }
2180
+ if (octets.length !== 6 || !octets.every((o) => /^[0-9a-fA-F]{1,2}$/.test(o))) return value;
2181
+ octets = octets.map((o) => o.padStart(2, "0"));
2182
+ let sep;
2183
+ if (outputSeparator != null) sep = outputSeparator;
2184
+ else if (separator !== "any") sep = separator;
2185
+ else sep = detectedSep;
2186
+ return joinOctets(octets, sep);
2187
+ }
2188
+ const parserObj = {
2148
2189
  $mode: "sync",
2149
2190
  metavar,
2150
2191
  get placeholder() {
@@ -2221,28 +2262,51 @@ function macAddress(options) {
2221
2262
  };
2222
2263
  }
2223
2264
  octets = octets.map((o) => o.padStart(2, "0"));
2224
- let formattedOctets = octets;
2225
- if (caseOption === "upper") formattedOctets = octets.map((octet) => octet.toUpperCase());
2226
- else if (caseOption === "lower") formattedOctets = octets.map((octet) => octet.toLowerCase());
2227
2265
  const finalSeparator = outputSeparator ?? inputSeparator ?? ":";
2228
- let result;
2229
- if (finalSeparator === ":") result = formattedOctets.join(":");
2230
- else if (finalSeparator === "-") result = formattedOctets.join("-");
2231
- else if (finalSeparator === ".") result = [
2232
- formattedOctets[0] + formattedOctets[1],
2233
- formattedOctets[2] + formattedOctets[3],
2234
- formattedOctets[4] + formattedOctets[5]
2235
- ].join(".");
2236
- else result = formattedOctets.join("");
2237
2266
  return {
2238
2267
  success: true,
2239
- value: result
2268
+ value: joinOctets(octets, finalSeparator)
2240
2269
  };
2241
2270
  },
2242
- format() {
2243
- return metavar;
2244
- }
2271
+ format: normalizeMac
2245
2272
  };
2273
+ const macParser = parserObj;
2274
+ let macParsing = false;
2275
+ Object.defineProperty(parserObj, "format", {
2276
+ value(v) {
2277
+ if (typeof v !== "string") return metavar;
2278
+ if (macParsing) return v;
2279
+ macParsing = true;
2280
+ try {
2281
+ const result = macParser.parse(v);
2282
+ return result.success ? result.value : v;
2283
+ } catch {
2284
+ return v;
2285
+ } finally {
2286
+ macParsing = false;
2287
+ }
2288
+ },
2289
+ configurable: true,
2290
+ enumerable: true
2291
+ });
2292
+ Object.defineProperty(parserObj, "normalize", {
2293
+ value(v) {
2294
+ if (typeof v !== "string") return v;
2295
+ if (macParsing) return v;
2296
+ macParsing = true;
2297
+ try {
2298
+ const result = macParser.parse(v);
2299
+ return result.success ? result.value : v;
2300
+ } catch {
2301
+ return v;
2302
+ } finally {
2303
+ macParsing = false;
2304
+ }
2305
+ },
2306
+ configurable: true,
2307
+ enumerable: true
2308
+ });
2309
+ return parserObj;
2246
2310
  }
2247
2311
  /**
2248
2312
  * Creates a value parser for domain names.
@@ -2314,7 +2378,7 @@ function domain(options) {
2314
2378
  const tooFewLabels = options?.errors?.tooFewLabels;
2315
2379
  const subdomainsNotAllowed = options?.errors?.subdomainsNotAllowed;
2316
2380
  const tldNotAllowed = options?.errors?.tldNotAllowed;
2317
- return {
2381
+ const domainParserObj = {
2318
2382
  $mode: "sync",
2319
2383
  metavar,
2320
2384
  placeholder: options?.placeholder ?? `example.${allowedTldsLower?.[0] ?? "com"}`,
@@ -2518,10 +2582,51 @@ function domain(options) {
2518
2582
  value: result
2519
2583
  };
2520
2584
  },
2521
- format() {
2522
- return metavar;
2585
+ format(value) {
2586
+ if (typeof value !== "string") return metavar;
2587
+ if (!lowercase) return value;
2588
+ return value.split(".").length >= minLabels ? value.toLowerCase() : value;
2523
2589
  }
2524
2590
  };
2591
+ if (lowercase) {
2592
+ const domParser = domainParserObj;
2593
+ let domParsing = false;
2594
+ Object.defineProperty(domainParserObj, "format", {
2595
+ value(v) {
2596
+ if (typeof v !== "string") return metavar;
2597
+ if (domParsing) return v;
2598
+ domParsing = true;
2599
+ try {
2600
+ const result = domParser.parse(v);
2601
+ return result.success ? result.value : v;
2602
+ } catch {
2603
+ return v;
2604
+ } finally {
2605
+ domParsing = false;
2606
+ }
2607
+ },
2608
+ configurable: true,
2609
+ enumerable: true
2610
+ });
2611
+ Object.defineProperty(domainParserObj, "normalize", {
2612
+ value(v) {
2613
+ if (typeof v !== "string") return v;
2614
+ if (domParsing) return v;
2615
+ domParsing = true;
2616
+ try {
2617
+ const result = domParser.parse(v);
2618
+ return result.success ? result.value : v;
2619
+ } catch {
2620
+ return v;
2621
+ } finally {
2622
+ domParsing = false;
2623
+ }
2624
+ },
2625
+ configurable: true,
2626
+ enumerable: true
2627
+ });
2628
+ }
2629
+ return domainParserObj;
2525
2630
  }
2526
2631
  /**
2527
2632
  * Creates a value parser for IPv6 addresses.
@@ -2554,7 +2659,7 @@ function ipv6(options) {
2554
2659
  const allowZero = options?.allowZero ?? true;
2555
2660
  const errors = options?.errors;
2556
2661
  const metavar = options?.metavar ?? "IPV6";
2557
- return {
2662
+ const ipv6ParserObj = {
2558
2663
  $mode: "sync",
2559
2664
  metavar,
2560
2665
  placeholder: allowZero ? "::" : allowLoopback ? "::1" : "2001:db8::1",
@@ -2707,10 +2812,47 @@ function ipv6(options) {
2707
2812
  value: normalized
2708
2813
  };
2709
2814
  },
2710
- format() {
2711
- return metavar;
2815
+ format(value) {
2816
+ if (typeof value !== "string") return metavar;
2817
+ return parseAndNormalizeIpv6(value) ?? value;
2712
2818
  }
2713
2819
  };
2820
+ let ipv6Parsing = false;
2821
+ Object.defineProperty(ipv6ParserObj, "format", {
2822
+ value(v) {
2823
+ if (typeof v !== "string") return metavar;
2824
+ if (ipv6Parsing) return v;
2825
+ ipv6Parsing = true;
2826
+ try {
2827
+ const result = ipv6ParserObj.parse(v);
2828
+ return result.success ? result.value : v;
2829
+ } catch {
2830
+ return v;
2831
+ } finally {
2832
+ ipv6Parsing = false;
2833
+ }
2834
+ },
2835
+ configurable: true,
2836
+ enumerable: true
2837
+ });
2838
+ Object.defineProperty(ipv6ParserObj, "normalize", {
2839
+ value(v) {
2840
+ if (typeof v !== "string") return v;
2841
+ if (ipv6Parsing) return v;
2842
+ ipv6Parsing = true;
2843
+ try {
2844
+ const result = ipv6ParserObj.parse(v);
2845
+ return result.success ? result.value : v;
2846
+ } catch {
2847
+ return v;
2848
+ } finally {
2849
+ ipv6Parsing = false;
2850
+ }
2851
+ },
2852
+ configurable: true,
2853
+ enumerable: true
2854
+ });
2855
+ return ipv6ParserObj;
2714
2856
  }
2715
2857
  /**
2716
2858
  * Parses and normalizes an IPv6 address to canonical form.
@@ -2971,7 +3113,7 @@ function ip(options) {
2971
3113
  broadcastNotAllowed: errors?.broadcastNotAllowed,
2972
3114
  zeroNotAllowed: errors?.zeroNotAllowed
2973
3115
  } : void 0;
2974
- return {
3116
+ const ipParserObj = {
2975
3117
  $mode: "sync",
2976
3118
  metavar,
2977
3119
  placeholder: version === 6 ? ipv6Parser.placeholder : ipv4Parser.placeholder,
@@ -3031,10 +3173,47 @@ function ip(options) {
3031
3173
  error: msg
3032
3174
  };
3033
3175
  },
3034
- format() {
3035
- return metavar;
3176
+ format(value) {
3177
+ if (typeof value !== "string") return metavar;
3178
+ return parseAndNormalizeIpv6(value) ?? value;
3036
3179
  }
3037
3180
  };
3181
+ let ipParsing = false;
3182
+ Object.defineProperty(ipParserObj, "format", {
3183
+ value(v) {
3184
+ if (typeof v !== "string") return metavar;
3185
+ if (ipParsing) return v;
3186
+ ipParsing = true;
3187
+ try {
3188
+ const result = ipParserObj.parse(v);
3189
+ return result.success ? result.value : v;
3190
+ } catch {
3191
+ return v;
3192
+ } finally {
3193
+ ipParsing = false;
3194
+ }
3195
+ },
3196
+ configurable: true,
3197
+ enumerable: true
3198
+ });
3199
+ Object.defineProperty(ipParserObj, "normalize", {
3200
+ value(v) {
3201
+ if (typeof v !== "string") return v;
3202
+ if (ipParsing) return v;
3203
+ ipParsing = true;
3204
+ try {
3205
+ const result = ipParserObj.parse(v);
3206
+ return result.success ? result.value : v;
3207
+ } catch {
3208
+ return v;
3209
+ } finally {
3210
+ ipParsing = false;
3211
+ }
3212
+ },
3213
+ configurable: true,
3214
+ enumerable: true
3215
+ });
3216
+ return ipParserObj;
3038
3217
  }
3039
3218
  /**
3040
3219
  * Creates a value parser for CIDR notation (IP address with prefix length).
@@ -3113,7 +3292,7 @@ function cidr(options) {
3113
3292
  broadcastNotAllowed: errors?.broadcastNotAllowed,
3114
3293
  zeroNotAllowed: errors?.zeroNotAllowed
3115
3294
  } : void 0;
3116
- return {
3295
+ const cidrParserObj = {
3117
3296
  $mode: "sync",
3118
3297
  metavar,
3119
3298
  get placeholder() {
@@ -3484,10 +3663,47 @@ function cidr(options) {
3484
3663
  }
3485
3664
  };
3486
3665
  },
3487
- format() {
3488
- return metavar;
3489
- }
3666
+ format: ((_) => metavar)
3490
3667
  };
3668
+ let cidrParsing = false;
3669
+ Object.defineProperty(cidrParserObj, "format", {
3670
+ value(value) {
3671
+ if (typeof value !== "object" || value == null || !("address" in value) || !("prefix" in value) || !("version" in value)) return metavar;
3672
+ if (cidrParsing) return `${value.address}/${value.prefix}`;
3673
+ cidrParsing = true;
3674
+ try {
3675
+ const raw = `${value.address}/${value.prefix}`;
3676
+ const result = cidrParserObj.parse(raw);
3677
+ return result.success && result.value.version === value.version ? `${result.value.address}/${result.value.prefix}` : raw;
3678
+ } catch {
3679
+ return `${value.address}/${value.prefix}`;
3680
+ } finally {
3681
+ cidrParsing = false;
3682
+ }
3683
+ },
3684
+ configurable: true,
3685
+ enumerable: true
3686
+ });
3687
+ Object.defineProperty(cidrParserObj, "normalize", {
3688
+ value(v) {
3689
+ if (typeof v !== "object" || v == null || !("address" in v) || !("prefix" in v) || !("version" in v)) return v;
3690
+ if (cidrParsing) return v;
3691
+ cidrParsing = true;
3692
+ const formatted = `${v.address}/${v.prefix}`;
3693
+ try {
3694
+ const result = cidrParserObj.parse(formatted);
3695
+ if (result.success && result.value.version === v.version) return result.value;
3696
+ return v;
3697
+ } catch {
3698
+ return v;
3699
+ } finally {
3700
+ cidrParsing = false;
3701
+ }
3702
+ },
3703
+ configurable: true,
3704
+ enumerable: true
3705
+ });
3706
+ return cidrParserObj;
3491
3707
  }
3492
3708
 
3493
3709
  //#endregion
@@ -49,6 +49,33 @@ interface ValueParser<M extends Mode = "sync", T = unknown> {
49
49
  * @returns A string representation of the value.
50
50
  */
51
51
  format(value: T): string;
52
+ /**
53
+ * Normalizes a value of type {@link T} according to this parser's
54
+ * configuration. This applies the same canonicalization that
55
+ * {@link parse} would apply (e.g., case conversion, separator
56
+ * normalization). Built-in implementations delegate to {@link parse}
57
+ * internally, so values that would fail validation are returned
58
+ * unchanged rather than being canonicalized.
59
+ *
60
+ * When present, combinators like `withDefault()` call this method on
61
+ * default values so that runtime defaults match the representation
62
+ * that {@link parse} would produce.
63
+ *
64
+ * Parsers that do not apply any normalization during parsing do not
65
+ * need to implement this method.
66
+ *
67
+ * **Limitation:** For dependency-derived value parsers (created via
68
+ * `deriveFrom()` or `dependency().derive()`), this method uses the
69
+ * default dependency value to build the inner parser, not the
70
+ * dependency value resolved during the current parse. This is the
71
+ * same trade-off that {@link format} makes. As a result, defaults
72
+ * may not be normalized according to a non-default dependency value.
73
+ *
74
+ * @param value The value to normalize.
75
+ * @returns The normalized value.
76
+ * @since 1.0.0
77
+ */
78
+ normalize?(value: T): T;
52
79
  /**
53
80
  * Provides completion suggestions for values of this type.
54
81
  * This is optional and used for shell completion functionality.
@@ -49,6 +49,33 @@ interface ValueParser<M extends Mode = "sync", T = unknown> {
49
49
  * @returns A string representation of the value.
50
50
  */
51
51
  format(value: T): string;
52
+ /**
53
+ * Normalizes a value of type {@link T} according to this parser's
54
+ * configuration. This applies the same canonicalization that
55
+ * {@link parse} would apply (e.g., case conversion, separator
56
+ * normalization). Built-in implementations delegate to {@link parse}
57
+ * internally, so values that would fail validation are returned
58
+ * unchanged rather than being canonicalized.
59
+ *
60
+ * When present, combinators like `withDefault()` call this method on
61
+ * default values so that runtime defaults match the representation
62
+ * that {@link parse} would produce.
63
+ *
64
+ * Parsers that do not apply any normalization during parsing do not
65
+ * need to implement this method.
66
+ *
67
+ * **Limitation:** For dependency-derived value parsers (created via
68
+ * `deriveFrom()` or `dependency().derive()`), this method uses the
69
+ * default dependency value to build the inner parser, not the
70
+ * dependency value resolved during the current parse. This is the
71
+ * same trade-off that {@link format} makes. As a result, defaults
72
+ * may not be normalized according to a non-default dependency value.
73
+ *
74
+ * @param value The value to normalize.
75
+ * @returns The normalized value.
76
+ * @since 1.0.0
77
+ */
78
+ normalize?(value: T): T;
52
79
  /**
53
80
  * Provides completion suggestions for values of this type.
54
81
  * This is optional and used for shell completion functionality.