@clerc/parser 1.0.0-beta.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022 Ray <https://github.com/so1ve>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,334 @@
1
+ # @clerc/parser
2
+
3
+ A powerful, lightweight, and flexible command-line arguments parser.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @clerc/parser
9
+ # or
10
+ yarn add @clerc/parser
11
+ # or
12
+ pnpm add @clerc/parser
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ The core of this package is the `parse` function. It takes an array of arguments and a configuration object, and returns a structured object containing the parsed flags, parameters, and other useful information.
18
+
19
+ ```typescript
20
+ import { parse } from "@clerc/parser";
21
+
22
+ const { flags, parameters, unknown } = parse(process.argv.slice(2), {
23
+ flags: {
24
+ // Define your flags here
25
+ port: {
26
+ type: Number,
27
+ alias: "p",
28
+ default: 8080,
29
+ },
30
+ help: {
31
+ type: Boolean,
32
+ alias: "h",
33
+ },
34
+ },
35
+ });
36
+
37
+ console.log("Flags:", flags);
38
+ console.log("Parameters:", parameters);
39
+ console.log("Unknown args:", unknown);
40
+ ```
41
+
42
+ ## API
43
+
44
+ ### `parse(args, options)`
45
+
46
+ #### `args`
47
+
48
+ - Type: `string[]`
49
+ - The array of command-line arguments to parse (e.g., `process.argv.slice(2)`).
50
+
51
+ #### `options`
52
+
53
+ - Type: `ParseOptions`
54
+
55
+ An object to configure the parser.
56
+
57
+ - **`flags`**: An object defining the flags to parse.
58
+ - Key: The name of the flag (in camelCase).
59
+ - Value: The flag's configuration, which can be a type constructor (`String`, `Number`, `Boolean`) or a custom function that takes a string and returns a parsed value or an object with the following properties:
60
+ - `type`: The type of the flag. Can be a constructor like `String`, `Number`, `Boolean`, `Object`, an array of a constructor (e.g., `[String]`), or a custom function that takes a string and returns a parsed value.
61
+ - `alias`: A string or an array of strings for alternative names (e.g., short flags).
62
+ - `default`: A default value for the flag if it's not provided in the arguments. Can be a value or a function that returns a value.
63
+ - `negatable`: (For Booleans) Whether to support `--no-<flag>` syntax. Defaults to `true`.
64
+ - **`ignore`**: A function to conditionally stop parsing. It receives the `type` (`flag` or `parameter`) and the `arg` string, and should return `true` to stop parsing from that point.
65
+
66
+ #### Returns
67
+
68
+ An object with the following properties:
69
+
70
+ - `flags`: An object containing the parsed flags.
71
+ - `parameters`: An array of positional arguments.
72
+ - `unknown`: An object containing flags that were not defined in the schema.
73
+ - `doubleDash`: An array of arguments that appear after a `--`.
74
+ - `ignored`: An array of arguments that were ignored due to the `ignore` function.
75
+
76
+ ## Features
77
+
78
+ ### Basic Types
79
+
80
+ Supports `Boolean`, `String`, and `Number`.
81
+
82
+ ```typescript
83
+ const { flags } = parse(["--bool", "--str", "hello", "--num", "42"], {
84
+ flags: {
85
+ bool: { type: Boolean },
86
+ str: { type: String },
87
+ num: { type: Number },
88
+ },
89
+ });
90
+ // flags: { bool: true, str: "hello", num: 42 }
91
+ ```
92
+
93
+ ### Aliases
94
+
95
+ Use `alias` to define short or alternative names for flags.
96
+
97
+ ```typescript
98
+ const { flags } = parse(["-b", "-s", "hello"], {
99
+ flags: {
100
+ bool: { type: Boolean, alias: "b" },
101
+ str: { type: String, alias: "s" },
102
+ },
103
+ });
104
+ // flags: { bool: true, str: "hello" }
105
+ ```
106
+
107
+ ### Arrays and Counters
108
+
109
+ - To collect multiple values for a flag, use an array type like `[String]`.
110
+ - To count the occurrences of a boolean flag, use `[Boolean]`.
111
+
112
+ ```typescript
113
+ // Array of strings
114
+ const { flags: arrayFlags } = parse(["--arr", "a", "--arr", "b"], {
115
+ flags: {
116
+ arr: { type: [String] },
117
+ },
118
+ });
119
+ // arrayFlags: { arr: ["a", "b"] }
120
+
121
+ // Counter
122
+ const { flags: counterFlags } = parse(["-vvv"], {
123
+ flags: {
124
+ verbose: { type: [Boolean], alias: "v" },
125
+ },
126
+ });
127
+ // counterFlags: { verbose: 3 }
128
+ ```
129
+
130
+ ### Merged Short Flags
131
+
132
+ Multiple boolean short flags can be combined. The last flag in the group can take a value.
133
+
134
+ ```typescript
135
+ // -abc => a=true, b=true, c=true
136
+ const { flags: merged } = parse(["-abc"], {
137
+ flags: {
138
+ a: Boolean,
139
+ b: Boolean,
140
+ c: Boolean,
141
+ },
142
+ });
143
+ // merged: { a: true, b: true, c: true }
144
+
145
+ // -ab val => a=true, b="val"
146
+ const { flags: withValue } = parse(["-ab", "val"], {
147
+ flags: {
148
+ a: Boolean,
149
+ b: String,
150
+ },
151
+ });
152
+ // or -abval, b is not boolean
153
+ const { flags: withValue } = parse(["-abval"], {
154
+ flags: {
155
+ a: Boolean,
156
+ b: String,
157
+ },
158
+ });
159
+ // withValue: { a: true, b: "val" }
160
+ ```
161
+
162
+ ### Default Values
163
+
164
+ Provide a `default` value for flags that are not present.
165
+
166
+ ```typescript
167
+ const { flags } = parse([], {
168
+ flags: {
169
+ str: { type: String, default: "default" },
170
+ num: { type: Number, default: 123 },
171
+ fn: { type: String, default: () => "computed" },
172
+ },
173
+ });
174
+ // flags: { str: "default", num: 123, fn: "computed" }
175
+ ```
176
+
177
+ By default, booleans, counters, arrays and objects have implicit defaults when `default` is not provided:
178
+
179
+ - Boolean: `false`
180
+ - Counter: `0`
181
+ - Array: `[]`
182
+ - Object: `{}`
183
+
184
+ ### Negatable Flags
185
+
186
+ Boolean flags are negatable by default using the `--no-` prefix.
187
+
188
+ If you want to disable this behavior, set `negatable: false` in the flag configuration. Passing `--no-<flag>` will then be treated as an unknown flag.
189
+
190
+ ```typescript
191
+ const { flags, unknown } = parse(
192
+ ["--no-cache", "--no-ssl=false", "--no-disable-negate"],
193
+ {
194
+ flags: {
195
+ cache: { type: Boolean, default: true },
196
+ ssl: { type: Boolean, default: true },
197
+ disableNegate: { type: Boolean, negatable: false, default: true },
198
+ },
199
+ },
200
+ );
201
+ // flags: { cache: false, ssl: true, disableNegate: true }
202
+ // unknown: { noDisableNegate: true }
203
+ ```
204
+
205
+ ### Custom Type Functions
206
+
207
+ You can provide a custom function to the `type` property for advanced parsing logic. The function receives the string value and should return the parsed value.
208
+
209
+ ```typescript
210
+ // Let's limit port between 1 and 65535
211
+ const { flags } = parse(["--port", "8080"], {
212
+ flags: {
213
+ port: {
214
+ type: (value: string) => {
215
+ const parsed = Number.parseInt(value, 10);
216
+ if (Number.isNaN(parsed)) {
217
+ throw new TypeError("Port must be a number!");
218
+ }
219
+ if (parsed < 1 || parsed > 65_535) {
220
+ throw new Error("Port must be between 1 and 65535!");
221
+ }
222
+
223
+ return parsed;
224
+ },
225
+ },
226
+ },
227
+ });
228
+ // flags: { port: 8080 }
229
+ ```
230
+
231
+ ### Dot-Nested Objects
232
+
233
+ Define flags with `Object` type to parse dot-notation arguments into a nested object.
234
+
235
+ ```typescript
236
+ const { flags } = parse(
237
+ ["--config.port", "8080", "--config.host", "localhost"],
238
+ {
239
+ flags: {
240
+ config: { type: Object },
241
+ },
242
+ },
243
+ );
244
+ // flags: { config: { port: "8080", host: "localhost" } }
245
+ ```
246
+
247
+ Multi-level nesting is also supported.
248
+
249
+ ```typescript
250
+ const { flags } = parse(
251
+ [
252
+ "--db.host",
253
+ "localhost",
254
+ "--db.port",
255
+ "5432",
256
+ "--db.credentials.user",
257
+ "admin",
258
+ "--db.credentials.password",
259
+ "secret",
260
+ ],
261
+ {
262
+ flags: {
263
+ db: { type: Object },
264
+ },
265
+ },
266
+ );
267
+ // flags: { db: { host: "localhost", port: "5432", credentials: { user: "admin", password: "secret" } } }
268
+ ```
269
+
270
+ ### Unknown Flags
271
+
272
+ Flags that are not defined in the schema are collected in the `unknown` object. If possible, they will be converted to boolean `true`.
273
+
274
+ ```typescript
275
+ const { flags, unknown } = parse([
276
+ "--unknown1",
277
+ "--unknown2=foo",
278
+ "--unknown3",
279
+ "bar",
280
+ "--unknown.foo",
281
+ ]);
282
+
283
+ // unknown: { unknown1: true, unknown2: "foo", unknown3: "bar", "unknown.foo": true }
284
+ ```
285
+
286
+ ### Stop Parsing
287
+
288
+ Use the `ignore` function to stop parsing when a certain condition is met. Subsequent arguments will be added to the `ignored` array.
289
+
290
+ ```typescript
291
+ const { flags, ignored } = parse(["--a", "--b", "stop", "--c"], {
292
+ flags: {
293
+ a: Boolean,
294
+ b: Boolean,
295
+ c: Boolean,
296
+ },
297
+ ignore: (type, arg) => arg === "stop",
298
+ });
299
+ // flags: { a: true, b: true, c: false }
300
+ // ignored: ["stop", "--c"]
301
+ ```
302
+
303
+ You can ignore everything after the first positional parameter by checking the `type`.
304
+
305
+ ```typescript
306
+ const { flags, parameters, ignored } = parse(
307
+ ["--allow-all", "./deno.ts", "--param"],
308
+ {
309
+ flags: {
310
+ allowAll: Boolean,
311
+ },
312
+ ignore: (type) => type === "parameter",
313
+ },
314
+ );
315
+
316
+ // flags: { allowAll: true }
317
+ // parameters: []
318
+ // ignored: ["./deno.ts", "--param"]
319
+ ```
320
+
321
+ ### Double Dash (`--`)
322
+
323
+ Arguments after `--` are not parsed as flags and are collected in the `doubleDash` array.
324
+
325
+ ```typescript
326
+ const { flags, doubleDash } = parse(["--foo", "--", "--bar"], {
327
+ flags: {
328
+ foo: Boolean,
329
+ bar: Boolean,
330
+ },
331
+ });
332
+ // flags: { foo: true, bar: false }
333
+ // doubleDash: ["--bar"]
334
+ ```
@@ -0,0 +1,136 @@
1
+ //#region src/errors.d.ts
2
+ declare class InvalidSchemaError extends Error {
3
+ constructor(message: string);
4
+ }
5
+ //#endregion
6
+ //#region ../utils/src/types/type-fest.d.ts
7
+ type Prettify<T> = { [K in keyof T]: T[K] } & {};
8
+ //#endregion
9
+ //#region ../utils/src/types/index.d.ts
10
+ type MaybeArray<T> = T | T[];
11
+ //#endregion
12
+ //#region src/types.d.ts
13
+ type FlagDefaultValue<T = unknown> = T | (() => T);
14
+ /**
15
+ * Defines how a string input is converted to the target type T.
16
+ *
17
+ * @template T The target type.
18
+ */
19
+ type FlagTypeFunction<T = unknown> = (value: string) => T;
20
+ /**
21
+ * A callback function to conditionally stop parsing.
22
+ * When it returns true, parsing stops and remaining arguments are preserved in `ignored`.
23
+ *
24
+ * @param type - The type of the current argument: 'known-flag' or 'unknown-flag' for flags, 'parameter' for positional arguments
25
+ * @param arg - The current argument being processed
26
+ * @returns true to stop parsing, false to continue
27
+ */
28
+ type IgnoreFunction = (type: typeof KNOWN_FLAG | typeof UNKNOWN_FLAG | typeof PARAMETER, arg: string) => boolean;
29
+ type FlagType<T = unknown> = FlagTypeFunction<T> | readonly [FlagTypeFunction<T>];
30
+ interface BaseFlagOptions<T extends FlagType = FlagType> {
31
+ /**
32
+ * The type constructor or a function to convert the string value.
33
+ * To support multiple occurrences of a flag (e.g., --file a --file b), wrap the type in an array: [String], [Number].
34
+ * e.g., String, Number, [String], (val) => val.split(',')
35
+ */
36
+ type: T;
37
+ /** Aliases for the flag. */
38
+ alias?: MaybeArray<string>;
39
+ /** The default value of the flag. */
40
+ default?: unknown;
41
+ }
42
+ type FlagOptions = (BaseFlagOptions<BooleanConstructor> & {
43
+ /**
44
+ * Whether to enable the `--no-<flag>` syntax to set the value to false.
45
+ * Only useful for boolean flags.
46
+ * When set on a non-boolean flag, a type error will be shown.
47
+ *
48
+ * @default true
49
+ */
50
+ negatable?: boolean;
51
+ }) | (BaseFlagOptions & {
52
+ negatable?: never;
53
+ });
54
+ type FlagDefinitionValue = FlagOptions | FlagType;
55
+ type FlagsDefinition = Record<string, FlagDefinitionValue>;
56
+ /**
57
+ * Configuration options for the parser.
58
+ */
59
+ interface ParserOptions<T extends FlagsDefinition = {}> {
60
+ /**
61
+ * Detailed configuration for flags.
62
+ * Supports the full object syntax or a type constructor as a shorthand.
63
+ * The key is the flag name (e.g., "file" for "--file").
64
+ */
65
+ flags?: T;
66
+ /**
67
+ * Delimiters to split flag names and values.
68
+ *
69
+ * @default ['=', ':']
70
+ */
71
+ delimiters?: string[];
72
+ /**
73
+ * A callback function to conditionally stop parsing.
74
+ * When it returns true, parsing stops and remaining arguments are preserved in `ignored`.
75
+ */
76
+ ignore?: IgnoreFunction;
77
+ }
78
+ type RawInputType = string | boolean;
79
+ interface ObjectInputType {
80
+ [key: string]: RawInputType | ObjectInputType;
81
+ }
82
+ /**
83
+ * The parsed result.
84
+ * @template TFlags The specific flags type inferred from ParserOptions.
85
+ */
86
+ interface ParsedResult<TFlags extends Record<string, any>> {
87
+ /** Positional arguments or commands. */
88
+ parameters: string[];
89
+ /** Arguments after the `--` delimiter. */
90
+ doubleDash: string[];
91
+ /**
92
+ * The parsed flags.
93
+ * This is a strongly-typed object whose structure is inferred from the `flags` configuration in ParserOptions.
94
+ */
95
+ flags: TFlags;
96
+ /** The raw command-line arguments. */
97
+ raw: string[];
98
+ /** Unknown flags encountered during parsing. */
99
+ unknown: Record<string, RawInputType>;
100
+ /** Arguments that were not parsed due to ignore callback. */
101
+ ignored: string[];
102
+ }
103
+ type InferFlagDefault<T extends FlagDefinitionValue, Fallback> = T extends {
104
+ default: FlagDefaultValue<infer DefaultType>;
105
+ } ? DefaultType : Fallback;
106
+ type _InferFlags<T extends FlagsDefinition> = { [K in keyof T]: T[K] extends readonly [BooleanConstructor] | {
107
+ type: readonly [BooleanConstructor];
108
+ } ? number : T[K] extends ObjectConstructor | {
109
+ type: ObjectConstructor;
110
+ } ? ObjectInputType : T[K] extends readonly [FlagType<infer U>] | {
111
+ type: readonly [FlagType<infer U>];
112
+ } ? U[] | InferFlagDefault<T[K], never> : T[K] extends FlagType<infer U> | {
113
+ type: FlagType<infer U>;
114
+ } ? U | InferFlagDefault<T[K], [U] extends [boolean] ? never : undefined> : never };
115
+ /**
116
+ * An advanced utility type that infers the exact type of the `flags` object in the parsed result,
117
+ * based on the provided `flags` configuration object T.
118
+ *
119
+ * @template T The type of the flags configuration object.
120
+ */
121
+ type InferFlags<T extends FlagsDefinition> = Prettify<_InferFlags<T>>;
122
+ //#endregion
123
+ //#region src/iterator.d.ts
124
+ declare const KNOWN_FLAG = "known-flag";
125
+ declare const UNKNOWN_FLAG = "unknown-flag";
126
+ declare const PARAMETER = "parameter";
127
+ //#endregion
128
+ //#region src/parse.d.ts
129
+ declare const DOUBLE_DASH = "--";
130
+ type ParseFunction<T extends FlagsDefinition> = (args: string[]) => ParsedResult<InferFlags<T>>;
131
+ declare function createParser<T extends FlagsDefinition>(options?: ParserOptions<T>): {
132
+ parse: ParseFunction<T>;
133
+ };
134
+ declare const parse: <T extends FlagsDefinition>(args: string[], options?: ParserOptions<T>) => ParsedResult<InferFlags<T>>;
135
+ //#endregion
136
+ export { BaseFlagOptions, DOUBLE_DASH, FlagDefaultValue, FlagDefinitionValue, FlagOptions, FlagType, FlagTypeFunction, FlagsDefinition, IgnoreFunction, InferFlags, InvalidSchemaError, KNOWN_FLAG, ObjectInputType, PARAMETER, ParsedResult, ParserOptions, RawInputType, UNKNOWN_FLAG, createParser, parse };
package/dist/index.js ADDED
@@ -0,0 +1,319 @@
1
+ //#region src/errors.ts
2
+ var InvalidSchemaError = class extends Error {
3
+ constructor(message) {
4
+ super(`Invalid schema: ${message}`);
5
+ this.name = "InvalidSchemaError";
6
+ }
7
+ };
8
+
9
+ //#endregion
10
+ //#region src/iterator.ts
11
+ const KNOWN_FLAG = "known-flag";
12
+ const UNKNOWN_FLAG = "unknown-flag";
13
+ const PARAMETER = "parameter";
14
+ function iterateArgs(args, result, shouldProcessAsFlag, isKnownFlag, ignore, callback) {
15
+ let index = 0;
16
+ let stopped = false;
17
+ const argsLength = args.length;
18
+ const iterator = {
19
+ current: "",
20
+ index: 0,
21
+ hasNext: false,
22
+ next: "",
23
+ shouldIgnore: (arg) => {
24
+ if (ignore) return ignore(shouldProcessAsFlag(arg) ? isKnownFlag(arg) ? KNOWN_FLAG : UNKNOWN_FLAG : PARAMETER, arg);
25
+ return false;
26
+ },
27
+ eat: () => {
28
+ if (index + 1 >= argsLength) return;
29
+ const nextArg = args[index + 1];
30
+ if (iterator.shouldIgnore(nextArg)) {
31
+ iterator.exit();
32
+ return;
33
+ }
34
+ index++;
35
+ next();
36
+ return args[index];
37
+ },
38
+ exit: (push = true) => {
39
+ if (!stopped) {
40
+ if (push) result.ignored.push(...args.slice(index + 1));
41
+ stopped = true;
42
+ }
43
+ }
44
+ };
45
+ function next() {
46
+ iterator.current = args[index];
47
+ iterator.index = index;
48
+ iterator.hasNext = index + 1 < argsLength;
49
+ iterator.next = iterator.hasNext ? args[index + 1] : "";
50
+ }
51
+ for (index = 0; index < argsLength; index++) {
52
+ if (stopped) break;
53
+ next();
54
+ callback(iterator);
55
+ }
56
+ }
57
+
58
+ //#endregion
59
+ //#region ../utils/dist/index.js
60
+ const toArray = (a) => Array.isArray(a) ? a : [a];
61
+ /**
62
+ * Converts a dash-separated string to camelCase.
63
+ * Not using regexp for better performance, because this function is used in parser.
64
+ */
65
+ function camelCase(str) {
66
+ const dashIdx = str.indexOf("-");
67
+ if (dashIdx === -1) return str;
68
+ let result = str.slice(0, dashIdx);
69
+ for (let i = dashIdx; i < str.length; i++) if (str[i] === "-" && i + 1 < str.length) {
70
+ const nextChar = str.charCodeAt(i + 1);
71
+ if (nextChar >= 97 && nextChar <= 122) {
72
+ result += String.fromCharCode(nextChar - 32);
73
+ i++;
74
+ } else {
75
+ result += str[i + 1];
76
+ i++;
77
+ }
78
+ } else if (str[i] !== "-") result += str[i];
79
+ return result;
80
+ }
81
+
82
+ //#endregion
83
+ //#region src/utils.ts
84
+ const looseIsArray = (arr) => Array.isArray(arr);
85
+ const isArrayOfType = (arr, type) => Array.isArray(arr) && arr[0] === type;
86
+ /**
87
+ * Check if it's a letter (a-z: 97-122, A-Z: 65-90)
88
+ */
89
+ const isLetter = (charCode) => charCode >= 65 && charCode <= 90 || charCode >= 97 && charCode <= 122;
90
+ /**
91
+ * Check if it's a digit (0-9: 48-57)
92
+ */
93
+ const isDigit = (charCode) => charCode >= 48 && charCode <= 57;
94
+ function setValueByType(flags, key, value, config) {
95
+ const { type } = config;
96
+ if (looseIsArray(type)) if (isArrayOfType(type, Boolean)) flags[key] = (flags[key] ?? 0) + 1;
97
+ else (flags[key] ??= []).push(type[0](value));
98
+ else flags[key] = type(value);
99
+ }
100
+ function setDotValues(obj, path, value) {
101
+ const keys = path.split(".");
102
+ let current = obj;
103
+ for (let i = 0; i < keys.length - 1; i++) {
104
+ const key = keys[i];
105
+ current[key] ??= {};
106
+ current = current[key];
107
+ }
108
+ const lastKey = keys[keys.length - 1];
109
+ if (value === "true" || value === "") current[lastKey] = true;
110
+ else if (value === "false") current[lastKey] = false;
111
+ else current[lastKey] = value;
112
+ }
113
+ function splitNameAndValue(arg, delimiters) {
114
+ let sepIdx = -1;
115
+ let delimiterLen = 0;
116
+ for (const delimiter of delimiters) {
117
+ const idx = arg.indexOf(delimiter);
118
+ if (idx !== -1 && (sepIdx === -1 || idx < sepIdx)) {
119
+ sepIdx = idx;
120
+ delimiterLen = delimiter.length;
121
+ }
122
+ }
123
+ if (!(sepIdx !== -1)) return {
124
+ rawName: arg,
125
+ rawValue: void 0,
126
+ hasSep: false
127
+ };
128
+ return {
129
+ rawName: arg.slice(0, sepIdx),
130
+ rawValue: arg.slice(sepIdx + delimiterLen),
131
+ hasSep: true
132
+ };
133
+ }
134
+
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
+ //#endregion
173
+ //#region src/parse.ts
174
+ const DOUBLE_DASH = "--";
175
+ function createParser(options = {}) {
176
+ const { flags: flagsConfig = {}, delimiters, ignore } = resolveParserOptions(options);
177
+ const { configs, aliases } = buildConfigsAndAliases(delimiters, flagsConfig);
178
+ function resolve(name) {
179
+ const dotIdx = name.indexOf(".");
180
+ const rootName = dotIdx === -1 ? name : name.slice(0, dotIdx);
181
+ let key = aliases.get(rootName);
182
+ if (!key) {
183
+ key = aliases.get(camelCase(rootName));
184
+ if (!key) return;
185
+ }
186
+ const config = configs.get(key);
187
+ return {
188
+ key,
189
+ config,
190
+ path: dotIdx === -1 ? void 0 : name.slice(dotIdx + 1)
191
+ };
192
+ }
193
+ function resolveNegated(name) {
194
+ if (!name.startsWith("no")) return;
195
+ const possibleName = name[2] === "-" ? name.slice(3) : name.length > 2 && /[A-Z]/.test(name[2]) ? name[2].toLowerCase() + name.slice(3) : "";
196
+ if (possibleName) {
197
+ const possibleResolved = resolve(possibleName);
198
+ if (possibleResolved?.config.type === Boolean && possibleResolved.config.negatable !== false) return possibleResolved;
199
+ }
200
+ }
201
+ function shouldProcessAsFlag(arg) {
202
+ if (arg.charCodeAt(0) !== 45) return false;
203
+ const len = arg.length;
204
+ if (len < 2) return false;
205
+ const secondChar = arg.charCodeAt(1);
206
+ if (isLetter(secondChar)) return true;
207
+ if (isDigit(secondChar)) return !!resolve(secondChar !== 45 ? arg[1] : arg.slice(2));
208
+ if (secondChar === 45 && len > 2) return isLetter(arg.charCodeAt(2));
209
+ return false;
210
+ }
211
+ function isKnownFlag(arg) {
212
+ const secondChar = arg.charCodeAt(1);
213
+ if (secondChar === 45) {
214
+ const { rawName } = splitNameAndValue(arg.slice(2), delimiters);
215
+ if (resolve(rawName)) return true;
216
+ if (resolveNegated(rawName)) return true;
217
+ return false;
218
+ }
219
+ if (isDigit(secondChar)) return true;
220
+ const chars = arg.slice(1);
221
+ for (const char of chars) if (!resolve(char)) return false;
222
+ return true;
223
+ }
224
+ const parse$1 = (args) => {
225
+ const result = {
226
+ parameters: [],
227
+ doubleDash: [],
228
+ flags: {},
229
+ raw: args,
230
+ unknown: {},
231
+ ignored: []
232
+ };
233
+ iterateArgs(args, result, shouldProcessAsFlag, isKnownFlag, ignore, ({ current, eat, exit, hasNext, index, next, shouldIgnore }) => {
234
+ if (current === DOUBLE_DASH) {
235
+ result.doubleDash.push(...args.slice(index + 1));
236
+ exit(false);
237
+ return;
238
+ }
239
+ if (shouldIgnore(current)) {
240
+ result.ignored.push(current);
241
+ exit();
242
+ return;
243
+ }
244
+ if (!shouldProcessAsFlag(current)) {
245
+ result.parameters.push(current);
246
+ return;
247
+ }
248
+ const isAlias = !current.startsWith(DOUBLE_DASH);
249
+ const chars = current.slice(isAlias ? 1 : 2);
250
+ if (isAlias) {
251
+ const charsLen = chars.length;
252
+ for (let j = 0; j < charsLen; j++) {
253
+ const char = chars[j];
254
+ const resolved = resolve(char);
255
+ if (!resolved) {
256
+ result.unknown[char] = true;
257
+ continue;
258
+ }
259
+ const { key, config } = resolved;
260
+ const configType = config.type;
261
+ if (configType === Boolean || isArrayOfType(configType, Boolean)) setValueByType(result.flags, key, "true", config);
262
+ else if (j + 1 < charsLen) {
263
+ setValueByType(result.flags, key, chars.slice(j + 1), config);
264
+ break;
265
+ } else {
266
+ const nextValue = eat();
267
+ if (nextValue && !shouldProcessAsFlag(nextValue)) setValueByType(result.flags, key, nextValue, config);
268
+ }
269
+ }
270
+ } else {
271
+ const { rawName, rawValue, hasSep } = splitNameAndValue(chars, delimiters);
272
+ let resolved = resolve(rawName);
273
+ let isNegated = false;
274
+ if (!resolved) {
275
+ const negated = resolveNegated(rawName);
276
+ if (negated) {
277
+ resolved = negated;
278
+ isNegated = true;
279
+ }
280
+ }
281
+ if (!resolved) {
282
+ const key$1 = camelCase(rawName);
283
+ if (hasSep) result.unknown[key$1] = rawValue;
284
+ else if (hasNext && !shouldProcessAsFlag(next)) {
285
+ const value = eat();
286
+ result.unknown[key$1] = value ?? true;
287
+ } else result.unknown[key$1] = true;
288
+ return;
289
+ }
290
+ const { key, config, path } = resolved;
291
+ if (path) {
292
+ if (config.type === Object) {
293
+ result.flags[key] ??= {};
294
+ const value = hasSep ? rawValue : hasNext && !shouldProcessAsFlag(next) ? eat() ?? "" : "";
295
+ setDotValues(result.flags[key], path, value);
296
+ }
297
+ } else if (config.type === Boolean) {
298
+ const value = hasSep ? rawValue !== "false" : true;
299
+ result.flags[key] = isNegated ? !value : value;
300
+ } else {
301
+ const value = hasSep ? rawValue : hasNext && !shouldProcessAsFlag(next) ? eat() ?? "" : "";
302
+ setValueByType(result.flags, key, value, config);
303
+ }
304
+ }
305
+ });
306
+ 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;
308
+ else if (Array.isArray(config.type)) result.flags[key] = isArrayOfType(config.type, Boolean) ? 0 : [];
309
+ else if (config.type === Object) result.flags[key] = {};
310
+ else if (config.type === Boolean) result.flags[key] = false;
311
+ }
312
+ return result;
313
+ };
314
+ return { parse: parse$1 };
315
+ }
316
+ const parse = (args, options = {}) => createParser(options).parse(args);
317
+
318
+ //#endregion
319
+ export { DOUBLE_DASH, InvalidSchemaError, KNOWN_FLAG, PARAMETER, UNKNOWN_FLAG, createParser, parse };
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "@clerc/parser",
3
+ "version": "1.0.0-beta.1",
4
+ "author": "Ray <i@mk1.io> (https://github.com/so1ve)",
5
+ "type": "module",
6
+ "description": "Clerc parser",
7
+ "keywords": [
8
+ "args",
9
+ "arguments",
10
+ "argv",
11
+ "clerc",
12
+ "cli",
13
+ "getopt",
14
+ "parser",
15
+ "terminal",
16
+ "utils"
17
+ ],
18
+ "homepage": "https://github.com/clercjs/clerc/tree/main/packages/parser#readme",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/clercjs/clerc.git",
22
+ "directory": "/"
23
+ },
24
+ "bugs": {
25
+ "url": "https://github.com/clercjs/clerc/issues"
26
+ },
27
+ "license": "MIT",
28
+ "sideEffects": false,
29
+ "exports": {
30
+ ".": "./dist/index.js"
31
+ },
32
+ "main": "./dist/index.js",
33
+ "module": "./dist/index.js",
34
+ "types": "dist/index.d.ts",
35
+ "typesVersions": {
36
+ "*": {
37
+ "*": [
38
+ "./dist/*",
39
+ "./dist/index.d.ts"
40
+ ]
41
+ }
42
+ },
43
+ "files": [
44
+ "dist"
45
+ ],
46
+ "publishConfig": {
47
+ "access": "public"
48
+ },
49
+ "devDependencies": {
50
+ "@types/minimist": "^1.2.5",
51
+ "@types/nopt": "^3.0.32",
52
+ "@types/yargs-parser": "^21.0.3",
53
+ "minimist": "^1.2.8",
54
+ "mri": "^1.2.0",
55
+ "nopt": "^9.0.0",
56
+ "type-flag": "^4.0.3",
57
+ "yargs-parser": "^22.0.0",
58
+ "@clerc/utils": "1.0.0-beta.1"
59
+ },
60
+ "scripts": {
61
+ "build": "tsdown",
62
+ "watch": "tsdown --watch"
63
+ }
64
+ }