argot-cli 0.0.0-stage → 0.4.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/CHANGELOG.md ADDED
@@ -0,0 +1,14 @@
1
+ # Changelog
2
+
3
+ ## v0.3.0 -- 2026-04-23
4
+ - Added new error types for clearer distinction between runtime and
5
+ configuration errors.
6
+ - Deprecated `LabeledEntry`; option names should now be defined as object keys
7
+ in configuration.
8
+
9
+ ## v0.2.0 -- 2026-02-08
10
+ - Added filesystem support for Node.js and Bun runtimes.
11
+
12
+ ## v0.1.0 -- 2026-02-04
13
+ - Implemented `argot-cli`, a command-line interface argument parser in
14
+ TypeScript. As of its first publication, it is only compatible with Deno.
package/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright 2026 Lucas L. S. Haine
2
+
3
+ Permission to use, copy, modify, and/or distribute this software for any
4
+ purpose with or without fee is hereby granted, provided that the above
5
+ copyright notice and this permission notice appear in all copies.
6
+
7
+ THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
8
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
9
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
10
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
11
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
12
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
13
+ PERFORMANCE OF THIS SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,3 @@
1
- # Temporary Holding Version
1
+ # argot-cli
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ This is a command-line argument parser with a simple configuration format.
Binary file
@@ -0,0 +1,60 @@
1
+ import type { ParseResult } from './types.ts';
2
+ import { ParserConfig } from './parser_config.ts';
3
+ /**
4
+ * Command-line argument parser.
5
+ * The parser processes an array of strings according to a
6
+ * configuration and produces a structured result.
7
+ * Parsing follows UNIX-style short options and GNU-style long
8
+ * options.
9
+ */
10
+ export declare class ArgParser {
11
+ private longOptExp;
12
+ private shortOptExp;
13
+ private assignmentExp;
14
+ private parameterExp;
15
+ private configs;
16
+ constructor(configs: ParserConfig);
17
+ /**
18
+ * Parse an array of command-line arguments.
19
+ * Arguments are processed from left to right. Each argument is
20
+ * classified as one of:
21
+ * - option: matches a configured short or long option
22
+ * - parameter: of the form "key=value"
23
+ * - operand: any other argument
24
+ * Parsing rules:
25
+ * - The literal "--" stops option parsing. All subsequent
26
+ * arguments are treated as operands.
27
+ * - Short options may be combined (e.g. "-abc").
28
+ * - If a short option that accepts an associated value is not the
29
+ * last character in a group, the remainder of the argument is
30
+ * used as its value (e.g. "-n10").
31
+ * - If a short option that accepts an associated value appears as
32
+ * the last character:
33
+ * - If the option has a default value, no additional argument
34
+ * is consumed.
35
+ * - Otherwise, the next argument is used as its value.
36
+ * - Long options must be provided in the form "--name" if they do
37
+ * not accept an associated value, or "--name=value" if they do.
38
+ * - Long options that accept an associated value never consume
39
+ * the next argument.
40
+ * - Parameters are parsed from arguments matching "key=value" and
41
+ * are stored separately from options.
42
+ * - If an option is repeated:
43
+ * - count and list options accumulate values
44
+ * - other options overwrite previous values
45
+ * - Alias options store their values under the target option's
46
+ * name.
47
+ * Returns:
48
+ * - options: parsed option values
49
+ * - parameters: key/value assignments
50
+ * - operands: positional arguments
51
+ * Throws:
52
+ * - UnknownOptionError if an unrecognized option is encountered
53
+ * - NullArgError or NullIntError if a required value is missing
54
+ * - InvalidIntError or UnsafeIntegerError for invalid numeric values
55
+ */
56
+ parse(argList: string[]): Readonly<ParseResult>;
57
+ slice(argList: string[], index: number): [string[], string[]];
58
+ private parseLongOption;
59
+ private parseShortOption;
60
+ }
@@ -0,0 +1,557 @@
1
+ import { ParserConfig } from './parser_config.js';
2
+ import { Options, Parameters, Operands } from './types.js';
3
+ import { NullArgError, NullFloatError, NullIntError, UnknownOptionError, } from './errors.js';
4
+ import { parseFloatStrict, parseIntStrict } from './utils.js';
5
+ /**
6
+ * Command-line argument parser.
7
+ * The parser processes an array of strings according to a
8
+ * configuration and produces a structured result.
9
+ * Parsing follows UNIX-style short options and GNU-style long
10
+ * options.
11
+ */
12
+ export class ArgParser {
13
+ longOptExp = /^--/;
14
+ shortOptExp = /^-[^-]/;
15
+ assignmentExp = /^--([^=]+)=(.+)?/d;
16
+ parameterExp = /^([^=]+)=(.+)?/d;
17
+ configs;
18
+ constructor(configs) {
19
+ if (!(configs instanceof ParserConfig)) {
20
+ throw new TypeError('input value must be an instance of ParserConfig');
21
+ }
22
+ this.configs = configs;
23
+ }
24
+ /**
25
+ * Parse an array of command-line arguments.
26
+ * Arguments are processed from left to right. Each argument is
27
+ * classified as one of:
28
+ * - option: matches a configured short or long option
29
+ * - parameter: of the form "key=value"
30
+ * - operand: any other argument
31
+ * Parsing rules:
32
+ * - The literal "--" stops option parsing. All subsequent
33
+ * arguments are treated as operands.
34
+ * - Short options may be combined (e.g. "-abc").
35
+ * - If a short option that accepts an associated value is not the
36
+ * last character in a group, the remainder of the argument is
37
+ * used as its value (e.g. "-n10").
38
+ * - If a short option that accepts an associated value appears as
39
+ * the last character:
40
+ * - If the option has a default value, no additional argument
41
+ * is consumed.
42
+ * - Otherwise, the next argument is used as its value.
43
+ * - Long options must be provided in the form "--name" if they do
44
+ * not accept an associated value, or "--name=value" if they do.
45
+ * - Long options that accept an associated value never consume
46
+ * the next argument.
47
+ * - Parameters are parsed from arguments matching "key=value" and
48
+ * are stored separately from options.
49
+ * - If an option is repeated:
50
+ * - count and list options accumulate values
51
+ * - other options overwrite previous values
52
+ * - Alias options store their values under the target option's
53
+ * name.
54
+ * Returns:
55
+ * - options: parsed option values
56
+ * - parameters: key/value assignments
57
+ * - operands: positional arguments
58
+ * Throws:
59
+ * - UnknownOptionError if an unrecognized option is encountered
60
+ * - NullArgError or NullIntError if a required value is missing
61
+ * - InvalidIntError or UnsafeIntegerError for invalid numeric values
62
+ */
63
+ parse(argList) {
64
+ // options: parsed option values
65
+ const options = new Options();
66
+ // parameters: name=value variable assignments
67
+ const parameters = new Parameters();
68
+ // operands: command-line positional arguments
69
+ const operands = new Operands();
70
+ if (!Array.isArray(argList)) {
71
+ throw new TypeError(`argList must be an array of strings`);
72
+ }
73
+ for (let i = 0, n = argList.length; i < n; i++) {
74
+ if (typeof argList[i] !== 'string') {
75
+ throw new TypeError(`argList must be an array of strings`);
76
+ }
77
+ }
78
+ let stopParsing = false;
79
+ for (let i = 0, n = argList.length; i < n; i++) {
80
+ const arg = argList[i];
81
+ if (stopParsing) {
82
+ operands.push(arg);
83
+ continue;
84
+ }
85
+ if (arg === '--') {
86
+ stopParsing = true;
87
+ continue;
88
+ }
89
+ try {
90
+ if (this.longOptExp.test(arg)) {
91
+ const { name, value, shouldSkip } = this.parseLongOption(arg, argList[i + 1] ?? null);
92
+ if (this.configs.get(name)?.type === 'count') {
93
+ const oldValue = options.get(name) ?? 0;
94
+ const newValue = value;
95
+ options.set(name, oldValue + newValue);
96
+ }
97
+ else if (this.configs.get(name)?.type === 'list') {
98
+ const oldValue = options.get(name) ?? [];
99
+ const newValue = value;
100
+ options.set(name, oldValue.concat(newValue));
101
+ }
102
+ else {
103
+ options.set(name, value);
104
+ }
105
+ if (shouldSkip)
106
+ i += 1;
107
+ }
108
+ else if (this.shortOptExp.test(arg)) {
109
+ const [shouldSkip, pairs,] = this.parseShortOption(arg, argList[i + 1] ?? null);
110
+ for (const [name, value] of Object.entries(pairs)) {
111
+ if (this.configs.get(name)?.type === 'count') {
112
+ const oldValue = options.get(name) ?? 0;
113
+ const newValue = value;
114
+ options.set(name, oldValue + newValue);
115
+ }
116
+ else if (this.configs.get(name)?.type === 'list') {
117
+ const oldValue = options.get(name) ?? [];
118
+ const newValue = value;
119
+ options.set(name, oldValue.concat(newValue));
120
+ }
121
+ else {
122
+ options.set(name, value);
123
+ }
124
+ }
125
+ if (shouldSkip)
126
+ i += 1;
127
+ }
128
+ else if (this.configs.parseParameters && this.parameterExp.test(arg)) {
129
+ const [, name, value] = this.parameterExp.exec(arg);
130
+ parameters.set(name, value ?? '');
131
+ }
132
+ else {
133
+ operands.push(arg);
134
+ }
135
+ }
136
+ catch (err) {
137
+ if (!(err instanceof UnknownOptionError) || !this.configs.allowUnknown)
138
+ throw err;
139
+ }
140
+ }
141
+ options.freeze();
142
+ parameters.freeze();
143
+ Object.freeze(operands);
144
+ if (this.configs.parseParameters) {
145
+ const result = { options, parameters, operands };
146
+ return Object.freeze(result);
147
+ }
148
+ else {
149
+ const result = { options, operands };
150
+ return Object.freeze(result);
151
+ }
152
+ }
153
+ slice(argList, index) {
154
+ let i;
155
+ let operands = 0;
156
+ let stopParsing = false;
157
+ for (i = 0; i < argList.length; i++) {
158
+ if (operands >= index)
159
+ break;
160
+ const arg = argList[i];
161
+ if (arg === '--' && !stopParsing) {
162
+ stopParsing = true;
163
+ continue;
164
+ }
165
+ if (stopParsing) {
166
+ operands += 1;
167
+ continue;
168
+ }
169
+ try {
170
+ if (this.longOptExp.test(arg)) {
171
+ const { shouldSkip } = this.parseLongOption(arg, argList[i + 1] ?? null);
172
+ if (shouldSkip)
173
+ i += 1;
174
+ }
175
+ else if (this.shortOptExp.test(arg)) {
176
+ const [shouldSkip,] = this.parseShortOption(arg, argList[i + 1] ?? null);
177
+ if (shouldSkip)
178
+ i += 1;
179
+ }
180
+ else if (this.configs.parseParameters && this.parameterExp.test(arg)) {
181
+ continue;
182
+ }
183
+ else {
184
+ operands += 1;
185
+ }
186
+ }
187
+ catch (err) {
188
+ if (!(err instanceof UnknownOptionError) || !this.configs.allowUnknown)
189
+ throw err;
190
+ }
191
+ }
192
+ return [argList.slice(0, i), argList.slice(i)];
193
+ }
194
+ parseLongOption(arg, nextArg) {
195
+ const offset = 2;
196
+ let name;
197
+ let value;
198
+ try {
199
+ const [, _name, _value] = this.assignmentExp.exec(arg);
200
+ name = _name;
201
+ value = _value ?? '';
202
+ }
203
+ catch {
204
+ name = arg.slice(offset);
205
+ value = null;
206
+ }
207
+ const entry = this.configs.get(name);
208
+ if (entry == null) {
209
+ throw new UnknownOptionError(name);
210
+ }
211
+ const tag = entry.type;
212
+ switch (tag) {
213
+ case 'flag': {
214
+ return { name, value: true, shouldSkip: false };
215
+ }
216
+ case 'text': {
217
+ if (value != null) {
218
+ return { name, value, shouldSkip: false };
219
+ }
220
+ else if (Object.hasOwn(entry, 'default')) {
221
+ const { default: value } = entry;
222
+ return { name, value: value, shouldSkip: false };
223
+ }
224
+ else if (nextArg != null) {
225
+ return { name, value: nextArg, shouldSkip: true };
226
+ }
227
+ throw new NullArgError(name);
228
+ }
229
+ case 'int': {
230
+ if (value != null) {
231
+ return { name, value: parseIntStrict(value), shouldSkip: false };
232
+ }
233
+ else if (Object.hasOwn(entry, 'default')) {
234
+ const { default: value } = entry;
235
+ return { name, value: value, shouldSkip: false };
236
+ }
237
+ else if (nextArg != null) {
238
+ return { name, value: parseIntStrict(nextArg), shouldSkip: true };
239
+ }
240
+ throw new NullIntError(name);
241
+ }
242
+ case 'float': {
243
+ if (value != null) {
244
+ return { name, value: parseFloatStrict(value), shouldSkip: false };
245
+ }
246
+ else if (Object.hasOwn(entry, 'default')) {
247
+ const { default: value } = entry;
248
+ return { name, value: value, shouldSkip: false };
249
+ }
250
+ else if (nextArg != null) {
251
+ return { name, value: parseFloatStrict(nextArg), shouldSkip: true };
252
+ }
253
+ throw new NullFloatError(name);
254
+ }
255
+ case 'count': {
256
+ if (value != null)
257
+ return { name, value: parseIntStrict(value), shouldSkip: false };
258
+ return { name, value: 1, shouldSkip: false };
259
+ }
260
+ case 'list': {
261
+ if (value === '') {
262
+ return { name, value: [], shouldSkip: false };
263
+ }
264
+ else if (value != null) {
265
+ const { sep } = entry;
266
+ return { name, value: value.split(sep ?? ','), shouldSkip: false };
267
+ }
268
+ else if (nextArg != null) {
269
+ const { sep } = entry;
270
+ return { name, value: nextArg.split(sep ?? ','), shouldSkip: true };
271
+ }
272
+ throw new NullArgError(name);
273
+ }
274
+ case 'alias': {
275
+ const { target } = entry;
276
+ const targetEntry = this.configs.get(target);
277
+ const targetType = targetEntry.type;
278
+ switch (targetType) {
279
+ case 'flag': {
280
+ return { name: target, value: true, shouldSkip: false };
281
+ }
282
+ case 'text': {
283
+ if (value != null) {
284
+ return { name: target, value, shouldSkip: false };
285
+ }
286
+ else if (Object.hasOwn(targetEntry, 'default')) {
287
+ const { default: value } = targetEntry;
288
+ return { name: target, value: value, shouldSkip: false };
289
+ }
290
+ else if (nextArg != null) {
291
+ return { name: target, value: nextArg, shouldSkip: true };
292
+ }
293
+ throw new NullArgError(name, target);
294
+ }
295
+ case 'int': {
296
+ if (value != null) {
297
+ return {
298
+ name: target,
299
+ value: parseIntStrict(value),
300
+ shouldSkip: false,
301
+ };
302
+ }
303
+ else if (Object.hasOwn(targetEntry, 'default')) {
304
+ const { default: value } = targetEntry;
305
+ return { name: target, value: value, shouldSkip: false };
306
+ }
307
+ else if (nextArg != null) {
308
+ return {
309
+ name: target,
310
+ value: parseIntStrict(nextArg),
311
+ shouldSkip: true
312
+ };
313
+ }
314
+ throw new NullIntError(name, target);
315
+ }
316
+ case 'float': {
317
+ if (value != null) {
318
+ return {
319
+ name: target,
320
+ value: parseFloatStrict(value),
321
+ shouldSkip: false,
322
+ };
323
+ }
324
+ else if (Object.hasOwn(targetEntry, 'default')) {
325
+ const { default: value } = targetEntry;
326
+ return { name: target, value: value, shouldSkip: false };
327
+ }
328
+ else if (nextArg != null) {
329
+ return {
330
+ name: target,
331
+ value: parseFloatStrict(nextArg),
332
+ shouldSkip: true,
333
+ };
334
+ }
335
+ throw new NullFloatError(name, target);
336
+ }
337
+ case 'count': {
338
+ if (value != null)
339
+ return {
340
+ name: target,
341
+ value: parseIntStrict(value),
342
+ shouldSkip: false
343
+ };
344
+ return { name: target, value: 1, shouldSkip: false };
345
+ }
346
+ case 'list': {
347
+ if (value === '') {
348
+ return { name: target, value: [], shouldSkip: false };
349
+ }
350
+ else if (value != null) {
351
+ const { sep } = targetEntry;
352
+ return {
353
+ name: target,
354
+ value: value.split(sep ?? ','),
355
+ shouldSkip: false
356
+ };
357
+ }
358
+ else if (nextArg != null) {
359
+ const { sep } = targetEntry;
360
+ return {
361
+ name: target,
362
+ value: nextArg.split(sep ?? ','),
363
+ shouldSkip: true
364
+ };
365
+ }
366
+ throw new NullArgError(name, target);
367
+ }
368
+ }
369
+ }
370
+ }
371
+ }
372
+ parseShortOption(arg, nextArg) {
373
+ let name;
374
+ let value;
375
+ let entry;
376
+ const pairs = Object.create(null);
377
+ for (let i = 1, n = arg.length; i < n; i++) {
378
+ name = arg[i];
379
+ value = nextArg ?? null;
380
+ entry = this.configs.get(name);
381
+ if (entry == null && this.configs.allowUnknown) {
382
+ /* silently skip unknown options */
383
+ continue;
384
+ }
385
+ else if (entry == null) {
386
+ throw new UnknownOptionError(name);
387
+ }
388
+ const tag = entry.type;
389
+ switch (tag) {
390
+ case 'flag': {
391
+ pairs[name] = true;
392
+ break;
393
+ }
394
+ case 'text': {
395
+ if (i < n - 1) {
396
+ value = arg.slice(i + 1, n);
397
+ pairs[name] = value;
398
+ return [false, pairs];
399
+ }
400
+ else if (Object.hasOwn(entry, 'default')) {
401
+ const { default: value } = entry;
402
+ pairs[name] = value;
403
+ return [false, pairs];
404
+ }
405
+ else if (value != null) {
406
+ pairs[name] = value;
407
+ return [true, pairs];
408
+ }
409
+ throw new NullArgError(name);
410
+ }
411
+ case 'int': {
412
+ if (i < n - 1) {
413
+ value = arg.slice(i + 1, n);
414
+ pairs[name] = parseIntStrict(value);
415
+ return [false, pairs];
416
+ }
417
+ else if (Object.hasOwn(entry, 'default')) {
418
+ const { default: value } = entry;
419
+ pairs[name] = value;
420
+ return [false, pairs];
421
+ }
422
+ else if (value != null) {
423
+ pairs[name] = parseIntStrict(value);
424
+ return [true, pairs];
425
+ }
426
+ throw new NullIntError(name);
427
+ }
428
+ case 'float': {
429
+ if (i < n - 1) {
430
+ value = arg.slice(i + 1, n);
431
+ pairs[name] = parseFloatStrict(value);
432
+ return [false, pairs];
433
+ }
434
+ else if (Object.hasOwn(entry, 'default')) {
435
+ const { default: value } = entry;
436
+ pairs[name] = value;
437
+ return [false, pairs];
438
+ }
439
+ else if (value != null) {
440
+ pairs[name] = parseFloatStrict(value);
441
+ return [true, pairs];
442
+ }
443
+ throw new NullFloatError(name);
444
+ }
445
+ case 'count': {
446
+ const oldValue = pairs[name] ?? 0;
447
+ pairs[name] = oldValue + 1;
448
+ break;
449
+ }
450
+ case 'list': {
451
+ if (i < n - 1) {
452
+ value = arg.slice(i + 1, n);
453
+ const { sep } = entry;
454
+ pairs[name] = value.split(sep ?? ',');
455
+ return [false, pairs];
456
+ }
457
+ else if (value === '') {
458
+ pairs[name] = [];
459
+ return [true, pairs];
460
+ }
461
+ else if (value != null) {
462
+ const { sep } = entry;
463
+ pairs[name] = value.split(sep ?? ',');
464
+ return [true, pairs];
465
+ }
466
+ throw new NullArgError(name);
467
+ }
468
+ case 'alias': {
469
+ const { target } = entry;
470
+ const targetEntry = this.configs.get(target);
471
+ const targetType = targetEntry.type;
472
+ switch (targetType) {
473
+ case 'flag': {
474
+ pairs[target] = true;
475
+ break;
476
+ }
477
+ case 'text': {
478
+ if (i < n - 1) {
479
+ value = arg.slice(i + 1, n);
480
+ pairs[target] = value;
481
+ return [false, pairs];
482
+ }
483
+ else if (Object.hasOwn(targetEntry, 'default')) {
484
+ const { default: value } = targetEntry;
485
+ pairs[target] = value;
486
+ return [false, pairs];
487
+ }
488
+ else if (value != null) {
489
+ pairs[target] = value;
490
+ return [true, pairs];
491
+ }
492
+ throw new NullArgError(name, target);
493
+ }
494
+ case 'int': {
495
+ if (i < n - 1) {
496
+ value = arg.slice(i + 1, n);
497
+ pairs[target] = parseIntStrict(value);
498
+ return [false, pairs];
499
+ }
500
+ else if (Object.hasOwn(targetEntry, 'default')) {
501
+ const { default: value } = targetEntry;
502
+ pairs[target] = value;
503
+ return [false, pairs];
504
+ }
505
+ else if (value != null) {
506
+ pairs[target] = parseIntStrict(value);
507
+ return [true, pairs];
508
+ }
509
+ throw new NullIntError(name, target);
510
+ }
511
+ case 'float': {
512
+ if (i < n - 1) {
513
+ value = arg.slice(i + 1, n);
514
+ pairs[target] = parseFloatStrict(value);
515
+ return [false, pairs];
516
+ }
517
+ else if (Object.hasOwn(targetEntry, 'default')) {
518
+ const { default: value } = targetEntry;
519
+ pairs[target] = value;
520
+ return [false, pairs];
521
+ }
522
+ else if (value != null) {
523
+ pairs[target] = parseFloatStrict(value);
524
+ return [true, pairs];
525
+ }
526
+ throw new NullFloatError(name, target);
527
+ }
528
+ case 'count': {
529
+ const oldValue = pairs[target] ?? 0;
530
+ pairs[target] = oldValue + 1;
531
+ break;
532
+ }
533
+ case 'list': {
534
+ if (i < n - 1) {
535
+ value = arg.slice(i + 1, n);
536
+ const { sep } = targetEntry;
537
+ pairs[target] = value.split(sep ?? ',');
538
+ return [false, pairs];
539
+ }
540
+ else if (value === '') {
541
+ pairs[target] = [];
542
+ return [true, pairs];
543
+ }
544
+ else if (value != null) {
545
+ const { sep } = targetEntry;
546
+ pairs[target] = value.split(sep ?? ',');
547
+ return [true, pairs];
548
+ }
549
+ throw new NullArgError(name, target);
550
+ }
551
+ }
552
+ }
553
+ }
554
+ }
555
+ return [false, pairs];
556
+ }
557
+ }