breadc 0.7.0 → 0.8.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,21 +1,142 @@
1
1
  'use strict';
2
2
 
3
- const minimist = require('minimist');
4
- const kolorist = require('kolorist');
3
+ Object.defineProperty(exports, '__esModule', { value: true });
5
4
 
6
- function _interopNamespaceDefault(e) {
7
- const n = Object.create(null);
8
- if (e) {
9
- for (const k in e) {
10
- n[k] = e[k];
5
+ const color = require('@breadc/color');
6
+
7
+ function makePluginContainer(plugins = []) {
8
+ const onPreCommand = {};
9
+ const onPostCommand = {};
10
+ for (const plugin of plugins) {
11
+ for (const [key, fn] of Object.entries(plugin.onPreCommand ?? {})) {
12
+ if (!(key in onPreCommand)) {
13
+ onPreCommand[key] = [];
14
+ }
15
+ onPreCommand[key].push(fn);
16
+ }
17
+ for (const [key, fn] of Object.entries(plugin.onPostCommand ?? {})) {
18
+ if (!(key in onPostCommand)) {
19
+ onPostCommand[key] = [];
20
+ }
21
+ onPostCommand[key].push(fn);
11
22
  }
12
23
  }
13
- n.default = e;
14
- return n;
24
+ const run = async (container, command) => {
25
+ const prefix = command._arguments.filter((a) => a.type === "const").map((a) => a.name);
26
+ for (let i = 0; i <= prefix.length; i++) {
27
+ const key = i === 0 ? "*" : prefix.slice(0, i).map(
28
+ (t, idx) => idx === 0 ? t : t[0].toUpperCase() + t.slice(1)
29
+ ).join("");
30
+ const fns = container[key];
31
+ if (fns && fns.length > 0) {
32
+ await Promise.all(fns.map((fn) => fn()));
33
+ }
34
+ }
35
+ };
36
+ return {
37
+ async preRun(breadc) {
38
+ for (const p of plugins) {
39
+ await p.onPreRun?.(breadc);
40
+ }
41
+ },
42
+ async preCommand(command) {
43
+ await run(onPreCommand, command);
44
+ },
45
+ async postCommand(command) {
46
+ await run(onPostCommand, command);
47
+ },
48
+ async postRun(breadc) {
49
+ for (const p of plugins) {
50
+ await p.onPostRun?.(breadc);
51
+ }
52
+ }
53
+ };
54
+ }
55
+ function definePlugin(plugin) {
56
+ return plugin;
15
57
  }
16
58
 
17
- const kolorist__namespace = /*#__PURE__*/_interopNamespaceDefault(kolorist);
59
+ class Token {
60
+ constructor(text) {
61
+ this.text = text;
62
+ }
63
+ /**
64
+ * @returns Raw argument text
65
+ */
66
+ raw() {
67
+ return this.text;
68
+ }
69
+ /**
70
+ * @returns Number representation
71
+ */
72
+ number() {
73
+ return Number(this.text);
74
+ }
75
+ /**
76
+ * @returns Remove start - for long or short option
77
+ */
78
+ option() {
79
+ return this.text.replace(/^-+/, "");
80
+ }
81
+ isOption() {
82
+ return this.type() === "long" || this._type === "short";
83
+ }
84
+ isText() {
85
+ return this.type() === "number" || this._type === "string";
86
+ }
87
+ type() {
88
+ if (this._type) {
89
+ return this._type;
90
+ } else if (this.text === "--") {
91
+ return this._type = "--";
92
+ } else if (this.text === "-") {
93
+ return this._type = "-";
94
+ } else if (!isNaN(Number(this.text))) {
95
+ return this._type = "number";
96
+ } else if (this.text.startsWith("--")) {
97
+ return this._type = "long";
98
+ } else if (this.text.startsWith("-")) {
99
+ return this._type = "short";
100
+ } else {
101
+ return this._type = "string";
102
+ }
103
+ }
104
+ }
105
+ class Lexer {
106
+ constructor(rawArgs) {
107
+ this.cursor = 0;
108
+ this.rawArgs = rawArgs;
109
+ }
110
+ next() {
111
+ const value = this.rawArgs[this.cursor];
112
+ this.cursor += 1;
113
+ return value ? new Token(value) : void 0;
114
+ }
115
+ hasNext() {
116
+ return this.cursor + 1 < this.rawArgs.length;
117
+ }
118
+ peek() {
119
+ const value = this.rawArgs[this.cursor];
120
+ return value ? new Token(value) : void 0;
121
+ }
122
+ [Symbol.iterator]() {
123
+ const that = this;
124
+ return {
125
+ next() {
126
+ const value = that.rawArgs[that.cursor];
127
+ that.cursor += 1;
128
+ return {
129
+ value: value ? new Token(value) : void 0,
130
+ done: that.cursor > that.rawArgs.length
131
+ };
132
+ }
133
+ };
134
+ }
135
+ }
18
136
 
137
+ function camelCase(text) {
138
+ return text.split("-").map((t, idx) => idx === 0 ? t : t[0].toUpperCase() + t.slice(1)).join("");
139
+ }
19
140
  function twoColumn(texts, split = " ") {
20
141
  const left = padRight(texts.map((t) => t[0]));
21
142
  return left.map((l, idx) => l + split + texts[idx][1]);
@@ -25,507 +146,518 @@ function padRight(texts, fill = " ") {
25
146
  return texts.map((t) => t + fill.repeat(length - t.length));
26
147
  }
27
148
 
28
- function createDefaultLogger(name, logger) {
29
- const println = !!logger && typeof logger === "function" ? logger : logger?.println ?? ((message, ...args) => {
30
- console.log(message, ...args);
31
- });
32
- const info = typeof logger === "object" && logger?.info ? logger.info : (message, ...args) => {
33
- println(`${kolorist.blue("INFO")} ${message}`, ...args);
34
- };
35
- const warn = typeof logger === "object" && logger?.warn ? logger.warn : (message, ...args) => {
36
- println(`${kolorist.yellow("WARN")} ${message}`, ...args);
37
- };
38
- const error = typeof logger === "object" && logger?.error ? logger.error : (message, ...args) => {
39
- println(`${kolorist.red("ERROR")} ${message}`, ...args);
40
- };
41
- const debug = typeof logger === "object" && logger?.debug ? logger.debug : (message, ...args) => {
42
- println(`${kolorist.gray(name)} ${message}`, ...args);
43
- };
44
- return {
45
- println,
46
- info,
47
- warn,
48
- error,
49
- debug
50
- };
149
+ class BreadcError extends Error {
150
+ }
151
+ class ParseError extends Error {
51
152
  }
52
153
 
53
- const _Option = class {
54
- constructor(format, config = {}) {
55
- this.format = format;
56
- const match = _Option.OptionRE.exec(format);
57
- if (match) {
58
- if (match[3]) {
59
- this.type = "string";
154
+ function makeTreeNode(pnode) {
155
+ const node = {
156
+ children: /* @__PURE__ */ new Map(),
157
+ init() {
158
+ },
159
+ next(token, context) {
160
+ const t = token.raw();
161
+ context.result["--"].push(t);
162
+ if (node.children.has(t)) {
163
+ const next = node.children.get(t);
164
+ next.init(context);
165
+ return next;
60
166
  } else {
61
- this.type = "boolean";
167
+ return node;
62
168
  }
63
- this.name = match[2];
64
- if (match[1]) {
65
- this.shortcut = match[1][1];
169
+ },
170
+ finish() {
171
+ },
172
+ ...pnode
173
+ };
174
+ return node;
175
+ }
176
+ function parseOption(cursor, token, context) {
177
+ const o = token.option();
178
+ const [key, rawV] = o.split("=");
179
+ if (context.options.has(key)) {
180
+ const option = context.options.get(key);
181
+ const name = camelCase(option.name);
182
+ if (option.action) {
183
+ return option.action(cursor, token, context);
184
+ } else if (option.type === "boolean") {
185
+ context.result.options[name] = !key.startsWith("no-") ? true : false;
186
+ } else if (option.type === "string") {
187
+ if (rawV !== void 0) {
188
+ context.result.options[name] = rawV;
189
+ } else {
190
+ const value = context.lexer.next();
191
+ if (value !== void 0 && !value.isOption()) {
192
+ context.result.options[name] = value.raw();
193
+ } else {
194
+ throw new ParseError(
195
+ `You should provide arguments for ${option.format}`
196
+ );
197
+ }
66
198
  }
67
199
  } else {
68
- throw new Error(`Can not parse option format from "${format}"`);
200
+ throw new ParseError("unreachable");
201
+ }
202
+ if (option.cast) {
203
+ context.result.options[name] = option.cast(context.result.options[name]);
69
204
  }
70
- this.description = config.description ?? "";
71
- this.required = format.indexOf("<") !== -1;
72
- this.default = config.default;
73
- this.construct = config.construct;
205
+ } else {
206
+ throw new ParseError(`Unknown option ${token.raw()}`);
74
207
  }
75
- };
76
- let Option = _Option;
77
- Option.OptionRE = /^(-[a-zA-Z0-9], )?--([a-zA-Z0-9\-]+)( \[[a-zA-Z0-9]+\]| <[a-zA-Z0-9]+>)?$/;
78
-
79
- const _Command = class {
80
- constructor(format, config) {
81
- this.options = [];
82
- this.format = format;
83
- const pieces = format.split(" ").map((t) => t.trim()).filter(Boolean);
84
- const prefix = pieces.filter((p) => !isArg(p));
85
- this.default = prefix.length === 0;
86
- this.prefix = this.default ? [] : [prefix];
87
- this.arguments = pieces.filter(isArg);
88
- this.description = config.description ?? "";
89
- this.logger = config.logger;
90
- {
91
- const restArgs = this.arguments.findIndex((a) => a.startsWith("[..."));
92
- if (restArgs !== -1 && restArgs !== this.arguments.length - 1) {
93
- this.logger.warn(
94
- `Expand arguments ${this.arguments[restArgs]} should be placed at the last position`
95
- );
208
+ return cursor;
209
+ }
210
+ function parse(root, args) {
211
+ const lexer = new Lexer(args);
212
+ const context = {
213
+ lexer,
214
+ options: /* @__PURE__ */ new Map(),
215
+ result: {
216
+ arguments: [],
217
+ options: {},
218
+ "--": []
219
+ }
220
+ };
221
+ let cursor = root;
222
+ root.init(context);
223
+ for (const token of lexer) {
224
+ if (token.type() === "--") {
225
+ break;
226
+ } else if (token.isOption()) {
227
+ const res = parseOption(cursor, token, context);
228
+ if (res === false) {
229
+ break;
230
+ } else {
231
+ cursor = res;
96
232
  }
97
- if (pieces.length > _Command.MaxDep) {
98
- this.logger.warn(`Command format string "${format}" is too long`);
233
+ } else if (token.isText()) {
234
+ const res = cursor.next(token, context);
235
+ if (res === false) {
236
+ break;
237
+ } else {
238
+ cursor = res;
99
239
  }
240
+ } else {
241
+ throw new ParseError("unreachable");
100
242
  }
101
243
  }
102
- get isInternal() {
103
- return this instanceof InternalCommand;
104
- }
105
- alias(command) {
106
- const pieces = command.split(" ").map((t) => t.trim()).filter(Boolean);
107
- this.prefix.push(pieces);
108
- return this;
244
+ cursor.finish(context);
245
+ for (const token of lexer) {
246
+ context.result["--"].push(token.raw());
109
247
  }
110
- option(format, configOrDescription = "", otherConfig = {}) {
111
- const config = typeof configOrDescription === "object" ? configOrDescription : { ...otherConfig, description: configOrDescription };
112
- try {
113
- const option = new Option(format, config);
114
- this.options.push(option);
115
- } catch (error) {
116
- this.logger.warn(error.message);
248
+ return {
249
+ command: cursor.command,
250
+ arguments: context.result.arguments,
251
+ options: context.result.options,
252
+ "--": context.result["--"]
253
+ };
254
+ }
255
+
256
+ const OptionRE = /^(-[a-zA-Z], )?--([a-zA-Z0-9\-]+)( <[a-zA-Z0-9\-]+>)?$/;
257
+ function makeOption(format, config = {}) {
258
+ let name = "";
259
+ let short = void 0;
260
+ const match = OptionRE.exec(format);
261
+ if (match) {
262
+ name = match[2];
263
+ if (name.startsWith("no-")) {
264
+ throw new BreadcError(`Can not parse option format (${format})`);
117
265
  }
118
- return this;
119
- }
120
- hasPrefix(parsedArgs) {
121
- const argv = parsedArgs["_"];
122
- if (argv.length === 0) {
123
- return this.default;
266
+ if (match[1]) {
267
+ short = match[1][1];
268
+ }
269
+ if (match[3]) {
270
+ const initial = config.default ?? "";
271
+ return {
272
+ format,
273
+ type: "string",
274
+ name,
275
+ short,
276
+ description: config.description ?? "",
277
+ order: 0,
278
+ // @ts-ignore
279
+ initial: config.cast ? config.cast(initial) : initial,
280
+ cast: config.cast
281
+ };
124
282
  } else {
125
- for (const prefix of this.prefix) {
126
- if (prefix.length > 0 && prefix[0] === argv[0]) {
127
- return true;
128
- }
129
- }
130
- return false;
283
+ const initial = config.default === void 0 || config.default === null ? false : config.default;
284
+ return {
285
+ format,
286
+ type: "boolean",
287
+ name,
288
+ short,
289
+ description: config.description ?? "",
290
+ order: 0,
291
+ // @ts-ignore
292
+ initial: config.cast ? config.cast(initial) : initial,
293
+ cast: config.cast
294
+ };
131
295
  }
296
+ } else {
297
+ throw new BreadcError(`Can not parse option format (${format})`);
132
298
  }
133
- shouldRun(parsedArgs) {
134
- const args = parsedArgs["_"];
135
- for (const prefix of this.prefix) {
136
- let match = true;
137
- for (let i = 0; match && i < prefix.length; i++) {
138
- if (args[i] !== prefix[i]) {
139
- match = false;
299
+ }
300
+ const initContextOptions = (options, context) => {
301
+ for (const option of options) {
302
+ context.options.set(option.name, option);
303
+ if (option.short) {
304
+ context.options.set(option.short, option);
305
+ }
306
+ if (option.type === "boolean") {
307
+ context.options.set("no-" + option.name, option);
308
+ }
309
+ if (option.initial !== void 0) {
310
+ context.result.options[camelCase(option.name)] = option.initial;
311
+ }
312
+ }
313
+ };
314
+
315
+ function makeCommand(format, config, root, container) {
316
+ let cursor = root;
317
+ const args = [];
318
+ const options = [];
319
+ const command = {
320
+ callback: void 0,
321
+ format,
322
+ description: config.description ?? "",
323
+ _arguments: args,
324
+ _options: options,
325
+ option(format2, _config, _config2 = {}) {
326
+ const config2 = typeof _config === "string" ? { description: _config, ..._config2 } : _config;
327
+ const option = makeOption(format2, config2);
328
+ options.push(option);
329
+ return command;
330
+ },
331
+ action(fn) {
332
+ command.callback = async (...args2) => {
333
+ await container.preCommand(command);
334
+ const result = await fn(...args2);
335
+ await container.postCommand(command);
336
+ return result;
337
+ };
338
+ }
339
+ };
340
+ const node = makeTreeNode({
341
+ command,
342
+ init(context) {
343
+ initContextOptions(options, context);
344
+ },
345
+ finish(context) {
346
+ const rest = context.result["--"];
347
+ for (let i = 0; i < args.length; i++) {
348
+ if (args[i].type === "const") {
349
+ if (rest[i] !== args[i].name) {
350
+ throw new ParseError(`Sub-command ${args[i].name} mismatch`);
351
+ }
352
+ } else if (args[i].type === "require") {
353
+ if (i >= rest.length) {
354
+ throw new ParseError(
355
+ `You must provide require argument ${args[i].name}`
356
+ );
357
+ }
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));
140
363
  }
141
364
  }
142
- if (match) {
143
- args.splice(0, prefix.length);
144
- return true;
145
- }
365
+ context.result["--"] = rest.splice(args.length);
146
366
  }
147
- if (this.default)
148
- return true;
149
- return false;
150
- }
151
- parseArgs(argv, globalOptions) {
152
- const pieces = argv["_"];
153
- const args = [];
154
- const restArgs = [];
155
- for (let i = 0, used = 0; i <= this.arguments.length; i++) {
156
- if (i === this.arguments.length) {
157
- restArgs.push(...pieces.slice(used).map(String));
158
- restArgs.push(...(argv["--"] ?? []).map(String));
159
- } else if (i < pieces.length) {
160
- if (this.arguments[i].startsWith("[...")) {
161
- args.push(pieces.slice(i).map(String));
162
- used = pieces.length;
163
- } else {
164
- args.push(String(pieces[i]));
165
- used++;
367
+ });
368
+ {
369
+ let state = 0;
370
+ for (let i = 0; i < format.length; i++) {
371
+ if (format[i] === "<") {
372
+ if (state !== 0 && state !== 1) {
373
+ throw new BreadcError(
374
+ `Required arguments should be placed before optional or rest arguments`
375
+ );
166
376
  }
167
- } else {
168
- if (this.arguments[i].startsWith("<")) {
169
- this.logger.warn(
170
- `You should provide the argument "${this.arguments[i]}"`
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`
171
388
  );
172
- args.push("");
173
- } else if (this.arguments[i].startsWith("[...")) {
174
- args.push([]);
175
- } else if (this.arguments[i].startsWith("[")) {
176
- args.push(void 0);
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 });
177
398
  } else {
178
- this.logger.warn(`unknown format string ("${this.arguments[i]}")`);
399
+ args.push({ type: "optional", name });
179
400
  }
180
- }
181
- }
182
- const fullOptions = globalOptions.concat(this.options).reduce((map, o) => {
183
- map.set(o.name, o);
184
- return map;
185
- }, /* @__PURE__ */ new Map());
186
- const options = argv;
187
- delete options["_"];
188
- for (const [name, rawOption] of fullOptions) {
189
- if (rawOption.type === "boolean")
190
- continue;
191
- if (rawOption.required) {
192
- if (options[name] === void 0) {
193
- options[name] = false;
194
- } else if (options[name] === "") {
195
- options[name] = true;
401
+ } else if (format[i] !== " ") {
402
+ if (state !== 0) {
403
+ throw new BreadcError(
404
+ `Sub-command should be placed at the beginning`
405
+ );
196
406
  }
197
- } else {
198
- if (options[name] === false) {
199
- options[name] = void 0;
200
- } else if (!(name in options)) {
201
- options[name] = void 0;
407
+ const start = i;
408
+ while (i < format.length && format[i] !== " ") {
409
+ i++;
202
410
  }
203
- }
204
- if (rawOption.construct !== void 0) {
205
- options[name] = rawOption.construct(options[name]);
206
- } else if (rawOption.default !== void 0) {
207
- if (options[name] === void 0 || options[name] === false || options[name] === "") {
208
- options[name] = rawOption.default;
411
+ const name = format.slice(start, i);
412
+ if (cursor.children.has(name)) {
413
+ cursor = cursor.children.get(name);
414
+ } else {
415
+ const internalNode = makeTreeNode({
416
+ next(token, context) {
417
+ const t = token.raw();
418
+ context.result["--"].push(t);
419
+ if (internalNode.children.has(t)) {
420
+ const next = internalNode.children.get(t);
421
+ next.init(context);
422
+ return next;
423
+ } else {
424
+ throw new ParseError(`Unknown sub-command (${t})`);
425
+ }
426
+ },
427
+ finish() {
428
+ throw new ParseError(`Unknown sub-command`);
429
+ }
430
+ });
431
+ cursor.children.set(name, internalNode);
432
+ cursor = internalNode;
209
433
  }
434
+ state = 0;
435
+ args.push({ type: "const", name });
210
436
  }
211
437
  }
212
- for (const key of Object.keys(options)) {
213
- if (!fullOptions.has(key)) {
214
- delete options[key];
438
+ cursor.command = command;
439
+ if (cursor !== root) {
440
+ for (const [key, value] of cursor.children) {
441
+ node.children.set(key, value);
215
442
  }
216
- }
217
- return {
218
- // @ts-ignore
219
- command: this,
220
- arguments: args,
221
- options,
222
- "--": restArgs
223
- };
224
- }
225
- action(fn) {
226
- this.actionFn = fn;
227
- }
228
- async run(...args) {
229
- if (this.actionFn) {
230
- return await this.actionFn(...args, {
231
- logger: this.logger,
232
- color: kolorist__namespace
233
- });
443
+ cursor.children = node.children;
444
+ cursor.next = node.next;
445
+ cursor.init = node.init;
446
+ cursor.finish = node.finish;
234
447
  } else {
235
- this.logger.warn(
236
- `You may miss action function in ${this.format ? `"${this.format}"` : "<default command>"}`
237
- );
238
- return void 0;
448
+ cursor.finish = node.finish;
239
449
  }
240
450
  }
241
- };
242
- let Command = _Command;
243
- Command.MaxDep = 5;
244
- class InternalCommand extends Command {
245
- hasPrefix(_args) {
246
- return false;
247
- }
248
- parseArgs(args, _globalOptions) {
249
- const argumentss = args["_"];
250
- const options = args;
251
- delete options["_"];
252
- delete options["help"];
253
- delete options["version"];
254
- return {
255
- // @ts-ignore
256
- command: this,
257
- arguments: argumentss,
258
- options: args,
259
- "--": []
260
- };
261
- }
451
+ return command;
262
452
  }
263
- class HelpCommand extends InternalCommand {
264
- constructor(commands, help, logger) {
265
- super("-h, --help", { description: "Display this message", logger });
266
- this.runCommands = [];
267
- this.helpCommands = [];
268
- this.commands = commands;
269
- this.help = help;
270
- }
271
- shouldRun(args) {
272
- const isRestEmpty = !args["--"]?.length;
273
- if ((args.help || args.h) && isRestEmpty) {
274
- if (args["_"].length > 0) {
275
- for (const cmd of this.commands) {
276
- if (!cmd.default && !cmd.isInternal) {
277
- if (cmd.shouldRun(args)) {
278
- this.runCommands.push(cmd);
279
- } else if (cmd.hasPrefix(args)) {
280
- this.helpCommands.push(cmd);
281
- }
282
- }
283
- }
284
- }
285
- return true;
286
- } else {
287
- return false;
453
+ function makeVersionCommand(name, config) {
454
+ const command = {
455
+ callback() {
456
+ const text = `${name}/${config.version ? config.version : "unknown"}`;
457
+ console.log(text);
458
+ return text;
459
+ },
460
+ format: "-v, --version",
461
+ description: "Print version",
462
+ _arguments: [],
463
+ _options: [],
464
+ option() {
465
+ return command;
466
+ },
467
+ action() {
288
468
  }
289
- }
290
- async run() {
291
- const shouldHelp = this.runCommands.length > 0 ? this.runCommands : this.helpCommands;
292
- for (const line of this.help(shouldHelp)) {
293
- this.logger.println(line);
294
- }
295
- this.runCommands.splice(0);
296
- this.helpCommands.splice(0);
297
- }
298
- }
299
- class VersionCommand extends InternalCommand {
300
- constructor(version, logger) {
301
- super("-v, --version", { description: "Display version number", logger });
302
- this.version = version;
303
- }
304
- shouldRun(args) {
305
- const isEmpty = !args["_"].length && !args["--"]?.length;
306
- if (args.version && isEmpty) {
307
- return true;
308
- } else if (args.v && isEmpty) {
309
- return true;
310
- } else {
469
+ };
470
+ const node = makeTreeNode({
471
+ command,
472
+ next() {
311
473
  return false;
312
474
  }
313
- }
314
- async run() {
315
- this.logger.println(this.version);
316
- }
317
- }
318
- function isArg(arg) {
319
- return arg[0] === "[" && arg[arg.length - 1] === "]" || arg[0] === "<" && arg[arg.length - 1] === ">";
475
+ });
476
+ const option = {
477
+ format: "-v, --version",
478
+ name: "version",
479
+ short: "v",
480
+ type: "boolean",
481
+ initial: void 0,
482
+ order: 999999999 + 1,
483
+ description: "Print version",
484
+ action() {
485
+ return node;
486
+ }
487
+ };
488
+ return option;
320
489
  }
321
-
322
- class Breadc {
323
- constructor(name, option) {
324
- this.options = [];
325
- this.commands = [];
326
- this.callbacks = {
327
- pre: [],
328
- post: []
329
- };
330
- this.name = name;
331
- this._version = option.version ?? "unknown";
332
- this.description = option.description;
333
- this.logger = createDefaultLogger(name, option.logger);
334
- this.commands.push(
335
- new VersionCommand(this.version(), this.logger),
336
- new HelpCommand(this.commands, this.help.bind(this), this.logger)
337
- );
338
- }
339
- version() {
340
- return `${this.name}/${this._version}`;
341
- }
342
- help(commands = []) {
343
- const output = [];
344
- const println = (msg) => output.push(msg);
345
- println(this.version());
346
- if (commands.length === 0) {
347
- if (this.description) {
348
- println("");
349
- if (Array.isArray(this.description)) {
350
- for (const line of this.description) {
351
- println(line);
352
- }
353
- } else {
354
- println(this.description);
490
+ function makeHelpCommand(name, config) {
491
+ function expandMessage(message) {
492
+ const result = [];
493
+ for (const row of message) {
494
+ if (typeof row === "function") {
495
+ const r = row();
496
+ if (r) {
497
+ result.push(...expandMessage(r));
498
+ }
499
+ } else if (typeof row === "string") {
500
+ result.push(row);
501
+ } else if (Array.isArray(row)) {
502
+ const lines = twoColumn(row);
503
+ for (const line of lines) {
504
+ result.push(line);
355
505
  }
356
506
  }
357
- if (this.defaultCommand) {
358
- println(``);
359
- println(`Usage:`);
360
- println(` $ ${this.name} ${this.defaultCommand.format}`);
361
- }
362
- } else if (commands.length === 1) {
363
- const command = commands[0];
364
- if (command.description) {
365
- println("");
366
- println(command.description);
367
- }
368
- println(``);
369
- println(`Usage:`);
370
- println(` $ ${this.name} ${command.format}`);
371
- }
372
- if (commands.length !== 1) {
373
- const cmdList = (commands.length === 0 ? this.commands : commands).filter(
374
- (c) => !c.isInternal
375
- );
376
- println(``);
377
- println(`Commands:`);
378
- const commandHelps = cmdList.map(
379
- (c) => [` $ ${this.name} ${c.format}`, c.description]
380
- );
381
- for (const line of twoColumn(commandHelps)) {
382
- println(line);
383
- }
384
- }
385
- println(``);
386
- println(`Options:`);
387
- const optionHelps = [].concat([
388
- ...commands.length > 0 ? commands.flatMap(
389
- (cmd) => cmd.options.map(
390
- (o) => [` ${o.format}`, o.description]
391
- )
392
- ) : [],
393
- ...this.options.map(
394
- (o) => [` ${o.format}`, o.description]
395
- ),
396
- [` -h, --help`, `Display this message`],
397
- [` -v, --version`, `Display version number`]
398
- ]);
399
- for (const line of twoColumn(optionHelps)) {
400
- println(line);
401
- }
402
- println(``);
403
- return output;
404
- }
405
- option(format, configOrDescription = "", otherConfig = {}) {
406
- const config = typeof configOrDescription === "object" ? configOrDescription : { ...otherConfig, description: configOrDescription };
407
- try {
408
- const option = new Option(format, config);
409
- this.options.push(option);
410
- } catch (error) {
411
- this.logger.warn(error.message);
412
- }
413
- return this;
414
- }
415
- command(format, configOrDescription = "", otherConfig = {}) {
416
- const config = typeof configOrDescription === "object" ? configOrDescription : { ...otherConfig, description: configOrDescription };
417
- const command = new Command(format, { ...config, logger: this.logger });
418
- if (command.default) {
419
- if (this.defaultCommand) {
420
- this.logger.warn("You can not have two default commands.");
421
- }
422
- this.defaultCommand = command;
423
507
  }
424
- this.commands.push(command);
425
- return command;
508
+ return result;
426
509
  }
427
- parse(args) {
428
- const allowOptions = [
429
- ...this.options,
430
- ...this.commands.flatMap((c) => c.options)
431
- ];
432
- {
433
- const names = /* @__PURE__ */ new Map();
434
- for (const option of allowOptions) {
435
- if (names.has(option.name)) {
436
- const otherOption = names.get(option.name);
437
- if (otherOption.type !== option.type) {
438
- this.logger.warn(`Option "${option.name}" encounters conflict`);
510
+ function expandCommands(cursor) {
511
+ const visited = /* @__PURE__ */ new WeakSet();
512
+ const commands = cursor.command ? [cursor.command] : [];
513
+ const q = [cursor];
514
+ visited.add(cursor);
515
+ for (let i = 0; i < q.length; i++) {
516
+ const cur = q[i];
517
+ for (const [_key, cmd] of cur.children) {
518
+ if (!visited.has(cmd)) {
519
+ visited.add(cmd);
520
+ if (cmd.command) {
521
+ commands.push(cmd.command);
439
522
  }
440
- } else {
441
- names.set(option.name, option);
523
+ q.push(cmd);
442
524
  }
443
525
  }
444
526
  }
445
- const alias = allowOptions.reduce(
446
- (map, o) => {
447
- if (o.shortcut) {
448
- map[o.shortcut] = o.name;
449
- }
450
- return map;
451
- },
452
- { h: "help", v: "version" }
453
- );
454
- const defaultValue = allowOptions.filter(
455
- (o) => o.type === "boolean" && o.default !== void 0 && o.default !== null && typeof o.default === "boolean"
456
- ).reduce((map, o) => {
457
- map[o.name] = o.default;
458
- return map;
459
- }, {});
460
- const argv = minimist(args, {
461
- string: allowOptions.filter((o) => o.type === "string").map((o) => o.name),
462
- boolean: allowOptions.filter((o) => o.type === "boolean").map((o) => o.name).concat(["help", "version"]),
463
- default: defaultValue,
464
- alias,
465
- "--": true,
466
- unknown: (t) => {
467
- if (t[0] !== "-")
468
- return true;
469
- else {
470
- if (["--help", "-h", "--version", "-v"].includes(t)) {
471
- return true;
527
+ return commands;
528
+ }
529
+ const command = {
530
+ callback(option2) {
531
+ const context = option2.__context__;
532
+ const cursor = option2.__cursor__;
533
+ const output = [
534
+ `${name}/${config.version ? config.version : "unknown"}`,
535
+ () => {
536
+ if (config.description) {
537
+ return ["", config.description];
472
538
  } else {
473
- this.logger.warn(`Find unknown flag "${t}"`);
474
- return false;
539
+ return void 0;
475
540
  }
476
- }
477
- }
478
- });
479
- for (const shortcut of Object.keys(alias)) {
480
- delete argv[shortcut];
541
+ },
542
+ () => {
543
+ const cmds = expandCommands(cursor);
544
+ if (cmds.length > 0) {
545
+ return [
546
+ "",
547
+ color.bold(color.underline("Commands:")),
548
+ cmds.map((cmd) => [
549
+ ` ${color.bold(name)} ${color.bold(cmd.format)}`,
550
+ cmd.description
551
+ ])
552
+ ];
553
+ } else {
554
+ return void 0;
555
+ }
556
+ },
557
+ "",
558
+ color.bold(color.underline("Options:")),
559
+ [...context.options.entries()].filter(([key, op]) => key === op.name).sort((lhs, rhs) => lhs[1].order - rhs[1].order).map(([_key, op]) => [
560
+ " " + (!op.short ? " " : "") + color.bold(op.format),
561
+ op.description
562
+ ]),
563
+ ""
564
+ ];
565
+ const text = expandMessage(output).join("\n");
566
+ console.log(text);
567
+ return text;
568
+ },
569
+ format: "-h, --help",
570
+ description: "Print help",
571
+ _arguments: [],
572
+ _options: [],
573
+ option() {
574
+ return command;
575
+ },
576
+ action() {
481
577
  }
482
- for (const command of this.commands) {
483
- if (!command.default && command.shouldRun(argv)) {
484
- return command.parseArgs(argv, this.options);
485
- }
578
+ };
579
+ const node = makeTreeNode({
580
+ command,
581
+ next() {
582
+ return false;
486
583
  }
487
- if (this.defaultCommand) {
488
- this.defaultCommand.shouldRun(argv);
489
- return this.defaultCommand.parseArgs(argv, this.options);
584
+ });
585
+ const option = {
586
+ format: "-h, --help",
587
+ name: "help",
588
+ short: "h",
589
+ type: "boolean",
590
+ initial: void 0,
591
+ description: "Print help",
592
+ order: 999999999,
593
+ action(cursor, _token, context) {
594
+ context.result.options.__cursor__ = cursor;
595
+ context.result.options.__context__ = context;
596
+ return node;
490
597
  }
491
- const argumentss = argv["_"];
492
- const options = argv;
493
- delete options["_"];
494
- delete options["--"];
495
- delete options["help"];
496
- delete options["version"];
497
- return {
498
- command: void 0,
499
- arguments: argumentss,
500
- options,
501
- "--": []
502
- };
503
- }
504
- on(event, fn) {
505
- this.callbacks[event].push(fn);
506
- }
507
- async run(args) {
508
- const parsed = this.parse(args);
509
- if (parsed.command) {
510
- await Promise.all(
511
- this.callbacks.pre.map((fn) => fn(parsed.options))
512
- );
513
- const returnValue = await parsed.command.run(...parsed.arguments, {
514
- "--": parsed["--"],
515
- ...parsed.options
516
- });
517
- await Promise.all(
518
- this.callbacks.post.map((fn) => fn(parsed.options))
598
+ };
599
+ return option;
600
+ }
601
+
602
+ function breadc(name, config = {}) {
603
+ let defaultCommand = void 0;
604
+ const globalOptions = [];
605
+ const container = makePluginContainer(config.plugins);
606
+ const root = makeTreeNode({
607
+ init(context) {
608
+ initContextOptions(globalOptions, context);
609
+ if (defaultCommand) {
610
+ initContextOptions(defaultCommand._options, context);
611
+ }
612
+ initContextOptions(
613
+ [makeHelpCommand(name, config), makeVersionCommand(name, config)],
614
+ context
519
615
  );
520
- return returnValue;
521
- } else {
616
+ },
617
+ finish() {
618
+ }
619
+ });
620
+ const breadc2 = {
621
+ option(format, _config, _config2 = {}) {
622
+ const config2 = typeof _config === "string" ? { description: _config, ..._config2 } : _config;
623
+ const option = makeOption(format, config2);
624
+ globalOptions.push(option);
625
+ return breadc2;
626
+ },
627
+ command(text, _config = {}) {
628
+ const config2 = typeof _config === "string" ? { description: _config } : _config;
629
+ const command = makeCommand(text, config2, root, container);
630
+ if (command._arguments.length === 0 || command._arguments[0].type !== "const") {
631
+ defaultCommand = command;
632
+ }
633
+ return command;
634
+ },
635
+ parse(args) {
636
+ const result = parse(root, args);
637
+ return result;
638
+ },
639
+ async run(args) {
640
+ const result = breadc2.parse(args);
641
+ const command = result.command;
642
+ if (command) {
643
+ if (command.callback) {
644
+ await container.preRun(breadc2);
645
+ const r = command.callback(...result.arguments, {
646
+ ...result.options,
647
+ "--": result["--"]
648
+ });
649
+ await container.postRun(breadc2);
650
+ return r;
651
+ }
652
+ }
522
653
  return void 0;
523
654
  }
524
- }
525
- }
526
-
527
- function breadc(name, option = {}) {
528
- return new Breadc(name, option);
655
+ };
656
+ return breadc2;
529
657
  }
530
658
 
531
- module.exports = breadc;
659
+ exports.BreadcError = BreadcError;
660
+ exports.ParseError = ParseError;
661
+ exports.breadc = breadc;
662
+ exports.default = breadc;
663
+ exports.definePlugin = definePlugin;