breadc 0.8.1 → 0.8.3
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 +1 -1
- package/dist/index.cjs +172 -114
- package/dist/index.d.ts +6 -3
- package/dist/index.mjs +172 -114
- package/package.json +2 -3
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ Yet another Command Line Application Framework with fully strong **[TypeScript](
|
|
|
8
8
|
|
|
9
9
|
## Features
|
|
10
10
|
|
|
11
|
-
+ ⚡️ **Light-weight**: Only
|
|
11
|
+
+ ⚡️ **Light-weight**: Only 75 kB (Unpacked).
|
|
12
12
|
+ 📖 **East to Learn**: Breadc is basically compatible with [cac](https://github.com/cacjs/cac) and there are only 5 APIs for building a CLI application: `breadc`, `command`, `option`, `action`, `run`.
|
|
13
13
|
+ 💻 **TypeScript Infer**: IDE will automatically infer the type of your command action function.
|
|
14
14
|
|
package/dist/index.cjs
CHANGED
|
@@ -8,44 +8,71 @@ function makePluginContainer(plugins = []) {
|
|
|
8
8
|
const onPreCommand = {};
|
|
9
9
|
const onPostCommand = {};
|
|
10
10
|
for (const plugin of plugins) {
|
|
11
|
-
|
|
11
|
+
if (typeof plugin.onPreCommand === "function") {
|
|
12
|
+
const key = "*";
|
|
12
13
|
if (!(key in onPreCommand)) {
|
|
13
14
|
onPreCommand[key] = [];
|
|
14
15
|
}
|
|
15
|
-
onPreCommand[key].push(
|
|
16
|
+
onPreCommand[key].push(plugin.onPreCommand);
|
|
17
|
+
} else {
|
|
18
|
+
for (const [key, fn] of Object.entries(plugin.onPreCommand ?? {})) {
|
|
19
|
+
if (!(key in onPreCommand)) {
|
|
20
|
+
onPreCommand[key] = [];
|
|
21
|
+
}
|
|
22
|
+
onPreCommand[key].push(fn);
|
|
23
|
+
}
|
|
16
24
|
}
|
|
17
|
-
|
|
25
|
+
if (typeof plugin.onPostCommand === "function") {
|
|
26
|
+
const key = "*";
|
|
18
27
|
if (!(key in onPostCommand)) {
|
|
19
28
|
onPostCommand[key] = [];
|
|
20
29
|
}
|
|
21
|
-
onPostCommand[key].push(
|
|
30
|
+
onPostCommand[key].push(plugin.onPostCommand);
|
|
31
|
+
} else {
|
|
32
|
+
for (const [key, fn] of Object.entries(plugin.onPostCommand ?? {})) {
|
|
33
|
+
if (!(key in onPostCommand)) {
|
|
34
|
+
onPostCommand[key] = [];
|
|
35
|
+
}
|
|
36
|
+
onPostCommand[key].push(fn);
|
|
37
|
+
}
|
|
22
38
|
}
|
|
23
39
|
}
|
|
24
|
-
const run = async (container, command) => {
|
|
40
|
+
const run = async (container, command, result) => {
|
|
25
41
|
const prefix = command._arguments.filter((a) => a.type === "const").map((a) => a.name);
|
|
42
|
+
if (prefix.length === 0) {
|
|
43
|
+
prefix.push("_");
|
|
44
|
+
}
|
|
26
45
|
for (let i = 0; i <= prefix.length; i++) {
|
|
27
46
|
const key = i === 0 ? "*" : prefix.slice(0, i).map(
|
|
28
47
|
(t, idx) => idx === 0 ? t : t[0].toUpperCase() + t.slice(1)
|
|
29
48
|
).join("");
|
|
30
49
|
const fns = container[key];
|
|
31
50
|
if (fns && fns.length > 0) {
|
|
32
|
-
await Promise.all(fns.map((fn) => fn()));
|
|
51
|
+
await Promise.all(fns.map((fn) => fn(result)));
|
|
33
52
|
}
|
|
34
53
|
}
|
|
35
54
|
};
|
|
36
55
|
return {
|
|
37
56
|
async preRun(breadc) {
|
|
57
|
+
if (plugins.length === 0)
|
|
58
|
+
return;
|
|
38
59
|
for (const p of plugins) {
|
|
39
60
|
await p.onPreRun?.(breadc);
|
|
40
61
|
}
|
|
41
62
|
},
|
|
42
|
-
async preCommand(command) {
|
|
43
|
-
|
|
63
|
+
async preCommand(command, result) {
|
|
64
|
+
if (plugins.length === 0)
|
|
65
|
+
return;
|
|
66
|
+
await run(onPreCommand, command, result);
|
|
44
67
|
},
|
|
45
|
-
async postCommand(command) {
|
|
46
|
-
|
|
68
|
+
async postCommand(command, result) {
|
|
69
|
+
if (plugins.length === 0)
|
|
70
|
+
return;
|
|
71
|
+
await run(onPostCommand, command, result);
|
|
47
72
|
},
|
|
48
73
|
async postRun(breadc) {
|
|
74
|
+
if (plugins.length === 0)
|
|
75
|
+
return;
|
|
49
76
|
for (const p of plugins) {
|
|
50
77
|
await p.onPostRun?.(breadc);
|
|
51
78
|
}
|
|
@@ -101,6 +128,7 @@ class Token {
|
|
|
101
128
|
return this._type = "string";
|
|
102
129
|
}
|
|
103
130
|
}
|
|
131
|
+
/* c8 ignore next 1 */
|
|
104
132
|
}
|
|
105
133
|
class Lexer {
|
|
106
134
|
constructor(rawArgs) {
|
|
@@ -113,7 +141,7 @@ class Lexer {
|
|
|
113
141
|
return value ? new Token(value) : void 0;
|
|
114
142
|
}
|
|
115
143
|
hasNext() {
|
|
116
|
-
return this.cursor
|
|
144
|
+
return this.cursor < this.rawArgs.length;
|
|
117
145
|
}
|
|
118
146
|
peek() {
|
|
119
147
|
const value = this.rawArgs[this.cursor];
|
|
@@ -126,7 +154,7 @@ class Lexer {
|
|
|
126
154
|
const value = that.rawArgs[that.cursor];
|
|
127
155
|
that.cursor += 1;
|
|
128
156
|
return {
|
|
129
|
-
value: value ? new Token(value) : void 0,
|
|
157
|
+
value: value !== void 0 ? new Token(value) : void 0,
|
|
130
158
|
done: that.cursor > that.rawArgs.length
|
|
131
159
|
};
|
|
132
160
|
}
|
|
@@ -313,13 +341,13 @@ const initContextOptions = (options, context) => {
|
|
|
313
341
|
};
|
|
314
342
|
|
|
315
343
|
function makeCommand(format, config, root, container) {
|
|
316
|
-
let cursor = root;
|
|
317
344
|
const args = [];
|
|
318
345
|
const options = [];
|
|
319
346
|
const command = {
|
|
320
347
|
callback: void 0,
|
|
321
348
|
format,
|
|
322
349
|
description: config.description ?? "",
|
|
350
|
+
_default: false,
|
|
323
351
|
_arguments: args,
|
|
324
352
|
_options: options,
|
|
325
353
|
option(format2, _config, _config2 = {}) {
|
|
@@ -328,87 +356,72 @@ function makeCommand(format, config, root, container) {
|
|
|
328
356
|
options.push(option);
|
|
329
357
|
return command;
|
|
330
358
|
},
|
|
359
|
+
alias(format2) {
|
|
360
|
+
const aliasArgs = [];
|
|
361
|
+
const node2 = makeNode(aliasArgs);
|
|
362
|
+
function* g() {
|
|
363
|
+
for (const f of format2.split(" ")) {
|
|
364
|
+
yield { type: "const", name: f };
|
|
365
|
+
}
|
|
366
|
+
for (const a of args.filter((a2) => a2.type !== "const")) {
|
|
367
|
+
yield a;
|
|
368
|
+
}
|
|
369
|
+
return void 0;
|
|
370
|
+
}
|
|
371
|
+
insertTreeNode(aliasArgs, node2, g());
|
|
372
|
+
return command;
|
|
373
|
+
},
|
|
331
374
|
action(fn) {
|
|
332
|
-
command.callback = async (
|
|
333
|
-
await container.preCommand(command);
|
|
334
|
-
const result = await fn(...
|
|
335
|
-
|
|
375
|
+
command.callback = async (parsed) => {
|
|
376
|
+
await container.preCommand(command, parsed);
|
|
377
|
+
const result = await fn(...parsed.arguments, {
|
|
378
|
+
...parsed.options,
|
|
379
|
+
"--": parsed["--"]
|
|
380
|
+
});
|
|
381
|
+
await container.postCommand(command, parsed);
|
|
336
382
|
return result;
|
|
337
383
|
};
|
|
338
384
|
}
|
|
339
385
|
};
|
|
340
|
-
const node =
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
386
|
+
const node = makeNode(args);
|
|
387
|
+
insertTreeNode(args, node, parseCommandFormat(format));
|
|
388
|
+
return command;
|
|
389
|
+
function makeNode(args2) {
|
|
390
|
+
return makeTreeNode({
|
|
391
|
+
command,
|
|
392
|
+
init(context) {
|
|
393
|
+
initContextOptions(options, context);
|
|
394
|
+
},
|
|
395
|
+
finish(context) {
|
|
396
|
+
const rest = context.result["--"];
|
|
397
|
+
for (let i = 0; i < args2.length; i++) {
|
|
398
|
+
if (args2[i].type === "const") {
|
|
399
|
+
if (rest[i] !== args2[i].name) {
|
|
400
|
+
throw new ParseError(`Sub-command ${args2[i].name} mismatch`);
|
|
401
|
+
}
|
|
402
|
+
} else if (args2[i].type === "require") {
|
|
403
|
+
if (i >= rest.length) {
|
|
404
|
+
throw new ParseError(
|
|
405
|
+
`You must provide require argument ${args2[i].name}`
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
context.result.arguments.push(rest[i]);
|
|
409
|
+
} else if (args2[i].type === "optional") {
|
|
410
|
+
context.result.arguments.push(rest[i]);
|
|
411
|
+
} else if (args2[i].type === "rest") {
|
|
412
|
+
context.result.arguments.push(rest.splice(i));
|
|
357
413
|
}
|
|
358
|
-
context.result.arguments.push(rest[i]);
|
|
359
|
-
} else if (args[i].type === "optional") {
|
|
360
|
-
context.result.arguments.push(rest[i]);
|
|
361
|
-
} else if (args[i].type === "rest") {
|
|
362
|
-
context.result.arguments.push(rest.splice(i));
|
|
363
414
|
}
|
|
415
|
+
context.result["--"] = rest.splice(args2.length);
|
|
364
416
|
}
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
if (
|
|
372
|
-
|
|
373
|
-
throw new BreadcError(
|
|
374
|
-
`Required arguments should be placed before optional or rest arguments`
|
|
375
|
-
);
|
|
376
|
-
}
|
|
377
|
-
const start = i;
|
|
378
|
-
while (i < format.length && format[i] !== ">") {
|
|
379
|
-
i++;
|
|
380
|
-
}
|
|
381
|
-
const name = format.slice(start + 1, i);
|
|
382
|
-
state = 1;
|
|
383
|
-
args.push({ type: "require", name });
|
|
384
|
-
} else if (format[i] === "[") {
|
|
385
|
-
if (state !== 0 && state !== 1) {
|
|
386
|
-
throw new BreadcError(
|
|
387
|
-
`There is at most one optional or rest arguments`
|
|
388
|
-
);
|
|
389
|
-
}
|
|
390
|
-
const start = i;
|
|
391
|
-
while (i < format.length && format[i] !== "]") {
|
|
392
|
-
i++;
|
|
393
|
-
}
|
|
394
|
-
const name = format.slice(start + 1, i);
|
|
395
|
-
state = 2;
|
|
396
|
-
if (name.startsWith("...")) {
|
|
397
|
-
args.push({ type: "rest", name });
|
|
398
|
-
} else {
|
|
399
|
-
args.push({ type: "optional", name });
|
|
400
|
-
}
|
|
401
|
-
} else if (format[i] !== " ") {
|
|
402
|
-
if (state !== 0) {
|
|
403
|
-
throw new BreadcError(
|
|
404
|
-
`Sub-command should be placed at the beginning`
|
|
405
|
-
);
|
|
406
|
-
}
|
|
407
|
-
const start = i;
|
|
408
|
-
while (i < format.length && format[i] !== " ") {
|
|
409
|
-
i++;
|
|
410
|
-
}
|
|
411
|
-
const name = format.slice(start, i);
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
function insertTreeNode(args2, node2, parsed) {
|
|
420
|
+
let cursor = root;
|
|
421
|
+
for (const arg of parsed) {
|
|
422
|
+
args2.push(arg);
|
|
423
|
+
if (arg.type === "const") {
|
|
424
|
+
const name = arg.name;
|
|
412
425
|
if (cursor.children.has(name)) {
|
|
413
426
|
cursor = cursor.children.get(name);
|
|
414
427
|
} else {
|
|
@@ -431,28 +444,74 @@ function makeCommand(format, config, root, container) {
|
|
|
431
444
|
cursor.children.set(name, internalNode);
|
|
432
445
|
cursor = internalNode;
|
|
433
446
|
}
|
|
434
|
-
state = 0;
|
|
435
|
-
args.push({ type: "const", name });
|
|
436
447
|
}
|
|
437
448
|
}
|
|
438
449
|
cursor.command = command;
|
|
439
450
|
if (cursor !== root) {
|
|
440
451
|
for (const [key, value] of cursor.children) {
|
|
441
|
-
|
|
452
|
+
node2.children.set(key, value);
|
|
442
453
|
}
|
|
443
|
-
cursor.children =
|
|
444
|
-
cursor.next =
|
|
445
|
-
cursor.init =
|
|
446
|
-
cursor.finish =
|
|
454
|
+
cursor.children = node2.children;
|
|
455
|
+
cursor.next = node2.next;
|
|
456
|
+
cursor.init = node2.init;
|
|
457
|
+
cursor.finish = node2.finish;
|
|
447
458
|
} else {
|
|
448
|
-
|
|
459
|
+
command._default = true;
|
|
460
|
+
cursor.finish = node2.finish;
|
|
449
461
|
}
|
|
450
462
|
}
|
|
451
|
-
|
|
463
|
+
}
|
|
464
|
+
function* parseCommandFormat(format) {
|
|
465
|
+
let state = 0;
|
|
466
|
+
for (let i = 0; i < format.length; i++) {
|
|
467
|
+
if (format[i] === "<") {
|
|
468
|
+
if (state !== 0 && state !== 1) {
|
|
469
|
+
throw new BreadcError(
|
|
470
|
+
`Required arguments should be placed before optional or rest arguments`
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
const start = i;
|
|
474
|
+
while (i < format.length && format[i] !== ">") {
|
|
475
|
+
i++;
|
|
476
|
+
}
|
|
477
|
+
const name = format.slice(start + 1, i);
|
|
478
|
+
state = 1;
|
|
479
|
+
yield { type: "require", name };
|
|
480
|
+
} else if (format[i] === "[") {
|
|
481
|
+
if (state !== 0 && state !== 1) {
|
|
482
|
+
throw new BreadcError(
|
|
483
|
+
`There is at most one optional or rest arguments`
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
const start = i;
|
|
487
|
+
while (i < format.length && format[i] !== "]") {
|
|
488
|
+
i++;
|
|
489
|
+
}
|
|
490
|
+
const name = format.slice(start + 1, i);
|
|
491
|
+
state = 2;
|
|
492
|
+
if (name.startsWith("...")) {
|
|
493
|
+
yield { type: "rest", name };
|
|
494
|
+
} else {
|
|
495
|
+
yield { type: "optional", name };
|
|
496
|
+
}
|
|
497
|
+
} else if (format[i] !== " ") {
|
|
498
|
+
if (state !== 0) {
|
|
499
|
+
throw new BreadcError(`Sub-command should be placed at the beginning`);
|
|
500
|
+
}
|
|
501
|
+
const start = i;
|
|
502
|
+
while (i < format.length && format[i] !== " ") {
|
|
503
|
+
i++;
|
|
504
|
+
}
|
|
505
|
+
const name = format.slice(start, i);
|
|
506
|
+
state = 0;
|
|
507
|
+
yield { type: "const", name };
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
return void 0;
|
|
452
511
|
}
|
|
453
512
|
function makeVersionCommand(name, config) {
|
|
454
513
|
const command = {
|
|
455
|
-
callback() {
|
|
514
|
+
async callback() {
|
|
456
515
|
const text = `${name}/${config.version ? config.version : "unknown"}`;
|
|
457
516
|
console.log(text);
|
|
458
517
|
return text;
|
|
@@ -461,11 +520,12 @@ function makeVersionCommand(name, config) {
|
|
|
461
520
|
description: "Print version",
|
|
462
521
|
_arguments: [],
|
|
463
522
|
_options: [],
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
523
|
+
// @ts-ignore
|
|
524
|
+
option: void 0,
|
|
525
|
+
// @ts-ignore
|
|
526
|
+
alias: void 0,
|
|
527
|
+
// @ts-ignore
|
|
528
|
+
action: void 0
|
|
469
529
|
};
|
|
470
530
|
const node = makeTreeNode({
|
|
471
531
|
command,
|
|
@@ -527,9 +587,9 @@ function makeHelpCommand(name, config) {
|
|
|
527
587
|
return commands;
|
|
528
588
|
}
|
|
529
589
|
const command = {
|
|
530
|
-
callback(
|
|
531
|
-
const context =
|
|
532
|
-
const cursor =
|
|
590
|
+
async callback(parsed) {
|
|
591
|
+
const context = parsed.options.__context__;
|
|
592
|
+
const cursor = parsed.options.__cursor__;
|
|
533
593
|
const output = [
|
|
534
594
|
`${name}/${config.version ? config.version : "unknown"}`,
|
|
535
595
|
() => {
|
|
@@ -570,11 +630,12 @@ function makeHelpCommand(name, config) {
|
|
|
570
630
|
description: "Print help",
|
|
571
631
|
_arguments: [],
|
|
572
632
|
_options: [],
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
633
|
+
// @ts-ignore
|
|
634
|
+
option: void 0,
|
|
635
|
+
// @ts-ignore
|
|
636
|
+
alias: void 0,
|
|
637
|
+
// @ts-ignore
|
|
638
|
+
action: void 0
|
|
578
639
|
};
|
|
579
640
|
const node = makeTreeNode({
|
|
580
641
|
command,
|
|
@@ -627,7 +688,7 @@ function breadc(name, config = {}) {
|
|
|
627
688
|
command(text, _config = {}) {
|
|
628
689
|
const config2 = typeof _config === "string" ? { description: _config } : _config;
|
|
629
690
|
const command = makeCommand(text, config2, root, container);
|
|
630
|
-
if (command.
|
|
691
|
+
if (command._default) {
|
|
631
692
|
defaultCommand = command;
|
|
632
693
|
}
|
|
633
694
|
return command;
|
|
@@ -642,10 +703,7 @@ function breadc(name, config = {}) {
|
|
|
642
703
|
if (command) {
|
|
643
704
|
if (command.callback) {
|
|
644
705
|
await container.preRun(breadc2);
|
|
645
|
-
const r = command.callback(
|
|
646
|
-
...result.options,
|
|
647
|
-
"--": result["--"]
|
|
648
|
-
});
|
|
706
|
+
const r = await command.callback(result);
|
|
649
707
|
await container.postRun(breadc2);
|
|
650
708
|
return r;
|
|
651
709
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -101,13 +101,15 @@ interface Breadc<GlobalOption extends object = {}> {
|
|
|
101
101
|
run<T = any>(args: string[]): Promise<T>;
|
|
102
102
|
}
|
|
103
103
|
interface Command<F extends string = string, AT extends any[] = ExtractCommand<F>, CommandOption extends object = {}, GlobalOption extends object = {}> {
|
|
104
|
-
callback?:
|
|
104
|
+
callback?: (result: ParseResult) => Promise<any>;
|
|
105
105
|
format: F;
|
|
106
106
|
description: string;
|
|
107
|
+
_default: boolean;
|
|
107
108
|
_arguments: Argument[];
|
|
108
109
|
_options: Option[];
|
|
109
110
|
option<OF extends string = string, OT extends string | boolean = ExtractOptionType<OF>, OR extends any = ExtractOptionType<OF>>(format: OF, description?: string, option?: OptionOption<OT, OR>): Command<F, AT, CommandOption & ExtractOption<OF, OR>, GlobalOption>;
|
|
110
111
|
option<OF extends string = string, OT extends string | boolean = ExtractOptionType<OF>, OR extends any = ExtractOptionType<OF>>(format: OF, option?: OptionOption<OT, OR>): Command<F, AT, CommandOption & ExtractOption<OF, OR>, GlobalOption>;
|
|
112
|
+
alias(format: string): Command<F, AT, CommandOption, GlobalOption>;
|
|
111
113
|
action(fn: ActionFn<AT, CommandOption & GlobalOption>): void;
|
|
112
114
|
}
|
|
113
115
|
interface CommandOption {
|
|
@@ -133,10 +135,11 @@ interface OptionOption<T extends string | boolean, R extends any = T> {
|
|
|
133
135
|
default?: T;
|
|
134
136
|
cast?: (value: T) => R;
|
|
135
137
|
}
|
|
138
|
+
type CommandHookFn = (result: ParseResult) => void | Promise<void>;
|
|
136
139
|
interface Plugin {
|
|
137
140
|
onPreRun(breadc: Breadc): void | Promise<void>;
|
|
138
|
-
onPreCommand: Record<string,
|
|
139
|
-
onPostCommand: Record<string,
|
|
141
|
+
onPreCommand: Record<string, CommandHookFn> | CommandHookFn;
|
|
142
|
+
onPostCommand: Record<string, CommandHookFn> | CommandHookFn;
|
|
140
143
|
onPostRun(breadc: Breadc): void | Promise<void>;
|
|
141
144
|
}
|
|
142
145
|
|
package/dist/index.mjs
CHANGED
|
@@ -4,44 +4,71 @@ function makePluginContainer(plugins = []) {
|
|
|
4
4
|
const onPreCommand = {};
|
|
5
5
|
const onPostCommand = {};
|
|
6
6
|
for (const plugin of plugins) {
|
|
7
|
-
|
|
7
|
+
if (typeof plugin.onPreCommand === "function") {
|
|
8
|
+
const key = "*";
|
|
8
9
|
if (!(key in onPreCommand)) {
|
|
9
10
|
onPreCommand[key] = [];
|
|
10
11
|
}
|
|
11
|
-
onPreCommand[key].push(
|
|
12
|
+
onPreCommand[key].push(plugin.onPreCommand);
|
|
13
|
+
} else {
|
|
14
|
+
for (const [key, fn] of Object.entries(plugin.onPreCommand ?? {})) {
|
|
15
|
+
if (!(key in onPreCommand)) {
|
|
16
|
+
onPreCommand[key] = [];
|
|
17
|
+
}
|
|
18
|
+
onPreCommand[key].push(fn);
|
|
19
|
+
}
|
|
12
20
|
}
|
|
13
|
-
|
|
21
|
+
if (typeof plugin.onPostCommand === "function") {
|
|
22
|
+
const key = "*";
|
|
14
23
|
if (!(key in onPostCommand)) {
|
|
15
24
|
onPostCommand[key] = [];
|
|
16
25
|
}
|
|
17
|
-
onPostCommand[key].push(
|
|
26
|
+
onPostCommand[key].push(plugin.onPostCommand);
|
|
27
|
+
} else {
|
|
28
|
+
for (const [key, fn] of Object.entries(plugin.onPostCommand ?? {})) {
|
|
29
|
+
if (!(key in onPostCommand)) {
|
|
30
|
+
onPostCommand[key] = [];
|
|
31
|
+
}
|
|
32
|
+
onPostCommand[key].push(fn);
|
|
33
|
+
}
|
|
18
34
|
}
|
|
19
35
|
}
|
|
20
|
-
const run = async (container, command) => {
|
|
36
|
+
const run = async (container, command, result) => {
|
|
21
37
|
const prefix = command._arguments.filter((a) => a.type === "const").map((a) => a.name);
|
|
38
|
+
if (prefix.length === 0) {
|
|
39
|
+
prefix.push("_");
|
|
40
|
+
}
|
|
22
41
|
for (let i = 0; i <= prefix.length; i++) {
|
|
23
42
|
const key = i === 0 ? "*" : prefix.slice(0, i).map(
|
|
24
43
|
(t, idx) => idx === 0 ? t : t[0].toUpperCase() + t.slice(1)
|
|
25
44
|
).join("");
|
|
26
45
|
const fns = container[key];
|
|
27
46
|
if (fns && fns.length > 0) {
|
|
28
|
-
await Promise.all(fns.map((fn) => fn()));
|
|
47
|
+
await Promise.all(fns.map((fn) => fn(result)));
|
|
29
48
|
}
|
|
30
49
|
}
|
|
31
50
|
};
|
|
32
51
|
return {
|
|
33
52
|
async preRun(breadc) {
|
|
53
|
+
if (plugins.length === 0)
|
|
54
|
+
return;
|
|
34
55
|
for (const p of plugins) {
|
|
35
56
|
await p.onPreRun?.(breadc);
|
|
36
57
|
}
|
|
37
58
|
},
|
|
38
|
-
async preCommand(command) {
|
|
39
|
-
|
|
59
|
+
async preCommand(command, result) {
|
|
60
|
+
if (plugins.length === 0)
|
|
61
|
+
return;
|
|
62
|
+
await run(onPreCommand, command, result);
|
|
40
63
|
},
|
|
41
|
-
async postCommand(command) {
|
|
42
|
-
|
|
64
|
+
async postCommand(command, result) {
|
|
65
|
+
if (plugins.length === 0)
|
|
66
|
+
return;
|
|
67
|
+
await run(onPostCommand, command, result);
|
|
43
68
|
},
|
|
44
69
|
async postRun(breadc) {
|
|
70
|
+
if (plugins.length === 0)
|
|
71
|
+
return;
|
|
45
72
|
for (const p of plugins) {
|
|
46
73
|
await p.onPostRun?.(breadc);
|
|
47
74
|
}
|
|
@@ -97,6 +124,7 @@ class Token {
|
|
|
97
124
|
return this._type = "string";
|
|
98
125
|
}
|
|
99
126
|
}
|
|
127
|
+
/* c8 ignore next 1 */
|
|
100
128
|
}
|
|
101
129
|
class Lexer {
|
|
102
130
|
constructor(rawArgs) {
|
|
@@ -109,7 +137,7 @@ class Lexer {
|
|
|
109
137
|
return value ? new Token(value) : void 0;
|
|
110
138
|
}
|
|
111
139
|
hasNext() {
|
|
112
|
-
return this.cursor
|
|
140
|
+
return this.cursor < this.rawArgs.length;
|
|
113
141
|
}
|
|
114
142
|
peek() {
|
|
115
143
|
const value = this.rawArgs[this.cursor];
|
|
@@ -122,7 +150,7 @@ class Lexer {
|
|
|
122
150
|
const value = that.rawArgs[that.cursor];
|
|
123
151
|
that.cursor += 1;
|
|
124
152
|
return {
|
|
125
|
-
value: value ? new Token(value) : void 0,
|
|
153
|
+
value: value !== void 0 ? new Token(value) : void 0,
|
|
126
154
|
done: that.cursor > that.rawArgs.length
|
|
127
155
|
};
|
|
128
156
|
}
|
|
@@ -309,13 +337,13 @@ const initContextOptions = (options, context) => {
|
|
|
309
337
|
};
|
|
310
338
|
|
|
311
339
|
function makeCommand(format, config, root, container) {
|
|
312
|
-
let cursor = root;
|
|
313
340
|
const args = [];
|
|
314
341
|
const options = [];
|
|
315
342
|
const command = {
|
|
316
343
|
callback: void 0,
|
|
317
344
|
format,
|
|
318
345
|
description: config.description ?? "",
|
|
346
|
+
_default: false,
|
|
319
347
|
_arguments: args,
|
|
320
348
|
_options: options,
|
|
321
349
|
option(format2, _config, _config2 = {}) {
|
|
@@ -324,87 +352,72 @@ function makeCommand(format, config, root, container) {
|
|
|
324
352
|
options.push(option);
|
|
325
353
|
return command;
|
|
326
354
|
},
|
|
355
|
+
alias(format2) {
|
|
356
|
+
const aliasArgs = [];
|
|
357
|
+
const node2 = makeNode(aliasArgs);
|
|
358
|
+
function* g() {
|
|
359
|
+
for (const f of format2.split(" ")) {
|
|
360
|
+
yield { type: "const", name: f };
|
|
361
|
+
}
|
|
362
|
+
for (const a of args.filter((a2) => a2.type !== "const")) {
|
|
363
|
+
yield a;
|
|
364
|
+
}
|
|
365
|
+
return void 0;
|
|
366
|
+
}
|
|
367
|
+
insertTreeNode(aliasArgs, node2, g());
|
|
368
|
+
return command;
|
|
369
|
+
},
|
|
327
370
|
action(fn) {
|
|
328
|
-
command.callback = async (
|
|
329
|
-
await container.preCommand(command);
|
|
330
|
-
const result = await fn(...
|
|
331
|
-
|
|
371
|
+
command.callback = async (parsed) => {
|
|
372
|
+
await container.preCommand(command, parsed);
|
|
373
|
+
const result = await fn(...parsed.arguments, {
|
|
374
|
+
...parsed.options,
|
|
375
|
+
"--": parsed["--"]
|
|
376
|
+
});
|
|
377
|
+
await container.postCommand(command, parsed);
|
|
332
378
|
return result;
|
|
333
379
|
};
|
|
334
380
|
}
|
|
335
381
|
};
|
|
336
|
-
const node =
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
382
|
+
const node = makeNode(args);
|
|
383
|
+
insertTreeNode(args, node, parseCommandFormat(format));
|
|
384
|
+
return command;
|
|
385
|
+
function makeNode(args2) {
|
|
386
|
+
return makeTreeNode({
|
|
387
|
+
command,
|
|
388
|
+
init(context) {
|
|
389
|
+
initContextOptions(options, context);
|
|
390
|
+
},
|
|
391
|
+
finish(context) {
|
|
392
|
+
const rest = context.result["--"];
|
|
393
|
+
for (let i = 0; i < args2.length; i++) {
|
|
394
|
+
if (args2[i].type === "const") {
|
|
395
|
+
if (rest[i] !== args2[i].name) {
|
|
396
|
+
throw new ParseError(`Sub-command ${args2[i].name} mismatch`);
|
|
397
|
+
}
|
|
398
|
+
} else if (args2[i].type === "require") {
|
|
399
|
+
if (i >= rest.length) {
|
|
400
|
+
throw new ParseError(
|
|
401
|
+
`You must provide require argument ${args2[i].name}`
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
context.result.arguments.push(rest[i]);
|
|
405
|
+
} else if (args2[i].type === "optional") {
|
|
406
|
+
context.result.arguments.push(rest[i]);
|
|
407
|
+
} else if (args2[i].type === "rest") {
|
|
408
|
+
context.result.arguments.push(rest.splice(i));
|
|
353
409
|
}
|
|
354
|
-
context.result.arguments.push(rest[i]);
|
|
355
|
-
} else if (args[i].type === "optional") {
|
|
356
|
-
context.result.arguments.push(rest[i]);
|
|
357
|
-
} else if (args[i].type === "rest") {
|
|
358
|
-
context.result.arguments.push(rest.splice(i));
|
|
359
410
|
}
|
|
411
|
+
context.result["--"] = rest.splice(args2.length);
|
|
360
412
|
}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
if (
|
|
368
|
-
|
|
369
|
-
throw new BreadcError(
|
|
370
|
-
`Required arguments should be placed before optional or rest arguments`
|
|
371
|
-
);
|
|
372
|
-
}
|
|
373
|
-
const start = i;
|
|
374
|
-
while (i < format.length && format[i] !== ">") {
|
|
375
|
-
i++;
|
|
376
|
-
}
|
|
377
|
-
const name = format.slice(start + 1, i);
|
|
378
|
-
state = 1;
|
|
379
|
-
args.push({ type: "require", name });
|
|
380
|
-
} else if (format[i] === "[") {
|
|
381
|
-
if (state !== 0 && state !== 1) {
|
|
382
|
-
throw new BreadcError(
|
|
383
|
-
`There is at most one optional or rest arguments`
|
|
384
|
-
);
|
|
385
|
-
}
|
|
386
|
-
const start = i;
|
|
387
|
-
while (i < format.length && format[i] !== "]") {
|
|
388
|
-
i++;
|
|
389
|
-
}
|
|
390
|
-
const name = format.slice(start + 1, i);
|
|
391
|
-
state = 2;
|
|
392
|
-
if (name.startsWith("...")) {
|
|
393
|
-
args.push({ type: "rest", name });
|
|
394
|
-
} else {
|
|
395
|
-
args.push({ type: "optional", name });
|
|
396
|
-
}
|
|
397
|
-
} else if (format[i] !== " ") {
|
|
398
|
-
if (state !== 0) {
|
|
399
|
-
throw new BreadcError(
|
|
400
|
-
`Sub-command should be placed at the beginning`
|
|
401
|
-
);
|
|
402
|
-
}
|
|
403
|
-
const start = i;
|
|
404
|
-
while (i < format.length && format[i] !== " ") {
|
|
405
|
-
i++;
|
|
406
|
-
}
|
|
407
|
-
const name = format.slice(start, i);
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
function insertTreeNode(args2, node2, parsed) {
|
|
416
|
+
let cursor = root;
|
|
417
|
+
for (const arg of parsed) {
|
|
418
|
+
args2.push(arg);
|
|
419
|
+
if (arg.type === "const") {
|
|
420
|
+
const name = arg.name;
|
|
408
421
|
if (cursor.children.has(name)) {
|
|
409
422
|
cursor = cursor.children.get(name);
|
|
410
423
|
} else {
|
|
@@ -427,28 +440,74 @@ function makeCommand(format, config, root, container) {
|
|
|
427
440
|
cursor.children.set(name, internalNode);
|
|
428
441
|
cursor = internalNode;
|
|
429
442
|
}
|
|
430
|
-
state = 0;
|
|
431
|
-
args.push({ type: "const", name });
|
|
432
443
|
}
|
|
433
444
|
}
|
|
434
445
|
cursor.command = command;
|
|
435
446
|
if (cursor !== root) {
|
|
436
447
|
for (const [key, value] of cursor.children) {
|
|
437
|
-
|
|
448
|
+
node2.children.set(key, value);
|
|
438
449
|
}
|
|
439
|
-
cursor.children =
|
|
440
|
-
cursor.next =
|
|
441
|
-
cursor.init =
|
|
442
|
-
cursor.finish =
|
|
450
|
+
cursor.children = node2.children;
|
|
451
|
+
cursor.next = node2.next;
|
|
452
|
+
cursor.init = node2.init;
|
|
453
|
+
cursor.finish = node2.finish;
|
|
443
454
|
} else {
|
|
444
|
-
|
|
455
|
+
command._default = true;
|
|
456
|
+
cursor.finish = node2.finish;
|
|
445
457
|
}
|
|
446
458
|
}
|
|
447
|
-
|
|
459
|
+
}
|
|
460
|
+
function* parseCommandFormat(format) {
|
|
461
|
+
let state = 0;
|
|
462
|
+
for (let i = 0; i < format.length; i++) {
|
|
463
|
+
if (format[i] === "<") {
|
|
464
|
+
if (state !== 0 && state !== 1) {
|
|
465
|
+
throw new BreadcError(
|
|
466
|
+
`Required arguments should be placed before optional or rest arguments`
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
const start = i;
|
|
470
|
+
while (i < format.length && format[i] !== ">") {
|
|
471
|
+
i++;
|
|
472
|
+
}
|
|
473
|
+
const name = format.slice(start + 1, i);
|
|
474
|
+
state = 1;
|
|
475
|
+
yield { type: "require", name };
|
|
476
|
+
} else if (format[i] === "[") {
|
|
477
|
+
if (state !== 0 && state !== 1) {
|
|
478
|
+
throw new BreadcError(
|
|
479
|
+
`There is at most one optional or rest arguments`
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
const start = i;
|
|
483
|
+
while (i < format.length && format[i] !== "]") {
|
|
484
|
+
i++;
|
|
485
|
+
}
|
|
486
|
+
const name = format.slice(start + 1, i);
|
|
487
|
+
state = 2;
|
|
488
|
+
if (name.startsWith("...")) {
|
|
489
|
+
yield { type: "rest", name };
|
|
490
|
+
} else {
|
|
491
|
+
yield { type: "optional", name };
|
|
492
|
+
}
|
|
493
|
+
} else if (format[i] !== " ") {
|
|
494
|
+
if (state !== 0) {
|
|
495
|
+
throw new BreadcError(`Sub-command should be placed at the beginning`);
|
|
496
|
+
}
|
|
497
|
+
const start = i;
|
|
498
|
+
while (i < format.length && format[i] !== " ") {
|
|
499
|
+
i++;
|
|
500
|
+
}
|
|
501
|
+
const name = format.slice(start, i);
|
|
502
|
+
state = 0;
|
|
503
|
+
yield { type: "const", name };
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
return void 0;
|
|
448
507
|
}
|
|
449
508
|
function makeVersionCommand(name, config) {
|
|
450
509
|
const command = {
|
|
451
|
-
callback() {
|
|
510
|
+
async callback() {
|
|
452
511
|
const text = `${name}/${config.version ? config.version : "unknown"}`;
|
|
453
512
|
console.log(text);
|
|
454
513
|
return text;
|
|
@@ -457,11 +516,12 @@ function makeVersionCommand(name, config) {
|
|
|
457
516
|
description: "Print version",
|
|
458
517
|
_arguments: [],
|
|
459
518
|
_options: [],
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
519
|
+
// @ts-ignore
|
|
520
|
+
option: void 0,
|
|
521
|
+
// @ts-ignore
|
|
522
|
+
alias: void 0,
|
|
523
|
+
// @ts-ignore
|
|
524
|
+
action: void 0
|
|
465
525
|
};
|
|
466
526
|
const node = makeTreeNode({
|
|
467
527
|
command,
|
|
@@ -523,9 +583,9 @@ function makeHelpCommand(name, config) {
|
|
|
523
583
|
return commands;
|
|
524
584
|
}
|
|
525
585
|
const command = {
|
|
526
|
-
callback(
|
|
527
|
-
const context =
|
|
528
|
-
const cursor =
|
|
586
|
+
async callback(parsed) {
|
|
587
|
+
const context = parsed.options.__context__;
|
|
588
|
+
const cursor = parsed.options.__cursor__;
|
|
529
589
|
const output = [
|
|
530
590
|
`${name}/${config.version ? config.version : "unknown"}`,
|
|
531
591
|
() => {
|
|
@@ -566,11 +626,12 @@ function makeHelpCommand(name, config) {
|
|
|
566
626
|
description: "Print help",
|
|
567
627
|
_arguments: [],
|
|
568
628
|
_options: [],
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
629
|
+
// @ts-ignore
|
|
630
|
+
option: void 0,
|
|
631
|
+
// @ts-ignore
|
|
632
|
+
alias: void 0,
|
|
633
|
+
// @ts-ignore
|
|
634
|
+
action: void 0
|
|
574
635
|
};
|
|
575
636
|
const node = makeTreeNode({
|
|
576
637
|
command,
|
|
@@ -623,7 +684,7 @@ function breadc(name, config = {}) {
|
|
|
623
684
|
command(text, _config = {}) {
|
|
624
685
|
const config2 = typeof _config === "string" ? { description: _config } : _config;
|
|
625
686
|
const command = makeCommand(text, config2, root, container);
|
|
626
|
-
if (command.
|
|
687
|
+
if (command._default) {
|
|
627
688
|
defaultCommand = command;
|
|
628
689
|
}
|
|
629
690
|
return command;
|
|
@@ -638,10 +699,7 @@ function breadc(name, config = {}) {
|
|
|
638
699
|
if (command) {
|
|
639
700
|
if (command.callback) {
|
|
640
701
|
await container.preRun(breadc2);
|
|
641
|
-
const r = command.callback(
|
|
642
|
-
...result.options,
|
|
643
|
-
"--": result["--"]
|
|
644
|
-
});
|
|
702
|
+
const r = await command.callback(result);
|
|
645
703
|
await container.postRun(breadc2);
|
|
646
704
|
return r;
|
|
647
705
|
}
|
package/package.json
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "breadc",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.3",
|
|
4
4
|
"description": "Yet another Command Line Application Framework with fully strong TypeScript support",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"breadc",
|
|
7
7
|
"cli",
|
|
8
8
|
"framework",
|
|
9
9
|
"command-line",
|
|
10
|
-
"minimist",
|
|
11
10
|
"typescript"
|
|
12
11
|
],
|
|
13
12
|
"homepage": "https://github.com/yjl9903/Breadc#readme",
|
|
@@ -35,7 +34,7 @@
|
|
|
35
34
|
"dist"
|
|
36
35
|
],
|
|
37
36
|
"dependencies": {
|
|
38
|
-
"@breadc/color": "0.8.
|
|
37
|
+
"@breadc/color": "0.8.3"
|
|
39
38
|
},
|
|
40
39
|
"devDependencies": {
|
|
41
40
|
"@types/node": "^18.11.18",
|