breadc 0.4.6 → 0.6.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/README.md CHANGED
@@ -6,8 +6,8 @@ Yet another Command Line Application Framework powered by [minimist](https://www
6
6
 
7
7
  ## Features
8
8
 
9
- + ⚡️ **Light-weight**: Only 61 kB.
10
- + 📖 **East to Learn**: Breadc is basically compatible with [cac](https://github.com/cacjs/cac) and there are only 4 APIs for building a CLI application: `command`, `option`, `action`, `run`.
9
+ + ⚡️ **Light-weight**: Only 40 kB (Unpacked).
10
+ + 📖 **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`.
11
11
  + 💻 **TypeScript Infer**: IDE will automatically infer the type of your command action function.
12
12
 
13
13
  ## Installation
package/dist/index.cjs CHANGED
@@ -1,14 +1,21 @@
1
1
  'use strict';
2
2
 
3
- Object.defineProperty(exports, '__esModule', { value: true });
4
-
5
- const kolorist = require('kolorist');
6
3
  const minimist = require('minimist');
4
+ const kolorist = require('kolorist');
7
5
 
8
6
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e["default"] : e; }
9
7
 
10
- const kolorist__default = /*#__PURE__*/_interopDefaultLegacy(kolorist);
11
8
  const minimist__default = /*#__PURE__*/_interopDefaultLegacy(minimist);
9
+ const kolorist__default = /*#__PURE__*/_interopDefaultLegacy(kolorist);
10
+
11
+ function twoColumn(texts, split = " ") {
12
+ const left = padRight(texts.map((t) => t[0]));
13
+ return left.map((l, idx) => l + split + texts[idx][1]);
14
+ }
15
+ function padRight(texts, fill = " ") {
16
+ const length = texts.map((t) => t.length).reduce((max, l) => Math.max(max, l), 0);
17
+ return texts.map((t) => t + fill.repeat(length - t.length));
18
+ }
12
19
 
13
20
  function createDefaultLogger(name, logger) {
14
21
  const println = !!logger && typeof logger === "function" ? logger : logger?.println ?? ((message, ...args) => {
@@ -64,15 +71,32 @@ Option.OptionRE = /^(-[a-zA-Z0-9], )?--([a-zA-Z0-9\-]+)( \[[a-zA-Z0-9]+\]| <[a-z
64
71
  const _Command = class {
65
72
  constructor(format, config) {
66
73
  this.options = [];
67
- this.format = config.condition ? [format] : format.split(" ").map((t) => t.trim()).filter(Boolean);
68
- this.default = this.format.length === 0 || this.format[0][0] === "[" || this.format[0][0] === "<";
74
+ this.format = format;
75
+ const pieces = format.split(" ").map((t) => t.trim()).filter(Boolean);
76
+ const prefix = pieces.filter((p) => !isArg(p));
77
+ this.default = prefix.length === 0;
78
+ this.prefix = this.default ? [] : [prefix];
79
+ this.arguments = pieces.filter(isArg);
69
80
  this.description = config.description ?? "";
70
- this.conditionFn = config.condition;
71
81
  this.logger = config.logger;
72
- if (this.format.length > _Command.MaxDep) {
73
- this.logger.warn(`Command format string "${format}" is too long`);
82
+ {
83
+ const restArgs = this.arguments.findIndex((a) => a.startsWith("[..."));
84
+ if (restArgs !== -1 && restArgs !== this.arguments.length - 1) {
85
+ this.logger.warn(`Expand arguments ${this.arguments[restArgs]} should be placed at the last position`);
86
+ }
87
+ if (pieces.length > _Command.MaxDep) {
88
+ this.logger.warn(`Command format string "${format}" is too long`);
89
+ }
74
90
  }
75
91
  }
92
+ get isInternal() {
93
+ return this instanceof InternalCommand;
94
+ }
95
+ alias(command) {
96
+ const pieces = command.split(" ").map((t) => t.trim()).filter(Boolean);
97
+ this.prefix.push(pieces);
98
+ return this;
99
+ }
76
100
  option(format, configOrDescription = "", otherConfig = {}) {
77
101
  const config = typeof configOrDescription === "object" ? configOrDescription : { ...otherConfig, description: configOrDescription };
78
102
  try {
@@ -83,59 +107,63 @@ const _Command = class {
83
107
  }
84
108
  return this;
85
109
  }
86
- get hasConditionFn() {
87
- return !!this.conditionFn;
88
- }
89
- shouldRun(args) {
90
- if (this.conditionFn) {
91
- return this.conditionFn(args);
110
+ hasPrefix(parsedArgs) {
111
+ const argv = parsedArgs["_"];
112
+ if (argv.length === 0) {
113
+ return this.default;
92
114
  } else {
93
- if (this.default)
94
- return true;
95
- const isCmd = (t) => t[0] !== "[" && t[0] !== "<";
96
- for (let i = 0; i < this.format.length; i++) {
97
- if (!isCmd(this.format[i])) {
115
+ for (const prefix of this.prefix) {
116
+ if (prefix.length > 0 && prefix[0] === argv[0]) {
98
117
  return true;
99
118
  }
100
- if (i >= args["_"].length || this.format[i] !== args["_"][i]) {
101
- return false;
102
- }
103
119
  }
104
- return true;
120
+ return false;
105
121
  }
106
122
  }
107
- parseArgs(args, globalOptions) {
108
- if (this.conditionFn) {
109
- const argumentss2 = args["_"];
110
- const options2 = args;
111
- delete options2["_"];
112
- return {
113
- command: this,
114
- arguments: argumentss2,
115
- options: args
116
- };
123
+ shouldRun(parsedArgs) {
124
+ const args = parsedArgs["_"];
125
+ for (const prefix of this.prefix) {
126
+ let match = true;
127
+ for (let i = 0; match && i < prefix.length; i++) {
128
+ if (args[i] !== prefix[i]) {
129
+ match = false;
130
+ }
131
+ }
132
+ if (match) {
133
+ args.splice(0, prefix.length);
134
+ return true;
135
+ }
117
136
  }
118
- const isCmd = (t) => t[0] !== "[" && t[0] !== "<";
119
- const argumentss = [];
120
- for (let i = 0; i < this.format.length; i++) {
121
- if (isCmd(this.format[i]))
122
- continue;
123
- if (i < args["_"].length) {
124
- if (this.format[i].startsWith("[...")) {
125
- argumentss.push(args["_"].slice(i).map(String));
137
+ if (this.default)
138
+ return true;
139
+ return false;
140
+ }
141
+ parseArgs(argv, globalOptions) {
142
+ const pieces = argv["_"];
143
+ const args = [];
144
+ const restArgs = [];
145
+ for (let i = 0, used = 0; i <= this.arguments.length; i++) {
146
+ if (i === this.arguments.length) {
147
+ restArgs.push(...pieces.slice(used).map(String));
148
+ restArgs.push(...(argv["--"] ?? []).map(String));
149
+ } else if (i < pieces.length) {
150
+ if (this.arguments[i].startsWith("[...")) {
151
+ args.push(pieces.slice(i).map(String));
152
+ used = pieces.length;
126
153
  } else {
127
- argumentss.push(String(args["_"][i]));
154
+ args.push(String(pieces[i]));
155
+ used++;
128
156
  }
129
157
  } else {
130
- if (this.format[i].startsWith("<")) {
131
- this.logger.warn(`You should provide the argument "${this.format[i]}"`);
132
- argumentss.push(void 0);
133
- } else if (this.format[i].startsWith("[...")) {
134
- argumentss.push([]);
135
- } else if (this.format[i].startsWith("[")) {
136
- argumentss.push(void 0);
158
+ if (this.arguments[i].startsWith("<")) {
159
+ this.logger.warn(`You should provide the argument "${this.arguments[i]}"`);
160
+ args.push("");
161
+ } else if (this.arguments[i].startsWith("[...")) {
162
+ args.push([]);
163
+ } else if (this.arguments[i].startsWith("[")) {
164
+ args.push(void 0);
137
165
  } else {
138
- this.logger.warn(`unknown format string ("${this.format[i]}")`);
166
+ this.logger.warn(`unknown format string ("${this.arguments[i]}")`);
139
167
  }
140
168
  }
141
169
  }
@@ -143,7 +171,7 @@ const _Command = class {
143
171
  map.set(o.name, o);
144
172
  return map;
145
173
  }, /* @__PURE__ */ new Map());
146
- const options = args;
174
+ const options = argv;
147
175
  delete options["_"];
148
176
  for (const [name, rawOption] of fullOptions) {
149
177
  if (rawOption.required) {
@@ -159,13 +187,14 @@ const _Command = class {
159
187
  options[name] = void 0;
160
188
  }
161
189
  }
162
- if (rawOption.construct) {
163
- options[name] = rawOption.construct(options[name]);
164
- } else if (rawOption.default) {
165
- if (!options[name]) {
190
+ if (rawOption.default !== void 0) {
191
+ if (options[name] === void 0 || options[name] === false) {
166
192
  options[name] = rawOption.default;
167
193
  }
168
194
  }
195
+ if (rawOption.construct !== void 0) {
196
+ options[name] = rawOption.construct(options[name]);
197
+ }
169
198
  }
170
199
  for (const key of Object.keys(options)) {
171
200
  if (!fullOptions.has(key)) {
@@ -174,66 +203,103 @@ const _Command = class {
174
203
  }
175
204
  return {
176
205
  command: this,
177
- arguments: argumentss,
178
- options
206
+ arguments: args,
207
+ options,
208
+ "--": restArgs
179
209
  };
180
210
  }
181
211
  action(fn) {
182
212
  this.actionFn = fn;
183
- return this;
184
213
  }
185
214
  async run(...args) {
186
215
  if (this.actionFn) {
187
- return await this.actionFn(...args, { logger: this.logger });
216
+ return await this.actionFn(...args, {
217
+ logger: this.logger,
218
+ color: kolorist__default
219
+ });
188
220
  } else {
189
- this.logger.warn(`You may miss action function in "${this.format}"`);
221
+ this.logger.warn(`You may miss action function in ${this.format ? `"${this.format}"` : "<default command>"}`);
222
+ return void 0;
190
223
  }
191
224
  }
192
225
  };
193
226
  let Command = _Command;
194
227
  Command.MaxDep = 5;
195
- function createHelpCommand(breadc) {
196
- let helpCommand = void 0;
197
- return new Command("-h, --help", {
198
- condition(args) {
199
- const isEmpty = !args["--"]?.length;
200
- if ((args.help || args.h) && isEmpty) {
201
- if (args["_"].length > 0) {
202
- for (const cmd of breadc.commands) {
203
- if (!cmd.hasConditionFn && !cmd.default && cmd.shouldRun(args)) {
204
- helpCommand = cmd;
205
- return true;
228
+ class InternalCommand extends Command {
229
+ hasPrefix(_args) {
230
+ return false;
231
+ }
232
+ parseArgs(args, _globalOptions) {
233
+ const argumentss = args["_"];
234
+ const options = args;
235
+ delete options["_"];
236
+ delete options["help"];
237
+ delete options["version"];
238
+ return {
239
+ command: this,
240
+ arguments: argumentss,
241
+ options: args,
242
+ "--": []
243
+ };
244
+ }
245
+ }
246
+ class HelpCommand extends InternalCommand {
247
+ constructor(commands, help, logger) {
248
+ super("-h, --help", { description: "Display this message", logger });
249
+ this.runCommands = [];
250
+ this.helpCommands = [];
251
+ this.commands = commands;
252
+ this.help = help;
253
+ }
254
+ shouldRun(args) {
255
+ const isRestEmpty = !args["--"]?.length;
256
+ if ((args.help || args.h) && isRestEmpty) {
257
+ if (args["_"].length > 0) {
258
+ for (const cmd of this.commands) {
259
+ if (!cmd.default && !cmd.isInternal) {
260
+ if (cmd.shouldRun(args)) {
261
+ this.runCommands.push(cmd);
262
+ } else if (cmd.hasPrefix(args)) {
263
+ this.helpCommands.push(cmd);
206
264
  }
207
265
  }
208
266
  }
209
- return true;
210
- } else {
211
- return false;
212
267
  }
213
- },
214
- logger: breadc.logger
215
- }).action(() => {
216
- for (const line of breadc.help(helpCommand)) {
217
- breadc.logger.println(line);
268
+ return true;
269
+ } else {
270
+ return false;
218
271
  }
219
- });
272
+ }
273
+ async run() {
274
+ const shouldHelp = this.runCommands.length > 0 ? this.runCommands : this.helpCommands;
275
+ for (const line of this.help(shouldHelp)) {
276
+ this.logger.println(line);
277
+ }
278
+ this.runCommands.splice(0);
279
+ this.helpCommands.splice(0);
280
+ }
220
281
  }
221
- function createVersionCommand(breadc) {
222
- return new Command("-v, --version", {
223
- condition(args) {
224
- const isEmpty = !args["_"].length && !args["--"]?.length;
225
- if (args.version && isEmpty) {
226
- return true;
227
- } else if (args.v && isEmpty) {
228
- return true;
229
- } else {
230
- return false;
231
- }
232
- },
233
- logger: breadc.logger
234
- }).action(() => {
235
- breadc.logger.println(breadc.version());
236
- });
282
+ class VersionCommand extends InternalCommand {
283
+ constructor(version, logger) {
284
+ super("-v, --version", { description: "Display version number", logger });
285
+ this.version = version;
286
+ }
287
+ shouldRun(args) {
288
+ const isEmpty = !args["_"].length && !args["--"]?.length;
289
+ if (args.version && isEmpty) {
290
+ return true;
291
+ } else if (args.v && isEmpty) {
292
+ return true;
293
+ } else {
294
+ return false;
295
+ }
296
+ }
297
+ async run() {
298
+ this.logger.println(this.version);
299
+ }
300
+ }
301
+ function isArg(arg) {
302
+ return arg[0] === "[" && arg[arg.length - 1] === "]" || arg[0] === "<" && arg[arg.length - 1] === ">";
237
303
  }
238
304
 
239
305
  class Breadc {
@@ -248,24 +314,16 @@ class Breadc {
248
314
  this._version = option.version ?? "unknown";
249
315
  this.description = option.description;
250
316
  this.logger = createDefaultLogger(name, option.logger);
251
- const breadc = {
252
- name: this.name,
253
- version: () => this.version.call(this),
254
- help: (command) => this.help.call(this, command),
255
- logger: this.logger,
256
- options: this.options,
257
- commands: this.commands
258
- };
259
- this.commands.push(createVersionCommand(breadc), createHelpCommand(breadc));
317
+ this.commands.push(new VersionCommand(this.version(), this.logger), new HelpCommand(this.commands, this.help.bind(this), this.logger));
260
318
  }
261
319
  version() {
262
320
  return `${this.name}/${this._version}`;
263
321
  }
264
- help(command) {
322
+ help(commands = []) {
265
323
  const output = [];
266
324
  const println = (msg) => output.push(msg);
267
325
  println(this.version());
268
- if (!command) {
326
+ if (commands.length === 0) {
269
327
  if (this.description) {
270
328
  println("");
271
329
  if (Array.isArray(this.description)) {
@@ -276,27 +334,26 @@ class Breadc {
276
334
  println(this.description);
277
335
  }
278
336
  }
279
- } else {
280
- if (command.description) {
281
- println("");
282
- println(command.description);
283
- }
284
- }
285
- if (!command) {
286
337
  if (this.defaultCommand) {
287
338
  println(``);
288
339
  println(`Usage:`);
289
- println(` $ ${this.name} ${this.defaultCommand.format.join(" ")}`);
340
+ println(` $ ${this.name} ${this.defaultCommand.format}`);
341
+ }
342
+ } else if (commands.length === 1) {
343
+ const command = commands[0];
344
+ if (command.description) {
345
+ println("");
346
+ println(command.description);
290
347
  }
291
- } else {
292
348
  println(``);
293
349
  println(`Usage:`);
294
- println(` $ ${this.name} ${command.format.join(" ")}`);
350
+ println(` $ ${this.name} ${command.format}`);
295
351
  }
296
- if (!command && this.commands.length > 2) {
352
+ if (commands.length !== 1) {
353
+ const cmdList = (commands.length === 0 ? this.commands : commands).filter((c) => !c.isInternal);
297
354
  println(``);
298
355
  println(`Commands:`);
299
- const commandHelps = this.commands.filter((c) => !c.hasConditionFn).map((c) => [` $ ${this.name} ${c.format.join(" ")}`, c.description]);
356
+ const commandHelps = cmdList.map((c) => [` $ ${this.name} ${c.format}`, c.description]);
300
357
  for (const line of twoColumn(commandHelps)) {
301
358
  println(line);
302
359
  }
@@ -304,7 +361,7 @@ class Breadc {
304
361
  println(``);
305
362
  println(`Options:`);
306
363
  const optionHelps = [].concat([
307
- ...command ? command.options.map((o) => [` ${o.format}`, o.description]) : [],
364
+ ...commands.length > 0 ? commands.flatMap((cmd) => cmd.options.map((o) => [` ${o.format}`, o.description])) : [],
308
365
  ...this.options.map((o) => [` ${o.format}`, o.description]),
309
366
  [` -h, --help`, `Display this message`],
310
367
  [` -v, --version`, `Display version number`]
@@ -342,16 +399,30 @@ class Breadc {
342
399
  ...this.options,
343
400
  ...this.commands.flatMap((c) => c.options)
344
401
  ];
402
+ {
403
+ const names = /* @__PURE__ */ new Map();
404
+ for (const option of allowOptions) {
405
+ if (names.has(option.name)) {
406
+ const otherOption = names.get(option.name);
407
+ if (otherOption.type !== option.type) {
408
+ this.logger.warn(`Option "${option.name}" encounters conflict`);
409
+ }
410
+ } else {
411
+ names.set(option.name, option);
412
+ }
413
+ }
414
+ }
345
415
  const alias = allowOptions.reduce((map, o) => {
346
416
  if (o.shortcut) {
347
417
  map[o.shortcut] = o.name;
348
418
  }
349
419
  return map;
350
- }, {});
420
+ }, { h: "help", v: "version" });
351
421
  const argv = minimist__default(args, {
352
422
  string: allowOptions.filter((o) => o.type === "string").map((o) => o.name),
353
- boolean: allowOptions.filter((o) => o.type === "boolean").map((o) => o.name),
423
+ boolean: allowOptions.filter((o) => o.type === "boolean").map((o) => o.name).concat(["help", "version"]),
354
424
  alias,
425
+ "--": true,
355
426
  unknown: (t) => {
356
427
  if (t[0] !== "-")
357
428
  return true;
@@ -374,15 +445,20 @@ class Breadc {
374
445
  }
375
446
  }
376
447
  if (this.defaultCommand) {
448
+ this.defaultCommand.shouldRun(argv);
377
449
  return this.defaultCommand.parseArgs(argv, this.options);
378
450
  }
379
451
  const argumentss = argv["_"];
380
452
  const options = argv;
381
453
  delete options["_"];
454
+ delete options["--"];
455
+ delete options["help"];
456
+ delete options["version"];
382
457
  return {
383
458
  command: void 0,
384
459
  arguments: argumentss,
385
- options
460
+ options,
461
+ "--": []
386
462
  };
387
463
  }
388
464
  on(event, fn) {
@@ -392,25 +468,20 @@ class Breadc {
392
468
  const parsed = this.parse(args);
393
469
  if (parsed.command) {
394
470
  await Promise.all(this.callbacks.pre.map((fn) => fn(parsed.options)));
395
- const returnValue = await parsed.command.run(...parsed.arguments, parsed.options);
471
+ const returnValue = await parsed.command.run(...parsed.arguments, {
472
+ "--": parsed["--"],
473
+ ...parsed.options
474
+ });
396
475
  await Promise.all(this.callbacks.post.map((fn) => fn(parsed.options)));
397
476
  return returnValue;
477
+ } else {
478
+ return void 0;
398
479
  }
399
480
  }
400
481
  }
401
- function twoColumn(texts, split = " ") {
402
- const left = padRight(texts.map((t) => t[0]));
403
- return left.map((l, idx) => l + split + texts[idx][1]);
404
- }
405
- function padRight(texts, fill = " ") {
406
- const length = texts.map((t) => t.length).reduce((max, l) => Math.max(max, l), 0);
407
- return texts.map((t) => t + fill.repeat(length - t.length));
408
- }
409
482
 
410
483
  function breadc(name, option = {}) {
411
484
  return new Breadc(name, option);
412
485
  }
413
486
 
414
- exports.kolorist = kolorist__default;
415
- exports.minimist = minimist__default;
416
- exports["default"] = breadc;
487
+ module.exports = breadc;
package/dist/index.d.ts CHANGED
@@ -1,10 +1,18 @@
1
+ import kolorist from 'kolorist';
1
2
  import { ParsedArgs } from 'minimist';
2
- export { default as minimist } from 'minimist';
3
- export { default as kolorist } from 'kolorist';
4
3
 
5
4
  interface OptionConfig<F extends string, T = never> {
5
+ /**
6
+ * Option description
7
+ */
6
8
  description?: string;
7
- default?: T;
9
+ /**
10
+ * Option string default value
11
+ */
12
+ default?: string;
13
+ /**
14
+ * Transform option text
15
+ */
8
16
  construct?: (rawText: ExtractOptionType<F>) => T;
9
17
  }
10
18
  /**
@@ -20,7 +28,7 @@ declare class Option<T extends string = string, F = string> {
20
28
  private static OptionRE;
21
29
  readonly name: string;
22
30
  readonly shortcut?: string;
23
- readonly default?: F;
31
+ readonly default?: string;
24
32
  readonly format: string;
25
33
  readonly description: string;
26
34
  readonly type: 'string' | 'boolean';
@@ -29,29 +37,30 @@ declare class Option<T extends string = string, F = string> {
29
37
  constructor(format: T, config?: OptionConfig<T, F>);
30
38
  }
31
39
 
32
- declare type ConditionFn = (args: ParsedArgs) => boolean;
33
40
  interface CommandConfig {
34
41
  description?: string;
35
42
  }
36
43
  declare class Command<F extends string = string, CommandOption extends object = {}> {
37
- private static MaxDep;
38
- private readonly conditionFn?;
39
- private readonly logger;
40
- readonly format: string[];
41
- readonly default: boolean;
44
+ protected static MaxDep: number;
45
+ protected readonly logger: Logger;
46
+ readonly format: string;
42
47
  readonly description: string;
48
+ readonly prefix: string[][];
49
+ readonly arguments: string[];
50
+ readonly default: boolean;
43
51
  readonly options: Option[];
44
52
  private actionFn?;
45
53
  constructor(format: F, config: CommandConfig & {
46
- condition?: ConditionFn;
47
54
  logger: Logger;
48
55
  });
56
+ get isInternal(): boolean;
57
+ alias(command: string): this;
49
58
  option<OF extends string, T = undefined>(format: OF, description: string, config?: Omit<OptionConfig<OF, T>, 'description'>): Command<F, CommandOption & ExtractOption<OF, T>>;
50
59
  option<OF extends string, T = undefined>(format: OF, config?: OptionConfig<OF, T>): Command<F, CommandOption & ExtractOption<OF, T>>;
51
- get hasConditionFn(): boolean;
52
- shouldRun(args: ParsedArgs): boolean;
53
- parseArgs(args: ParsedArgs, globalOptions: Option[]): ParseResult;
54
- action(fn: ActionFn<ExtractCommand<F>, CommandOption>): this;
60
+ hasPrefix(parsedArgs: ParsedArgs): boolean;
61
+ shouldRun(parsedArgs: ParsedArgs): boolean;
62
+ parseArgs(argv: ParsedArgs, globalOptions: Option[]): ParseResult;
63
+ action(fn: ActionFn<ExtractCommand<F>, CommandOption>): void;
55
64
  run(...args: any[]): Promise<any>;
56
65
  }
57
66
 
@@ -73,6 +82,7 @@ interface ParseResult {
73
82
  command: Command | undefined;
74
83
  arguments: any[];
75
84
  options: Record<string, string>;
85
+ '--': string[];
76
86
  }
77
87
  declare type ExtractOption<T extends string, D = undefined> = {
78
88
  [k in ExtractOptionName<T>]: D extends undefined ? ExtractOptionType<T> : D;
@@ -92,8 +102,11 @@ declare type Letter = Lowercase | Uppercase;
92
102
  declare type Push<T extends any[], U, R> = [...T, U, R];
93
103
  declare type Context = {
94
104
  logger: Logger;
105
+ color: typeof kolorist;
95
106
  };
96
- declare type ActionFn<T extends any[], Option extends object = {}, R = any> = (...arg: Push<T, Option, Context>) => R | Promise<R>;
107
+ declare type ActionFn<T extends any[], Option extends object = {}, R = any> = (...arg: Push<T, Option & {
108
+ '--': string[];
109
+ }, Context>) => R | Promise<R>;
97
110
  /**
98
111
  * Max Dep: 5
99
112
  *
@@ -105,13 +118,13 @@ declare class Breadc<GlobalOption extends object = {}> {
105
118
  private readonly name;
106
119
  private readonly _version;
107
120
  private readonly description?;
121
+ readonly logger: Logger;
108
122
  private readonly options;
109
123
  private readonly commands;
110
124
  private defaultCommand?;
111
- readonly logger: Logger;
112
125
  constructor(name: string, option: AppOption);
113
126
  version(): string;
114
- help(command?: Command): string[];
127
+ help(commands?: Command[]): string[];
115
128
  option<F extends string, T = undefined>(format: F, description: string, config?: Omit<OptionConfig<F, T>, 'description'>): Breadc<GlobalOption & ExtractOption<F, T>>;
116
129
  option<F extends string, T = undefined>(format: F, config?: OptionConfig<F, T>): Breadc<GlobalOption & ExtractOption<F, T>>;
117
130
  command<F extends string>(format: F, description: string, config?: Omit<CommandConfig, 'description'>): Command<F, GlobalOption>;
@@ -119,9 +132,9 @@ declare class Breadc<GlobalOption extends object = {}> {
119
132
  parse(args: string[]): ParseResult;
120
133
  private readonly callbacks;
121
134
  on(event: 'pre' | 'post', fn: (option: GlobalOption) => void | Promise<void>): void;
122
- run(args: string[]): Promise<any>;
135
+ run<T>(args: string[]): Promise<T | undefined>;
123
136
  }
124
137
 
125
- declare function breadc(name: string, option?: AppOption): Breadc<{}>;
138
+ declare function breadc<T extends object = {}>(name: string, option?: AppOption): Breadc<T>;
126
139
 
127
140
  export { breadc as default };
package/dist/index.mjs CHANGED
@@ -1,7 +1,14 @@
1
- import { blue, yellow, red, gray } from 'kolorist';
2
- export { default as kolorist } from 'kolorist';
3
1
  import minimist from 'minimist';
4
- export { default as minimist } from 'minimist';
2
+ import kolorist, { blue, yellow, red, gray } from 'kolorist';
3
+
4
+ function twoColumn(texts, split = " ") {
5
+ const left = padRight(texts.map((t) => t[0]));
6
+ return left.map((l, idx) => l + split + texts[idx][1]);
7
+ }
8
+ function padRight(texts, fill = " ") {
9
+ const length = texts.map((t) => t.length).reduce((max, l) => Math.max(max, l), 0);
10
+ return texts.map((t) => t + fill.repeat(length - t.length));
11
+ }
5
12
 
6
13
  function createDefaultLogger(name, logger) {
7
14
  const println = !!logger && typeof logger === "function" ? logger : logger?.println ?? ((message, ...args) => {
@@ -57,15 +64,32 @@ Option.OptionRE = /^(-[a-zA-Z0-9], )?--([a-zA-Z0-9\-]+)( \[[a-zA-Z0-9]+\]| <[a-z
57
64
  const _Command = class {
58
65
  constructor(format, config) {
59
66
  this.options = [];
60
- this.format = config.condition ? [format] : format.split(" ").map((t) => t.trim()).filter(Boolean);
61
- this.default = this.format.length === 0 || this.format[0][0] === "[" || this.format[0][0] === "<";
67
+ this.format = format;
68
+ const pieces = format.split(" ").map((t) => t.trim()).filter(Boolean);
69
+ const prefix = pieces.filter((p) => !isArg(p));
70
+ this.default = prefix.length === 0;
71
+ this.prefix = this.default ? [] : [prefix];
72
+ this.arguments = pieces.filter(isArg);
62
73
  this.description = config.description ?? "";
63
- this.conditionFn = config.condition;
64
74
  this.logger = config.logger;
65
- if (this.format.length > _Command.MaxDep) {
66
- this.logger.warn(`Command format string "${format}" is too long`);
75
+ {
76
+ const restArgs = this.arguments.findIndex((a) => a.startsWith("[..."));
77
+ if (restArgs !== -1 && restArgs !== this.arguments.length - 1) {
78
+ this.logger.warn(`Expand arguments ${this.arguments[restArgs]} should be placed at the last position`);
79
+ }
80
+ if (pieces.length > _Command.MaxDep) {
81
+ this.logger.warn(`Command format string "${format}" is too long`);
82
+ }
67
83
  }
68
84
  }
85
+ get isInternal() {
86
+ return this instanceof InternalCommand;
87
+ }
88
+ alias(command) {
89
+ const pieces = command.split(" ").map((t) => t.trim()).filter(Boolean);
90
+ this.prefix.push(pieces);
91
+ return this;
92
+ }
69
93
  option(format, configOrDescription = "", otherConfig = {}) {
70
94
  const config = typeof configOrDescription === "object" ? configOrDescription : { ...otherConfig, description: configOrDescription };
71
95
  try {
@@ -76,59 +100,63 @@ const _Command = class {
76
100
  }
77
101
  return this;
78
102
  }
79
- get hasConditionFn() {
80
- return !!this.conditionFn;
81
- }
82
- shouldRun(args) {
83
- if (this.conditionFn) {
84
- return this.conditionFn(args);
103
+ hasPrefix(parsedArgs) {
104
+ const argv = parsedArgs["_"];
105
+ if (argv.length === 0) {
106
+ return this.default;
85
107
  } else {
86
- if (this.default)
87
- return true;
88
- const isCmd = (t) => t[0] !== "[" && t[0] !== "<";
89
- for (let i = 0; i < this.format.length; i++) {
90
- if (!isCmd(this.format[i])) {
108
+ for (const prefix of this.prefix) {
109
+ if (prefix.length > 0 && prefix[0] === argv[0]) {
91
110
  return true;
92
111
  }
93
- if (i >= args["_"].length || this.format[i] !== args["_"][i]) {
94
- return false;
95
- }
96
112
  }
97
- return true;
113
+ return false;
98
114
  }
99
115
  }
100
- parseArgs(args, globalOptions) {
101
- if (this.conditionFn) {
102
- const argumentss2 = args["_"];
103
- const options2 = args;
104
- delete options2["_"];
105
- return {
106
- command: this,
107
- arguments: argumentss2,
108
- options: args
109
- };
116
+ shouldRun(parsedArgs) {
117
+ const args = parsedArgs["_"];
118
+ for (const prefix of this.prefix) {
119
+ let match = true;
120
+ for (let i = 0; match && i < prefix.length; i++) {
121
+ if (args[i] !== prefix[i]) {
122
+ match = false;
123
+ }
124
+ }
125
+ if (match) {
126
+ args.splice(0, prefix.length);
127
+ return true;
128
+ }
110
129
  }
111
- const isCmd = (t) => t[0] !== "[" && t[0] !== "<";
112
- const argumentss = [];
113
- for (let i = 0; i < this.format.length; i++) {
114
- if (isCmd(this.format[i]))
115
- continue;
116
- if (i < args["_"].length) {
117
- if (this.format[i].startsWith("[...")) {
118
- argumentss.push(args["_"].slice(i).map(String));
130
+ if (this.default)
131
+ return true;
132
+ return false;
133
+ }
134
+ parseArgs(argv, globalOptions) {
135
+ const pieces = argv["_"];
136
+ const args = [];
137
+ const restArgs = [];
138
+ for (let i = 0, used = 0; i <= this.arguments.length; i++) {
139
+ if (i === this.arguments.length) {
140
+ restArgs.push(...pieces.slice(used).map(String));
141
+ restArgs.push(...(argv["--"] ?? []).map(String));
142
+ } else if (i < pieces.length) {
143
+ if (this.arguments[i].startsWith("[...")) {
144
+ args.push(pieces.slice(i).map(String));
145
+ used = pieces.length;
119
146
  } else {
120
- argumentss.push(String(args["_"][i]));
147
+ args.push(String(pieces[i]));
148
+ used++;
121
149
  }
122
150
  } else {
123
- if (this.format[i].startsWith("<")) {
124
- this.logger.warn(`You should provide the argument "${this.format[i]}"`);
125
- argumentss.push(void 0);
126
- } else if (this.format[i].startsWith("[...")) {
127
- argumentss.push([]);
128
- } else if (this.format[i].startsWith("[")) {
129
- argumentss.push(void 0);
151
+ if (this.arguments[i].startsWith("<")) {
152
+ this.logger.warn(`You should provide the argument "${this.arguments[i]}"`);
153
+ args.push("");
154
+ } else if (this.arguments[i].startsWith("[...")) {
155
+ args.push([]);
156
+ } else if (this.arguments[i].startsWith("[")) {
157
+ args.push(void 0);
130
158
  } else {
131
- this.logger.warn(`unknown format string ("${this.format[i]}")`);
159
+ this.logger.warn(`unknown format string ("${this.arguments[i]}")`);
132
160
  }
133
161
  }
134
162
  }
@@ -136,7 +164,7 @@ const _Command = class {
136
164
  map.set(o.name, o);
137
165
  return map;
138
166
  }, /* @__PURE__ */ new Map());
139
- const options = args;
167
+ const options = argv;
140
168
  delete options["_"];
141
169
  for (const [name, rawOption] of fullOptions) {
142
170
  if (rawOption.required) {
@@ -152,13 +180,14 @@ const _Command = class {
152
180
  options[name] = void 0;
153
181
  }
154
182
  }
155
- if (rawOption.construct) {
156
- options[name] = rawOption.construct(options[name]);
157
- } else if (rawOption.default) {
158
- if (!options[name]) {
183
+ if (rawOption.default !== void 0) {
184
+ if (options[name] === void 0 || options[name] === false) {
159
185
  options[name] = rawOption.default;
160
186
  }
161
187
  }
188
+ if (rawOption.construct !== void 0) {
189
+ options[name] = rawOption.construct(options[name]);
190
+ }
162
191
  }
163
192
  for (const key of Object.keys(options)) {
164
193
  if (!fullOptions.has(key)) {
@@ -167,66 +196,103 @@ const _Command = class {
167
196
  }
168
197
  return {
169
198
  command: this,
170
- arguments: argumentss,
171
- options
199
+ arguments: args,
200
+ options,
201
+ "--": restArgs
172
202
  };
173
203
  }
174
204
  action(fn) {
175
205
  this.actionFn = fn;
176
- return this;
177
206
  }
178
207
  async run(...args) {
179
208
  if (this.actionFn) {
180
- return await this.actionFn(...args, { logger: this.logger });
209
+ return await this.actionFn(...args, {
210
+ logger: this.logger,
211
+ color: kolorist
212
+ });
181
213
  } else {
182
- this.logger.warn(`You may miss action function in "${this.format}"`);
214
+ this.logger.warn(`You may miss action function in ${this.format ? `"${this.format}"` : "<default command>"}`);
215
+ return void 0;
183
216
  }
184
217
  }
185
218
  };
186
219
  let Command = _Command;
187
220
  Command.MaxDep = 5;
188
- function createHelpCommand(breadc) {
189
- let helpCommand = void 0;
190
- return new Command("-h, --help", {
191
- condition(args) {
192
- const isEmpty = !args["--"]?.length;
193
- if ((args.help || args.h) && isEmpty) {
194
- if (args["_"].length > 0) {
195
- for (const cmd of breadc.commands) {
196
- if (!cmd.hasConditionFn && !cmd.default && cmd.shouldRun(args)) {
197
- helpCommand = cmd;
198
- return true;
221
+ class InternalCommand extends Command {
222
+ hasPrefix(_args) {
223
+ return false;
224
+ }
225
+ parseArgs(args, _globalOptions) {
226
+ const argumentss = args["_"];
227
+ const options = args;
228
+ delete options["_"];
229
+ delete options["help"];
230
+ delete options["version"];
231
+ return {
232
+ command: this,
233
+ arguments: argumentss,
234
+ options: args,
235
+ "--": []
236
+ };
237
+ }
238
+ }
239
+ class HelpCommand extends InternalCommand {
240
+ constructor(commands, help, logger) {
241
+ super("-h, --help", { description: "Display this message", logger });
242
+ this.runCommands = [];
243
+ this.helpCommands = [];
244
+ this.commands = commands;
245
+ this.help = help;
246
+ }
247
+ shouldRun(args) {
248
+ const isRestEmpty = !args["--"]?.length;
249
+ if ((args.help || args.h) && isRestEmpty) {
250
+ if (args["_"].length > 0) {
251
+ for (const cmd of this.commands) {
252
+ if (!cmd.default && !cmd.isInternal) {
253
+ if (cmd.shouldRun(args)) {
254
+ this.runCommands.push(cmd);
255
+ } else if (cmd.hasPrefix(args)) {
256
+ this.helpCommands.push(cmd);
199
257
  }
200
258
  }
201
259
  }
202
- return true;
203
- } else {
204
- return false;
205
260
  }
206
- },
207
- logger: breadc.logger
208
- }).action(() => {
209
- for (const line of breadc.help(helpCommand)) {
210
- breadc.logger.println(line);
261
+ return true;
262
+ } else {
263
+ return false;
211
264
  }
212
- });
265
+ }
266
+ async run() {
267
+ const shouldHelp = this.runCommands.length > 0 ? this.runCommands : this.helpCommands;
268
+ for (const line of this.help(shouldHelp)) {
269
+ this.logger.println(line);
270
+ }
271
+ this.runCommands.splice(0);
272
+ this.helpCommands.splice(0);
273
+ }
213
274
  }
214
- function createVersionCommand(breadc) {
215
- return new Command("-v, --version", {
216
- condition(args) {
217
- const isEmpty = !args["_"].length && !args["--"]?.length;
218
- if (args.version && isEmpty) {
219
- return true;
220
- } else if (args.v && isEmpty) {
221
- return true;
222
- } else {
223
- return false;
224
- }
225
- },
226
- logger: breadc.logger
227
- }).action(() => {
228
- breadc.logger.println(breadc.version());
229
- });
275
+ class VersionCommand extends InternalCommand {
276
+ constructor(version, logger) {
277
+ super("-v, --version", { description: "Display version number", logger });
278
+ this.version = version;
279
+ }
280
+ shouldRun(args) {
281
+ const isEmpty = !args["_"].length && !args["--"]?.length;
282
+ if (args.version && isEmpty) {
283
+ return true;
284
+ } else if (args.v && isEmpty) {
285
+ return true;
286
+ } else {
287
+ return false;
288
+ }
289
+ }
290
+ async run() {
291
+ this.logger.println(this.version);
292
+ }
293
+ }
294
+ function isArg(arg) {
295
+ return arg[0] === "[" && arg[arg.length - 1] === "]" || arg[0] === "<" && arg[arg.length - 1] === ">";
230
296
  }
231
297
 
232
298
  class Breadc {
@@ -241,24 +307,16 @@ class Breadc {
241
307
  this._version = option.version ?? "unknown";
242
308
  this.description = option.description;
243
309
  this.logger = createDefaultLogger(name, option.logger);
244
- const breadc = {
245
- name: this.name,
246
- version: () => this.version.call(this),
247
- help: (command) => this.help.call(this, command),
248
- logger: this.logger,
249
- options: this.options,
250
- commands: this.commands
251
- };
252
- this.commands.push(createVersionCommand(breadc), createHelpCommand(breadc));
310
+ this.commands.push(new VersionCommand(this.version(), this.logger), new HelpCommand(this.commands, this.help.bind(this), this.logger));
253
311
  }
254
312
  version() {
255
313
  return `${this.name}/${this._version}`;
256
314
  }
257
- help(command) {
315
+ help(commands = []) {
258
316
  const output = [];
259
317
  const println = (msg) => output.push(msg);
260
318
  println(this.version());
261
- if (!command) {
319
+ if (commands.length === 0) {
262
320
  if (this.description) {
263
321
  println("");
264
322
  if (Array.isArray(this.description)) {
@@ -269,27 +327,26 @@ class Breadc {
269
327
  println(this.description);
270
328
  }
271
329
  }
272
- } else {
273
- if (command.description) {
274
- println("");
275
- println(command.description);
276
- }
277
- }
278
- if (!command) {
279
330
  if (this.defaultCommand) {
280
331
  println(``);
281
332
  println(`Usage:`);
282
- println(` $ ${this.name} ${this.defaultCommand.format.join(" ")}`);
333
+ println(` $ ${this.name} ${this.defaultCommand.format}`);
334
+ }
335
+ } else if (commands.length === 1) {
336
+ const command = commands[0];
337
+ if (command.description) {
338
+ println("");
339
+ println(command.description);
283
340
  }
284
- } else {
285
341
  println(``);
286
342
  println(`Usage:`);
287
- println(` $ ${this.name} ${command.format.join(" ")}`);
343
+ println(` $ ${this.name} ${command.format}`);
288
344
  }
289
- if (!command && this.commands.length > 2) {
345
+ if (commands.length !== 1) {
346
+ const cmdList = (commands.length === 0 ? this.commands : commands).filter((c) => !c.isInternal);
290
347
  println(``);
291
348
  println(`Commands:`);
292
- const commandHelps = this.commands.filter((c) => !c.hasConditionFn).map((c) => [` $ ${this.name} ${c.format.join(" ")}`, c.description]);
349
+ const commandHelps = cmdList.map((c) => [` $ ${this.name} ${c.format}`, c.description]);
293
350
  for (const line of twoColumn(commandHelps)) {
294
351
  println(line);
295
352
  }
@@ -297,7 +354,7 @@ class Breadc {
297
354
  println(``);
298
355
  println(`Options:`);
299
356
  const optionHelps = [].concat([
300
- ...command ? command.options.map((o) => [` ${o.format}`, o.description]) : [],
357
+ ...commands.length > 0 ? commands.flatMap((cmd) => cmd.options.map((o) => [` ${o.format}`, o.description])) : [],
301
358
  ...this.options.map((o) => [` ${o.format}`, o.description]),
302
359
  [` -h, --help`, `Display this message`],
303
360
  [` -v, --version`, `Display version number`]
@@ -335,16 +392,30 @@ class Breadc {
335
392
  ...this.options,
336
393
  ...this.commands.flatMap((c) => c.options)
337
394
  ];
395
+ {
396
+ const names = /* @__PURE__ */ new Map();
397
+ for (const option of allowOptions) {
398
+ if (names.has(option.name)) {
399
+ const otherOption = names.get(option.name);
400
+ if (otherOption.type !== option.type) {
401
+ this.logger.warn(`Option "${option.name}" encounters conflict`);
402
+ }
403
+ } else {
404
+ names.set(option.name, option);
405
+ }
406
+ }
407
+ }
338
408
  const alias = allowOptions.reduce((map, o) => {
339
409
  if (o.shortcut) {
340
410
  map[o.shortcut] = o.name;
341
411
  }
342
412
  return map;
343
- }, {});
413
+ }, { h: "help", v: "version" });
344
414
  const argv = minimist(args, {
345
415
  string: allowOptions.filter((o) => o.type === "string").map((o) => o.name),
346
- boolean: allowOptions.filter((o) => o.type === "boolean").map((o) => o.name),
416
+ boolean: allowOptions.filter((o) => o.type === "boolean").map((o) => o.name).concat(["help", "version"]),
347
417
  alias,
418
+ "--": true,
348
419
  unknown: (t) => {
349
420
  if (t[0] !== "-")
350
421
  return true;
@@ -367,15 +438,20 @@ class Breadc {
367
438
  }
368
439
  }
369
440
  if (this.defaultCommand) {
441
+ this.defaultCommand.shouldRun(argv);
370
442
  return this.defaultCommand.parseArgs(argv, this.options);
371
443
  }
372
444
  const argumentss = argv["_"];
373
445
  const options = argv;
374
446
  delete options["_"];
447
+ delete options["--"];
448
+ delete options["help"];
449
+ delete options["version"];
375
450
  return {
376
451
  command: void 0,
377
452
  arguments: argumentss,
378
- options
453
+ options,
454
+ "--": []
379
455
  };
380
456
  }
381
457
  on(event, fn) {
@@ -385,20 +461,17 @@ class Breadc {
385
461
  const parsed = this.parse(args);
386
462
  if (parsed.command) {
387
463
  await Promise.all(this.callbacks.pre.map((fn) => fn(parsed.options)));
388
- const returnValue = await parsed.command.run(...parsed.arguments, parsed.options);
464
+ const returnValue = await parsed.command.run(...parsed.arguments, {
465
+ "--": parsed["--"],
466
+ ...parsed.options
467
+ });
389
468
  await Promise.all(this.callbacks.post.map((fn) => fn(parsed.options)));
390
469
  return returnValue;
470
+ } else {
471
+ return void 0;
391
472
  }
392
473
  }
393
474
  }
394
- function twoColumn(texts, split = " ") {
395
- const left = padRight(texts.map((t) => t[0]));
396
- return left.map((l, idx) => l + split + texts[idx][1]);
397
- }
398
- function padRight(texts, fill = " ") {
399
- const length = texts.map((t) => t.length).reduce((max, l) => Math.max(max, l), 0);
400
- return texts.map((t) => t + fill.repeat(length - t.length));
401
- }
402
475
 
403
476
  function breadc(name, option = {}) {
404
477
  return new Breadc(name, option);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "breadc",
3
- "version": "0.4.6",
3
+ "version": "0.6.1",
4
4
  "description": "Yet another Command Line Application Framework with fully strong TypeScript support",
5
5
  "keywords": [
6
6
  "cli",
@@ -43,11 +43,11 @@
43
43
  "bumpp": "^8.2.1",
44
44
  "prettier": "^2.7.1",
45
45
  "typescript": "^4.7.4",
46
- "unbuild": "^0.7.4",
47
- "vite": "^2.9.12",
48
- "vitest": "^0.16.0"
46
+ "unbuild": "^0.7.6",
47
+ "vite": "^3.0.4",
48
+ "vitest": "^0.19.1"
49
49
  },
50
- "packageManager": "pnpm@7.3.0",
50
+ "packageManager": "pnpm@7.5.2",
51
51
  "scripts": {
52
52
  "build": "unbuild",
53
53
  "format": "prettier --write src/**/*.ts test/*.ts examples/*.ts",