@clerc/parser 1.1.0 → 1.2.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/dist/index.d.mts CHANGED
@@ -172,4 +172,49 @@ declare function createParser<T extends FlagsDefinition>(options?: ParserOptions
172
172
  };
173
173
  declare const parse: <T extends FlagsDefinition>(args: string[], options?: ParserOptions<T>) => ParsedResult<InferFlags<T>>;
174
174
  //#endregion
175
- 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 };
175
+ //#region src/utils.d.ts
176
+ /**
177
+ * Infers the implicit default value for a flag type based on its type
178
+ * constructor. This is useful for help output to show the default values of
179
+ * types that have built-in defaults.
180
+ *
181
+ * - Boolean: false
182
+ * - [Boolean] (Counter): 0
183
+ * - [T] (Array): []
184
+ * - Object: {}
185
+ * - Others: undefined (no implicit default)
186
+ *
187
+ * @param type The type value (constructor or array of constructor)
188
+ * @returns The inferred default value, or undefined if no implicit default
189
+ */
190
+ declare function inferDefault(type: TypeValue): unknown;
191
+ /**
192
+ * Default value coercion for Object type. Converts "true" / "" to true, "false"
193
+ * to false, other values remain unchanged.
194
+ *
195
+ * @param value The raw string value from CLI
196
+ * @returns Coerced value (boolean or string)
197
+ */
198
+ declare function coerceObjectValue(value: string): string | boolean;
199
+ /**
200
+ * Sets a value at a nested path in an object, creating intermediate objects as
201
+ * needed. Does NOT apply type conversion - value is set as-is. Overwrites
202
+ * existing values.
203
+ *
204
+ * @param obj The target object
205
+ * @param path Dot-separated path (e.g., "foo.bar.baz")
206
+ * @param value The value to set (used as-is, no type conversion)
207
+ */
208
+ declare function setDotValues(obj: any, path: string, value: any): void;
209
+ /**
210
+ * Similar to setDotValues but handles duplicate keys by converting to arrays.
211
+ * Does NOT apply type conversion - value is set as-is. Useful for flags that
212
+ * can be specified multiple times.
213
+ *
214
+ * @param obj The target object
215
+ * @param path Dot-separated path (e.g., "foo.bar")
216
+ * @param value The value to set or append (used as-is, no type conversion)
217
+ */
218
+ declare function appendDotValues(obj: any, path: string, value: any): void;
219
+ //#endregion
220
+ export { BaseFlagOptions, DOUBLE_DASH, FlagDefaultValue, FlagDefaultValueFunction, FlagDefinitionValue, FlagOptions, FlagsDefinition, IgnoreFunction, InferFlags, InvalidSchemaError, KNOWN_FLAG, ObjectInputType, PARAMETER, ParsedResult, ParserOptions, RawInputType, TypeFunction, TypeValue, UNKNOWN_FLAG, appendDotValues, coerceObjectValue, createParser, inferDefault, parse, setDotValues };
package/dist/index.mjs CHANGED
@@ -1,302 +1 @@
1
- import { camelCase, looseIsArray, resolveValue } from "@clerc/utils";
2
-
3
- //#region src/errors.ts
4
- var InvalidSchemaError = class extends Error {
5
- constructor(message) {
6
- super(`Invalid schema: ${message}`);
7
- this.name = "InvalidSchemaError";
8
- }
9
- };
10
-
11
- //#endregion
12
- //#region src/iterator.ts
13
- const KNOWN_FLAG = "known-flag";
14
- const UNKNOWN_FLAG = "unknown-flag";
15
- const PARAMETER = "parameter";
16
- function iterateArgs(args, result, shouldProcessAsFlag, isKnownFlag, ignore, callback) {
17
- let index = 0;
18
- let stopped = false;
19
- const argsLength = args.length;
20
- const iterator = {
21
- current: "",
22
- index: 0,
23
- hasNext: false,
24
- next: "",
25
- shouldIgnore: (arg) => {
26
- if (ignore) return ignore(shouldProcessAsFlag(arg) ? isKnownFlag(arg) ? KNOWN_FLAG : UNKNOWN_FLAG : PARAMETER, arg);
27
- return false;
28
- },
29
- eat: () => {
30
- if (index + 1 >= argsLength) return;
31
- const nextArg = args[index + 1];
32
- if (iterator.shouldIgnore(nextArg)) {
33
- iterator.exit();
34
- return;
35
- }
36
- index++;
37
- next();
38
- return args[index];
39
- },
40
- exit: (push = true) => {
41
- if (!stopped) {
42
- if (push) result.ignored.push(...args.slice(index + 1));
43
- stopped = true;
44
- }
45
- }
46
- };
47
- function next() {
48
- iterator.current = args[index];
49
- iterator.index = index;
50
- iterator.hasNext = index + 1 < argsLength;
51
- iterator.next = iterator.hasNext ? args[index + 1] : "";
52
- }
53
- for (index = 0; index < argsLength; index++) {
54
- if (stopped) break;
55
- next();
56
- callback(iterator);
57
- }
58
- }
59
-
60
- //#endregion
61
- //#region src/config.ts
62
- const defaultParserOptions = { delimiters: ["=", ":"] };
63
- const resolveParserOptions = (options = {}) => ({
64
- ...defaultParserOptions,
65
- ...options
66
- });
67
- const normalizeConfig = (config) => typeof config === "function" || looseIsArray(config) ? { type: config } : config;
68
- const BUILDTIN_DELIMITERS_RE = /[\s.]/;
69
- function buildConfigsAndAliases(delimiters, flags) {
70
- const configs = /* @__PURE__ */ new Map();
71
- const aliases = /* @__PURE__ */ new Map();
72
- const isNameInvalid = (name) => delimiters.some((char) => name.includes(char)) || BUILDTIN_DELIMITERS_RE.test(name);
73
- function validateFlagOptions(name, options) {
74
- const prefix = `Flag "${name}"`;
75
- 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.`);
76
- if (name.length < 2) throw new InvalidSchemaError(`${prefix} name must be at least 2 characters long.`);
77
- const names = [name];
78
- if (options.short) {
79
- if (options.short.length !== 1) throw new InvalidSchemaError(`${prefix} short flag must be exactly 1 character long.`);
80
- names.push(options.short);
81
- }
82
- if (names.some(isNameInvalid)) throw new InvalidSchemaError(`${prefix} contains reserved characters, which are used as delimiters.`);
83
- if (options.required && options.default !== void 0) throw new InvalidSchemaError(`${prefix} cannot be both required and have a default value.`);
84
- }
85
- for (const [name, config] of Object.entries(flags)) {
86
- const normalized = normalizeConfig(config);
87
- validateFlagOptions(name, normalized);
88
- configs.set(name, normalized);
89
- aliases.set(name, name);
90
- aliases.set(camelCase(name), name);
91
- if (normalized.short) aliases.set(normalized.short, name);
92
- }
93
- return {
94
- configs,
95
- aliases
96
- };
97
- }
98
-
99
- //#endregion
100
- //#region src/utils.ts
101
- const isArrayOfType = (arr, type) => Array.isArray(arr) && arr[0] === type;
102
- /**
103
- * Check if it's a letter (a-z: 97-122, A-Z: 65-90)
104
- */
105
- const isLetter = (charCode) => charCode >= 65 && charCode <= 90 || charCode >= 97 && charCode <= 122;
106
- /**
107
- * Check if it's a digit (0-9: 48-57)
108
- */
109
- const isDigit = (charCode) => charCode >= 48 && charCode <= 57;
110
- function setValueByType(flags, key, value, config) {
111
- const { type } = config;
112
- if (looseIsArray(type)) if (isArrayOfType(type, Boolean)) flags[key] = (flags[key] ?? 0) + 1;
113
- else (flags[key] ??= []).push(type[0](value));
114
- else flags[key] = type(value);
115
- }
116
- function setDotValues(obj, path, value) {
117
- const keys = path.split(".");
118
- let current = obj;
119
- for (let i = 0; i < keys.length - 1; i++) {
120
- const key = keys[i];
121
- current[key] ??= {};
122
- current = current[key];
123
- }
124
- const lastKey = keys[keys.length - 1];
125
- if (value === "true" || value === "") current[lastKey] = true;
126
- else if (value === "false") current[lastKey] = false;
127
- else current[lastKey] = value;
128
- }
129
- function splitNameAndValue(arg, delimiters) {
130
- let sepIdx = -1;
131
- let delimiterLen = 0;
132
- for (const delimiter of delimiters) {
133
- const idx = arg.indexOf(delimiter);
134
- if (idx !== -1 && (sepIdx === -1 || idx < sepIdx)) {
135
- sepIdx = idx;
136
- delimiterLen = delimiter.length;
137
- }
138
- }
139
- if (!(sepIdx !== -1)) return {
140
- rawName: arg,
141
- rawValue: void 0,
142
- hasSep: false
143
- };
144
- return {
145
- rawName: arg.slice(0, sepIdx),
146
- rawValue: arg.slice(sepIdx + delimiterLen),
147
- hasSep: true
148
- };
149
- }
150
-
151
- //#endregion
152
- //#region src/parse.ts
153
- const DOUBLE_DASH = "--";
154
- function createParser(options = {}) {
155
- const { flags: flagsConfig = {}, delimiters, ignore } = resolveParserOptions(options);
156
- const { configs, aliases } = buildConfigsAndAliases(delimiters, flagsConfig);
157
- function resolve(name, isShortFlag = false) {
158
- const dotIdx = name.indexOf(".");
159
- const rootName = dotIdx === -1 ? name : name.slice(0, dotIdx);
160
- let key = aliases.get(rootName);
161
- if (!key) {
162
- key = aliases.get(camelCase(rootName));
163
- if (!key) return;
164
- }
165
- const config = configs.get(key);
166
- if (!isShortFlag && config.short === rootName) return;
167
- return {
168
- key,
169
- config,
170
- path: dotIdx === -1 ? void 0 : name.slice(dotIdx + 1)
171
- };
172
- }
173
- function resolveNegated(name) {
174
- if (!name.startsWith("no")) return;
175
- const possibleName = name[2] === "-" ? name.slice(3) : name.length > 2 && /[A-Z]/.test(name[2]) ? name[2].toLowerCase() + name.slice(3) : "";
176
- if (possibleName) {
177
- const possibleResolved = resolve(possibleName);
178
- if (possibleResolved?.config.type === Boolean && possibleResolved.config.negatable !== false) return possibleResolved;
179
- }
180
- }
181
- function shouldProcessAsFlag(arg) {
182
- if (arg.charCodeAt(0) !== 45) return false;
183
- const len = arg.length;
184
- if (len < 2) return false;
185
- const secondChar = arg.charCodeAt(1);
186
- if (isLetter(secondChar)) return true;
187
- if (isDigit(secondChar)) {
188
- const isShortFlag = secondChar !== 45;
189
- return !!resolve(isShortFlag ? arg[1] : arg.slice(2), isShortFlag);
190
- }
191
- if (secondChar === 45 && len > 2) return isLetter(arg.charCodeAt(2));
192
- return false;
193
- }
194
- function isKnownFlag(arg) {
195
- if (arg.charCodeAt(1) === 45) {
196
- const { rawName } = splitNameAndValue(arg.slice(2), delimiters);
197
- if (resolve(rawName, false)) return true;
198
- if (resolveNegated(rawName)) return true;
199
- return false;
200
- }
201
- const chars = arg.slice(1);
202
- for (const char of chars) if (!resolve(char, true)) return false;
203
- return true;
204
- }
205
- const parse$1 = (args) => {
206
- const result = {
207
- parameters: [],
208
- doubleDash: [],
209
- flags: {},
210
- raw: args,
211
- unknown: {},
212
- ignored: [],
213
- missingRequiredFlags: []
214
- };
215
- iterateArgs(args, result, shouldProcessAsFlag, isKnownFlag, ignore, ({ current, eat, exit, hasNext, index, next, shouldIgnore }) => {
216
- if (current === DOUBLE_DASH) {
217
- result.doubleDash.push(...args.slice(index + 1));
218
- exit(false);
219
- return;
220
- }
221
- if (shouldIgnore(current)) {
222
- result.ignored.push(current);
223
- exit();
224
- return;
225
- }
226
- if (!shouldProcessAsFlag(current)) {
227
- result.parameters.push(current);
228
- return;
229
- }
230
- const isShortFlag = !current.startsWith(DOUBLE_DASH);
231
- const chars = current.slice(isShortFlag ? 1 : 2);
232
- if (isShortFlag) {
233
- const charsLen = chars.length;
234
- for (let j = 0; j < charsLen; j++) {
235
- const char = chars[j];
236
- const resolved = resolve(char, true);
237
- if (!resolved) {
238
- result.unknown[char] = true;
239
- continue;
240
- }
241
- const { key, config } = resolved;
242
- const configType = config.type;
243
- if (configType === Boolean || isArrayOfType(configType, Boolean)) setValueByType(result.flags, key, "true", config);
244
- else if (j + 1 < charsLen) {
245
- setValueByType(result.flags, key, chars.slice(j + 1), config);
246
- break;
247
- } else {
248
- const value = hasNext && !shouldProcessAsFlag(next) ? eat() ?? "" : "";
249
- setValueByType(result.flags, key, value, config);
250
- }
251
- }
252
- } else {
253
- const { rawName, rawValue, hasSep } = splitNameAndValue(chars, delimiters);
254
- let resolved = resolve(rawName);
255
- let isNegated = false;
256
- if (!resolved) {
257
- const negated = resolveNegated(rawName);
258
- if (negated) {
259
- resolved = negated;
260
- isNegated = true;
261
- }
262
- }
263
- if (!resolved) {
264
- const key$1 = camelCase(rawName);
265
- if (hasSep) result.unknown[key$1] = rawValue;
266
- else if (hasNext && !shouldProcessAsFlag(next)) {
267
- const value = eat();
268
- result.unknown[key$1] = value ?? true;
269
- } else result.unknown[key$1] = true;
270
- return;
271
- }
272
- const { key, config, path } = resolved;
273
- if (path) {
274
- if (config.type === Object) {
275
- result.flags[key] ??= {};
276
- const value = hasSep ? rawValue : hasNext && !shouldProcessAsFlag(next) ? eat() ?? "" : "";
277
- setDotValues(result.flags[key], path, value);
278
- }
279
- } else if (config.type === Boolean) {
280
- const value = hasSep ? rawValue !== "false" : true;
281
- result.flags[key] = isNegated ? !value : value;
282
- } else {
283
- const value = hasSep ? rawValue : hasNext && !shouldProcessAsFlag(next) ? eat() ?? "" : "";
284
- setValueByType(result.flags, key, value, config);
285
- }
286
- }
287
- });
288
- for (const [key, config] of configs.entries()) if (result.flags[key] === void 0) {
289
- if (config.default !== void 0) result.flags[key] = resolveValue(config.default);
290
- else if (Array.isArray(config.type)) result.flags[key] = isArrayOfType(config.type, Boolean) ? 0 : [];
291
- else if (config.type === Object) result.flags[key] = {};
292
- else if (config.type === Boolean) result.flags[key] = false;
293
- else if (config.required) result.missingRequiredFlags.push(key);
294
- }
295
- return result;
296
- };
297
- return { parse: parse$1 };
298
- }
299
- const parse = (args, options = {}) => createParser(options).parse(args);
300
-
301
- //#endregion
302
- export { DOUBLE_DASH, InvalidSchemaError, KNOWN_FLAG, PARAMETER, UNKNOWN_FLAG, createParser, parse };
1
+ import{camelCase as e,hasOwn as t,looseIsArray as n,resolveValue as r}from"@clerc/utils";var i=class extends Error{constructor(e){super(`Invalid schema: ${e}`),this.name=`InvalidSchemaError`}};const a=`known-flag`,o=`unknown-flag`,s=`parameter`;function c(e,t,n,r,i,c){let l=0,u=!1,d=e.length,f={current:``,index:0,hasNext:!1,next:``,shouldIgnore:e=>i?i(n(e)?r(e)?a:o:s,e):!1,eat:()=>{if(l+1>=d)return;let t=e[l+1];if(f.shouldIgnore(t)){f.exit();return}return l++,p(),e[l]},exit:(n=!0)=>{u||=(n&&t.ignored.push(...e.slice(l+1)),!0)}};function p(){f.current=e[l],f.index=l,f.hasNext=l+1<d,f.next=f.hasNext?e[l+1]:``}for(l=0;l<d&&!u;l++)p(),c(f)}const l={delimiters:[`=`,`:`]},u=(e={})=>({...l,...e}),d=e=>typeof e==`function`||n(e)?{type:e}:e;function f(t,n){let r=new Map,a=new Map,o=e=>t.some(t=>e.includes(t))||e.includes(` `)||e.includes(`.`);function s(e,t){let n=`Flag "${e}"`;if(Array.isArray(t.type)&&t.type.length>1)throw new i(`${n} has an invalid type array. Only single-element arrays are allowed to denote multiple occurrences.`);if(e.length<2)throw new i(`${n} name must be at least 2 characters long.`);let r=[e];if(t.short){if(t.short.length!==1)throw new i(`${n} short flag must be exactly 1 character long.`);r.push(t.short)}if(r.some(o))throw new i(`${n} contains reserved characters, which are used as delimiters.`);if(t.required&&t.default!==void 0)throw new i(`${n} cannot be both required and have a default value.`)}for(let[t,i]of Object.entries(n)){let n=d(i);s(t,n),r.set(t,n),a.set(t,t),a.set(e(t),t),n.short&&a.set(n.short,t)}return{configs:r,aliases:a}}const p=(e,t)=>Array.isArray(e)&&e[0]===t;function m(e){if(n(e))return p(e,Boolean)?0:[];if(e===Boolean)return!1;if(e===Object)return{}}const h=e=>e>=65&&e<=90||e>=97&&e<=122,g=e=>e>=48&&e<=57;function _(e,t,r,i){let{type:a}=i;n(a)?p(a,Boolean)?e[t]=(e[t]??0)+1:(e[t]??=[]).push(a[0](r)):e[t]=a(r)}function v(e){return e===`true`||e===``?!0:e===`false`?!1:e}function y(e,t,n){let r=t.split(`.`),i=e;for(let e=0;e<r.length-1;e++){let t=r[e];i[t]??={},i=i[t]}let a=r[r.length-1];i[a]=n}function b(e,n,r){let i=n.split(`.`),a=e;for(let e=0;e<i.length-1;e++){let n=i[e];if(t(a,n)&&(typeof a[n]!=`object`||a[n]===null))return;a[n]??={},a=a[n]}let o=i[i.length-1];if(t(a,o)){let e=a[o];Array.isArray(e)?e.push(r):a[o]=[e,r]}else a[o]=r}function x(e,t){let n=-1,r=0;for(let i of t){let t=e.indexOf(i);t!==-1&&(n===-1||t<n)&&(n=t,r=i.length)}return n===-1?{rawName:e,rawValue:void 0,hasSep:!1}:{rawName:e.slice(0,n),rawValue:e.slice(n+r),hasSep:!0}}const S=`--`;function C(t={}){let{flags:n={},delimiters:i,ignore:a}=u(t),{configs:o,aliases:s}=f(i,n);function l(t,n=!1){let r=t.indexOf(`.`),i=r===-1?t:t.slice(0,r),a=s.get(i);if(!a&&(a=s.get(e(i)),!a))return;let c=o.get(a);if(!(!n&&c.short===i))return{key:a,config:c,path:r===-1?void 0:t.slice(r+1)}}function d(e){if(!e.startsWith(`no`))return;let t=e[2]===`-`?e.slice(3):e.length>2&&e.charCodeAt(2)>=65&&e.charCodeAt(2)<=90?e[2].toLowerCase()+e.slice(3):``;if(t){let e=l(t);if(e?.config.type===Boolean&&e.config.negatable!==!1)return e}}function y(e){if(e.charCodeAt(0)!==45)return!1;let t=e.length;if(t<2)return!1;let n=e.charCodeAt(1);if(h(n))return!0;if(g(n)){let t=n!==45;return!!l(t?e[1]:e.slice(2),t)}return n===45&&t>2?h(e.charCodeAt(2)):!1}function S(e){if(e.charCodeAt(1)===45){let{rawName:t}=x(e.slice(2),i);return!!(l(t,!1)||d(t))}let t=e.slice(1);for(let e of t)if(!l(e,!0))return!1;return!0}return{parse:t=>{let n={parameters:[],doubleDash:[],flags:{},raw:t,unknown:{},ignored:[],missingRequiredFlags:[]};c(t,n,y,S,a,({current:r,eat:a,exit:o,hasNext:s,index:c,next:u,shouldIgnore:f})=>{if(r===`--`){n.doubleDash.push(...t.slice(c+1)),o(!1);return}if(f(r)){n.ignored.push(r),o();return}if(!y(r)){n.parameters.push(r);return}let m=!r.startsWith(`--`),h=r.slice(m?1:2);if(m){let e=h.length;for(let t=0;t<e;t++){let r=h[t],i=l(r,!0);if(!i){n.unknown[r]=!0;continue}let{key:o,config:c}=i,d=c.type;if(d===Boolean||p(d,Boolean))_(n.flags,o,`true`,c);else if(t+1<e){_(n.flags,o,h.slice(t+1),c);break}else{let e=s&&!y(u)?a()??``:``;_(n.flags,o,e,c)}}}else{let{rawName:t,rawValue:r,hasSep:o}=x(h,i),c=l(t),f=!1;if(!c){let e=d(t);e&&(c=e,f=!0)}if(!c){let i=e(t);if(o)n.unknown[i]=r;else if(s&&!y(u)){let e=a();n.unknown[i]=e??!0}else n.unknown[i]=!0;return}let{key:p,config:m,path:g}=c;if(g){if(m.type===Object){n.flags[p]??={};let e=o?r:s&&!y(u)?a()??``:``;b(n.flags[p],g,v(e))}}else if(m.type===Boolean){let e=o?r!==`false`:!0;n.flags[p]=f?!e:e}else{let e=o?r:s&&!y(u)?a()??``:``;_(n.flags,p,e,m)}}});for(let[e,t]of o.entries())n.flags[e]===void 0&&(t.default===void 0?t.required?n.missingRequiredFlags.push(e):n.flags[e]=m(t.type):n.flags[e]=r(t.default));return n}}}const w=(e,t={})=>C(t).parse(e);export{S as DOUBLE_DASH,i as InvalidSchemaError,a as KNOWN_FLAG,s as PARAMETER,o as UNKNOWN_FLAG,b as appendDotValues,v as coerceObjectValue,C as createParser,m as inferDefault,w as parse,y as setDotValues};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clerc/parser",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "author": "Ray <i@mk1.io> (https://github.com/so1ve)",
5
5
  "type": "module",
6
6
  "description": "Clerc parser",
@@ -40,7 +40,7 @@
40
40
  "access": "public"
41
41
  },
42
42
  "dependencies": {
43
- "@clerc/utils": "1.1.0"
43
+ "@clerc/utils": "1.2.0"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@types/minimist": "^1.2.5",