@clerc/parser 1.0.0-beta.8 → 1.0.0

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
@@ -1,15 +1,15 @@
1
1
  # @clerc/parser
2
2
 
3
+ [![NPM version](https://img.shields.io/npm/v/@clerc/parser?color=a1b858&label=)](https://www.npmjs.com/package/@clerc/parser)
4
+
3
5
  A powerful, lightweight, and flexible command-line arguments parser.
4
6
 
5
7
  ## Installation
6
8
 
7
9
  ```bash
8
- npm install @clerc/parser
9
- # or
10
- yarn add @clerc/parser
11
- # or
12
- pnpm add @clerc/parser
10
+ $ npm install @clerc/parser
11
+ $ yarn add @clerc/parser
12
+ $ pnpm add @clerc/parser
13
13
  ```
14
14
 
15
15
  ## Usage
@@ -360,3 +360,7 @@ BENCH Summary
360
360
  3.85x faster than nopt
361
361
  27.11x faster than yargs-parser
362
362
  ```
363
+
364
+ ## 📝 License
365
+
366
+ [MIT](../../LICENSE). Made with ❤️ by [Ray](https://github.com/so1ve)
package/dist/index.d.ts CHANGED
@@ -3,44 +3,30 @@ declare class InvalidSchemaError extends Error {
3
3
  constructor(message: string);
4
4
  }
5
5
  //#endregion
6
- //#region src/flag-types.d.ts
7
- /**
8
- * Creates a Choices type function that validates the input against allowed values.
9
- * The display name will be formatted as "value1 | value2 | ..." for help output.
10
- *
11
- * @param values - Array of allowed string values
12
- * @returns A FlagTypeFunction that validates and returns the input value
13
- * @throws {Error} If the value is not in the allowed values list
14
- *
15
- * @example
16
- * ```typescript
17
- * const format = Choices(['json', 'yaml', 'xml']);
18
- * // Help output will show: json | yaml | xml
19
- * ```
20
- */
21
- declare function Choices<T extends string>(...values: T[]): FlagTypeFunction<T>;
22
- //#endregion
23
6
  //#region ../utils/src/types/type-fest.d.ts
24
7
  type Prettify<T> = { [K in keyof T]: T[K] } & {};
25
8
  type IsAny<T> = 0 extends 1 & T ? true : false;
26
- //#endregion
27
- //#region ../utils/src/types/index.d.ts
28
- type MaybeArray<T> = T | T[];
9
+ type RequireExactlyOneOrNone<T, Keys extends keyof T = keyof T> = ({ [K in Keys]-?: Required<Pick<T, K>> & Partial<Record<Exclude<Keys, K>, never>> }[Keys] & Omit<T, Keys>) | (Partial<Record<Keys, never>> & Omit<T, Keys>);
29
10
  //#endregion
30
11
  //#region src/types.d.ts
31
- type FlagDefaultValue<T = unknown> = T | (() => T);
12
+ interface FlagDefaultValueFunction<T> {
13
+ (): T;
14
+ display?: string;
15
+ }
16
+ type FlagDefaultValue<T = unknown> = T | FlagDefaultValueFunction<T>;
32
17
  /**
33
18
  * Defines how a string input is converted to the target type T.
34
19
  *
35
20
  * @template T The target type.
36
21
  */
37
- type FlagTypeFunction<T = unknown> = ((value: string) => T) & {
22
+ interface TypeFunction<T = unknown> {
23
+ (value: string): T;
38
24
  /**
39
25
  * Optional display name for the type, useful in help output.
40
26
  * If provided, this will be shown instead of the function name.
41
27
  */
42
- displayName?: string;
43
- };
28
+ display?: string;
29
+ }
44
30
  /**
45
31
  * A callback function to conditionally stop parsing.
46
32
  * When it returns true, parsing stops and remaining arguments are preserved in `ignored`.
@@ -50,19 +36,23 @@ type FlagTypeFunction<T = unknown> = ((value: string) => T) & {
50
36
  * @returns true to stop parsing, false to continue
51
37
  */
52
38
  type IgnoreFunction = (type: typeof KNOWN_FLAG | typeof UNKNOWN_FLAG | typeof PARAMETER, arg: string) => boolean;
53
- type FlagType<T = unknown> = FlagTypeFunction<T> | readonly [FlagTypeFunction<T>];
54
- interface BaseFlagOptions<T extends FlagType = FlagType> {
39
+ type TypeValue<T = unknown> = TypeFunction<T> | readonly [TypeFunction<T>];
40
+ type FlagRequiredOrDefault = RequireExactlyOneOrNone<{
41
+ /** The default value of the flag. */
42
+ default?: unknown;
43
+ /** Whether the flag is required. */
44
+ required?: boolean;
45
+ }, "default" | "required">;
46
+ type BaseFlagOptions<T extends TypeValue = TypeValue> = FlagRequiredOrDefault & {
55
47
  /**
56
48
  * The type constructor or a function to convert the string value.
57
49
  * To support multiple occurrences of a flag (e.g., --file a --file b), wrap the type in an array: [String], [Number].
58
50
  * e.g., String, Number, [String], (val) => val.split(',')
59
51
  */
60
52
  type: T;
61
- /** Aliases for the flag. */
62
- alias?: MaybeArray<string>;
63
- /** The default value of the flag. */
64
- default?: unknown;
65
- }
53
+ /** Short flag alias (single character). */
54
+ short?: string;
55
+ };
66
56
  type FlagOptions = (BaseFlagOptions<BooleanConstructor> & {
67
57
  /**
68
58
  * Whether to enable the `--no-<flag>` syntax to set the value to false.
@@ -75,7 +65,7 @@ type FlagOptions = (BaseFlagOptions<BooleanConstructor> & {
75
65
  }) | (BaseFlagOptions & {
76
66
  negatable?: never;
77
67
  });
78
- type FlagDefinitionValue = FlagOptions | FlagType;
68
+ type FlagDefinitionValue = FlagOptions | TypeValue;
79
69
  type FlagsDefinition = Record<string, FlagDefinitionValue>;
80
70
  /**
81
71
  * Configuration options for the parser.
@@ -123,6 +113,8 @@ interface ParsedResult<TFlags extends Record<string, any>> {
123
113
  unknown: Record<string, RawInputType>;
124
114
  /** Arguments that were not parsed due to ignore callback. */
125
115
  ignored: string[];
116
+ /** List of required flags that were not provided. */
117
+ missingRequiredFlags: string[];
126
118
  }
127
119
  type InferFlagDefault<T extends FlagDefinitionValue, Fallback> = T extends {
128
120
  default: FlagDefaultValue<infer DefaultType>;
@@ -132,13 +124,15 @@ type IsTypeAny<T extends FlagDefinitionValue> = IsAny<T> extends true ? true : T
132
124
  } ? IsAny<Type> extends true ? true : false : false;
133
125
  type _InferFlags<T extends FlagsDefinition> = { [K in keyof T]: IsTypeAny<T[K]> extends true ? any : T[K] extends readonly [BooleanConstructor] | {
134
126
  type: readonly [BooleanConstructor];
135
- } ? number : T[K] extends ObjectConstructor | {
127
+ } ? number | InferFlagDefault<T[K], never> : T[K] extends ObjectConstructor | {
136
128
  type: ObjectConstructor;
137
- } ? ObjectInputType : T[K] extends readonly [FlagType<infer U>] | {
138
- type: readonly [FlagType<infer U>];
139
- } ? U[] | InferFlagDefault<T[K], never> : T[K] extends FlagType<infer U> | {
140
- type: FlagType<infer U>;
141
- } ? U | InferFlagDefault<T[K], [U] extends [boolean] ? never : undefined> : never };
129
+ } ? ObjectInputType | InferFlagDefault<T[K], never> : T[K] extends readonly [TypeValue<infer U>] | {
130
+ type: readonly [TypeValue<infer U>];
131
+ } ? U[] | InferFlagDefault<T[K], never> : T[K] extends TypeValue<infer U> | {
132
+ type: TypeValue<infer U>;
133
+ } ? U | InferFlagDefault<T[K], [U] extends [boolean] ? never : T[K] extends {
134
+ required: true;
135
+ } ? never : undefined> : never };
142
136
  /**
143
137
  * An advanced utility type that infers the exact type of the `flags` object in the parsed result,
144
138
  * based on the provided `flags` configuration object T.
@@ -160,4 +154,4 @@ declare function createParser<T extends FlagsDefinition>(options?: ParserOptions
160
154
  };
161
155
  declare const parse: <T extends FlagsDefinition>(args: string[], options?: ParserOptions<T>) => ParsedResult<InferFlags<T>>;
162
156
  //#endregion
163
- export { BaseFlagOptions, Choices, DOUBLE_DASH, FlagDefaultValue, FlagDefinitionValue, FlagOptions, FlagType, FlagTypeFunction, FlagsDefinition, IgnoreFunction, InferFlags, InvalidSchemaError, KNOWN_FLAG, ObjectInputType, PARAMETER, ParsedResult, ParserOptions, RawInputType, UNKNOWN_FLAG, createParser, parse };
157
+ export { BaseFlagOptions, DOUBLE_DASH, FlagDefaultValue, FlagDefaultValueFunction, FlagDefinitionValue, FlagOptions, FlagsDefinition, IgnoreFunction, InferFlags, InvalidSchemaError, KNOWN_FLAG, ObjectInputType, PARAMETER, ParsedResult, ParserOptions, RawInputType, TypeFunction, TypeValue, UNKNOWN_FLAG, createParser, parse };
package/dist/index.js CHANGED
@@ -6,31 +6,6 @@ var InvalidSchemaError = class extends Error {
6
6
  }
7
7
  };
8
8
 
9
- //#endregion
10
- //#region src/flag-types.ts
11
- /**
12
- * Creates a Choices type function that validates the input against allowed values.
13
- * The display name will be formatted as "value1 | value2 | ..." for help output.
14
- *
15
- * @param values - Array of allowed string values
16
- * @returns A FlagTypeFunction that validates and returns the input value
17
- * @throws {Error} If the value is not in the allowed values list
18
- *
19
- * @example
20
- * ```typescript
21
- * const format = Choices(['json', 'yaml', 'xml']);
22
- * // Help output will show: json | yaml | xml
23
- * ```
24
- */
25
- function Choices(...values) {
26
- const fn = ((value) => {
27
- if (!values.includes(value)) throw new Error(`Invalid value: ${value}. Must be one of: ${values.join(", ")}`);
28
- return value;
29
- });
30
- fn.displayName = values.join(" | ");
31
- return fn;
32
- }
33
-
34
9
  //#endregion
35
10
  //#region src/iterator.ts
36
11
  const KNOWN_FLAG = "known-flag";
@@ -81,8 +56,8 @@ function iterateArgs(args, result, shouldProcessAsFlag, isKnownFlag, ignore, cal
81
56
  }
82
57
 
83
58
  //#endregion
84
- //#region ../utils/dist/index.js
85
- const toArray = (a) => Array.isArray(a) ? a : [a];
59
+ //#region ../utils/src/index.ts
60
+ const looseIsArray = (arr) => Array.isArray(arr);
86
61
  /**
87
62
  * Converts a dash- or space-separated string to camelCase.
88
63
  * Not using regexp for better performance, because this function is used in parser.
@@ -103,10 +78,49 @@ function camelCase(str) {
103
78
  } else if (str[i] !== "-" && str[i] !== " ") result += str[i];
104
79
  return result;
105
80
  }
81
+ const resolveValue = (value) => typeof value === "function" ? value() : value;
82
+
83
+ //#endregion
84
+ //#region src/config.ts
85
+ const defaultParserOptions = { delimiters: ["=", ":"] };
86
+ const resolveParserOptions = (options = {}) => ({
87
+ ...defaultParserOptions,
88
+ ...options
89
+ });
90
+ const normalizeConfig = (config) => typeof config === "function" || looseIsArray(config) ? { type: config } : config;
91
+ const BUILDTIN_DELIMITERS_RE = /[\s.]/;
92
+ function buildConfigsAndAliases(delimiters, flags) {
93
+ const configs = /* @__PURE__ */ new Map();
94
+ const aliases = /* @__PURE__ */ new Map();
95
+ const isNameInvalid = (name) => delimiters.some((char) => name.includes(char)) || BUILDTIN_DELIMITERS_RE.test(name);
96
+ function validateFlagOptions(name, options) {
97
+ const prefix = `Flag "${name}"`;
98
+ 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.`);
99
+ if (name.length < 2) throw new InvalidSchemaError(`${prefix} name must be at least 2 characters long.`);
100
+ const names = [name];
101
+ if (options.short) {
102
+ if (options.short.length !== 1) throw new InvalidSchemaError(`${prefix} short flag must be exactly 1 character long.`);
103
+ names.push(options.short);
104
+ }
105
+ if (names.some(isNameInvalid)) throw new InvalidSchemaError(`${prefix} contains reserved characters, which are used as delimiters.`);
106
+ if (options.required && options.default !== void 0) throw new InvalidSchemaError(`${prefix} cannot be both required and have a default value.`);
107
+ }
108
+ for (const [name, config] of Object.entries(flags)) {
109
+ const normalized = normalizeConfig(config);
110
+ validateFlagOptions(name, normalized);
111
+ configs.set(name, normalized);
112
+ aliases.set(name, name);
113
+ aliases.set(camelCase(name), name);
114
+ if (normalized.short) aliases.set(normalized.short, name);
115
+ }
116
+ return {
117
+ configs,
118
+ aliases
119
+ };
120
+ }
106
121
 
107
122
  //#endregion
108
123
  //#region src/utils.ts
109
- const looseIsArray = (arr) => Array.isArray(arr);
110
124
  const isArrayOfType = (arr, type) => Array.isArray(arr) && arr[0] === type;
111
125
  /**
112
126
  * Check if it's a letter (a-z: 97-122, A-Z: 65-90)
@@ -157,50 +171,13 @@ function splitNameAndValue(arg, delimiters) {
157
171
  };
158
172
  }
159
173
 
160
- //#endregion
161
- //#region src/config.ts
162
- const defaultParserOptions = { delimiters: ["=", ":"] };
163
- const resolveParserOptions = (options = {}) => ({
164
- ...defaultParserOptions,
165
- ...options
166
- });
167
- const normalizeConfig = (config) => typeof config === "function" || looseIsArray(config) ? { type: config } : config;
168
- const BUILDTIN_DELIMITERS_RE = /[\s.]/;
169
- function buildConfigsAndAliases(delimiters, flags) {
170
- const configs = /* @__PURE__ */ new Map();
171
- const aliases = /* @__PURE__ */ new Map();
172
- const isNameInvalid = (name) => delimiters.some((char) => name.includes(char)) || BUILDTIN_DELIMITERS_RE.test(name);
173
- function validateFlagOptions(name, options) {
174
- const prefix = `Flag "${name}"`;
175
- 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.`);
176
- const names = [name];
177
- if (options.alias) names.push(...toArray(options.alias));
178
- if (names.some(isNameInvalid)) throw new InvalidSchemaError(`${prefix} contains reserved characters, which are used as delimiters.`);
179
- }
180
- for (const [name, config] of Object.entries(flags)) {
181
- const normalized = normalizeConfig(config);
182
- validateFlagOptions(name, normalized);
183
- configs.set(name, normalized);
184
- aliases.set(name, name);
185
- aliases.set(camelCase(name), name);
186
- if (normalized.alias) {
187
- const list = Array.isArray(normalized.alias) ? normalized.alias : [normalized.alias];
188
- for (const a of list) aliases.set(a, name);
189
- }
190
- }
191
- return {
192
- configs,
193
- aliases
194
- };
195
- }
196
-
197
174
  //#endregion
198
175
  //#region src/parse.ts
199
176
  const DOUBLE_DASH = "--";
200
177
  function createParser(options = {}) {
201
178
  const { flags: flagsConfig = {}, delimiters, ignore } = resolveParserOptions(options);
202
179
  const { configs, aliases } = buildConfigsAndAliases(delimiters, flagsConfig);
203
- function resolve(name) {
180
+ function resolve(name, isShortFlag = false) {
204
181
  const dotIdx = name.indexOf(".");
205
182
  const rootName = dotIdx === -1 ? name : name.slice(0, dotIdx);
206
183
  let key = aliases.get(rootName);
@@ -209,6 +186,7 @@ function createParser(options = {}) {
209
186
  if (!key) return;
210
187
  }
211
188
  const config = configs.get(key);
189
+ if (!isShortFlag && config.short === rootName) return;
212
190
  return {
213
191
  key,
214
192
  config,
@@ -229,21 +207,22 @@ function createParser(options = {}) {
229
207
  if (len < 2) return false;
230
208
  const secondChar = arg.charCodeAt(1);
231
209
  if (isLetter(secondChar)) return true;
232
- if (isDigit(secondChar)) return !!resolve(secondChar !== 45 ? arg[1] : arg.slice(2));
210
+ if (isDigit(secondChar)) {
211
+ const isShortFlag = secondChar !== 45;
212
+ return !!resolve(isShortFlag ? arg[1] : arg.slice(2), isShortFlag);
213
+ }
233
214
  if (secondChar === 45 && len > 2) return isLetter(arg.charCodeAt(2));
234
215
  return false;
235
216
  }
236
217
  function isKnownFlag(arg) {
237
- const secondChar = arg.charCodeAt(1);
238
- if (secondChar === 45) {
218
+ if (arg.charCodeAt(1) === 45) {
239
219
  const { rawName } = splitNameAndValue(arg.slice(2), delimiters);
240
- if (resolve(rawName)) return true;
220
+ if (resolve(rawName, false)) return true;
241
221
  if (resolveNegated(rawName)) return true;
242
222
  return false;
243
223
  }
244
- if (isDigit(secondChar)) return true;
245
224
  const chars = arg.slice(1);
246
- for (const char of chars) if (!resolve(char)) return false;
225
+ for (const char of chars) if (!resolve(char, true)) return false;
247
226
  return true;
248
227
  }
249
228
  const parse$1 = (args) => {
@@ -253,7 +232,8 @@ function createParser(options = {}) {
253
232
  flags: {},
254
233
  raw: args,
255
234
  unknown: {},
256
- ignored: []
235
+ ignored: [],
236
+ missingRequiredFlags: []
257
237
  };
258
238
  iterateArgs(args, result, shouldProcessAsFlag, isKnownFlag, ignore, ({ current, eat, exit, hasNext, index, next, shouldIgnore }) => {
259
239
  if (current === DOUBLE_DASH) {
@@ -270,13 +250,13 @@ function createParser(options = {}) {
270
250
  result.parameters.push(current);
271
251
  return;
272
252
  }
273
- const isAlias = !current.startsWith(DOUBLE_DASH);
274
- const chars = current.slice(isAlias ? 1 : 2);
275
- if (isAlias) {
253
+ const isShortFlag = !current.startsWith(DOUBLE_DASH);
254
+ const chars = current.slice(isShortFlag ? 1 : 2);
255
+ if (isShortFlag) {
276
256
  const charsLen = chars.length;
277
257
  for (let j = 0; j < charsLen; j++) {
278
258
  const char = chars[j];
279
- const resolved = resolve(char);
259
+ const resolved = resolve(char, true);
280
260
  if (!resolved) {
281
261
  result.unknown[char] = true;
282
262
  continue;
@@ -288,8 +268,8 @@ function createParser(options = {}) {
288
268
  setValueByType(result.flags, key, chars.slice(j + 1), config);
289
269
  break;
290
270
  } else {
291
- const nextValue = eat();
292
- if (nextValue && !shouldProcessAsFlag(nextValue)) setValueByType(result.flags, key, nextValue, config);
271
+ const value = hasNext && !shouldProcessAsFlag(next) ? eat() ?? "" : "";
272
+ setValueByType(result.flags, key, value, config);
293
273
  }
294
274
  }
295
275
  } else {
@@ -329,10 +309,11 @@ function createParser(options = {}) {
329
309
  }
330
310
  });
331
311
  for (const [key, config] of configs.entries()) if (result.flags[key] === void 0) {
332
- if (config.default !== void 0) result.flags[key] = typeof config.default === "function" ? config.default() : config.default;
312
+ if (config.default !== void 0) result.flags[key] = resolveValue(config.default);
333
313
  else if (Array.isArray(config.type)) result.flags[key] = isArrayOfType(config.type, Boolean) ? 0 : [];
334
314
  else if (config.type === Object) result.flags[key] = {};
335
315
  else if (config.type === Boolean) result.flags[key] = false;
316
+ else if (config.required) result.missingRequiredFlags.push(key);
336
317
  }
337
318
  return result;
338
319
  };
@@ -341,4 +322,4 @@ function createParser(options = {}) {
341
322
  const parse = (args, options = {}) => createParser(options).parse(args);
342
323
 
343
324
  //#endregion
344
- export { Choices, DOUBLE_DASH, InvalidSchemaError, KNOWN_FLAG, PARAMETER, UNKNOWN_FLAG, createParser, parse };
325
+ export { DOUBLE_DASH, InvalidSchemaError, KNOWN_FLAG, PARAMETER, 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.8",
3
+ "version": "1.0.0",
4
4
  "author": "Ray <i@mk1.io> (https://github.com/so1ve)",
5
5
  "type": "module",
6
6
  "description": "Clerc parser",
@@ -56,10 +56,6 @@
56
56
  "nopt": "^9.0.0",
57
57
  "type-flag": "^4.0.3",
58
58
  "yargs-parser": "^22.0.0",
59
- "@clerc/utils": "1.0.0-beta.8"
60
- },
61
- "scripts": {
62
- "build": "tsdown",
63
- "watch": "tsdown --watch"
59
+ "@clerc/utils": "1.0.0"
64
60
  }
65
61
  }