@trenskow/arguments-parser 0.3.136 → 0.3.138

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/index.js CHANGED
@@ -6,87 +6,4 @@
6
6
  // See license in LICENSE.
7
7
  //
8
8
 
9
- import {
10
- basename,
11
- extname
12
- } from 'path';
13
-
14
- import {
15
- plugins
16
- } from 'isvalid';
17
-
18
- import argumentsParser from './lib/index.js';
19
- import autocomplete from './lib/autocomplete/index.js';
20
-
21
- plugins.use('argumentsParser.dummies', () => ({
22
- phase: 'pre',
23
- supportsType: () => true,
24
- validatorsForType: () => ({
25
- description: ['string'],
26
- defaultDescription: ['string'],
27
- secret: ['boolean'],
28
- short: ['string']
29
- }),
30
- validate: (data) => data,
31
- formalize: (schema) => schema
32
- }));
33
-
34
- plugins.use('argumentsParser.hints', () => ({
35
- phase: 'pre',
36
- supportsType: () => true,
37
- validatorsForType: () => ({
38
- hints: ['array', 'string']
39
- }),
40
- validate: (data) => data,
41
- formalize: (data, _, schema) => {
42
-
43
- if (!Array.isArray(data)) data = [data];
44
-
45
- if (schema.errors.hints) {
46
- data.push(schema.errors.hints);
47
- delete schema.errors.hints;
48
- }
49
-
50
- if (data.some((data) => typeof data !== 'string')) throw new Error('Must be a string.');
51
-
52
- return data;
53
-
54
- }
55
- }));
56
-
57
- plugins.use('argumentsParser.autocomplete', () => ({
58
- phase: 'pre',
59
- supportsType: () => true,
60
- validatorsForType: () => ({
61
- autocomplete: ['array', 'function']
62
- }),
63
- validate: (data) => data,
64
- formalize: (data) => {
65
-
66
- if (!(Array.isArray(data) && data.every((data) => typeof data === 'string')) && typeof data !== 'function') {
67
- throw new Error('Must be a string or a function.');
68
- }
69
-
70
- return data;
71
-
72
- }
73
- }));
74
-
75
- if (process.argv[2] === '_completion') {
76
-
77
- let command = basename(process.env.COMPLETION_PATH || process.argv[1]);
78
-
79
- const ext = extname(command);
80
-
81
- if (ext.length) command = command.slice(0, -ext.length);
82
-
83
- autocomplete.script({
84
- shell: process.argv[3],
85
- command
86
- });
87
-
88
- process.exit(0);
89
-
90
- }
91
-
92
- export default typeof process.env.AUTOCOMPLETE_INDEX !== 'undefined' ? autocomplete() : argumentsParser;
9
+ export { default } from './lib/index.js';
@@ -0,0 +1,488 @@
1
+ //
2
+ // arguments-parser.js
3
+ // @trenskow/arguments-parser
4
+ //
5
+ // Created by Kristian Trenskow on 2023/08/07
6
+ // See license in LICENSE.
7
+ //
8
+
9
+ import {
10
+ basename
11
+ } from 'path';
12
+
13
+ import caseit from '@trenskow/caseit';
14
+ import { default as isvalid, merge, keyPaths } from 'isvalid';
15
+ import print from '@trenskow/print';
16
+
17
+ const argumentsParser = (
18
+ {
19
+ args = process.argv.slice(2),
20
+ argvLevel = 0,
21
+ placeholder = '<>',
22
+ command,
23
+ onCommand,
24
+ onParameter,
25
+ strings = {},
26
+ help: {
27
+ usage: helpUsage,
28
+ options: helpOptions,
29
+ } = {},
30
+ options = {
31
+ schema: {},
32
+ data: {},
33
+ validationOptions: {}
34
+ },
35
+ parameters = {}
36
+ } = {}
37
+ ) => {
38
+
39
+ const [opening, closing] = placeholder.split('');
40
+
41
+ const base = `${process.argv.slice(1, argvLevel + 2).map(((arg) => basename(arg))).join(' ')}`;
42
+
43
+ const list = (print, items, spacing = false) => {
44
+
45
+ const maxLength = Object.keys(items)
46
+ .reduce((length, key) => Math.max(length, key.length), 0);
47
+
48
+ Object.entries(items)
49
+ .forEach(([key, description], idx) => {
50
+
51
+ if (spacing && idx > 0) print();
52
+
53
+ print.nn(` ${key}`, { minimumLength: maxLength + 4 });
54
+
55
+ if (!Array.isArray(description)) description = [description];
56
+
57
+ description
58
+ .forEach((description, idx) => {
59
+
60
+ if (idx > 0) {
61
+ print.nn('', { minimumLength: maxLength + 4 });
62
+ }
63
+
64
+ print.sentence(description);
65
+
66
+ });
67
+
68
+ });
69
+
70
+ };
71
+
72
+ if (typeof helpUsage === 'undefined') {
73
+ helpUsage = [
74
+ strings?.help?.usage || 'Usage:',
75
+ base
76
+ ];
77
+ }
78
+
79
+ const printHelp = (error) => {
80
+
81
+ print();
82
+
83
+ if (command) {
84
+ print.bold(command.description);
85
+ print();
86
+ }
87
+
88
+ print(helpUsage.join(' '));
89
+
90
+ if (helpOptions) {
91
+ print.nn(helpOptions());
92
+ }
93
+
94
+ if (error) {
95
+
96
+ print();
97
+
98
+ (error.errors || [error])
99
+ .forEach((error) => {
100
+
101
+ let message = error.message;
102
+
103
+ if (error.keyPath?.[0]) {
104
+ message = `${caseit(error.keyPath[0], 'kebab')}: ${message}`;
105
+ }
106
+
107
+ print.bold((strings?.commands?.help?.error || 'Error: <message>')
108
+ .replace('<message>', message));
109
+
110
+ });
111
+
112
+ }
113
+
114
+ print();
115
+
116
+ process.exit(error ? 1 : 0);
117
+
118
+ };
119
+
120
+ const checkHelp = () => {
121
+ if (args[0] === '--help') printHelp();
122
+ };
123
+
124
+ const next = async (identifier, command) => {
125
+
126
+ try {
127
+
128
+ return await command({
129
+ args: args.slice(1),
130
+ argumentsParser: argumentsParser({
131
+ args: args.slice(1),
132
+ argvLevel: argvLevel + 1,
133
+ onCommand,
134
+ onParameter,
135
+ placeholder,
136
+ command: command,
137
+ strings,
138
+ options,
139
+ parameters
140
+ }),
141
+ parameters
142
+ });
143
+
144
+ } catch (error) {
145
+ print.err(`${error.stack}`);
146
+ process.exit(1);
147
+ }
148
+
149
+ };
150
+
151
+ const result = {
152
+ get base() {
153
+ return base;
154
+ },
155
+ command: async (commands) => {
156
+
157
+ helpUsage.push(`${opening}${strings?.commands?.help?.placeholder || 'command'}${closing}`);
158
+
159
+ helpOptions = () => {
160
+
161
+ print();
162
+ print(strings?.commands?.help?.available || 'Available commands:');
163
+ print();
164
+
165
+ const tools = Object.entries(commands).map(([key, command]) => ({
166
+ name: `${caseit(key, 'kebab')}${Array.isArray(command.parameters) ? ` ${command.parameters.map((parameter) => `${opening}${caseit(parameter, 'kebab')}${closing}`).join(' ')}` : ''}`,
167
+ description: commands[key].description
168
+ }));
169
+
170
+ tools.sort((a, b) => a.name > b.name ? 1 : -1);
171
+
172
+ list(print, Object.fromEntries(tools
173
+ .map((tool) => [tool.name, tool.description || (strings?.commands?.help?.noDescription || 'No description')])));
174
+
175
+ };
176
+
177
+ if (args.length === 0) printHelp();
178
+
179
+ checkHelp();
180
+
181
+ const tool = caseit(args[0]);
182
+
183
+ if (!commands[tool]) {
184
+ printHelp(
185
+ new Error(
186
+ (strings?.commands?.help?.errors?.notFound || '<command>: Command not found')
187
+ .replace('<command>', args[0])));
188
+ }
189
+
190
+ onCommand?.(tool, args.slice(1));
191
+
192
+ return await next(
193
+ tool,
194
+ commands[tool]);
195
+
196
+ },
197
+ parameter: async ({
198
+ identifier,
199
+ description,
200
+ schema
201
+ } = {}, command) => {
202
+
203
+ if (typeof identifier !== 'string') {
204
+ throw new Error('Identifier must be a string');
205
+ }
206
+
207
+ description = description || command.description || strings?.commands?.help?.noDescription || 'No description.';
208
+
209
+ helpUsage.push(`${opening}${caseit(identifier, 'kebab')}${closing}`);
210
+
211
+ helpOptions = () => {
212
+
213
+ print();
214
+ print(strings?.commands?.help?.available || 'Available commands:');
215
+ print();
216
+
217
+ list(print, {
218
+ [`${opening}${caseit(identifier, 'kebab')}${closing}`]: description
219
+ });
220
+
221
+ };
222
+
223
+ if (args.length === 0) printHelp();
224
+
225
+ checkHelp();
226
+
227
+ parameters[identifier] = args[0];
228
+
229
+ try {
230
+ if (typeof schema !== 'undefined') {
231
+ parameters[identifier] = await isvalid(
232
+ parameters[identifier],
233
+ schema);
234
+ }
235
+ } catch (error) {
236
+ printHelp(error);
237
+ }
238
+
239
+ (onParameter || onCommand)?.(identifier, args.slice(1));
240
+
241
+ return await next(
242
+ identifier,
243
+ command);
244
+
245
+ },
246
+ options: (schema, validationOptions = {}) => {
247
+
248
+ validationOptions.command = validationOptions.command || command;
249
+
250
+ Object.assign(
251
+ options.validationOptions,
252
+ validationOptions);
253
+
254
+ options.schema = merge(
255
+ schema, {
256
+ transform: {
257
+ post: (key, validator) => {
258
+ if (key === 'enum') {
259
+ return validator.map((value) => caseit(value, 'kebab'));
260
+ }
261
+ }
262
+ }
263
+ })
264
+ .with(options.schema);
265
+
266
+ return result;
267
+
268
+ },
269
+ help(error) {
270
+ printHelp(error);
271
+ },
272
+ then: (...resultArguments) => {
273
+ return (async () => {
274
+
275
+ let nonOptions;
276
+ let nonOptionsIndex = args.indexOf('--');
277
+
278
+ if (nonOptionsIndex > -1) {
279
+ nonOptions = args.slice(nonOptionsIndex + 1).join(' ');
280
+ args = args.slice(0, nonOptionsIndex);
281
+ }
282
+
283
+ const allKeyPaths = keyPaths(options.schema)
284
+ .all()
285
+ .filter((keyPath) => keyPath);
286
+
287
+ if (allKeyPaths.length) {
288
+ helpUsage.push(`${opening}${strings?.options?.help?.placeholder || 'options'}${closing}`);
289
+ }
290
+
291
+ if (typeof options.validationOptions.usage === 'string') {
292
+ helpUsage.push(options.validationOptions.usage);
293
+ }
294
+
295
+ let shortOptions = {};
296
+
297
+ allKeyPaths
298
+ .forEach((keyPath) => {
299
+
300
+ const keyPathSchema = keyPaths(options.schema).get(keyPath);
301
+
302
+ if (keyPathSchema.short) {
303
+
304
+ if (shortOptions[keyPathSchema.short]) {
305
+ throw new Error(`Short option "${keyPathSchema.short}" already used.`);
306
+ }
307
+
308
+ shortOptions[keyPath] = keyPathSchema.short;
309
+
310
+ }
311
+
312
+ });
313
+
314
+ shortOptions = Object.fromEntries(
315
+ Object.entries(shortOptions)
316
+ .map(([key, value]) => [value, key]));
317
+
318
+ if (allKeyPaths.length) {
319
+
320
+ helpOptions = () => {
321
+
322
+ print();
323
+ print(strings?.options?.help?.title || 'Options:');
324
+ print();
325
+
326
+ list(print, Object.fromEntries(allKeyPaths
327
+ .map((keyPath) => {
328
+
329
+ const keyPathSchema = keyPaths(options.schema).get(keyPath);
330
+
331
+ let optionKeys = [`--${caseit(keyPath, 'kebab')}`];
332
+
333
+ let description = [keyPathSchema.description || strings?.commands?.help?.noDescription || 'No description'];
334
+
335
+ if (keyPathSchema.short) {
336
+ optionKeys.push(`-${keyPathSchema.short}`);
337
+ }
338
+
339
+ if (typeof keyPathSchema.enum !== 'undefined') {
340
+ description.push(`(${opening}${keyPath}${closing}: ${Object.keys(keyPathSchema.enum).map((value) => `\`${value}\``).join(', ')})`);
341
+ }
342
+
343
+ if (keyPathSchema.type === Array) {
344
+ description.push(`${strings?.options?.help?.allowsMultiple || '(allows multiple)'}`);
345
+ } else if (keyPathSchema.required === true) {
346
+ description.push(`${strings?.options?.help?.required || '(required)'}`);
347
+ } else if (typeof keyPathSchema.default !== 'undefined') {
348
+
349
+ let defaultDescription;
350
+
351
+ if (keyPathSchema.secret === true) {
352
+ defaultDescription = '`********`';
353
+ } else {
354
+
355
+ if (keyPathSchema.type === Boolean) {
356
+ defaultDescription = keyPathSchema.default === true ? 'enabled' : 'disabled';
357
+ } else {
358
+ defaultDescription = `\`${keyPathSchema.defaultDescription ?? keyPathSchema.default}\``;
359
+ }
360
+
361
+ }
362
+
363
+ description.push(`${(strings?.options?.help?.default || '(default: <default>)')
364
+ .replace('<default>', defaultDescription)}`);
365
+
366
+ }
367
+
368
+ if (keyPathSchema.hints?.length) {
369
+ description = description.concat(keyPathSchema.hints);
370
+ }
371
+
372
+ let option = optionKeys.reverse().join(', ');
373
+
374
+ if (keyPathSchema.type !== Boolean) {
375
+ option += ` ${strings?.options?.help?.argument || `${opening}${caseit(keyPath, 'kebab')}${closing}`}`;
376
+ }
377
+
378
+ return [option, description];
379
+
380
+ })), true);
381
+
382
+ };
383
+ }
384
+
385
+ checkHelp();
386
+
387
+ let rest = [];
388
+
389
+ let idx;
390
+
391
+ checkHelp();
392
+
393
+ for (idx = 0; idx < args.length; idx++) {
394
+
395
+ if (args[idx].slice(0, 1) !== '-') {
396
+ rest.push(args[idx]);
397
+ continue;
398
+ }
399
+
400
+ let key = args[idx].slice(1);
401
+
402
+ if (args[idx].slice(1, 2) === '-') {
403
+ key = caseit(args[idx].slice(2));
404
+ } else if (shortOptions[key]) {
405
+ key = caseit(shortOptions[key]);
406
+ }
407
+
408
+ if (!keyPaths(options.schema).all().includes(key)) {
409
+ printHelp(
410
+ new Error(
411
+ (strings?.options?.help?.errors?.unknownOption || 'Unknown option: <option>.')
412
+ .replace('<option>', args[idx])));
413
+ }
414
+
415
+ let value;
416
+
417
+ if (keyPaths(options.schema).get(key).type === Boolean) {
418
+ value = true;
419
+ } else {
420
+ if (args.length <= idx + 1 || args[idx + 1].slice(0, 2) === '--') {
421
+ printHelp(
422
+ new Error(
423
+ (strings?.options?.help?.errors?.missingArgument || 'Argument missing for option: <option>.')
424
+ .replace('<option>', args[idx])));
425
+ }
426
+ value = args[++idx];
427
+ }
428
+
429
+ if (typeof options.data[key] === 'undefined') {
430
+ options.data[key] = value;
431
+ } else if (Array.isArray(options.data[key])) {
432
+ options.data[key].push(value);
433
+ } else {
434
+ options.data[key] = [options.data[key], value];
435
+ }
436
+
437
+ }
438
+
439
+ if (rest.length) {
440
+ switch (options.validationOptions.variadic || 'deny') {
441
+ case 'allow':
442
+ break;
443
+ case 'ignore':
444
+ rest = [];
445
+ break;
446
+ default:
447
+ printHelp(
448
+ new Error(
449
+ (strings?.empty?.help?.errors?.unexpected || 'Unexpected argument: <argument>.')
450
+ .replace('<argument>', args[0])));
451
+ }
452
+ }
453
+
454
+ if (args.length - idx > 0) {
455
+ printHelp(
456
+ new Error(
457
+ (strings?.empty?.help?.errors?.unexpected || 'Unexpected argument: <argument>.')
458
+ .replace('<argument>', args[0])));
459
+ }
460
+
461
+ let data = {};
462
+
463
+ try {
464
+ data = await isvalid(options.data, options.schema, {
465
+ aggregatedErrors: 'flatten'
466
+ });
467
+ } catch (error) {
468
+ printHelp(error);
469
+ }
470
+
471
+ return Object.assign({}, data, {
472
+ onError: (error) => {
473
+ print.err(error.message);
474
+ process.exit(1);
475
+ },
476
+ rest,
477
+ nonOptions
478
+ });
479
+
480
+ })().then(...resultArguments);
481
+ }
482
+ };
483
+
484
+ return result;
485
+
486
+ };
487
+
488
+ export default argumentsParser;
@@ -27,7 +27,7 @@ const supported = {
27
27
 
28
28
  const currentShell = basename(process.env.SHELL || '');
29
29
 
30
- const argumentsParser = () => {
30
+ const autocomplete = () => {
31
31
 
32
32
  const writer = supported[currentShell];
33
33
 
@@ -188,15 +188,7 @@ const argumentsParser = () => {
188
188
  validationOptions);
189
189
 
190
190
  options.schema = merge(
191
- schema, {
192
- transform: {
193
- post: (key, validator) => {
194
- if (key === 'enum') {
195
- return validator.map((value) => caseit(value, 'kebab'));
196
- }
197
- }
198
- }
199
- })
191
+ schema)
200
192
  .with(options.schema);
201
193
 
202
194
  return result;
@@ -240,7 +232,7 @@ const argumentsParser = () => {
240
232
 
241
233
  if (schemas[idx].enum) {
242
234
  return Object.keys(schemas[idx].enum)
243
- .filter((item) => caseit(key, 'kebab') !== item && item.startsWith(current))
235
+ .filter((item) => key !== item && item.startsWith(current))
244
236
  .map((item) => ({
245
237
  type: 'option',
246
238
  argument: caseit(item, 'kebab')
@@ -351,7 +343,7 @@ const argumentsParser = () => {
351
343
 
352
344
  };
353
345
 
354
- argumentsParser.script = ({
346
+ autocomplete.script = ({
355
347
  shell = currentShell,
356
348
  command
357
349
  }) => {
@@ -370,4 +362,4 @@ argumentsParser.script = ({
370
362
 
371
363
  };
372
364
 
373
- export default argumentsParser;
365
+ export default autocomplete;
package/lib/index.js CHANGED
@@ -2,487 +2,37 @@
2
2
  // index.js
3
3
  // @trenskow/arguments-parser
4
4
  //
5
- // Created by Kristian Trenskow on 2023/08/07
5
+ // Created by Kristian Trenskow on 2026/08/04
6
6
  // See license in LICENSE.
7
7
  //
8
8
 
9
9
  import {
10
- basename
10
+ basename,
11
+ extname
11
12
  } from 'path';
12
13
 
13
- import caseit from '@trenskow/caseit';
14
- import { default as isvalid, merge, keyPaths } from 'isvalid';
15
- import print from '@trenskow/print';
14
+ import plugins from './plugins/index.js';
16
15
 
17
- const argumentsParser = (
18
- {
19
- args = process.argv.slice(2),
20
- argvLevel = 0,
21
- placeholder = '<>',
22
- command,
23
- onCommand,
24
- onParameter,
25
- strings = {},
26
- help: {
27
- usage: helpUsage,
28
- options: helpOptions,
29
- } = {},
30
- options = {
31
- schema: {},
32
- data: {},
33
- validationOptions: {}
34
- },
35
- parameters = {}
36
- } = {}
37
- ) => {
16
+ import argumentsParser from './arguments-parser.js';
17
+ import autocomplete from './autocomplete/index.js';
38
18
 
39
- const [opening, closing] = placeholder.split('');
19
+ plugins();
40
20
 
41
- const base = `${process.argv.slice(1, argvLevel + 2).map(((arg) => basename(arg))).join(' ')}`;
21
+ if (process.argv[2] === '_completion') {
42
22
 
43
- const list = (print, items, spacing = false) => {
23
+ let command = basename(process.env.COMPLETION_PATH || process.argv[1]);
44
24
 
45
- const maxLength = Object.keys(items)
46
- .reduce((length, key) => Math.max(length, key.length), 0);
25
+ const ext = extname(command);
47
26
 
48
- Object.entries(items)
49
- .forEach(([key, description], idx) => {
27
+ if (ext.length) command = command.slice(0, -ext.length);
50
28
 
51
- if (spacing && idx > 0) print();
29
+ autocomplete.script({
30
+ shell: process.argv[3],
31
+ command
32
+ });
52
33
 
53
- print.nn(` ${key}`, { minimumLength: maxLength + 4 });
34
+ process.exit(0);
54
35
 
55
- if (!Array.isArray(description)) description = [description];
36
+ }
56
37
 
57
- description
58
- .forEach((description, idx) => {
59
-
60
- if (idx > 0) {
61
- print.nn('', { minimumLength: maxLength + 4 });
62
- }
63
-
64
- print.sentence(description);
65
-
66
- });
67
-
68
- });
69
-
70
- };
71
-
72
- if (typeof helpUsage === 'undefined') {
73
- helpUsage = [
74
- strings?.help?.usage || 'Usage:',
75
- base
76
- ];
77
- }
78
-
79
- const printHelp = (error) => {
80
-
81
- print();
82
-
83
- if (command) {
84
- print.bold(command.description);
85
- print();
86
- }
87
-
88
- print(helpUsage.join(' '));
89
-
90
- if (helpOptions) {
91
- print.nn(helpOptions());
92
- }
93
-
94
- if (error) {
95
-
96
- print();
97
-
98
- (error.errors || [error])
99
- .forEach((error) => {
100
-
101
- let message = error.message;
102
-
103
- if (error.keyPath?.[0]) {
104
- message = `${caseit(error.keyPath[0], 'kebab')}: ${message}`;
105
- }
106
-
107
- print.bold((strings?.commands?.help?.error || 'Error: <message>')
108
- .replace('<message>', message));
109
-
110
- });
111
-
112
- }
113
-
114
- print();
115
-
116
- process.exit(error ? 1 : 0);
117
-
118
- };
119
-
120
- const checkHelp = () => {
121
- if (args[0] === '--help') printHelp();
122
- };
123
-
124
- const next = async (identifier, command) => {
125
-
126
- try {
127
-
128
- return await command({
129
- args: args.slice(1),
130
- argumentsParser: argumentsParser({
131
- args: args.slice(1),
132
- argvLevel: argvLevel + 1,
133
- onCommand,
134
- onParameter,
135
- placeholder,
136
- command: command,
137
- strings,
138
- options,
139
- parameters
140
- }),
141
- parameters
142
- });
143
-
144
- } catch (error) {
145
- print.err(`${error.stack}`);
146
- process.exit(1);
147
- }
148
-
149
- };
150
-
151
- const result = {
152
- get base() {
153
- return base;
154
- },
155
- command: async (commands) => {
156
-
157
- helpUsage.push(`${opening}${strings?.commands?.help?.placeholder || 'command'}${closing}`);
158
-
159
- helpOptions = () => {
160
-
161
- print();
162
- print(strings?.commands?.help?.available || 'Available commands:');
163
- print();
164
-
165
- const tools = Object.entries(commands).map(([key, command]) => ({
166
- name: `${caseit(key, 'kebab')}${Array.isArray(command.parameters) ? ` ${command.parameters.map((parameter) => `${opening}${caseit(parameter, 'kebab')}${closing}`).join(' ')}` : ''}`,
167
- description: commands[key].description
168
- }));
169
-
170
- tools.sort((a, b) => a.name > b.name ? 1 : -1);
171
-
172
- list(print, Object.fromEntries(tools
173
- .map((tool) => [tool.name, tool.description || (strings?.commands?.help?.noDescription || 'No description')])));
174
-
175
- };
176
-
177
- if (args.length === 0) printHelp();
178
-
179
- checkHelp();
180
-
181
- const tool = caseit(args[0]);
182
-
183
- if (!commands[tool]) {
184
- printHelp(
185
- new Error(
186
- (strings?.commands?.help?.errors?.notFound || '<command>: Command not found')
187
- .replace('<command>', args[0])));
188
- }
189
-
190
- onCommand?.(tool, args.slice(1));
191
-
192
- return await next(
193
- tool,
194
- commands[tool]);
195
-
196
- },
197
- parameter: async ({
198
- identifier,
199
- description,
200
- schema
201
- } = {}, command) => {
202
-
203
- if (typeof identifier !== 'string') {
204
- throw new Error('Identifier must be a string');
205
- }
206
-
207
- description = description || command.description || strings?.commands?.help?.noDescription || 'No description.';
208
-
209
- helpUsage.push(`${opening}${caseit(identifier, 'kebab')}${closing}`);
210
-
211
- helpOptions = () => {
212
-
213
- print();
214
- print(strings?.commands?.help?.available || 'Available commands:');
215
- print();
216
-
217
- list(print, {
218
- [`${opening}${caseit(identifier, 'kebab')}${closing}`]: description
219
- });
220
-
221
- };
222
-
223
- if (args.length === 0) printHelp();
224
-
225
- checkHelp();
226
-
227
- parameters[identifier] = args[0];
228
-
229
- try {
230
- if (typeof schema !== 'undefined') {
231
- parameters[identifier] = await isvalid(
232
- parameters[identifier],
233
- schema);
234
- }
235
- } catch (error) {
236
- printHelp(error);
237
- }
238
-
239
- (onParameter || onCommand)?.(identifier, args.slice(1));
240
-
241
- return await next(
242
- identifier,
243
- command);
244
-
245
- },
246
- options: (schema, validationOptions = {}) => {
247
-
248
- validationOptions.command = validationOptions.command || command;
249
-
250
- Object.assign(
251
- options.validationOptions,
252
- validationOptions);
253
-
254
- options.schema = merge(
255
- schema, {
256
- transform: {
257
- post: (key, validator) => {
258
- if (key === 'enum') {
259
- return validator.map((value) => caseit(value, 'kebab'));
260
- }
261
- }
262
- }
263
- })
264
- .with(options.schema);
265
-
266
- return result;
267
-
268
- },
269
- help(error) {
270
- printHelp(error);
271
- },
272
- then: (...resultArguments) => {
273
- return (async () => {
274
-
275
- let nonOptions;
276
- let nonOptionsIndex = args.indexOf('--');
277
-
278
- if (nonOptionsIndex > -1) {
279
- nonOptions = args.slice(nonOptionsIndex + 1).join(' ');
280
- args = args.slice(0, nonOptionsIndex);
281
- }
282
-
283
- const allKeyPaths = keyPaths(options.schema)
284
- .all()
285
- .filter((keyPath) => keyPath);
286
-
287
- if (allKeyPaths.length) {
288
- helpUsage.push(`${opening}${strings?.options?.help?.placeholder || 'options'}${closing}`);
289
- }
290
-
291
- if (typeof options.validationOptions.usage === 'string') {
292
- helpUsage.push(options.validationOptions.usage);
293
- }
294
-
295
- let shortOptions = {};
296
-
297
- allKeyPaths
298
- .forEach((keyPath) => {
299
-
300
- const keyPathSchema = keyPaths(options.schema).get(keyPath);
301
-
302
- if (keyPathSchema.short) {
303
-
304
- if (shortOptions[keyPathSchema.short]) {
305
- throw new Error(`Short option "${keyPathSchema.short}" already used.`);
306
- }
307
-
308
- shortOptions[keyPath] = keyPathSchema.short;
309
-
310
- }
311
-
312
- });
313
-
314
- shortOptions = Object.fromEntries(
315
- Object.entries(shortOptions)
316
- .map(([key, value]) => [value, key]));
317
-
318
- if (allKeyPaths.length) {
319
-
320
- helpOptions = () => {
321
-
322
- print();
323
- print(strings?.options?.help?.title || 'Options:');
324
- print();
325
-
326
- list(print, Object.fromEntries(allKeyPaths
327
- .map((keyPath) => {
328
-
329
- const keyPathSchema = keyPaths(options.schema).get(keyPath);
330
-
331
- let optionKeys = [`--${caseit(keyPath, 'kebab')}`];
332
-
333
- let description = [keyPathSchema.description || strings?.commands?.help?.noDescription || 'No description'];
334
-
335
- if (keyPathSchema.short) {
336
- optionKeys.push(`-${keyPathSchema.short}`);
337
- }
338
-
339
- if (typeof keyPathSchema.enum !== 'undefined') {
340
- description.push(`(${opening}${keyPath}${closing}: ${Object.keys(keyPathSchema.enum).map((value) => `\`${caseit(value, 'kebab')}\``).join(', ')})`);
341
- }
342
-
343
- if (keyPathSchema.type === Array) {
344
- description.push(`${strings?.options?.help?.allowsMultiple || '(allows multiple)'}`);
345
- } else if (keyPathSchema.required === true) {
346
- description.push(`${strings?.options?.help?.required || '(required)'}`);
347
- } else if (typeof keyPathSchema.default !== 'undefined') {
348
-
349
- let defaultDescription;
350
-
351
- if (keyPathSchema.secret === true) {
352
- defaultDescription = '`********`';
353
- } else {
354
-
355
- if (keyPathSchema.type === Boolean) {
356
- defaultDescription = keyPathSchema.default === true ? 'enabled' : 'disabled';
357
- } else {
358
- defaultDescription = `\`${keyPathSchema.defaultDescription ?? keyPathSchema.default}\``;
359
- }
360
-
361
- }
362
-
363
- description.push(`${(strings?.options?.help?.default || '(default: <default>)')
364
- .replace('<default>', defaultDescription)}`);
365
-
366
- }
367
-
368
- if (keyPathSchema.hints?.length) {
369
- description = description.concat(keyPathSchema.hints);
370
- }
371
-
372
- let option = optionKeys.reverse().join(', ');
373
-
374
- if (keyPathSchema.type !== Boolean) {
375
- option += ` ${strings?.options?.help?.argument || `${opening}${caseit(keyPath, 'kebab')}${closing}`}`;
376
- }
377
-
378
- return [option, description];
379
-
380
- })), true);
381
-
382
- };
383
- }
384
-
385
- checkHelp();
386
-
387
- let rest = [];
388
-
389
- let idx;
390
-
391
- checkHelp();
392
-
393
- for (idx = 0; idx < args.length; idx++) {
394
-
395
- if (args[idx].slice(0, 1) !== '-') {
396
- rest.push(args[idx]);
397
- continue;
398
- }
399
-
400
- let key = args[idx].slice(1);
401
-
402
- if (args[idx].slice(1, 2) === '-') {
403
- key = caseit(args[idx].slice(2));
404
- } else if (shortOptions[key]) {
405
- key = caseit(shortOptions[key]);
406
- }
407
-
408
- if (!keyPaths(options.schema).all().includes(key)) {
409
- printHelp(
410
- new Error(
411
- (strings?.options?.help?.errors?.unknownOption || 'Unknown option: <option>.')
412
- .replace('<option>', args[idx])));
413
- }
414
-
415
- let value;
416
-
417
- if (keyPaths(options.schema).get(key).type === Boolean) {
418
- value = true;
419
- } else {
420
- if (args.length <= idx + 1 || args[idx + 1].slice(0, 2) === '--') {
421
- printHelp(
422
- new Error(
423
- (strings?.options?.help?.errors?.missingArgument || 'Argument missing for option: <option>.')
424
- .replace('<option>', args[idx])));
425
- }
426
- value = args[++idx];
427
- }
428
-
429
- if (typeof options.data[key] === 'undefined') {
430
- options.data[key] = value;
431
- } else if (Array.isArray(options.data[key])) {
432
- options.data[key].push(value);
433
- } else {
434
- options.data[key] = [options.data[key], value];
435
- }
436
-
437
- }
438
-
439
- if (rest.length) {
440
- switch (options.validationOptions.variadic || 'deny') {
441
- case 'allow':
442
- break;
443
- case 'ignore':
444
- rest = [];
445
- break;
446
- default:
447
- printHelp(
448
- new Error(
449
- (strings?.empty?.help?.errors?.unexpected || 'Unexpected argument: <argument>.')
450
- .replace('<argument>', args[0])));
451
- }
452
- }
453
-
454
- if (args.length - idx > 0) {
455
- printHelp(
456
- new Error(
457
- (strings?.empty?.help?.errors?.unexpected || 'Unexpected argument: <argument>.')
458
- .replace('<argument>', args[0])));
459
- }
460
-
461
- let data = {};
462
-
463
- try {
464
- data = await isvalid(options.data, options.schema, {
465
- aggregatedErrors: 'flatten'
466
- });
467
- } catch (error) {
468
- printHelp(error);
469
- }
470
-
471
- return Object.assign({}, data, {
472
- onError: (error) => {
473
- print.err(error.message);
474
- process.exit(1);
475
- },
476
- rest,
477
- nonOptions
478
- });
479
-
480
- })().then(...resultArguments);
481
- }
482
- };
483
-
484
- return result;
485
-
486
- };
487
-
488
- export default argumentsParser;
38
+ export default typeof process.env.AUTOCOMPLETE_INDEX !== 'undefined' ? autocomplete() : argumentsParser;
@@ -0,0 +1,25 @@
1
+ //
2
+ // autocomplete.js
3
+ // @trenskow/arguments-parser
4
+ //
5
+ // Created by Kristian Trenskow on 2028/08/04
6
+ // See license in LICENSE.
7
+ //
8
+
9
+ export default () => ({
10
+ phase: 'pre',
11
+ supportsType: () => true,
12
+ validatorsForType: () => ({
13
+ autocomplete: ['array', 'function']
14
+ }),
15
+ validate: (data) => data,
16
+ formalize: (data) => {
17
+
18
+ if (!(Array.isArray(data) && data.every((data) => typeof data === 'string')) && typeof data !== 'function') {
19
+ throw new Error('Must be a string or a function.');
20
+ }
21
+
22
+ return data;
23
+
24
+ }
25
+ });
@@ -0,0 +1,20 @@
1
+ //
2
+ // dummies.js
3
+ // @trenskow/arguments-parser
4
+ //
5
+ // Created by Kristian Trenskow on 2028/08/04
6
+ // See license in LICENSE.
7
+ //
8
+
9
+ export default () => ({
10
+ phase: 'pre',
11
+ supportsType: () => true,
12
+ validatorsForType: () => ({
13
+ description: ['string'],
14
+ defaultDescription: ['string'],
15
+ secret: ['boolean'],
16
+ short: ['string']
17
+ }),
18
+ validate: (data) => data,
19
+ formalize: (schema) => schema
20
+ });
@@ -0,0 +1,30 @@
1
+ //
2
+ // hints.js
3
+ // @trenskow/arguments-parser
4
+ //
5
+ // Created by Kristian Trenskow on 2028/08/04
6
+ // See license in LICENSE.
7
+ //
8
+
9
+ export default () => ({
10
+ phase: 'pre',
11
+ supportsType: () => true,
12
+ validatorsForType: () => ({
13
+ hints: ['array', 'string']
14
+ }),
15
+ validate: (data) => data,
16
+ formalize: (data, _, schema) => {
17
+
18
+ if (!Array.isArray(data)) data = [data];
19
+
20
+ if (schema.errors.hints) {
21
+ data.push(schema.errors.hints);
22
+ delete schema.errors.hints;
23
+ }
24
+
25
+ if (data.some((data) => typeof data !== 'string')) throw new Error('Must be a string.');
26
+
27
+ return data;
28
+
29
+ }
30
+ });
@@ -0,0 +1,21 @@
1
+ //
2
+ // plugins.js
3
+ // @trenskow/arguments-parser
4
+ //
5
+ // Created by Kristian Trenskow on 2028/08/04
6
+ // See license in LICENSE.
7
+ //
8
+
9
+ import {
10
+ plugins
11
+ } from 'isvalid';
12
+
13
+ import autocomplete from './autocomplete.js';
14
+ import dummies from './dummies.js';
15
+ import hints from './hints.js';
16
+
17
+ export default () => {
18
+ plugins.use('argumentsParser.dummies', dummies);
19
+ plugins.use('argumentsParser.hints', hints);
20
+ plugins.use('argumentsParser.autocomplete', autocomplete);
21
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trenskow/arguments-parser",
3
- "version": "0.3.136",
3
+ "version": "0.3.138",
4
4
  "description": "Yet another arguments parser.",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -26,7 +26,7 @@
26
26
  "dependencies": {
27
27
  "@trenskow/caseit": "^1.4.30",
28
28
  "@trenskow/parallel": "^0.1.136",
29
- "@trenskow/print": "^0.1.74",
29
+ "@trenskow/print": "^0.1.75",
30
30
  "isvalid": "^5.0.21"
31
31
  },
32
32
  "devDependencies": {