@optique/core 0.1.0-dev.1
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/README.md +475 -0
- package/dist/doc.cjs +104 -0
- package/dist/doc.d.cts +129 -0
- package/dist/doc.d.ts +129 -0
- package/dist/doc.js +104 -0
- package/dist/facade.cjs +98 -0
- package/dist/facade.d.cts +113 -0
- package/dist/facade.d.ts +113 -0
- package/dist/facade.js +97 -0
- package/dist/index.cjs +42 -0
- package/dist/index.d.cts +7 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +8 -0
- package/dist/message.cjs +202 -0
- package/dist/message.d.cts +199 -0
- package/dist/message.d.ts +199 -0
- package/dist/message.js +194 -0
- package/dist/parser.cjs +1065 -0
- package/dist/parser.d.cts +580 -0
- package/dist/parser.d.ts +580 -0
- package/dist/parser.js +1053 -0
- package/dist/usage.cjs +242 -0
- package/dist/usage.d.cts +217 -0
- package/dist/usage.d.ts +217 -0
- package/dist/usage.js +239 -0
- package/dist/valueparser.cjs +332 -0
- package/dist/valueparser.d.cts +332 -0
- package/dist/valueparser.d.ts +332 -0
- package/dist/valueparser.js +325 -0
- package/package.json +117 -0
package/dist/usage.js
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
//#region src/usage.ts
|
|
2
|
+
/**
|
|
3
|
+
* Formats a usage description into a human-readable string representation
|
|
4
|
+
* suitable for command-line help text.
|
|
5
|
+
*
|
|
6
|
+
* This function converts a structured {@link Usage} description into a
|
|
7
|
+
* formatted string that follows common CLI conventions. It supports various
|
|
8
|
+
* formatting options including colors and compact option display.
|
|
9
|
+
* @param programName The name of the program or command for which the usage
|
|
10
|
+
* description is being formatted. This is typically the
|
|
11
|
+
* name of the executable or script that the user will run.
|
|
12
|
+
* @param usage The usage description to format, consisting of an array
|
|
13
|
+
* of usage terms representing the command-line structure.
|
|
14
|
+
* @param options Optional formatting options to customize the output.
|
|
15
|
+
* See {@link UsageFormatOptions} for available options.
|
|
16
|
+
* @returns A formatted string representation of the usage description.
|
|
17
|
+
*/
|
|
18
|
+
function formatUsage(programName, usage, options = {}) {
|
|
19
|
+
usage = normalizeUsage(usage);
|
|
20
|
+
if (options.expandCommands) {
|
|
21
|
+
const lastTerm = usage.at(-1);
|
|
22
|
+
if (usage.length > 0 && usage.slice(0, -1).every((t) => t.type === "command") && lastTerm.type === "exclusive" && lastTerm.terms.every((t) => t.length > 0 && (t[0].type === "command" || t[0].type === "option" || t[0].type === "argument" || t[0].type === "optional" && t[0].terms.length === 1 && (t[0].terms[0].type === "command" || t[0].terms[0].type === "option" || t[0].terms[0].type === "argument")))) {
|
|
23
|
+
const lines = [];
|
|
24
|
+
for (let command of lastTerm.terms) {
|
|
25
|
+
if (usage.length > 1) command = [...usage.slice(0, -1), ...command];
|
|
26
|
+
lines.push(formatUsage(programName, command, options));
|
|
27
|
+
}
|
|
28
|
+
return lines.join("\n");
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
let output = options.colors ? `\x1b[1m${programName}\x1b[0m ` : `${programName} `;
|
|
32
|
+
let lineWidth = programName.length + 1;
|
|
33
|
+
for (const { text, width } of formatUsageTerms(usage, options)) {
|
|
34
|
+
if (options.maxWidth != null && lineWidth + width > options.maxWidth) {
|
|
35
|
+
output += "\n";
|
|
36
|
+
lineWidth = 0;
|
|
37
|
+
if (text === " ") continue;
|
|
38
|
+
}
|
|
39
|
+
output += text;
|
|
40
|
+
lineWidth += width;
|
|
41
|
+
}
|
|
42
|
+
return output;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Normalizes a usage description by flattening nested exclusive terms,
|
|
46
|
+
* sorting terms for better readability, and ensuring consistent structure
|
|
47
|
+
* throughout the usage tree.
|
|
48
|
+
*
|
|
49
|
+
* This function performs two main operations:
|
|
50
|
+
*
|
|
51
|
+
* 1. *Flattening*: Recursively processes all usage terms and merges any
|
|
52
|
+
* nested exclusive terms into their parent exclusive term to avoid
|
|
53
|
+
* redundant nesting. For example, an exclusive term containing another
|
|
54
|
+
* exclusive term will have its nested terms flattened into the parent.
|
|
55
|
+
*
|
|
56
|
+
* 2. *Sorting*: Reorders terms to improve readability by placing:
|
|
57
|
+
* - Commands (subcommands) first
|
|
58
|
+
* - Options and other terms in the middle
|
|
59
|
+
* - Positional arguments last (including optional/multiple wrappers around
|
|
60
|
+
* arguments)
|
|
61
|
+
*
|
|
62
|
+
* The sorting logic also recognizes when optional or multiple terms contain
|
|
63
|
+
* positional arguments and treats them as arguments for sorting purposes.
|
|
64
|
+
*
|
|
65
|
+
* @param usage The usage description to normalize.
|
|
66
|
+
* @returns A normalized usage description with flattened exclusive terms
|
|
67
|
+
* and terms sorted for optimal readability.
|
|
68
|
+
*/
|
|
69
|
+
function normalizeUsage(usage) {
|
|
70
|
+
const terms = usage.map(normalizeUsageTerm);
|
|
71
|
+
terms.sort((a, b) => {
|
|
72
|
+
const aCmd = a.type === "command";
|
|
73
|
+
const bCmd = b.type === "command";
|
|
74
|
+
const aArg = a.type === "argument" || (a.type === "optional" || a.type === "multiple") && a.terms.at(-1)?.type === "argument";
|
|
75
|
+
const bArg = b.type === "argument" || (b.type === "optional" || b.type === "multiple") && b.terms.at(-1)?.type === "argument";
|
|
76
|
+
return aCmd === bCmd ? aArg === bArg ? 0 : aArg ? 1 : -1 : aCmd ? -1 : 1;
|
|
77
|
+
});
|
|
78
|
+
return terms;
|
|
79
|
+
}
|
|
80
|
+
function normalizeUsageTerm(term) {
|
|
81
|
+
if (term.type === "optional") return {
|
|
82
|
+
type: "optional",
|
|
83
|
+
terms: normalizeUsage(term.terms)
|
|
84
|
+
};
|
|
85
|
+
else if (term.type === "multiple") return {
|
|
86
|
+
type: "multiple",
|
|
87
|
+
terms: normalizeUsage(term.terms),
|
|
88
|
+
min: term.min
|
|
89
|
+
};
|
|
90
|
+
else if (term.type === "exclusive") {
|
|
91
|
+
const terms = [];
|
|
92
|
+
for (const usage of term.terms) {
|
|
93
|
+
const normalized = normalizeUsage(usage);
|
|
94
|
+
if (normalized.length === 1 && normalized[0].type === "exclusive") for (const subUsage of normalized[0].terms) terms.push(subUsage);
|
|
95
|
+
else terms.push(normalized);
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
type: "exclusive",
|
|
99
|
+
terms
|
|
100
|
+
};
|
|
101
|
+
} else return term;
|
|
102
|
+
}
|
|
103
|
+
function* formatUsageTerms(terms, options) {
|
|
104
|
+
let i = 0;
|
|
105
|
+
for (const t of terms) {
|
|
106
|
+
if (i > 0) yield {
|
|
107
|
+
text: " ",
|
|
108
|
+
width: 1
|
|
109
|
+
};
|
|
110
|
+
yield* formatUsageTermInternal(t, options);
|
|
111
|
+
i++;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Formats a single {@link UsageTerm} into a string representation
|
|
116
|
+
* suitable for command-line help text.
|
|
117
|
+
* @param term The usage term to format, which can be an argument,
|
|
118
|
+
* option, command, optional term, exclusive term, or multiple term.
|
|
119
|
+
* @param options Optional formatting options to customize the output.
|
|
120
|
+
* See {@link UsageTermFormatOptions} for available options.
|
|
121
|
+
* @returns A formatted string representation of the usage term.
|
|
122
|
+
*/
|
|
123
|
+
function formatUsageTerm(term, options = {}) {
|
|
124
|
+
let lineWidth = 0;
|
|
125
|
+
let output = "";
|
|
126
|
+
for (const { text, width } of formatUsageTermInternal(term, options)) {
|
|
127
|
+
if (options.maxWidth != null && lineWidth + width > options.maxWidth) {
|
|
128
|
+
output += "\n";
|
|
129
|
+
lineWidth = 0;
|
|
130
|
+
if (text === " ") continue;
|
|
131
|
+
}
|
|
132
|
+
output += text;
|
|
133
|
+
lineWidth += width;
|
|
134
|
+
}
|
|
135
|
+
return output;
|
|
136
|
+
}
|
|
137
|
+
function* formatUsageTermInternal(term, options) {
|
|
138
|
+
const optionsSeparator = options.optionsSeparator ?? "/";
|
|
139
|
+
if (term.type === "argument") yield {
|
|
140
|
+
text: options?.colors ? `\x1b[4m${term.metavar}\x1b[0m` : term.metavar,
|
|
141
|
+
width: term.metavar.length
|
|
142
|
+
};
|
|
143
|
+
else if (term.type === "option") if (options?.onlyShortestOptions) {
|
|
144
|
+
const shortestName = term.names.reduce((a, b) => a.length <= b.length ? a : b);
|
|
145
|
+
yield {
|
|
146
|
+
text: options?.colors ? `\x1b[3m${shortestName}\x1b[0m` : shortestName,
|
|
147
|
+
width: shortestName.length
|
|
148
|
+
};
|
|
149
|
+
} else {
|
|
150
|
+
let i = 0;
|
|
151
|
+
for (const optionName of term.names) {
|
|
152
|
+
if (i > 0) yield {
|
|
153
|
+
text: options?.colors ? `\x1b[2m${optionsSeparator}\x1b[0m` : optionsSeparator,
|
|
154
|
+
width: optionsSeparator.length
|
|
155
|
+
};
|
|
156
|
+
yield {
|
|
157
|
+
text: options?.colors ? `\x1b[3m${optionName}\x1b[0m` : optionName,
|
|
158
|
+
width: optionName.length
|
|
159
|
+
};
|
|
160
|
+
i++;
|
|
161
|
+
}
|
|
162
|
+
if (term.metavar != null) {
|
|
163
|
+
yield {
|
|
164
|
+
text: " ",
|
|
165
|
+
width: 1
|
|
166
|
+
};
|
|
167
|
+
yield {
|
|
168
|
+
text: options?.colors ? `\x1b[4m\x1b[2m${term.metavar}\x1b[0m` : term.metavar,
|
|
169
|
+
width: term.metavar.length
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
else if (term.type === "command") yield {
|
|
174
|
+
text: options?.colors ? `\x1b[1m${term.name}\x1b[0m` : term.name,
|
|
175
|
+
width: term.name.length
|
|
176
|
+
};
|
|
177
|
+
else if (term.type === "optional") {
|
|
178
|
+
yield {
|
|
179
|
+
text: options?.colors ? `\x1b[2m[\x1b[0m` : "[",
|
|
180
|
+
width: 1
|
|
181
|
+
};
|
|
182
|
+
yield* formatUsageTerms(term.terms, options);
|
|
183
|
+
yield {
|
|
184
|
+
text: options?.colors ? `\x1b[2m]\x1b[0m` : "]",
|
|
185
|
+
width: 1
|
|
186
|
+
};
|
|
187
|
+
} else if (term.type === "exclusive") {
|
|
188
|
+
yield {
|
|
189
|
+
text: options?.colors ? `\x1b[2m(\x1b[0m` : "(",
|
|
190
|
+
width: 1
|
|
191
|
+
};
|
|
192
|
+
let i = 0;
|
|
193
|
+
for (const termGroup of term.terms) {
|
|
194
|
+
if (i > 0) {
|
|
195
|
+
yield {
|
|
196
|
+
text: " ",
|
|
197
|
+
width: 1
|
|
198
|
+
};
|
|
199
|
+
yield {
|
|
200
|
+
text: "|",
|
|
201
|
+
width: 1
|
|
202
|
+
};
|
|
203
|
+
yield {
|
|
204
|
+
text: " ",
|
|
205
|
+
width: 1
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
yield* formatUsageTerms(termGroup, options);
|
|
209
|
+
i++;
|
|
210
|
+
}
|
|
211
|
+
yield {
|
|
212
|
+
text: options?.colors ? `\x1b[2m)\x1b[0m` : ")",
|
|
213
|
+
width: 1
|
|
214
|
+
};
|
|
215
|
+
} else if (term.type === "multiple") {
|
|
216
|
+
if (term.min < 1) yield {
|
|
217
|
+
text: options?.colors ? `\x1b[2m[\x1b[0m` : "[",
|
|
218
|
+
width: 1
|
|
219
|
+
};
|
|
220
|
+
for (let i = 0; i < Math.max(1, term.min); i++) {
|
|
221
|
+
if (i > 0) yield {
|
|
222
|
+
text: " ",
|
|
223
|
+
width: 1
|
|
224
|
+
};
|
|
225
|
+
yield* formatUsageTerms(term.terms, options);
|
|
226
|
+
}
|
|
227
|
+
yield {
|
|
228
|
+
text: options?.colors ? `\x1b[2m...\x1b[0m` : "...",
|
|
229
|
+
width: 3
|
|
230
|
+
};
|
|
231
|
+
if (term.min < 1) yield {
|
|
232
|
+
text: options?.colors ? `\x1b[2m]\x1b[0m` : "]",
|
|
233
|
+
width: 1
|
|
234
|
+
};
|
|
235
|
+
} else throw new TypeError(`Unknown usage term type: ${term["type"]}.`);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
//#endregion
|
|
239
|
+
export { formatUsage, formatUsageTerm, normalizeUsage };
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
const require_message = require('./message.cjs');
|
|
2
|
+
|
|
3
|
+
//#region src/valueparser.ts
|
|
4
|
+
/**
|
|
5
|
+
* A predicate function that checks if an object is a {@link ValueParser}.
|
|
6
|
+
* @param object The object to check.
|
|
7
|
+
* @return `true` if the object is a {@link ValueParser}, `false` otherwise.
|
|
8
|
+
*/
|
|
9
|
+
function isValueParser(object) {
|
|
10
|
+
return typeof object === "object" && object != null && "metavar" in object && typeof object.metavar === "string" && "parse" in object && typeof object.parse === "function" && "format" in object && typeof object.format === "function";
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Creates a {@link ValueParser} that accepts one of multiple
|
|
14
|
+
* string values, so-called enumerated values.
|
|
15
|
+
*
|
|
16
|
+
* This parser validates that the input string matches one of
|
|
17
|
+
* the specified values. If the input does not match any of the values,
|
|
18
|
+
* it returns an error message indicating the valid options.
|
|
19
|
+
* @param values An array of valid string values that this parser can accept.
|
|
20
|
+
* @param options Configuration options for the choice parser.
|
|
21
|
+
* @returns A {@link ValueParser} that checks if the input matches one of the
|
|
22
|
+
* specified values.
|
|
23
|
+
*/
|
|
24
|
+
function choice(values, options = {}) {
|
|
25
|
+
const normalizedValues = options.caseInsensitive ? values.map((v) => v.toLowerCase()) : values;
|
|
26
|
+
return {
|
|
27
|
+
metavar: options.metavar ?? "TYPE",
|
|
28
|
+
parse(input) {
|
|
29
|
+
const normalizedInput = options.caseInsensitive ? input.toLowerCase() : input;
|
|
30
|
+
const index = normalizedValues.indexOf(normalizedInput);
|
|
31
|
+
if (index < 0) return {
|
|
32
|
+
success: false,
|
|
33
|
+
error: require_message.message`Expected one of ${values.join(", ")}, but got ${input}.`
|
|
34
|
+
};
|
|
35
|
+
return {
|
|
36
|
+
success: true,
|
|
37
|
+
value: values[index]
|
|
38
|
+
};
|
|
39
|
+
},
|
|
40
|
+
format(value) {
|
|
41
|
+
return value;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Creates a {@link ValueParser} for strings.
|
|
47
|
+
*
|
|
48
|
+
* This parser validates that the input is a string and optionally checks
|
|
49
|
+
* if it matches a specified regular expression pattern.
|
|
50
|
+
* @param options Configuration options for the string parser.
|
|
51
|
+
* @returns A {@link ValueParser} that parses strings according to the
|
|
52
|
+
* specified options.
|
|
53
|
+
*/
|
|
54
|
+
function string(options = {}) {
|
|
55
|
+
return {
|
|
56
|
+
metavar: options.metavar ?? "STRING",
|
|
57
|
+
parse(input) {
|
|
58
|
+
if (options.pattern != null && !options.pattern.test(input)) return {
|
|
59
|
+
success: false,
|
|
60
|
+
error: require_message.message`Expected a string matching pattern ${require_message.text(options.pattern.source)}, but got ${input}.`
|
|
61
|
+
};
|
|
62
|
+
return {
|
|
63
|
+
success: true,
|
|
64
|
+
value: input
|
|
65
|
+
};
|
|
66
|
+
},
|
|
67
|
+
format(value) {
|
|
68
|
+
return value;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Creates a ValueParser for parsing integer values from strings.
|
|
74
|
+
*
|
|
75
|
+
* This function provides two modes of operation:
|
|
76
|
+
*
|
|
77
|
+
* - Regular mode: Returns JavaScript numbers
|
|
78
|
+
* (safe up to `Number.MAX_SAFE_INTEGER`)
|
|
79
|
+
* - `bigint` mode: Returns `bigint` values for arbitrarily large integers
|
|
80
|
+
*
|
|
81
|
+
* The parser validates that the input is a valid integer and optionally
|
|
82
|
+
* enforces minimum and maximum value constraints.
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* ```typescript
|
|
86
|
+
* // Create a parser for regular integers
|
|
87
|
+
* const portParser = integer({ min: 1, max: 65535 });
|
|
88
|
+
*
|
|
89
|
+
* // Create a parser for BigInt values
|
|
90
|
+
* const bigIntParser = integer({ type: "bigint", min: 0n });
|
|
91
|
+
*
|
|
92
|
+
* // Use the parser
|
|
93
|
+
* const result = portParser.parse("8080");
|
|
94
|
+
* if (result.success) {
|
|
95
|
+
* console.log(`Port: ${result.value}`);
|
|
96
|
+
* } else {
|
|
97
|
+
* console.error(result.error);
|
|
98
|
+
* }
|
|
99
|
+
* ```
|
|
100
|
+
*
|
|
101
|
+
* @param options Configuration options specifying the type and constraints.
|
|
102
|
+
* @returns A {@link ValueParser} that converts string input to the specified
|
|
103
|
+
* integer type.
|
|
104
|
+
*/
|
|
105
|
+
function integer(options) {
|
|
106
|
+
if (options?.type === "bigint") return {
|
|
107
|
+
metavar: options.metavar ?? "INTEGER",
|
|
108
|
+
parse(input) {
|
|
109
|
+
let value;
|
|
110
|
+
try {
|
|
111
|
+
value = BigInt(input);
|
|
112
|
+
} catch (e) {
|
|
113
|
+
if (e instanceof SyntaxError) return {
|
|
114
|
+
success: false,
|
|
115
|
+
error: require_message.message`Expected a valid integer, but got ${input}.`
|
|
116
|
+
};
|
|
117
|
+
throw e;
|
|
118
|
+
}
|
|
119
|
+
if (options.min != null && value < options.min) return {
|
|
120
|
+
success: false,
|
|
121
|
+
error: require_message.message`Expected a value greater than or equal to ${require_message.text(options.min.toLocaleString("en"))}, but got ${input}.`
|
|
122
|
+
};
|
|
123
|
+
else if (options.max != null && value > options.max) return {
|
|
124
|
+
success: false,
|
|
125
|
+
error: require_message.message`Expected a value less than or equal to ${require_message.text(options.max.toLocaleString("en"))}, but got ${input}.`
|
|
126
|
+
};
|
|
127
|
+
return {
|
|
128
|
+
success: true,
|
|
129
|
+
value
|
|
130
|
+
};
|
|
131
|
+
},
|
|
132
|
+
format(value) {
|
|
133
|
+
return value.toString();
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
return {
|
|
137
|
+
metavar: options?.metavar ?? "INTEGER",
|
|
138
|
+
parse(input) {
|
|
139
|
+
if (!input.match(/^\d+$/)) return {
|
|
140
|
+
success: false,
|
|
141
|
+
error: require_message.message`Expected a valid integer, but got ${input}.`
|
|
142
|
+
};
|
|
143
|
+
const value = Number.parseInt(input);
|
|
144
|
+
if (options?.min != null && value < options.min) return {
|
|
145
|
+
success: false,
|
|
146
|
+
error: require_message.message`Expected a value greater than or equal to ${require_message.text(options.min.toLocaleString("en"))}, but got ${input}.`
|
|
147
|
+
};
|
|
148
|
+
else if (options?.max != null && value > options.max) return {
|
|
149
|
+
success: false,
|
|
150
|
+
error: require_message.message`Expected a value less than or equal to ${require_message.text(options.max.toLocaleString("en"))}, but got ${input}.`
|
|
151
|
+
};
|
|
152
|
+
return {
|
|
153
|
+
success: true,
|
|
154
|
+
value
|
|
155
|
+
};
|
|
156
|
+
},
|
|
157
|
+
format(value) {
|
|
158
|
+
return value.toString();
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Creates a {@link ValueParser} for floating-point numbers.
|
|
164
|
+
*
|
|
165
|
+
* This parser validates that the input is a valid floating-point number
|
|
166
|
+
* and optionally enforces minimum and maximum value constraints.
|
|
167
|
+
* @param options Configuration options for the float parser.
|
|
168
|
+
* @returns A {@link ValueParser} that parses strings into floating-point
|
|
169
|
+
* numbers.
|
|
170
|
+
*/
|
|
171
|
+
function float(options = {}) {
|
|
172
|
+
const floatRegex = /^[+-]?(?:(?:\d+\.?\d*)|(?:\d*\.\d+))(?:[eE][+-]?\d+)?$/;
|
|
173
|
+
return {
|
|
174
|
+
metavar: options.metavar ?? "NUMBER",
|
|
175
|
+
parse(input) {
|
|
176
|
+
let value;
|
|
177
|
+
const lowerInput = input.toLowerCase();
|
|
178
|
+
if (lowerInput === "nan" && options.allowNaN) value = NaN;
|
|
179
|
+
else if ((lowerInput === "infinity" || lowerInput === "+infinity") && options.allowInfinity) value = Infinity;
|
|
180
|
+
else if (lowerInput === "-infinity" && options.allowInfinity) value = -Infinity;
|
|
181
|
+
else if (floatRegex.test(input)) {
|
|
182
|
+
value = Number(input);
|
|
183
|
+
if (Number.isNaN(value)) return {
|
|
184
|
+
success: false,
|
|
185
|
+
error: require_message.message`Expected a valid number, but got ${input}.`
|
|
186
|
+
};
|
|
187
|
+
} else return {
|
|
188
|
+
success: false,
|
|
189
|
+
error: require_message.message`Expected a valid number, but got ${input}.`
|
|
190
|
+
};
|
|
191
|
+
if (options.min != null && value < options.min) return {
|
|
192
|
+
success: false,
|
|
193
|
+
error: require_message.message`Expected a value greater than or equal to ${require_message.text(options.min.toLocaleString("en"))}, but got ${input}.`
|
|
194
|
+
};
|
|
195
|
+
else if (options.max != null && value > options.max) return {
|
|
196
|
+
success: false,
|
|
197
|
+
error: require_message.message`Expected a value less than or equal to ${require_message.text(options.max.toLocaleString("en"))}, but got ${input}.`
|
|
198
|
+
};
|
|
199
|
+
return {
|
|
200
|
+
success: true,
|
|
201
|
+
value
|
|
202
|
+
};
|
|
203
|
+
},
|
|
204
|
+
format(value) {
|
|
205
|
+
return value.toString();
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Creates a {@link ValueParser} for URL values.
|
|
211
|
+
*
|
|
212
|
+
* This parser validates that the input is a well-formed URL and optionally
|
|
213
|
+
* restricts the allowed protocols. The parsed result is a JavaScript `URL`
|
|
214
|
+
* object.
|
|
215
|
+
* @param options Configuration options for the URL parser.
|
|
216
|
+
* @returns A {@link ValueParser} that converts string input to `URL` objects.
|
|
217
|
+
*/
|
|
218
|
+
function url(options = {}) {
|
|
219
|
+
const allowedProtocols = options.allowedProtocols?.map((p) => p.toLowerCase());
|
|
220
|
+
return {
|
|
221
|
+
metavar: options.metavar ?? "URL",
|
|
222
|
+
parse(input) {
|
|
223
|
+
if (!URL.canParse(input)) return {
|
|
224
|
+
success: false,
|
|
225
|
+
error: require_message.message`Invalid URL: ${input}.`
|
|
226
|
+
};
|
|
227
|
+
const url$1 = new URL(input);
|
|
228
|
+
if (allowedProtocols != null && !allowedProtocols.includes(url$1.protocol)) return {
|
|
229
|
+
success: false,
|
|
230
|
+
error: require_message.message`URL protocol ${url$1.protocol} is not allowed. Allowed protocols: ${allowedProtocols.join(", ")}.`
|
|
231
|
+
};
|
|
232
|
+
return {
|
|
233
|
+
success: true,
|
|
234
|
+
value: url$1
|
|
235
|
+
};
|
|
236
|
+
},
|
|
237
|
+
format(value) {
|
|
238
|
+
return value.href;
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Creates a {@link ValueParser} for locale values.
|
|
244
|
+
*
|
|
245
|
+
* This parser validates that the input is a well-formed locale identifier
|
|
246
|
+
* according to the Unicode Locale Identifier standard (BCP 47).
|
|
247
|
+
* The parsed result is a JavaScript `Intl.Locale` object.
|
|
248
|
+
* @param options Configuration options for the locale parser.
|
|
249
|
+
* @returns A {@link ValueParser} that converts string input to `Intl.Locale`
|
|
250
|
+
* objects.
|
|
251
|
+
*/
|
|
252
|
+
function locale(options = {}) {
|
|
253
|
+
return {
|
|
254
|
+
metavar: options.metavar ?? "LOCALE",
|
|
255
|
+
parse(input) {
|
|
256
|
+
let locale$1;
|
|
257
|
+
try {
|
|
258
|
+
locale$1 = new Intl.Locale(input);
|
|
259
|
+
} catch (e) {
|
|
260
|
+
if (e instanceof RangeError) return {
|
|
261
|
+
success: false,
|
|
262
|
+
error: require_message.message`Invalid locale: ${input}.`
|
|
263
|
+
};
|
|
264
|
+
throw e;
|
|
265
|
+
}
|
|
266
|
+
return {
|
|
267
|
+
success: true,
|
|
268
|
+
value: locale$1
|
|
269
|
+
};
|
|
270
|
+
},
|
|
271
|
+
format(value) {
|
|
272
|
+
return value.baseName;
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Creates a {@link ValueParser} for UUID values.
|
|
278
|
+
*
|
|
279
|
+
* This parser validates that the input is a well-formed UUID string in the
|
|
280
|
+
* standard format: `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` where each `x`
|
|
281
|
+
* is a hexadecimal digit. The parser can optionally restrict to specific
|
|
282
|
+
* UUID versions.
|
|
283
|
+
*
|
|
284
|
+
* @param options Configuration options for the UUID parser.
|
|
285
|
+
* @returns A {@link ValueParser} that converts string input to {@link Uuid}
|
|
286
|
+
* strings.
|
|
287
|
+
*/
|
|
288
|
+
function uuid(options = {}) {
|
|
289
|
+
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
290
|
+
return {
|
|
291
|
+
metavar: options.metavar ?? "UUID",
|
|
292
|
+
parse(input) {
|
|
293
|
+
if (!uuidRegex.test(input)) return {
|
|
294
|
+
success: false,
|
|
295
|
+
error: require_message.message`Expected a valid UUID in format ${"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"}, but got ${input}.`
|
|
296
|
+
};
|
|
297
|
+
if (options.allowedVersions != null && options.allowedVersions.length > 0) {
|
|
298
|
+
const versionChar = input.charAt(14);
|
|
299
|
+
const version = parseInt(versionChar, 16);
|
|
300
|
+
if (!options.allowedVersions.includes(version)) {
|
|
301
|
+
let expectedVersions = require_message.message``;
|
|
302
|
+
let i = 0;
|
|
303
|
+
for (const v of options.allowedVersions) {
|
|
304
|
+
expectedVersions = i < 1 ? require_message.message`${expectedVersions}${v.toLocaleString("en")}` : i + 1 >= options.allowedVersions.length ? require_message.message`${expectedVersions}, or ${v.toLocaleString("en")}` : require_message.message`${expectedVersions}, ${v.toLocaleString("en")}`;
|
|
305
|
+
i++;
|
|
306
|
+
}
|
|
307
|
+
return {
|
|
308
|
+
success: false,
|
|
309
|
+
error: require_message.message`Expected UUID version ${expectedVersions}, but got version ${version.toLocaleString("en")}.`
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
return {
|
|
314
|
+
success: true,
|
|
315
|
+
value: input
|
|
316
|
+
};
|
|
317
|
+
},
|
|
318
|
+
format(value) {
|
|
319
|
+
return value;
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
//#endregion
|
|
325
|
+
exports.choice = choice;
|
|
326
|
+
exports.float = float;
|
|
327
|
+
exports.integer = integer;
|
|
328
|
+
exports.isValueParser = isValueParser;
|
|
329
|
+
exports.locale = locale;
|
|
330
|
+
exports.string = string;
|
|
331
|
+
exports.url = url;
|
|
332
|
+
exports.uuid = uuid;
|