breadc 0.8.9 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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
  };
@@ -605,64 +621,58 @@ function makeHelpCommand(name, config, allCommands) {
605
621
  }
606
622
  return [...alias.values()];
607
623
  }
608
- const command = {
609
- async callback(parsed) {
610
- const context = parsed.options.__context__;
611
- const cursor = parsed.options.__cursor__;
612
- const usage = allCommands.length === 0 ? `[OPTIONS]` : allCommands.length === 1 ? `[OPTIONS] ${allCommands[0].format}` : allCommands.some((c) => c._default) ? `[OPTIONS] [COMMAND]` : `[OPTIONS] <COMMAND>`;
613
- const output = [
614
- `${name}/${config.version ? config.version : "unknown"}`,
615
- () => {
616
- if (config.description) {
617
- return ["", config.description];
618
- } else {
619
- return void 0;
620
- }
621
- },
622
- "",
623
- `${color.bold(color.underline("Usage:"))} ${color.bold(name)} ${usage}`,
624
- () => {
625
- const cmds = expandCommands(cursor);
626
- if (cmds.length > 0) {
627
- return [
628
- "",
629
- color.bold(color.underline("Commands:")),
630
- cmds.map((cmd) => [
631
- ` ${color.bold(name)} ${color.bold(cmd.format)}`,
632
- cmd.description
633
- ])
634
- ];
635
- } else {
636
- return void 0;
637
- }
638
- },
639
- "",
640
- color.bold(color.underline("Options:")),
641
- [...context.options.entries()].filter(([key, op]) => key === op.name).sort((lhs, rhs) => lhs[1].order - rhs[1].order).map(([_key, op]) => [
642
- " " + (!op.short ? " " : "") + color.bold(op.format),
643
- op.description
644
- ]),
645
- ""
646
- ];
647
- const text = expandMessage(output).join("\n");
648
- console.log(text);
649
- return text;
650
- },
651
- format: "-h, --help",
652
- description: "Print help",
653
- _arguments: [],
654
- _options: [],
655
- // @ts-ignore
656
- option: void 0,
657
- // @ts-ignore
658
- alias: void 0,
659
- // @ts-ignore
660
- action: void 0
661
- };
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
+ }
629
+ }
662
630
  const node = makeTreeNode({
663
- command,
664
631
  next() {
665
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
+ };
666
676
  }
667
677
  });
668
678
  const option = {
@@ -671,11 +681,10 @@ function makeHelpCommand(name, config, allCommands) {
671
681
  short: "h",
672
682
  type: "boolean",
673
683
  initial: void 0,
674
- description: "Print help",
684
+ description,
675
685
  order: 999999999,
676
- action(cursor, _token, context) {
677
- context.result.options.__cursor__ = cursor;
678
- context.result.options.__context__ = context;
686
+ parse(cursor, _token, context) {
687
+ context.meta.__cursor__ = cursor;
679
688
  return node;
680
689
  }
681
690
  };
@@ -685,10 +694,13 @@ function makeHelpCommand(name, config, allCommands) {
685
694
  function breadc(name, config = {}) {
686
695
  let defaultCommand = void 0;
687
696
  const allCommands = [];
688
- const globalOptions = [
689
- makeHelpCommand(name, config, allCommands),
690
- makeVersionCommand(name, config)
691
- ];
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
+ }
692
704
  const container = makePluginContainer(config.plugins);
693
705
  const root = makeTreeNode({
694
706
  init(context) {
@@ -696,8 +708,6 @@ function breadc(name, config = {}) {
696
708
  if (defaultCommand) {
697
709
  initContextOptions(defaultCommand._options, context);
698
710
  }
699
- },
700
- finish() {
701
711
  }
702
712
  });
703
713
  const breadc2 = {
@@ -709,8 +719,8 @@ function breadc(name, config = {}) {
709
719
  globalOptions.push(option);
710
720
  return breadc2;
711
721
  },
712
- command(text, _config = {}) {
713
- const config2 = typeof _config === "string" ? { description: _config } : _config;
722
+ command(text, _config = {}, _config2 = {}) {
723
+ const config2 = typeof _config === "string" ? { description: _config, ..._config2 } : _config;
714
724
  const command = makeCommand(text, config2, root, container);
715
725
  if (command._default) {
716
726
  defaultCommand = command;
@@ -719,19 +729,16 @@ function breadc(name, config = {}) {
719
729
  return command;
720
730
  },
721
731
  parse(args) {
722
- const result = parse(root, args);
723
- return result;
732
+ return parse(root, args);
724
733
  },
725
734
  async run(args) {
726
735
  const result = breadc2.parse(args);
727
- const command = result.command;
728
- if (command) {
729
- if (command.callback) {
730
- await container.preRun(breadc2);
731
- const r = await command.callback(result);
732
- await container.postRun(breadc2);
733
- return r;
734
- }
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;
735
742
  }
736
743
  return void 0;
737
744
  }
@@ -743,6 +750,5 @@ function breadc(name, config = {}) {
743
750
  exports.BreadcError = BreadcError;
744
751
  exports.ParseError = ParseError;
745
752
  exports.breadc = breadc;
746
- exports.default = breadc;
747
753
  exports.definePlugin = definePlugin;
748
754
  exports.makeTreeNode = makeTreeNode;