@trenskow/arguments-parser 0.3.121 → 0.3.123
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -0
- package/lib/autocomplete/bash.js +64 -0
- package/lib/autocomplete/index.js +79 -25
- package/lib/autocomplete/zsh.js +29 -17
- package/lib/index.js +0 -1
- package/package.json +1 -1
- package/lib/autocomplete/build-ins/files.js +0 -71
- package/lib/autocomplete/build-ins/index.js +0 -15
package/README.md
CHANGED
|
@@ -80,6 +80,42 @@ Options:
|
|
|
80
80
|
|
|
81
81
|
> The validation schema is described in package [isvalid](https://github.com/trenskow/isvalid).
|
|
82
82
|
|
|
83
|
+
## Autocomplete
|
|
84
|
+
|
|
85
|
+
This supports Bash and ZSH autocomplete of the box.
|
|
86
|
+
|
|
87
|
+
> In both examples below replace `my_script` with the actual name of the script.
|
|
88
|
+
|
|
89
|
+
### Bash
|
|
90
|
+
|
|
91
|
+
The completion script for Bash should be installed as below.
|
|
92
|
+
|
|
93
|
+
````bash
|
|
94
|
+
_my_script() {
|
|
95
|
+
eval "$(AUTOCOMPLETE=bash my_script "${COMP_WORDS[@]}")"
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
complete -F _my_script my_script
|
|
99
|
+
````
|
|
100
|
+
|
|
101
|
+
#### ZSH
|
|
102
|
+
|
|
103
|
+
The completion script for ZSH should be installed as below.
|
|
104
|
+
|
|
105
|
+
````zsh
|
|
106
|
+
#compdef my_script
|
|
107
|
+
|
|
108
|
+
_my_script() {
|
|
109
|
+
|
|
110
|
+
local -a completions
|
|
111
|
+
|
|
112
|
+
eval "$(AUTOCOMPLETE=zsh my_script ${words[@]})"
|
|
113
|
+
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
_my_script
|
|
117
|
+
````
|
|
118
|
+
|
|
83
119
|
# License
|
|
84
120
|
|
|
85
121
|
See license in LICENSE.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
//
|
|
2
|
+
// bash.js
|
|
3
|
+
// @trenskow/arguments-parser
|
|
4
|
+
//
|
|
5
|
+
// Created by Kristian Trenskow on 2026/08/03
|
|
6
|
+
// See license in LICENSE.
|
|
7
|
+
//
|
|
8
|
+
|
|
9
|
+
export default (items) => {
|
|
10
|
+
|
|
11
|
+
const keys = Object.keys(items);
|
|
12
|
+
|
|
13
|
+
if (keys.length === 0) {
|
|
14
|
+
process.exit(0);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let options = [];
|
|
18
|
+
let files = false;
|
|
19
|
+
|
|
20
|
+
keys
|
|
21
|
+
.forEach((key) => {
|
|
22
|
+
|
|
23
|
+
switch (key) {
|
|
24
|
+
case 'command':
|
|
25
|
+
// fall through
|
|
26
|
+
case 'option':
|
|
27
|
+
|
|
28
|
+
options = options.concat(items[key]
|
|
29
|
+
.map((item) => item.argument));
|
|
30
|
+
|
|
31
|
+
break;
|
|
32
|
+
case 'files':
|
|
33
|
+
files = true;
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
if (options.length > 0 || files) {
|
|
40
|
+
process.stdout.write('COMPREPLY=()\n');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (options.length > 0) {
|
|
44
|
+
|
|
45
|
+
process.stdout.write('COMPREPLY+=(');
|
|
46
|
+
|
|
47
|
+
options
|
|
48
|
+
.forEach((option) => {
|
|
49
|
+
process.stdout.write(` "${['"', '\\', '$', '`']
|
|
50
|
+
.reduce((option, char) => option.replaceAll(char, `\\${char}`), option)
|
|
51
|
+
}" `);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
process.stdout.write(')\n');
|
|
55
|
+
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (files) {
|
|
59
|
+
process.stdout.write('COMPREPLY+=($(compgen -f -- "${COMP_WORDS[COMP_CWORD]}"))\n');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
process.exit(0);
|
|
63
|
+
|
|
64
|
+
};
|
|
@@ -17,23 +17,75 @@ import {
|
|
|
17
17
|
|
|
18
18
|
import caseit from '@trenskow/caseit';
|
|
19
19
|
|
|
20
|
-
import
|
|
21
|
-
|
|
20
|
+
import bash from './bash.js';
|
|
22
21
|
import zsh from './zsh.js';
|
|
23
22
|
|
|
24
23
|
const supported = {
|
|
24
|
+
bash,
|
|
25
25
|
zsh
|
|
26
26
|
};
|
|
27
27
|
|
|
28
|
-
const argumentsParser = (
|
|
28
|
+
const argumentsParser = (shell) => {
|
|
29
|
+
|
|
30
|
+
if (!shell) return;
|
|
31
|
+
|
|
32
|
+
shell = supported[shell];
|
|
33
|
+
|
|
34
|
+
if (!shell) {
|
|
35
|
+
process.stderr.write(`Unsupported shell: ${shell}\n`);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const normalize = (items) => items
|
|
40
|
+
.map((item) => {
|
|
41
|
+
|
|
42
|
+
if (typeof item === 'string') {
|
|
43
|
+
return {
|
|
44
|
+
type: 'option',
|
|
45
|
+
argument: item
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return item;
|
|
50
|
+
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const filter = (items, current) => {
|
|
54
|
+
return normalize(items)
|
|
55
|
+
.filter((item) => {
|
|
56
|
+
switch (item.type) {
|
|
57
|
+
case 'option':
|
|
58
|
+
// fall through
|
|
59
|
+
case 'command':
|
|
60
|
+
return item.argument !== current && item.argument.startsWith(current);
|
|
61
|
+
default:
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const output = (items) => shell(
|
|
68
|
+
normalize(items)
|
|
69
|
+
.reduce((grouped, item) => {
|
|
70
|
+
|
|
71
|
+
grouped[item.type] = grouped[item.type] || [];
|
|
72
|
+
|
|
73
|
+
switch (item.type) {
|
|
74
|
+
case 'option':
|
|
75
|
+
// fall through
|
|
76
|
+
case 'command':
|
|
77
|
+
grouped[item.type].push(item);
|
|
78
|
+
break;
|
|
79
|
+
default:
|
|
80
|
+
}
|
|
29
81
|
|
|
30
|
-
|
|
82
|
+
return grouped;
|
|
31
83
|
|
|
32
|
-
|
|
84
|
+
}, {}));
|
|
33
85
|
|
|
34
86
|
const argumentsParser = (
|
|
35
87
|
{
|
|
36
|
-
args = process.argv.slice(
|
|
88
|
+
args = process.argv.slice(shell.argsOffset || 3),
|
|
37
89
|
argvLevel = 0,
|
|
38
90
|
placeholder = '<>',
|
|
39
91
|
command,
|
|
@@ -90,7 +142,8 @@ const argumentsParser = (output) => {
|
|
|
90
142
|
return output(Object.keys(commands)
|
|
91
143
|
.filter((command) => command.startsWith(tool))
|
|
92
144
|
.map((command) => ({
|
|
93
|
-
|
|
145
|
+
type: 'command',
|
|
146
|
+
argument: caseit(command, 'kebab'),
|
|
94
147
|
description: commands[command].description
|
|
95
148
|
})));
|
|
96
149
|
|
|
@@ -100,8 +153,7 @@ const argumentsParser = (output) => {
|
|
|
100
153
|
autocomplete
|
|
101
154
|
}, command) => {
|
|
102
155
|
|
|
103
|
-
const available = (await autocomplete?.(args[0] || '') || [])
|
|
104
|
-
.filter((item) => item !== args[0] && item.startsWith(args[0] || ''));
|
|
156
|
+
const available = filter(await autocomplete?.(args[0] || '') || []);
|
|
105
157
|
|
|
106
158
|
if (available.length) {
|
|
107
159
|
return output(available);
|
|
@@ -146,7 +198,9 @@ const argumentsParser = (output) => {
|
|
|
146
198
|
|
|
147
199
|
const {
|
|
148
200
|
variadic,
|
|
149
|
-
autocomplete =
|
|
201
|
+
autocomplete = () => [{
|
|
202
|
+
type: 'files'
|
|
203
|
+
}]
|
|
150
204
|
} = options.validationOptions;
|
|
151
205
|
|
|
152
206
|
const allKeyPaths = keyPaths(options.schema)
|
|
@@ -172,17 +226,19 @@ const argumentsParser = (output) => {
|
|
|
172
226
|
if (schemas[idx].enum) {
|
|
173
227
|
return Object.keys(schemas[idx].enum)
|
|
174
228
|
.filter((item) => caseit(key, 'kebab') !== item && item.startsWith(current))
|
|
175
|
-
.map((item) =>
|
|
229
|
+
.map((item) => ({
|
|
230
|
+
type: 'option',
|
|
231
|
+
argument: caseit(item, 'kebab')
|
|
232
|
+
}));
|
|
176
233
|
}
|
|
177
234
|
|
|
178
235
|
if (schemas[idx].type === String && !schemas[idx].autocomplete) {
|
|
179
|
-
return
|
|
180
|
-
|
|
181
|
-
|
|
236
|
+
return [{
|
|
237
|
+
type: 'files'
|
|
238
|
+
}];
|
|
182
239
|
}
|
|
183
240
|
|
|
184
|
-
return (await schemas[idx].autocomplete?.(current) || [])
|
|
185
|
-
.filter((item) => key !== item && item.startsWith(current));
|
|
241
|
+
return filter(await schemas[idx].autocomplete?.(current) || []);
|
|
186
242
|
|
|
187
243
|
} else {
|
|
188
244
|
|
|
@@ -193,8 +249,7 @@ const argumentsParser = (output) => {
|
|
|
193
249
|
if (!current.startsWith('-')) {
|
|
194
250
|
if (variadic === 'allow') {
|
|
195
251
|
items = items.concat(
|
|
196
|
-
(await autocomplete?.(current) || [])
|
|
197
|
-
.filter((item) => current !== item && item.startsWith(current)));
|
|
252
|
+
filter(await autocomplete?.(current) || []));
|
|
198
253
|
}
|
|
199
254
|
}
|
|
200
255
|
|
|
@@ -203,9 +258,9 @@ const argumentsParser = (output) => {
|
|
|
203
258
|
.filter((keyPath) => !spent.includes(keyPath))
|
|
204
259
|
.filter((keyPath) => key !== keyPath && keyPath.startsWith(key) && keyPaths(options.schema).get(keyPath).short)
|
|
205
260
|
.map((keyPath) => ({
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
261
|
+
type: 'option',
|
|
262
|
+
argument: `-${caseit(keyPaths(options.schema).get(keyPath).short, 'kebab')}`,
|
|
263
|
+
description: keyPaths(options.schema).get(keyPath).description
|
|
209
264
|
})));
|
|
210
265
|
|
|
211
266
|
}
|
|
@@ -215,9 +270,9 @@ const argumentsParser = (output) => {
|
|
|
215
270
|
.filter((keyPath) => !spent.includes(keyPath))
|
|
216
271
|
.filter((keyPath) => key !== keyPath && keyPath.startsWith(key))
|
|
217
272
|
.map((keyPath) => ({
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
273
|
+
type: 'option',
|
|
274
|
+
argument: `--${caseit(keyPath, 'kebab')}`,
|
|
275
|
+
description: keyPaths(options.schema).get(keyPath).description
|
|
221
276
|
})));
|
|
222
277
|
|
|
223
278
|
return items;
|
|
@@ -278,4 +333,3 @@ const argumentsParser = (output) => {
|
|
|
278
333
|
};
|
|
279
334
|
|
|
280
335
|
export default argumentsParser;
|
|
281
|
-
export * as buildIns from './build-ins/index.js';
|
package/lib/autocomplete/zsh.js
CHANGED
|
@@ -6,34 +6,46 @@
|
|
|
6
6
|
// See license in LICENSE.
|
|
7
7
|
//
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
export default (items) => {
|
|
10
10
|
|
|
11
|
-
const
|
|
11
|
+
const keys = Object.keys(items);
|
|
12
12
|
|
|
13
|
-
if (
|
|
13
|
+
if (keys.length === 0) {
|
|
14
14
|
process.exit(0);
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
keys
|
|
18
|
+
.forEach((key) => {
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
switch (key) {
|
|
21
|
+
case 'command':
|
|
22
|
+
// fall through
|
|
23
|
+
case 'option':
|
|
21
24
|
|
|
22
|
-
|
|
23
|
-
process.stdout.write(` "${command}"\n`);
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
25
|
+
process.stdout.write(`_describe '${key}s' '(\n`);
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
items[key]
|
|
28
|
+
.forEach((item) => {
|
|
28
29
|
|
|
29
|
-
|
|
30
|
+
const parts = [item.argument, item.description]
|
|
31
|
+
.filter((part) => part)
|
|
32
|
+
.map((part) => ['"', '\\', '$', '`']
|
|
33
|
+
.reduce((part, char) => part.replaceAll(char, `\\${char}`), part));
|
|
30
34
|
|
|
31
|
-
|
|
35
|
+
process.stdout.write(` "${parts.join(':')}"\n`);
|
|
32
36
|
|
|
33
|
-
|
|
37
|
+
});
|
|
34
38
|
|
|
35
|
-
|
|
39
|
+
process.stdout.write(')\'\n');
|
|
40
|
+
|
|
41
|
+
break;
|
|
42
|
+
case 'files':
|
|
43
|
+
process.stdout.write('_files\n');
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
36
46
|
|
|
37
|
-
|
|
47
|
+
});
|
|
38
48
|
|
|
39
|
-
|
|
49
|
+
process.exit(0);
|
|
50
|
+
|
|
51
|
+
};
|
package/lib/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
//
|
|
2
|
-
// files.js
|
|
3
|
-
// @trenskow/arguments-parser
|
|
4
|
-
//
|
|
5
|
-
// Created by Kristian Trenskow on 2026/08/02
|
|
6
|
-
// See license in LICENSE.
|
|
7
|
-
//
|
|
8
|
-
|
|
9
|
-
import {
|
|
10
|
-
join,
|
|
11
|
-
resolve
|
|
12
|
-
} from 'path';
|
|
13
|
-
|
|
14
|
-
import {
|
|
15
|
-
stat,
|
|
16
|
-
readdir
|
|
17
|
-
} from 'fs/promises';
|
|
18
|
-
|
|
19
|
-
import parallel from '@trenskow/parallel';
|
|
20
|
-
|
|
21
|
-
const safeStat = async (path) => {
|
|
22
|
-
try {
|
|
23
|
-
return await stat(path);
|
|
24
|
-
} catch (_) {
|
|
25
|
-
return undefined;
|
|
26
|
-
}
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
export default async (current = '') => {
|
|
30
|
-
|
|
31
|
-
if (current === '') {
|
|
32
|
-
return await parallel(((await readdir(process.cwd()))
|
|
33
|
-
.filter((file) => !file.startsWith('.')))
|
|
34
|
-
.map(async (file) => `${file}${(await safeStat(resolve(process.cwd(), file)))?.isDirectory() ? '/' : ''}`));
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const stat = await safeStat(
|
|
38
|
-
resolve(
|
|
39
|
-
process.cwd(),
|
|
40
|
-
current));
|
|
41
|
-
|
|
42
|
-
if (stat?.isDirectory()) {
|
|
43
|
-
|
|
44
|
-
current = current.endsWith('/') ? current : `${current}/`;
|
|
45
|
-
|
|
46
|
-
return (await parallel((await readdir(
|
|
47
|
-
resolve(
|
|
48
|
-
process.cwd(),
|
|
49
|
-
current)))
|
|
50
|
-
.map(async (file) => {
|
|
51
|
-
|
|
52
|
-
if ((await safeStat(resolve(process.cwd(), current, file)))?.isDirectory()) {
|
|
53
|
-
return `${file}/`;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
return file;
|
|
57
|
-
|
|
58
|
-
})))
|
|
59
|
-
.filter((file) => !file.startsWith('.'))
|
|
60
|
-
.map((file) => `${current}${file}`);
|
|
61
|
-
|
|
62
|
-
} else if (stat) {
|
|
63
|
-
return [];
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
return await parallel(((await readdir(join(current, '../')))
|
|
67
|
-
.filter((file) => !file.startsWith('.'))
|
|
68
|
-
.map((file) => join(current, '../', file)))
|
|
69
|
-
.map(async (file) => (await safeStat(file))?.isDirectory() ? `${file}/` : file));
|
|
70
|
-
|
|
71
|
-
};
|