breadc 0.8.8 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1,94 +1,86 @@
1
1
  'use strict';
2
2
 
3
- Object.defineProperty(exports, '__esModule', { value: true });
4
-
5
3
  const color = require('@breadc/color');
6
4
 
7
- function makePluginContainer(plugins = []) {
8
- const onPreCommand = {};
9
- const onPostCommand = {};
10
- for (const plugin of plugins) {
11
- if (typeof plugin.onPreCommand === "function") {
12
- const key = "*";
13
- if (!(key in onPreCommand)) {
14
- onPreCommand[key] = [];
15
- }
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
- }
5
+ class BreadcError extends Error {
6
+ }
7
+ class ParseError extends Error {
8
+ }
9
+
10
+ function camelCase(text) {
11
+ return text.split("-").map((t, idx) => idx === 0 ? t : t[0].toUpperCase() + t.slice(1)).join("");
12
+ }
13
+ function twoColumn(texts, split = " ") {
14
+ const left = padRight(texts.map((t) => t[0]));
15
+ return left.map((l, idx) => l + split + texts[idx][1]);
16
+ }
17
+ function padRight(texts, fill = " ") {
18
+ const length = texts.map((t) => t.length).reduce((max, l) => Math.max(max, l), 0);
19
+ return texts.map((t) => t + fill.repeat(length - t.length));
20
+ }
21
+
22
+ const OptionRE = /^(-[a-zA-Z], )?--([a-zA-Z0-9\-]+)( <[a-zA-Z0-9\-]+>)?$/;
23
+ function makeOption(format, config = { default: void 0 }) {
24
+ let name = "";
25
+ let short = void 0;
26
+ const match = OptionRE.exec(format);
27
+ if (match) {
28
+ name = match[2];
29
+ if (match[1]) {
30
+ short = match[1][1];
24
31
  }
25
- if (typeof plugin.onPostCommand === "function") {
26
- const key = "*";
27
- if (!(key in onPostCommand)) {
28
- onPostCommand[key] = [];
32
+ if (match[3]) {
33
+ if (name.startsWith("no-")) {
34
+ throw new BreadcError(`Can not parse option format (${format})`);
29
35
  }
30
- onPostCommand[key].push(plugin.onPostCommand);
36
+ const initial = config.default ?? void 0;
37
+ return {
38
+ format,
39
+ type: "string",
40
+ name,
41
+ short,
42
+ description: config.description ?? "",
43
+ order: 0,
44
+ // @ts-ignore
45
+ initial: config.cast ? config.cast(initial) : initial,
46
+ cast: config.cast
47
+ };
31
48
  } 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);
49
+ if (name.startsWith("no-")) {
50
+ name = name.slice(3);
51
+ config.default = true;
37
52
  }
53
+ const initial = config.default === void 0 || config.default === null ? false : config.default;
54
+ return {
55
+ format,
56
+ type: "boolean",
57
+ name,
58
+ short,
59
+ description: config.description ?? "",
60
+ order: 0,
61
+ // @ts-ignore
62
+ initial: config.cast ? config.cast(initial) : initial,
63
+ cast: config.cast
64
+ };
38
65
  }
66
+ } else {
67
+ throw new BreadcError(`Can not parse option format (${format})`);
39
68
  }
40
- const run = async (container, command, result) => {
41
- const prefix = command._arguments.filter((a) => a.type === "const").map((a) => a.name);
42
- if (prefix.length === 0) {
43
- prefix.push("_");
69
+ }
70
+ const initContextOptions = (options, context) => {
71
+ for (const option of options) {
72
+ context.options.set(option.name, option);
73
+ if (option.short) {
74
+ context.options.set(option.short, option);
44
75
  }
45
- for (let i = 0; i <= prefix.length; i++) {
46
- const key = i === 0 ? "*" : prefix.slice(0, i).map(
47
- (t, idx) => idx === 0 ? t : t[0].toUpperCase() + t.slice(1)
48
- ).join("");
49
- const fns = container[key];
50
- if (fns && fns.length > 0) {
51
- await Promise.all(fns.map((fn) => fn(result)));
52
- }
76
+ if (option.type === "boolean") {
77
+ context.options.set("no-" + option.name, option);
53
78
  }
54
- };
55
- return {
56
- init(breadc, allCommands, globalOptions) {
57
- if (plugins.length === 0)
58
- return;
59
- for (const p of plugins) {
60
- p.onInit?.(breadc, allCommands, globalOptions);
61
- }
62
- },
63
- async preRun(breadc) {
64
- if (plugins.length === 0)
65
- return;
66
- for (const p of plugins) {
67
- await p.onPreRun?.(breadc);
68
- }
69
- },
70
- async preCommand(command, result) {
71
- if (plugins.length === 0)
72
- return;
73
- await run(onPreCommand, command, result);
74
- },
75
- async postCommand(command, result) {
76
- if (plugins.length === 0)
77
- return;
78
- await run(onPostCommand, command, result);
79
- },
80
- async postRun(breadc) {
81
- if (plugins.length === 0)
82
- return;
83
- for (const p of plugins) {
84
- await p.onPostRun?.(breadc);
85
- }
79
+ if (option.initial !== void 0) {
80
+ context.result.options[camelCase(option.name)] = option.initial;
86
81
  }
87
- };
88
- }
89
- function definePlugin(plugin) {
90
- return plugin;
91
- }
82
+ }
83
+ };
92
84
 
93
85
  class Token {
94
86
  constructor(text) {
@@ -169,23 +161,6 @@ class Lexer {
169
161
  }
170
162
  }
171
163
 
172
- function camelCase(text) {
173
- return text.split("-").map((t, idx) => idx === 0 ? t : t[0].toUpperCase() + t.slice(1)).join("");
174
- }
175
- function twoColumn(texts, split = " ") {
176
- const left = padRight(texts.map((t) => t[0]));
177
- return left.map((l, idx) => l + split + texts[idx][1]);
178
- }
179
- function padRight(texts, fill = " ") {
180
- const length = texts.map((t) => t.length).reduce((max, l) => Math.max(max, l), 0);
181
- return texts.map((t) => t + fill.repeat(length - t.length));
182
- }
183
-
184
- class BreadcError extends Error {
185
- }
186
- class ParseError extends Error {
187
- }
188
-
189
164
  function makeTreeNode(pnode) {
190
165
  const node = {
191
166
  children: /* @__PURE__ */ new Map(),
@@ -203,6 +178,7 @@ function makeTreeNode(pnode) {
203
178
  }
204
179
  },
205
180
  finish() {
181
+ return pnode.command?.callback;
206
182
  },
207
183
  ...pnode
208
184
  };
@@ -214,8 +190,8 @@ function parseOption(cursor, token, context) {
214
190
  if (context.options.has(key)) {
215
191
  const option = context.options.get(key);
216
192
  const name = camelCase(option.name);
217
- if (option.action) {
218
- return option.action(cursor, token, context);
193
+ if (option.parse) {
194
+ return option.parse(cursor, token, context);
219
195
  } else if (option.type === "boolean") {
220
196
  context.result.options[name] = !key.startsWith("no-") ? true : false;
221
197
  } else if (option.type === "string") {
@@ -238,7 +214,15 @@ function parseOption(cursor, token, context) {
238
214
  context.result.options[name] = option.cast(context.result.options[name]);
239
215
  }
240
216
  } else {
241
- throw new ParseError(`Unknown option ${token.raw()}`);
217
+ switch (context.config.allowUnknownOption) {
218
+ case "rest":
219
+ context.result["--"].push(token.raw());
220
+ case "skip":
221
+ break;
222
+ case "error":
223
+ default:
224
+ throw new ParseError(`Unknown option ${token.raw()}`);
225
+ }
242
226
  }
243
227
  return cursor;
244
228
  }
@@ -251,7 +235,12 @@ function parse(root, args) {
251
235
  arguments: [],
252
236
  options: {},
253
237
  "--": []
254
- }
238
+ },
239
+ meta: {},
240
+ config: {
241
+ allowUnknownOption: "error"
242
+ },
243
+ parseOption
255
244
  };
256
245
  let cursor = root;
257
246
  root.init(context);
@@ -259,7 +248,7 @@ function parse(root, args) {
259
248
  if (token.type() === "--") {
260
249
  break;
261
250
  } else if (token.isOption()) {
262
- const res = parseOption(cursor, token, context);
251
+ const res = context.parseOption(cursor, token, context);
263
252
  if (res === false) {
264
253
  break;
265
254
  } else {
@@ -276,81 +265,24 @@ function parse(root, args) {
276
265
  throw new ParseError("unreachable");
277
266
  }
278
267
  }
279
- cursor.finish(context);
268
+ const callback = cursor.finish(context);
280
269
  for (const token of lexer) {
281
270
  context.result["--"].push(token.raw());
282
271
  }
283
272
  return {
284
- command: cursor.command,
273
+ callback,
274
+ matched: {
275
+ node: cursor,
276
+ command: cursor.command,
277
+ option: cursor.option
278
+ },
279
+ meta: context.meta,
285
280
  arguments: context.result.arguments,
286
281
  options: context.result.options,
287
282
  "--": context.result["--"]
288
283
  };
289
284
  }
290
285
 
291
- const OptionRE = /^(-[a-zA-Z], )?--([a-zA-Z0-9\-]+)( <[a-zA-Z0-9\-]+>)?$/;
292
- function makeOption(format, config = {}) {
293
- let name = "";
294
- let short = void 0;
295
- const match = OptionRE.exec(format);
296
- if (match) {
297
- name = match[2];
298
- if (match[1]) {
299
- short = match[1][1];
300
- }
301
- if (match[3]) {
302
- if (name.startsWith("no-")) {
303
- throw new BreadcError(`Can not parse option format (${format})`);
304
- }
305
- const initial = config.default ?? "";
306
- return {
307
- format,
308
- type: "string",
309
- name,
310
- short,
311
- description: config.description ?? "",
312
- order: 0,
313
- // @ts-ignore
314
- initial: config.cast ? config.cast(initial) : initial,
315
- cast: config.cast
316
- };
317
- } else {
318
- if (name.startsWith("no-")) {
319
- name = name.slice(3);
320
- config.default = true;
321
- }
322
- const initial = config.default === void 0 || config.default === null ? false : config.default;
323
- return {
324
- format,
325
- type: "boolean",
326
- name,
327
- short,
328
- description: config.description ?? "",
329
- order: 0,
330
- // @ts-ignore
331
- initial: config.cast ? config.cast(initial) : initial,
332
- cast: config.cast
333
- };
334
- }
335
- } else {
336
- throw new BreadcError(`Can not parse option format (${format})`);
337
- }
338
- }
339
- const initContextOptions = (options, context) => {
340
- for (const option of options) {
341
- context.options.set(option.name, option);
342
- if (option.short) {
343
- context.options.set(option.short, option);
344
- }
345
- if (option.type === "boolean") {
346
- context.options.set("no-" + option.name, option);
347
- }
348
- if (option.initial !== void 0) {
349
- context.result.options[camelCase(option.name)] = option.initial;
350
- }
351
- }
352
- };
353
-
354
286
  function makeCommand(format, config, root, container) {
355
287
  const args = [];
356
288
  const options = [];
@@ -401,6 +333,7 @@ function makeCommand(format, config, root, container) {
401
333
  return makeTreeNode({
402
334
  command,
403
335
  init(context) {
336
+ context.config.allowUnknownOption = config.allowUnknownOption ?? "error";
404
337
  initContextOptions(options, context);
405
338
  },
406
339
  finish(context) {
@@ -424,6 +357,7 @@ function makeCommand(format, config, root, container) {
424
357
  }
425
358
  }
426
359
  context.result["--"] = rest.splice(args2.length);
360
+ return command.callback;
427
361
  }
428
362
  });
429
363
  }
@@ -520,28 +454,110 @@ function* parseCommandFormat(format) {
520
454
  }
521
455
  return void 0;
522
456
  }
523
- function makeVersionCommand(name, config) {
524
- const command = {
525
- async callback() {
526
- const text = `${name}/${config.version ? config.version : "unknown"}`;
527
- console.log(text);
528
- return text;
457
+
458
+ function makePluginContainer(plugins = []) {
459
+ const onPreCommand = {};
460
+ const onPostCommand = {};
461
+ for (const plugin of plugins) {
462
+ if (typeof plugin.onPreCommand === "function") {
463
+ const key = "*";
464
+ if (!(key in onPreCommand)) {
465
+ onPreCommand[key] = [];
466
+ }
467
+ onPreCommand[key].push(plugin.onPreCommand);
468
+ } else {
469
+ for (const [key, fn] of Object.entries(plugin.onPreCommand ?? {})) {
470
+ if (!(key in onPreCommand)) {
471
+ onPreCommand[key] = [];
472
+ }
473
+ onPreCommand[key].push(fn);
474
+ }
475
+ }
476
+ if (typeof plugin.onPostCommand === "function") {
477
+ const key = "*";
478
+ if (!(key in onPostCommand)) {
479
+ onPostCommand[key] = [];
480
+ }
481
+ onPostCommand[key].push(plugin.onPostCommand);
482
+ } else {
483
+ for (const [key, fn] of Object.entries(plugin.onPostCommand ?? {})) {
484
+ if (!(key in onPostCommand)) {
485
+ onPostCommand[key] = [];
486
+ }
487
+ onPostCommand[key].push(fn);
488
+ }
489
+ }
490
+ }
491
+ const run = async (container, command, result) => {
492
+ const prefix = command._arguments.filter((a) => a.type === "const").map((a) => a.name);
493
+ if (prefix.length === 0) {
494
+ prefix.push("_");
495
+ }
496
+ for (let i = 0; i <= prefix.length; i++) {
497
+ const key = i === 0 ? "*" : prefix.slice(0, i).map(
498
+ (t, idx) => idx === 0 ? t : t[0].toUpperCase() + t.slice(1)
499
+ ).join("");
500
+ const fns = container[key];
501
+ if (fns && fns.length > 0) {
502
+ await Promise.all(fns.map((fn) => fn(result)));
503
+ }
504
+ }
505
+ };
506
+ return {
507
+ init(breadc, allCommands, globalOptions) {
508
+ if (plugins.length === 0)
509
+ return;
510
+ for (const p of plugins) {
511
+ p.onInit?.(breadc, allCommands, globalOptions);
512
+ }
529
513
  },
530
- format: "-v, --version",
531
- description: "Print version",
532
- _arguments: [],
533
- _options: [],
534
- // @ts-ignore
535
- option: void 0,
536
- // @ts-ignore
537
- alias: void 0,
538
- // @ts-ignore
539
- action: void 0
514
+ async preRun(breadc) {
515
+ if (plugins.length === 0)
516
+ return;
517
+ for (const p of plugins) {
518
+ await p.onPreRun?.(breadc);
519
+ }
520
+ },
521
+ async preCommand(command, result) {
522
+ if (plugins.length === 0)
523
+ return;
524
+ await run(onPreCommand, command, result);
525
+ },
526
+ async postCommand(command, result) {
527
+ if (plugins.length === 0)
528
+ return;
529
+ await run(onPostCommand, command, result);
530
+ },
531
+ async postRun(breadc) {
532
+ if (plugins.length === 0)
533
+ return;
534
+ for (const p of plugins) {
535
+ await p.onPostRun?.(breadc);
536
+ }
537
+ }
540
538
  };
539
+ }
540
+ function definePlugin(plugin) {
541
+ return plugin;
542
+ }
543
+
544
+ function makeVersionCommand(name, config) {
545
+ let description = "Print version";
546
+ if (typeof config.builtin?.version === "object") {
547
+ if (config.builtin.version.description) {
548
+ description = config.builtin.version.description;
549
+ }
550
+ }
541
551
  const node = makeTreeNode({
542
- command,
543
552
  next() {
544
553
  return false;
554
+ },
555
+ finish() {
556
+ return () => {
557
+ const text = typeof config.builtin?.version === "object" && config.builtin.version.content ? config.builtin.version.content : `${name}/${config.version ? config.version : "unknown"}`;
558
+ console.log(text);
559
+ return text;
560
+ };
545
561
  }
546
562
  });
547
563
  const option = {
@@ -551,8 +567,8 @@ function makeVersionCommand(name, config) {
551
567
  type: "boolean",
552
568
  initial: void 0,
553
569
  order: 999999999 + 1,
554
- description: "Print version",
555
- action() {
570
+ description,
571
+ parse() {
556
572
  return node;
557
573
  }
558
574
  };
@@ -597,66 +613,66 @@ function makeHelpCommand(name, config, allCommands) {
597
613
  }
598
614
  }
599
615
  }
600
- return commands;
616
+ const alias = /* @__PURE__ */ new Map();
617
+ for (const cmd of commands) {
618
+ if (!alias.has(cmd.format)) {
619
+ alias.set(cmd.format, cmd);
620
+ }
621
+ }
622
+ return [...alias.values()];
623
+ }
624
+ let description = "Print help";
625
+ if (typeof config.builtin?.help === "object") {
626
+ if (config.builtin.help.description) {
627
+ description = config.builtin.help.description;
628
+ }
601
629
  }
602
- const command = {
603
- async callback(parsed) {
604
- const context = parsed.options.__context__;
605
- const cursor = parsed.options.__cursor__;
606
- const usage = allCommands.length === 0 ? `[OPTIONS]` : allCommands.length === 1 ? `[OPTIONS] ${allCommands[0].format}` : allCommands.some((c) => c._default) ? `[OPTIONS] [COMMAND]` : `[OPTIONS] <COMMAND>`;
607
- const output = [
608
- `${name}/${config.version ? config.version : "unknown"}`,
609
- () => {
610
- if (config.description) {
611
- return ["", config.description];
612
- } else {
613
- return void 0;
614
- }
615
- },
616
- "",
617
- `${color.bold(color.underline("Usage:"))} ${color.bold(name)} ${usage}`,
618
- () => {
619
- const cmds = expandCommands(cursor);
620
- if (cmds.length > 0) {
621
- return [
622
- "",
623
- color.bold(color.underline("Commands:")),
624
- cmds.map((cmd) => [
625
- ` ${color.bold(name)} ${color.bold(cmd.format)}`,
626
- cmd.description
627
- ])
628
- ];
629
- } else {
630
- return void 0;
631
- }
632
- },
633
- "",
634
- color.bold(color.underline("Options:")),
635
- [...context.options.entries()].filter(([key, op]) => key === op.name).sort((lhs, rhs) => lhs[1].order - rhs[1].order).map(([_key, op]) => [
636
- " " + (!op.short ? " " : "") + color.bold(op.format),
637
- op.description
638
- ]),
639
- ""
640
- ];
641
- const text = expandMessage(output).join("\n");
642
- console.log(text);
643
- return text;
644
- },
645
- format: "-h, --help",
646
- description: "Print help",
647
- _arguments: [],
648
- _options: [],
649
- // @ts-ignore
650
- option: void 0,
651
- // @ts-ignore
652
- alias: void 0,
653
- // @ts-ignore
654
- action: void 0
655
- };
656
630
  const node = makeTreeNode({
657
- command,
658
631
  next() {
659
632
  return false;
633
+ },
634
+ finish(context) {
635
+ return () => {
636
+ const cursor = context.meta.__cursor__;
637
+ const usage = allCommands.length === 0 ? `[OPTIONS]` : allCommands.length === 1 ? `[OPTIONS] ${allCommands[0].format}` : allCommands.some((c) => c._default) ? `[OPTIONS] [COMMAND]` : `[OPTIONS] <COMMAND>`;
638
+ const output = [
639
+ `${name}/${config.version ? config.version : "unknown"}`,
640
+ () => {
641
+ if (config.description) {
642
+ return ["", config.description];
643
+ } else {
644
+ return void 0;
645
+ }
646
+ },
647
+ "",
648
+ `${color.bold(color.underline("Usage:"))} ${color.bold(name)} ${usage}`,
649
+ () => {
650
+ const cmds = expandCommands(cursor);
651
+ if (cmds.length > 0) {
652
+ return [
653
+ "",
654
+ color.bold(color.underline("Commands:")),
655
+ cmds.map((cmd) => [
656
+ ` ${color.bold(name)} ${color.bold(cmd.format)}`,
657
+ cmd.description
658
+ ])
659
+ ];
660
+ } else {
661
+ return void 0;
662
+ }
663
+ },
664
+ "",
665
+ color.bold(color.underline("Options:")),
666
+ [...context.options.entries()].filter(([key, op]) => key === op.name).sort((lhs, rhs) => lhs[1].order - rhs[1].order).map(([_key, op]) => [
667
+ " " + (!op.short ? " " : "") + color.bold(op.format),
668
+ op.description
669
+ ]),
670
+ ""
671
+ ];
672
+ const text = expandMessage(output).join("\n");
673
+ console.log(text);
674
+ return text;
675
+ };
660
676
  }
661
677
  });
662
678
  const option = {
@@ -665,11 +681,10 @@ function makeHelpCommand(name, config, allCommands) {
665
681
  short: "h",
666
682
  type: "boolean",
667
683
  initial: void 0,
668
- description: "Print help",
684
+ description,
669
685
  order: 999999999,
670
- action(cursor, _token, context) {
671
- context.result.options.__cursor__ = cursor;
672
- context.result.options.__context__ = context;
686
+ parse(cursor, _token, context) {
687
+ context.meta.__cursor__ = cursor;
673
688
  return node;
674
689
  }
675
690
  };
@@ -679,10 +694,13 @@ function makeHelpCommand(name, config, allCommands) {
679
694
  function breadc(name, config = {}) {
680
695
  let defaultCommand = void 0;
681
696
  const allCommands = [];
682
- const globalOptions = [
683
- makeHelpCommand(name, config, allCommands),
684
- makeVersionCommand(name, config)
685
- ];
697
+ const globalOptions = [];
698
+ if (config.builtin?.help !== false) {
699
+ globalOptions.push(makeHelpCommand(name, config, allCommands));
700
+ }
701
+ if (config.builtin?.version !== false) {
702
+ globalOptions.push(makeVersionCommand(name, config));
703
+ }
686
704
  const container = makePluginContainer(config.plugins);
687
705
  const root = makeTreeNode({
688
706
  init(context) {
@@ -690,8 +708,6 @@ function breadc(name, config = {}) {
690
708
  if (defaultCommand) {
691
709
  initContextOptions(defaultCommand._options, context);
692
710
  }
693
- },
694
- finish() {
695
711
  }
696
712
  });
697
713
  const breadc2 = {
@@ -703,8 +719,8 @@ function breadc(name, config = {}) {
703
719
  globalOptions.push(option);
704
720
  return breadc2;
705
721
  },
706
- command(text, _config = {}) {
707
- const config2 = typeof _config === "string" ? { description: _config } : _config;
722
+ command(text, _config = {}, _config2 = {}) {
723
+ const config2 = typeof _config === "string" ? { description: _config, ..._config2 } : _config;
708
724
  const command = makeCommand(text, config2, root, container);
709
725
  if (command._default) {
710
726
  defaultCommand = command;
@@ -713,19 +729,16 @@ function breadc(name, config = {}) {
713
729
  return command;
714
730
  },
715
731
  parse(args) {
716
- const result = parse(root, args);
717
- return result;
732
+ return parse(root, args);
718
733
  },
719
734
  async run(args) {
720
735
  const result = breadc2.parse(args);
721
- const command = result.command;
722
- if (command) {
723
- if (command.callback) {
724
- await container.preRun(breadc2);
725
- const r = await command.callback(result);
726
- await container.postRun(breadc2);
727
- return r;
728
- }
736
+ const callback = result.callback;
737
+ if (callback) {
738
+ await container.preRun(breadc2);
739
+ const r = await callback(result);
740
+ await container.postRun(breadc2);
741
+ return r;
729
742
  }
730
743
  return void 0;
731
744
  }
@@ -737,6 +750,5 @@ function breadc(name, config = {}) {
737
750
  exports.BreadcError = BreadcError;
738
751
  exports.ParseError = ParseError;
739
752
  exports.breadc = breadc;
740
- exports.default = breadc;
741
753
  exports.definePlugin = definePlugin;
742
754
  exports.makeTreeNode = makeTreeNode;