@clerc/parser 1.0.0-beta.2 → 1.0.0-beta.20

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 CHANGED
@@ -332,3 +332,31 @@ const { flags, doubleDash } = parse(["--foo", "--", "--bar"], {
332
332
  // flags: { foo: true, bar: false }
333
333
  // doubleDash: ["--bar"]
334
334
  ```
335
+
336
+ ## Benchmark
337
+
338
+ Benchmarked with vitest.
339
+
340
+ ```
341
+ ✓ packages/parser/bench/bench.bench.ts > bench 6267ms
342
+ name hz min max mean p75 p99 p995 p999 rme samples
343
+ · minimist 684,422.77 0.0012 3.5582 0.0015 0.0013 0.0031 0.0040 0.0222 ±1.50% 342212
344
+ · mri 2,451,855.02 0.0003 0.2422 0.0004 0.0004 0.0007 0.0009 0.0021 ±0.33% 1225928
345
+ · yargs-parser 90,437.66 0.0092 3.1595 0.0111 0.0103 0.0346 0.0602 0.1311 ±1.42% 45219
346
+ · nopt 636,086.73 0.0014 0.3214 0.0016 0.0015 0.0027 0.0031 0.0056 ±0.38% 318044
347
+ · type-flag 715,908.28 0.0012 0.3783 0.0014 0.0013 0.0022 0.0027 0.0282 ±0.61% 357955
348
+ · node:util parseArgs 1,204,359.72 0.0007 0.3827 0.0008 0.0008 0.0016 0.0017 0.0047 ±0.56% 602196
349
+ · args-tokens 2,379,934.00 0.0002 0.2923 0.0004 0.0004 0.0010 0.0015 0.0051 ±0.49% 1189967
350
+ · @clerc/parser 2,119,030.30 0.0003 0.2482 0.0005 0.0005 0.0009 0.0011 0.0017 ±0.49% 1059516
351
+
352
+ BENCH Summary
353
+
354
+ mri - packages/parser/bench/bench.bench.ts > bench
355
+ 1.03x faster than args-tokens
356
+ 1.16x faster than @clerc/parser
357
+ 2.04x faster than node:util parseArgs
358
+ 3.42x faster than type-flag
359
+ 3.58x faster than minimist
360
+ 3.85x faster than nopt
361
+ 27.11x faster than yargs-parser
362
+ ```
package/dist/index.d.ts CHANGED
@@ -2,6 +2,9 @@
2
2
  declare class InvalidSchemaError extends Error {
3
3
  constructor(message: string);
4
4
  }
5
+ declare class MissingRequiredFlagError extends Error {
6
+ constructor(name: string);
7
+ }
5
8
  //#endregion
6
9
  //#region ../utils/src/types/type-fest.d.ts
7
10
  type Prettify<T> = { [K in keyof T]: T[K] } & {};
@@ -10,14 +13,30 @@ type IsAny<T> = 0 extends 1 & T ? true : false;
10
13
  //#region ../utils/src/types/index.d.ts
11
14
  type MaybeArray<T> = T | T[];
12
15
  //#endregion
16
+ //#region src/iterator.d.ts
17
+ declare const KNOWN_FLAG = "known-flag";
18
+ declare const UNKNOWN_FLAG = "unknown-flag";
19
+ declare const PARAMETER = "parameter";
20
+ //#endregion
13
21
  //#region src/types.d.ts
14
- type FlagDefaultValue<T = unknown> = T | (() => T);
22
+ interface FlagDefaultValueFunction<T> {
23
+ (): T;
24
+ display?: string;
25
+ }
26
+ type FlagDefaultValue<T = unknown> = T | FlagDefaultValueFunction<T>;
15
27
  /**
16
28
  * Defines how a string input is converted to the target type T.
17
29
  *
18
30
  * @template T The target type.
19
31
  */
20
- type FlagTypeFunction<T = unknown> = (value: string) => T;
32
+ interface TypeFunction<T = unknown> {
33
+ (value: string): T;
34
+ /**
35
+ * Optional display name for the type, useful in help output.
36
+ * If provided, this will be shown instead of the function name.
37
+ */
38
+ display?: string;
39
+ }
21
40
  /**
22
41
  * A callback function to conditionally stop parsing.
23
42
  * When it returns true, parsing stops and remaining arguments are preserved in `ignored`.
@@ -27,7 +46,7 @@ type FlagTypeFunction<T = unknown> = (value: string) => T;
27
46
  * @returns true to stop parsing, false to continue
28
47
  */
29
48
  type IgnoreFunction = (type: typeof KNOWN_FLAG | typeof UNKNOWN_FLAG | typeof PARAMETER, arg: string) => boolean;
30
- type FlagType<T = unknown> = FlagTypeFunction<T> | readonly [FlagTypeFunction<T>];
49
+ type FlagType<T = unknown> = TypeFunction<T> | readonly [TypeFunction<T>];
31
50
  interface BaseFlagOptions<T extends FlagType = FlagType> {
32
51
  /**
33
52
  * The type constructor or a function to convert the string value.
@@ -39,6 +58,8 @@ interface BaseFlagOptions<T extends FlagType = FlagType> {
39
58
  alias?: MaybeArray<string>;
40
59
  /** The default value of the flag. */
41
60
  default?: unknown;
61
+ /** Whether the flag is required. */
62
+ required?: boolean;
42
63
  }
43
64
  type FlagOptions = (BaseFlagOptions<BooleanConstructor> & {
44
65
  /**
@@ -109,13 +130,15 @@ type IsTypeAny<T extends FlagDefinitionValue> = IsAny<T> extends true ? true : T
109
130
  } ? IsAny<Type> extends true ? true : false : false;
110
131
  type _InferFlags<T extends FlagsDefinition> = { [K in keyof T]: IsTypeAny<T[K]> extends true ? any : T[K] extends readonly [BooleanConstructor] | {
111
132
  type: readonly [BooleanConstructor];
112
- } ? number : T[K] extends ObjectConstructor | {
133
+ } ? number | InferFlagDefault<T[K], never> : T[K] extends ObjectConstructor | {
113
134
  type: ObjectConstructor;
114
- } ? ObjectInputType : T[K] extends readonly [FlagType<infer U>] | {
135
+ } ? ObjectInputType | InferFlagDefault<T[K], never> : T[K] extends readonly [FlagType<infer U>] | {
115
136
  type: readonly [FlagType<infer U>];
116
137
  } ? U[] | InferFlagDefault<T[K], never> : T[K] extends FlagType<infer U> | {
117
138
  type: FlagType<infer U>;
118
- } ? U | InferFlagDefault<T[K], [U] extends [boolean] ? never : undefined> : never };
139
+ } ? U | InferFlagDefault<T[K], [U] extends [boolean] ? never : T[K] extends {
140
+ required: true;
141
+ } ? never : undefined> : never };
119
142
  /**
120
143
  * An advanced utility type that infers the exact type of the `flags` object in the parsed result,
121
144
  * based on the provided `flags` configuration object T.
@@ -124,10 +147,40 @@ type _InferFlags<T extends FlagsDefinition> = { [K in keyof T]: IsTypeAny<T[K]>
124
147
  */
125
148
  type InferFlags<T extends FlagsDefinition> = Prettify<_InferFlags<T>>;
126
149
  //#endregion
127
- //#region src/iterator.d.ts
128
- declare const KNOWN_FLAG = "known-flag";
129
- declare const UNKNOWN_FLAG = "unknown-flag";
130
- declare const PARAMETER = "parameter";
150
+ //#region src/flag-types.d.ts
151
+ /**
152
+ * Creates a Enum type function that validates the input against allowed values.
153
+ * The display name will be formatted as "value1 | value2 | ..." for help output.
154
+ *
155
+ * @param values - Array of allowed string values
156
+ * @returns A TypeFunction that validates and returns the input value
157
+ * @throws {Error} If the value is not in the allowed values list
158
+ *
159
+ * @example
160
+ * ```typescript
161
+ * const format = Enum(['json', 'yaml', 'xml']);
162
+ * // Help output will show: json | yaml | xml
163
+ * ```
164
+ */
165
+ declare function Enum<T extends string>(...values: T[]): TypeFunction<T>;
166
+ /**
167
+ * Creates a range type function that validates the input is a number within the specified range.
168
+ *
169
+ * @param min - The minimum acceptable value (inclusive)
170
+ * @param max - The maximum acceptable value (inclusive)
171
+ * @returns A TypeFunction that validates the input value
172
+ * @throws {Error} If the value is not a number or is outside the specified range
173
+ */
174
+ declare function Range(min: number, max: number): TypeFunction<number>;
175
+ /**
176
+ * Creates a regex type function that validates the input against the provided pattern.
177
+ *
178
+ * @param pattern - The regular expression pattern to validate against
179
+ * @param description - Optional description for display purposes
180
+ * @returns A TypeFunction that validates the input value
181
+ * @throws {Error} If the value does not match the regex pattern
182
+ */
183
+ declare function Regex(pattern: RegExp, description?: string): TypeFunction<string>;
131
184
  //#endregion
132
185
  //#region src/parse.d.ts
133
186
  declare const DOUBLE_DASH = "--";
@@ -137,4 +190,4 @@ declare function createParser<T extends FlagsDefinition>(options?: ParserOptions
137
190
  };
138
191
  declare const parse: <T extends FlagsDefinition>(args: string[], options?: ParserOptions<T>) => ParsedResult<InferFlags<T>>;
139
192
  //#endregion
140
- export { BaseFlagOptions, DOUBLE_DASH, FlagDefaultValue, FlagDefinitionValue, FlagOptions, FlagType, FlagTypeFunction, FlagsDefinition, IgnoreFunction, InferFlags, InvalidSchemaError, KNOWN_FLAG, ObjectInputType, PARAMETER, ParsedResult, ParserOptions, RawInputType, UNKNOWN_FLAG, createParser, parse };
193
+ export { BaseFlagOptions, DOUBLE_DASH, Enum, FlagDefaultValue, FlagDefaultValueFunction, FlagDefinitionValue, FlagOptions, FlagType, FlagsDefinition, IgnoreFunction, InferFlags, InvalidSchemaError, KNOWN_FLAG, MissingRequiredFlagError, ObjectInputType, PARAMETER, ParsedResult, ParserOptions, Range, RawInputType, Regex, TypeFunction, UNKNOWN_FLAG, createParser, parse };
package/dist/index.js CHANGED
@@ -5,6 +5,70 @@ var InvalidSchemaError = class extends Error {
5
5
  this.name = "InvalidSchemaError";
6
6
  }
7
7
  };
8
+ var MissingRequiredFlagError = class extends Error {
9
+ constructor(name) {
10
+ super(`Missing required flag: ${name}`);
11
+ this.name = "MissingRequiredFlagError";
12
+ }
13
+ };
14
+
15
+ //#endregion
16
+ //#region src/flag-types.ts
17
+ /**
18
+ * Creates a Enum type function that validates the input against allowed values.
19
+ * The display name will be formatted as "value1 | value2 | ..." for help output.
20
+ *
21
+ * @param values - Array of allowed string values
22
+ * @returns A TypeFunction that validates and returns the input value
23
+ * @throws {Error} If the value is not in the allowed values list
24
+ *
25
+ * @example
26
+ * ```typescript
27
+ * const format = Enum(['json', 'yaml', 'xml']);
28
+ * // Help output will show: json | yaml | xml
29
+ * ```
30
+ */
31
+ function Enum(...values) {
32
+ const fn = ((value) => {
33
+ if (!values.includes(value)) throw new Error(`Invalid value: ${value}. Must be one of: ${values.join(", ")}`);
34
+ return value;
35
+ });
36
+ fn.display = values.join(" | ");
37
+ return fn;
38
+ }
39
+ /**
40
+ * Creates a range type function that validates the input is a number within the specified range.
41
+ *
42
+ * @param min - The minimum acceptable value (inclusive)
43
+ * @param max - The maximum acceptable value (inclusive)
44
+ * @returns A TypeFunction that validates the input value
45
+ * @throws {Error} If the value is not a number or is outside the specified range
46
+ */
47
+ function Range(min, max) {
48
+ const fn = ((value) => {
49
+ const num = Number(value);
50
+ if (Number.isNaN(num) || num < min || num > max) throw new Error(`Invalid value: ${value}. Must be a number between ${min} and ${max}`);
51
+ return num;
52
+ });
53
+ fn.display = `${min}-${max}`;
54
+ return fn;
55
+ }
56
+ /**
57
+ * Creates a regex type function that validates the input against the provided pattern.
58
+ *
59
+ * @param pattern - The regular expression pattern to validate against
60
+ * @param description - Optional description for display purposes
61
+ * @returns A TypeFunction that validates the input value
62
+ * @throws {Error} If the value does not match the regex pattern
63
+ */
64
+ function Regex(pattern, description) {
65
+ const fn = ((value) => {
66
+ if (!pattern.test(value)) throw new Error(`Invalid value: ${value}. Must match pattern: ${pattern}`);
67
+ return value;
68
+ });
69
+ fn.display = description ?? pattern.toString();
70
+ return fn;
71
+ }
8
72
 
9
73
  //#endregion
10
74
  //#region src/iterator.ts
@@ -56,7 +120,8 @@ function iterateArgs(args, result, shouldProcessAsFlag, isKnownFlag, ignore, cal
56
120
  }
57
121
 
58
122
  //#endregion
59
- //#region ../utils/dist/index.js
123
+ //#region ../utils/src/index.ts
124
+ const looseIsArray = (arr) => Array.isArray(arr);
60
125
  const toArray = (a) => Array.isArray(a) ? a : [a];
61
126
  /**
62
127
  * Converts a dash- or space-separated string to camelCase.
@@ -78,10 +143,47 @@ function camelCase(str) {
78
143
  } else if (str[i] !== "-" && str[i] !== " ") result += str[i];
79
144
  return result;
80
145
  }
146
+ const resolveValue = (value) => typeof value === "function" ? value() : value;
147
+
148
+ //#endregion
149
+ //#region src/config.ts
150
+ const defaultParserOptions = { delimiters: ["=", ":"] };
151
+ const resolveParserOptions = (options = {}) => ({
152
+ ...defaultParserOptions,
153
+ ...options
154
+ });
155
+ const normalizeConfig = (config) => typeof config === "function" || looseIsArray(config) ? { type: config } : config;
156
+ const BUILDTIN_DELIMITERS_RE = /[\s.]/;
157
+ function buildConfigsAndAliases(delimiters, flags) {
158
+ const configs = /* @__PURE__ */ new Map();
159
+ const aliases = /* @__PURE__ */ new Map();
160
+ const isNameInvalid = (name) => delimiters.some((char) => name.includes(char)) || BUILDTIN_DELIMITERS_RE.test(name);
161
+ function validateFlagOptions(name, options) {
162
+ const prefix = `Flag "${name}"`;
163
+ if (Array.isArray(options.type) && options.type.length > 1) throw new InvalidSchemaError(`${prefix} has an invalid type array. Only single-element arrays are allowed to denote multiple occurrences.`);
164
+ const names = [name];
165
+ if (options.alias) names.push(...toArray(options.alias));
166
+ if (names.some(isNameInvalid)) throw new InvalidSchemaError(`${prefix} contains reserved characters, which are used as delimiters.`);
167
+ }
168
+ for (const [name, config] of Object.entries(flags)) {
169
+ const normalized = normalizeConfig(config);
170
+ validateFlagOptions(name, normalized);
171
+ configs.set(name, normalized);
172
+ aliases.set(name, name);
173
+ aliases.set(camelCase(name), name);
174
+ if (normalized.alias) {
175
+ const list = Array.isArray(normalized.alias) ? normalized.alias : [normalized.alias];
176
+ for (const a of list) aliases.set(a, name);
177
+ }
178
+ }
179
+ return {
180
+ configs,
181
+ aliases
182
+ };
183
+ }
81
184
 
82
185
  //#endregion
83
186
  //#region src/utils.ts
84
- const looseIsArray = (arr) => Array.isArray(arr);
85
187
  const isArrayOfType = (arr, type) => Array.isArray(arr) && arr[0] === type;
86
188
  /**
87
189
  * Check if it's a letter (a-z: 97-122, A-Z: 65-90)
@@ -132,43 +234,6 @@ function splitNameAndValue(arg, delimiters) {
132
234
  };
133
235
  }
134
236
 
135
- //#endregion
136
- //#region src/config.ts
137
- const defaultParserOptions = { delimiters: ["=", ":"] };
138
- const resolveParserOptions = (options = {}) => ({
139
- ...defaultParserOptions,
140
- ...options
141
- });
142
- const normalizeConfig = (config) => typeof config === "function" || looseIsArray(config) ? { type: config } : config;
143
- const BUILDTIN_DELIMITERS_RE = /[\s.]/;
144
- function buildConfigsAndAliases(delimiters, flags) {
145
- const configs = /* @__PURE__ */ new Map();
146
- const aliases = /* @__PURE__ */ new Map();
147
- const isNameInvalid = (name) => delimiters.some((char) => name.includes(char)) || BUILDTIN_DELIMITERS_RE.test(name);
148
- function validateFlagOptions(name, options) {
149
- const prefix = `Flag "${name}"`;
150
- if (Array.isArray(options.type) && options.type.length > 1) throw new InvalidSchemaError(`${prefix} has an invalid type array. Only single-element arrays are allowed to denote multiple occurrences.`);
151
- const names = [name];
152
- if (options.alias) names.push(...toArray(options.alias));
153
- if (names.some(isNameInvalid)) throw new InvalidSchemaError(`${prefix} contains reserved characters, which are used as delimiters.`);
154
- }
155
- for (const [name, config] of Object.entries(flags)) {
156
- const normalized = normalizeConfig(config);
157
- validateFlagOptions(name, normalized);
158
- configs.set(name, normalized);
159
- aliases.set(name, name);
160
- aliases.set(camelCase(name), name);
161
- if (normalized.alias) {
162
- const list = Array.isArray(normalized.alias) ? normalized.alias : [normalized.alias];
163
- for (const a of list) aliases.set(a, name);
164
- }
165
- }
166
- return {
167
- configs,
168
- aliases
169
- };
170
- }
171
-
172
237
  //#endregion
173
238
  //#region src/parse.ts
174
239
  const DOUBLE_DASH = "--";
@@ -304,10 +369,11 @@ function createParser(options = {}) {
304
369
  }
305
370
  });
306
371
  for (const [key, config] of configs.entries()) if (result.flags[key] === void 0) {
307
- if (config.default !== void 0) result.flags[key] = typeof config.default === "function" ? config.default() : config.default;
372
+ if (config.default !== void 0) result.flags[key] = resolveValue(config.default);
308
373
  else if (Array.isArray(config.type)) result.flags[key] = isArrayOfType(config.type, Boolean) ? 0 : [];
309
374
  else if (config.type === Object) result.flags[key] = {};
310
375
  else if (config.type === Boolean) result.flags[key] = false;
376
+ else if (config.required) throw new MissingRequiredFlagError(key);
311
377
  }
312
378
  return result;
313
379
  };
@@ -316,4 +382,4 @@ function createParser(options = {}) {
316
382
  const parse = (args, options = {}) => createParser(options).parse(args);
317
383
 
318
384
  //#endregion
319
- export { DOUBLE_DASH, InvalidSchemaError, KNOWN_FLAG, PARAMETER, UNKNOWN_FLAG, createParser, parse };
385
+ export { DOUBLE_DASH, Enum, InvalidSchemaError, KNOWN_FLAG, MissingRequiredFlagError, PARAMETER, Range, Regex, UNKNOWN_FLAG, createParser, parse };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clerc/parser",
3
- "version": "1.0.0-beta.2",
3
+ "version": "1.0.0-beta.20",
4
4
  "author": "Ray <i@mk1.io> (https://github.com/so1ve)",
5
5
  "type": "module",
6
6
  "description": "Clerc parser",
@@ -50,15 +50,12 @@
50
50
  "@types/minimist": "^1.2.5",
51
51
  "@types/nopt": "^3.0.32",
52
52
  "@types/yargs-parser": "^21.0.3",
53
+ "args-tokens": "^0.23.0",
53
54
  "minimist": "^1.2.8",
54
55
  "mri": "^1.2.0",
55
56
  "nopt": "^9.0.0",
56
57
  "type-flag": "^4.0.3",
57
58
  "yargs-parser": "^22.0.0",
58
- "@clerc/utils": "1.0.0-beta.2"
59
- },
60
- "scripts": {
61
- "build": "tsdown",
62
- "watch": "tsdown --watch"
59
+ "@clerc/utils": "1.0.0-beta.20"
63
60
  }
64
61
  }