@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
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import { message, text } from "./message.js";
|
|
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: 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: message`Expected a string matching pattern ${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: 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: message`Expected a value greater than or equal to ${text(options.min.toLocaleString("en"))}, but got ${input}.`
|
|
122
|
+
};
|
|
123
|
+
else if (options.max != null && value > options.max) return {
|
|
124
|
+
success: false,
|
|
125
|
+
error: message`Expected a value less than or equal to ${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: 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: message`Expected a value greater than or equal to ${text(options.min.toLocaleString("en"))}, but got ${input}.`
|
|
147
|
+
};
|
|
148
|
+
else if (options?.max != null && value > options.max) return {
|
|
149
|
+
success: false,
|
|
150
|
+
error: message`Expected a value less than or equal to ${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: message`Expected a valid number, but got ${input}.`
|
|
186
|
+
};
|
|
187
|
+
} else return {
|
|
188
|
+
success: false,
|
|
189
|
+
error: message`Expected a valid number, but got ${input}.`
|
|
190
|
+
};
|
|
191
|
+
if (options.min != null && value < options.min) return {
|
|
192
|
+
success: false,
|
|
193
|
+
error: message`Expected a value greater than or equal to ${text(options.min.toLocaleString("en"))}, but got ${input}.`
|
|
194
|
+
};
|
|
195
|
+
else if (options.max != null && value > options.max) return {
|
|
196
|
+
success: false,
|
|
197
|
+
error: message`Expected a value less than or equal to ${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: 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: 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: 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: 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 = message``;
|
|
302
|
+
let i = 0;
|
|
303
|
+
for (const v of options.allowedVersions) {
|
|
304
|
+
expectedVersions = i < 1 ? message`${expectedVersions}${v.toLocaleString("en")}` : i + 1 >= options.allowedVersions.length ? message`${expectedVersions}, or ${v.toLocaleString("en")}` : message`${expectedVersions}, ${v.toLocaleString("en")}`;
|
|
305
|
+
i++;
|
|
306
|
+
}
|
|
307
|
+
return {
|
|
308
|
+
success: false,
|
|
309
|
+
error: 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
|
+
export { choice, float, integer, isValueParser, locale, string, url, uuid };
|
package/package.json
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@optique/core",
|
|
3
|
+
"version": "0.1.0-dev.1+4d43eddd",
|
|
4
|
+
"description": "Type-safe combinatorial command-line interface parser",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"CLI",
|
|
7
|
+
"command-line",
|
|
8
|
+
"commandline",
|
|
9
|
+
"parser",
|
|
10
|
+
"getopt",
|
|
11
|
+
"optparse"
|
|
12
|
+
],
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"author": {
|
|
15
|
+
"name": "Hong Minhee",
|
|
16
|
+
"email": "hong@minhee.org",
|
|
17
|
+
"url": "https://hongminhee.org/"
|
|
18
|
+
},
|
|
19
|
+
"homepage": "https://optique.dev/",
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/dahlia/optique.git",
|
|
23
|
+
"directory": "packages/core/"
|
|
24
|
+
},
|
|
25
|
+
"bugs": {
|
|
26
|
+
"url": "https://github.com/dahlia/optique/issues"
|
|
27
|
+
},
|
|
28
|
+
"funding": [
|
|
29
|
+
"https://github.com/sponsors/dahlia"
|
|
30
|
+
],
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=20.0.0",
|
|
33
|
+
"bun": ">=1.2.0",
|
|
34
|
+
"deno": ">=2.3.0"
|
|
35
|
+
},
|
|
36
|
+
"files": [
|
|
37
|
+
"dist/",
|
|
38
|
+
"package.json",
|
|
39
|
+
"README.md"
|
|
40
|
+
],
|
|
41
|
+
"type": "module",
|
|
42
|
+
"module": "./dist/index.js",
|
|
43
|
+
"main": "./dist/index.cjs",
|
|
44
|
+
"types": "./dist/index.d.ts",
|
|
45
|
+
"exports": {
|
|
46
|
+
".": {
|
|
47
|
+
"types": {
|
|
48
|
+
"import": "./dist/index.d.ts",
|
|
49
|
+
"require": "./dist/index.d.cts"
|
|
50
|
+
},
|
|
51
|
+
"import": "./dist/index.js",
|
|
52
|
+
"require": "./dist/index.cjs"
|
|
53
|
+
},
|
|
54
|
+
"./doc": {
|
|
55
|
+
"types": {
|
|
56
|
+
"import": "./dist/doc.d.ts",
|
|
57
|
+
"require": "./dist/doc.cts"
|
|
58
|
+
},
|
|
59
|
+
"import": "./dist/doc.js",
|
|
60
|
+
"require": "./dist/doc.cjs"
|
|
61
|
+
},
|
|
62
|
+
"./facade": {
|
|
63
|
+
"types": {
|
|
64
|
+
"import": "./dist/facade.d.ts",
|
|
65
|
+
"require": "./dist/facade.cts"
|
|
66
|
+
},
|
|
67
|
+
"import": "./dist/facade.js",
|
|
68
|
+
"require": "./dist/facade.cjs"
|
|
69
|
+
},
|
|
70
|
+
"./message": {
|
|
71
|
+
"types": {
|
|
72
|
+
"import": "./dist/message.d.ts",
|
|
73
|
+
"require": "./dist/message.cts"
|
|
74
|
+
},
|
|
75
|
+
"import": "./dist/message.js",
|
|
76
|
+
"require": "./dist/message.cjs"
|
|
77
|
+
},
|
|
78
|
+
"./parser": {
|
|
79
|
+
"types": {
|
|
80
|
+
"import": "./dist/parser.d.ts",
|
|
81
|
+
"require": "./dist/parser.cts"
|
|
82
|
+
},
|
|
83
|
+
"import": "./dist/parser.js",
|
|
84
|
+
"require": "./dist/parser.cjs"
|
|
85
|
+
},
|
|
86
|
+
"./usage": {
|
|
87
|
+
"types": {
|
|
88
|
+
"import": "./dist/usage.d.ts",
|
|
89
|
+
"require": "./dist/usage.cts"
|
|
90
|
+
},
|
|
91
|
+
"import": "./dist/usage.js",
|
|
92
|
+
"require": "./dist/usage.cjs"
|
|
93
|
+
},
|
|
94
|
+
"./valueparser": {
|
|
95
|
+
"types": {
|
|
96
|
+
"import": "./dist/valueparser.d.ts",
|
|
97
|
+
"require": "./dist/valueparser.cts"
|
|
98
|
+
},
|
|
99
|
+
"import": "./dist/valueparser.js",
|
|
100
|
+
"require": "./dist/valueparser.cjs"
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
"sideEffects": false,
|
|
104
|
+
"devDependencies": {
|
|
105
|
+
"@types/node": "^20.19.9",
|
|
106
|
+
"tsdown": "^0.13.0",
|
|
107
|
+
"typescript": "^5.8.3"
|
|
108
|
+
},
|
|
109
|
+
"scripts": {
|
|
110
|
+
"build": "tsdown",
|
|
111
|
+
"prepublish": "tsdown",
|
|
112
|
+
"test": "tsdown && node --experimental-transform-types --test",
|
|
113
|
+
"test:bun": "tsdown && bun test",
|
|
114
|
+
"test:deno": "deno test",
|
|
115
|
+
"test-all": "tsdown && node --experimental-transform-types --test && bun test && deno test"
|
|
116
|
+
}
|
|
117
|
+
}
|