@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.
- package/dist/constructs.cjs +60 -5
- package/dist/constructs.js +60 -5
- package/dist/dependency.cjs +34 -0
- package/dist/dependency.js +34 -0
- package/dist/facade.cjs +1 -0
- package/dist/facade.d.cts +7 -5
- package/dist/facade.d.ts +7 -5
- package/dist/facade.js +2 -1
- package/dist/modifiers.cjs +73 -9
- package/dist/modifiers.js +73 -9
- package/dist/parser.d.cts +20 -0
- package/dist/parser.d.ts +20 -0
- package/dist/primitives.cjs +33 -0
- package/dist/primitives.js +33 -0
- package/dist/program.cjs +4 -0
- package/dist/program.d.cts +2 -0
- package/dist/program.d.ts +2 -0
- package/dist/program.js +5 -0
- package/dist/valueparser.cjs +246 -30
- package/dist/valueparser.d.cts +27 -0
- package/dist/valueparser.d.ts +27 -0
- package/dist/valueparser.js +246 -30
- package/package.json +1 -1
package/dist/valueparser.cjs
CHANGED
|
@@ -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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
package/dist/valueparser.d.cts
CHANGED
|
@@ -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.
|
package/dist/valueparser.d.ts
CHANGED
|
@@ -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.
|