breadc 0.4.6 → 0.5.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/README.md CHANGED
@@ -6,7 +6,7 @@ Yet another Command Line Application Framework powered by [minimist](https://www
6
6
 
7
7
  ## Features
8
8
 
9
- + ⚡️ **Light-weight**: Only 61 kB.
9
+ + ⚡️ **Light-weight**: Only 40 kB (Unpacked).
10
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`.
11
11
  + 💻 **TypeScript Infer**: IDE will automatically infer the type of your command action function.
12
12
 
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,26 @@ 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) {
82
+ if (pieces.length > _Command.MaxDep) {
73
83
  this.logger.warn(`Command format string "${format}" is too long`);
74
84
  }
75
85
  }
86
+ get isInternal() {
87
+ return this instanceof InternalCommand;
88
+ }
89
+ alias(command) {
90
+ const pieces = command.split(" ").map((t) => t.trim()).filter(Boolean);
91
+ this.prefix.push(pieces);
92
+ return this;
93
+ }
76
94
  option(format, configOrDescription = "", otherConfig = {}) {
77
95
  const config = typeof configOrDescription === "object" ? configOrDescription : { ...otherConfig, description: configOrDescription };
78
96
  try {
@@ -83,59 +101,56 @@ const _Command = class {
83
101
  }
84
102
  return this;
85
103
  }
86
- get hasConditionFn() {
87
- return !!this.conditionFn;
88
- }
89
- shouldRun(args) {
90
- if (this.conditionFn) {
91
- return this.conditionFn(args);
104
+ hasPrefix(parsedArgs) {
105
+ const argv = parsedArgs["_"];
106
+ if (argv.length === 0) {
107
+ return this.default;
92
108
  } 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])) {
109
+ for (const prefix of this.prefix) {
110
+ if (prefix.length > 0 && prefix[0] === argv[0]) {
98
111
  return true;
99
112
  }
100
- if (i >= args["_"].length || this.format[i] !== args["_"][i]) {
101
- return false;
113
+ }
114
+ return false;
115
+ }
116
+ }
117
+ shouldRun(parsedArgs) {
118
+ const args = parsedArgs["_"];
119
+ for (const prefix of this.prefix) {
120
+ let match = true;
121
+ for (let i = 0; match && i < prefix.length; i++) {
122
+ if (args[i] !== prefix[i]) {
123
+ match = false;
102
124
  }
103
125
  }
104
- return true;
126
+ if (match) {
127
+ args.splice(0, prefix.length);
128
+ return true;
129
+ }
105
130
  }
131
+ if (this.default)
132
+ return true;
133
+ return false;
106
134
  }
107
135
  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
- };
117
- }
118
- const isCmd = (t) => t[0] !== "[" && t[0] !== "<";
119
136
  const argumentss = [];
120
- for (let i = 0; i < this.format.length; i++) {
121
- if (isCmd(this.format[i]))
122
- continue;
137
+ for (let i = 0; i < this.arguments.length; i++) {
123
138
  if (i < args["_"].length) {
124
- if (this.format[i].startsWith("[...")) {
139
+ if (this.arguments[i].startsWith("[...")) {
125
140
  argumentss.push(args["_"].slice(i).map(String));
126
141
  } else {
127
142
  argumentss.push(String(args["_"][i]));
128
143
  }
129
144
  } 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("[...")) {
145
+ if (this.arguments[i].startsWith("<")) {
146
+ this.logger.warn(`You should provide the argument "${this.arguments[i]}"`);
147
+ argumentss.push("");
148
+ } else if (this.arguments[i].startsWith("[...")) {
134
149
  argumentss.push([]);
135
- } else if (this.format[i].startsWith("[")) {
150
+ } else if (this.arguments[i].startsWith("[")) {
136
151
  argumentss.push(void 0);
137
152
  } else {
138
- this.logger.warn(`unknown format string ("${this.format[i]}")`);
153
+ this.logger.warn(`unknown format string ("${this.arguments[i]}")`);
139
154
  }
140
155
  }
141
156
  }
@@ -159,13 +174,14 @@ const _Command = class {
159
174
  options[name] = void 0;
160
175
  }
161
176
  }
162
- if (rawOption.construct) {
163
- options[name] = rawOption.construct(options[name]);
164
- } else if (rawOption.default) {
165
- if (!options[name]) {
177
+ if (rawOption.default !== void 0) {
178
+ if (options[name] === void 0 || options[name] === false) {
166
179
  options[name] = rawOption.default;
167
180
  }
168
181
  }
182
+ if (rawOption.construct !== void 0) {
183
+ options[name] = rawOption.construct(options[name]);
184
+ }
169
185
  }
170
186
  for (const key of Object.keys(options)) {
171
187
  if (!fullOptions.has(key)) {
@@ -180,60 +196,91 @@ const _Command = class {
180
196
  }
181
197
  action(fn) {
182
198
  this.actionFn = fn;
183
- return this;
184
199
  }
185
200
  async run(...args) {
186
201
  if (this.actionFn) {
187
- return await this.actionFn(...args, { logger: this.logger });
202
+ return await this.actionFn(...args, { logger: this.logger, color: kolorist__default });
188
203
  } else {
189
- this.logger.warn(`You may miss action function in "${this.format}"`);
204
+ this.logger.warn(`You may miss action function in ${this.format ? `"${this.format}"` : "<default command>"}`);
190
205
  }
191
206
  }
192
207
  };
193
208
  let Command = _Command;
194
209
  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;
210
+ class InternalCommand extends Command {
211
+ hasPrefix(_args) {
212
+ return false;
213
+ }
214
+ parseArgs(args, _globalOptions) {
215
+ const argumentss = args["_"];
216
+ const options = args;
217
+ delete options["_"];
218
+ delete options["help"];
219
+ delete options["version"];
220
+ return {
221
+ command: this,
222
+ arguments: argumentss,
223
+ options: args
224
+ };
225
+ }
226
+ }
227
+ class HelpCommand extends InternalCommand {
228
+ constructor(commands, help, logger) {
229
+ super("-h, --help", { description: "Display this message", logger });
230
+ this.runCommands = [];
231
+ this.helpCommands = [];
232
+ this.commands = commands;
233
+ this.help = help;
234
+ }
235
+ shouldRun(args) {
236
+ const isRestEmpty = !args["--"]?.length;
237
+ if ((args.help || args.h) && isRestEmpty) {
238
+ if (args["_"].length > 0) {
239
+ for (const cmd of this.commands) {
240
+ if (!cmd.default && !cmd.isInternal) {
241
+ if (cmd.shouldRun(args)) {
242
+ this.runCommands.push(cmd);
243
+ } else if (cmd.hasPrefix(args)) {
244
+ this.helpCommands.push(cmd);
206
245
  }
207
246
  }
208
247
  }
209
- return true;
210
- } else {
211
- return false;
212
248
  }
213
- },
214
- logger: breadc.logger
215
- }).action(() => {
216
- for (const line of breadc.help(helpCommand)) {
217
- breadc.logger.println(line);
249
+ return true;
250
+ } else {
251
+ return false;
218
252
  }
219
- });
253
+ }
254
+ async run() {
255
+ const shouldHelp = this.runCommands.length > 0 ? this.runCommands : this.helpCommands;
256
+ for (const line of this.help(shouldHelp)) {
257
+ this.logger.println(line);
258
+ }
259
+ this.runCommands.splice(0);
260
+ this.helpCommands.splice(0);
261
+ }
220
262
  }
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
- });
263
+ class VersionCommand extends InternalCommand {
264
+ constructor(version, logger) {
265
+ super("-v, --version", { description: "Display version number", logger });
266
+ this.version = version;
267
+ }
268
+ shouldRun(args) {
269
+ const isEmpty = !args["_"].length && !args["--"]?.length;
270
+ if (args.version && isEmpty) {
271
+ return true;
272
+ } else if (args.v && isEmpty) {
273
+ return true;
274
+ } else {
275
+ return false;
276
+ }
277
+ }
278
+ async run() {
279
+ this.logger.println(this.version);
280
+ }
281
+ }
282
+ function isArg(arg) {
283
+ return arg[0] === "[" && arg[arg.length - 1] === "]" || arg[0] === "<" && arg[arg.length - 1] === ">";
237
284
  }
238
285
 
239
286
  class Breadc {
@@ -248,24 +295,16 @@ class Breadc {
248
295
  this._version = option.version ?? "unknown";
249
296
  this.description = option.description;
250
297
  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));
298
+ this.commands.push(new VersionCommand(this.version(), this.logger), new HelpCommand(this.commands, this.help.bind(this), this.logger));
260
299
  }
261
300
  version() {
262
301
  return `${this.name}/${this._version}`;
263
302
  }
264
- help(command) {
303
+ help(commands = []) {
265
304
  const output = [];
266
305
  const println = (msg) => output.push(msg);
267
306
  println(this.version());
268
- if (!command) {
307
+ if (commands.length === 0) {
269
308
  if (this.description) {
270
309
  println("");
271
310
  if (Array.isArray(this.description)) {
@@ -276,27 +315,26 @@ class Breadc {
276
315
  println(this.description);
277
316
  }
278
317
  }
279
- } else {
280
- if (command.description) {
281
- println("");
282
- println(command.description);
283
- }
284
- }
285
- if (!command) {
286
318
  if (this.defaultCommand) {
287
319
  println(``);
288
320
  println(`Usage:`);
289
- println(` $ ${this.name} ${this.defaultCommand.format.join(" ")}`);
321
+ println(` $ ${this.name} ${this.defaultCommand.format}`);
322
+ }
323
+ } else if (commands.length === 1) {
324
+ const command = commands[0];
325
+ if (command.description) {
326
+ println("");
327
+ println(command.description);
290
328
  }
291
- } else {
292
329
  println(``);
293
330
  println(`Usage:`);
294
- println(` $ ${this.name} ${command.format.join(" ")}`);
331
+ println(` $ ${this.name} ${command.format}`);
295
332
  }
296
- if (!command && this.commands.length > 2) {
333
+ if (commands.length !== 1) {
334
+ const cmdList = (commands.length === 0 ? this.commands : commands).filter((c) => !c.isInternal);
297
335
  println(``);
298
336
  println(`Commands:`);
299
- const commandHelps = this.commands.filter((c) => !c.hasConditionFn).map((c) => [` $ ${this.name} ${c.format.join(" ")}`, c.description]);
337
+ const commandHelps = cmdList.map((c) => [` $ ${this.name} ${c.format}`, c.description]);
300
338
  for (const line of twoColumn(commandHelps)) {
301
339
  println(line);
302
340
  }
@@ -304,7 +342,7 @@ class Breadc {
304
342
  println(``);
305
343
  println(`Options:`);
306
344
  const optionHelps = [].concat([
307
- ...command ? command.options.map((o) => [` ${o.format}`, o.description]) : [],
345
+ ...commands.length > 0 ? commands.flatMap((cmd) => cmd.options.map((o) => [` ${o.format}`, o.description])) : [],
308
346
  ...this.options.map((o) => [` ${o.format}`, o.description]),
309
347
  [` -h, --help`, `Display this message`],
310
348
  [` -v, --version`, `Display version number`]
@@ -342,15 +380,28 @@ class Breadc {
342
380
  ...this.options,
343
381
  ...this.commands.flatMap((c) => c.options)
344
382
  ];
383
+ {
384
+ const names = /* @__PURE__ */ new Map();
385
+ for (const option of allowOptions) {
386
+ if (names.has(option.name)) {
387
+ const otherOption = names.get(option.name);
388
+ if (otherOption.type !== option.type) {
389
+ this.logger.warn(`Option "${option.name}" encounters conflict`);
390
+ }
391
+ } else {
392
+ names.set(option.name, option);
393
+ }
394
+ }
395
+ }
345
396
  const alias = allowOptions.reduce((map, o) => {
346
397
  if (o.shortcut) {
347
398
  map[o.shortcut] = o.name;
348
399
  }
349
400
  return map;
350
- }, {});
401
+ }, { h: "help", v: "version" });
351
402
  const argv = minimist__default(args, {
352
403
  string: allowOptions.filter((o) => o.type === "string").map((o) => o.name),
353
- boolean: allowOptions.filter((o) => o.type === "boolean").map((o) => o.name),
404
+ boolean: allowOptions.filter((o) => o.type === "boolean").map((o) => o.name).concat(["help", "version"]),
354
405
  alias,
355
406
  unknown: (t) => {
356
407
  if (t[0] !== "-")
@@ -374,11 +425,14 @@ class Breadc {
374
425
  }
375
426
  }
376
427
  if (this.defaultCommand) {
428
+ this.defaultCommand.shouldRun(argv);
377
429
  return this.defaultCommand.parseArgs(argv, this.options);
378
430
  }
379
431
  const argumentss = argv["_"];
380
432
  const options = argv;
381
433
  delete options["_"];
434
+ delete options["help"];
435
+ delete options["version"];
382
436
  return {
383
437
  command: void 0,
384
438
  arguments: argumentss,
@@ -398,19 +452,9 @@ class Breadc {
398
452
  }
399
453
  }
400
454
  }
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
455
 
410
456
  function breadc(name, option = {}) {
411
457
  return new Breadc(name, option);
412
458
  }
413
459
 
414
- exports.kolorist = kolorist__default;
415
- exports.minimist = minimist__default;
416
- exports["default"] = breadc;
460
+ 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;
60
+ hasPrefix(parsedArgs: ParsedArgs): boolean;
61
+ shouldRun(parsedArgs: ParsedArgs): boolean;
53
62
  parseArgs(args: ParsedArgs, globalOptions: Option[]): ParseResult;
54
- action(fn: ActionFn<ExtractCommand<F>, CommandOption>): this;
63
+ action(fn: ActionFn<ExtractCommand<F>, CommandOption>): void;
55
64
  run(...args: any[]): Promise<any>;
56
65
  }
57
66
 
@@ -92,6 +101,7 @@ declare type Letter = Lowercase | Uppercase;
92
101
  declare type Push<T extends any[], U, R> = [...T, U, R];
93
102
  declare type Context = {
94
103
  logger: Logger;
104
+ color: typeof kolorist;
95
105
  };
96
106
  declare type ActionFn<T extends any[], Option extends object = {}, R = any> = (...arg: Push<T, Option, Context>) => R | Promise<R>;
97
107
  /**
@@ -105,13 +115,13 @@ declare class Breadc<GlobalOption extends object = {}> {
105
115
  private readonly name;
106
116
  private readonly _version;
107
117
  private readonly description?;
118
+ readonly logger: Logger;
108
119
  private readonly options;
109
120
  private readonly commands;
110
121
  private defaultCommand?;
111
- readonly logger: Logger;
112
122
  constructor(name: string, option: AppOption);
113
123
  version(): string;
114
- help(command?: Command): string[];
124
+ help(commands?: Command[]): string[];
115
125
  option<F extends string, T = undefined>(format: F, description: string, config?: Omit<OptionConfig<F, T>, 'description'>): Breadc<GlobalOption & ExtractOption<F, T>>;
116
126
  option<F extends string, T = undefined>(format: F, config?: OptionConfig<F, T>): Breadc<GlobalOption & ExtractOption<F, T>>;
117
127
  command<F extends string>(format: F, description: string, config?: Omit<CommandConfig, 'description'>): Command<F, GlobalOption>;
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,26 @@ 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) {
75
+ if (pieces.length > _Command.MaxDep) {
66
76
  this.logger.warn(`Command format string "${format}" is too long`);
67
77
  }
68
78
  }
79
+ get isInternal() {
80
+ return this instanceof InternalCommand;
81
+ }
82
+ alias(command) {
83
+ const pieces = command.split(" ").map((t) => t.trim()).filter(Boolean);
84
+ this.prefix.push(pieces);
85
+ return this;
86
+ }
69
87
  option(format, configOrDescription = "", otherConfig = {}) {
70
88
  const config = typeof configOrDescription === "object" ? configOrDescription : { ...otherConfig, description: configOrDescription };
71
89
  try {
@@ -76,59 +94,56 @@ const _Command = class {
76
94
  }
77
95
  return this;
78
96
  }
79
- get hasConditionFn() {
80
- return !!this.conditionFn;
81
- }
82
- shouldRun(args) {
83
- if (this.conditionFn) {
84
- return this.conditionFn(args);
97
+ hasPrefix(parsedArgs) {
98
+ const argv = parsedArgs["_"];
99
+ if (argv.length === 0) {
100
+ return this.default;
85
101
  } 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])) {
102
+ for (const prefix of this.prefix) {
103
+ if (prefix.length > 0 && prefix[0] === argv[0]) {
91
104
  return true;
92
105
  }
93
- if (i >= args["_"].length || this.format[i] !== args["_"][i]) {
94
- return false;
106
+ }
107
+ return false;
108
+ }
109
+ }
110
+ shouldRun(parsedArgs) {
111
+ const args = parsedArgs["_"];
112
+ for (const prefix of this.prefix) {
113
+ let match = true;
114
+ for (let i = 0; match && i < prefix.length; i++) {
115
+ if (args[i] !== prefix[i]) {
116
+ match = false;
95
117
  }
96
118
  }
97
- return true;
119
+ if (match) {
120
+ args.splice(0, prefix.length);
121
+ return true;
122
+ }
98
123
  }
124
+ if (this.default)
125
+ return true;
126
+ return false;
99
127
  }
100
128
  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
- };
110
- }
111
- const isCmd = (t) => t[0] !== "[" && t[0] !== "<";
112
129
  const argumentss = [];
113
- for (let i = 0; i < this.format.length; i++) {
114
- if (isCmd(this.format[i]))
115
- continue;
130
+ for (let i = 0; i < this.arguments.length; i++) {
116
131
  if (i < args["_"].length) {
117
- if (this.format[i].startsWith("[...")) {
132
+ if (this.arguments[i].startsWith("[...")) {
118
133
  argumentss.push(args["_"].slice(i).map(String));
119
134
  } else {
120
135
  argumentss.push(String(args["_"][i]));
121
136
  }
122
137
  } 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("[...")) {
138
+ if (this.arguments[i].startsWith("<")) {
139
+ this.logger.warn(`You should provide the argument "${this.arguments[i]}"`);
140
+ argumentss.push("");
141
+ } else if (this.arguments[i].startsWith("[...")) {
127
142
  argumentss.push([]);
128
- } else if (this.format[i].startsWith("[")) {
143
+ } else if (this.arguments[i].startsWith("[")) {
129
144
  argumentss.push(void 0);
130
145
  } else {
131
- this.logger.warn(`unknown format string ("${this.format[i]}")`);
146
+ this.logger.warn(`unknown format string ("${this.arguments[i]}")`);
132
147
  }
133
148
  }
134
149
  }
@@ -152,13 +167,14 @@ const _Command = class {
152
167
  options[name] = void 0;
153
168
  }
154
169
  }
155
- if (rawOption.construct) {
156
- options[name] = rawOption.construct(options[name]);
157
- } else if (rawOption.default) {
158
- if (!options[name]) {
170
+ if (rawOption.default !== void 0) {
171
+ if (options[name] === void 0 || options[name] === false) {
159
172
  options[name] = rawOption.default;
160
173
  }
161
174
  }
175
+ if (rawOption.construct !== void 0) {
176
+ options[name] = rawOption.construct(options[name]);
177
+ }
162
178
  }
163
179
  for (const key of Object.keys(options)) {
164
180
  if (!fullOptions.has(key)) {
@@ -173,60 +189,91 @@ const _Command = class {
173
189
  }
174
190
  action(fn) {
175
191
  this.actionFn = fn;
176
- return this;
177
192
  }
178
193
  async run(...args) {
179
194
  if (this.actionFn) {
180
- return await this.actionFn(...args, { logger: this.logger });
195
+ return await this.actionFn(...args, { logger: this.logger, color: kolorist });
181
196
  } else {
182
- this.logger.warn(`You may miss action function in "${this.format}"`);
197
+ this.logger.warn(`You may miss action function in ${this.format ? `"${this.format}"` : "<default command>"}`);
183
198
  }
184
199
  }
185
200
  };
186
201
  let Command = _Command;
187
202
  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;
203
+ class InternalCommand extends Command {
204
+ hasPrefix(_args) {
205
+ return false;
206
+ }
207
+ parseArgs(args, _globalOptions) {
208
+ const argumentss = args["_"];
209
+ const options = args;
210
+ delete options["_"];
211
+ delete options["help"];
212
+ delete options["version"];
213
+ return {
214
+ command: this,
215
+ arguments: argumentss,
216
+ options: args
217
+ };
218
+ }
219
+ }
220
+ class HelpCommand extends InternalCommand {
221
+ constructor(commands, help, logger) {
222
+ super("-h, --help", { description: "Display this message", logger });
223
+ this.runCommands = [];
224
+ this.helpCommands = [];
225
+ this.commands = commands;
226
+ this.help = help;
227
+ }
228
+ shouldRun(args) {
229
+ const isRestEmpty = !args["--"]?.length;
230
+ if ((args.help || args.h) && isRestEmpty) {
231
+ if (args["_"].length > 0) {
232
+ for (const cmd of this.commands) {
233
+ if (!cmd.default && !cmd.isInternal) {
234
+ if (cmd.shouldRun(args)) {
235
+ this.runCommands.push(cmd);
236
+ } else if (cmd.hasPrefix(args)) {
237
+ this.helpCommands.push(cmd);
199
238
  }
200
239
  }
201
240
  }
202
- return true;
203
- } else {
204
- return false;
205
241
  }
206
- },
207
- logger: breadc.logger
208
- }).action(() => {
209
- for (const line of breadc.help(helpCommand)) {
210
- breadc.logger.println(line);
242
+ return true;
243
+ } else {
244
+ return false;
211
245
  }
212
- });
246
+ }
247
+ async run() {
248
+ const shouldHelp = this.runCommands.length > 0 ? this.runCommands : this.helpCommands;
249
+ for (const line of this.help(shouldHelp)) {
250
+ this.logger.println(line);
251
+ }
252
+ this.runCommands.splice(0);
253
+ this.helpCommands.splice(0);
254
+ }
213
255
  }
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
- });
256
+ class VersionCommand extends InternalCommand {
257
+ constructor(version, logger) {
258
+ super("-v, --version", { description: "Display version number", logger });
259
+ this.version = version;
260
+ }
261
+ shouldRun(args) {
262
+ const isEmpty = !args["_"].length && !args["--"]?.length;
263
+ if (args.version && isEmpty) {
264
+ return true;
265
+ } else if (args.v && isEmpty) {
266
+ return true;
267
+ } else {
268
+ return false;
269
+ }
270
+ }
271
+ async run() {
272
+ this.logger.println(this.version);
273
+ }
274
+ }
275
+ function isArg(arg) {
276
+ return arg[0] === "[" && arg[arg.length - 1] === "]" || arg[0] === "<" && arg[arg.length - 1] === ">";
230
277
  }
231
278
 
232
279
  class Breadc {
@@ -241,24 +288,16 @@ class Breadc {
241
288
  this._version = option.version ?? "unknown";
242
289
  this.description = option.description;
243
290
  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));
291
+ this.commands.push(new VersionCommand(this.version(), this.logger), new HelpCommand(this.commands, this.help.bind(this), this.logger));
253
292
  }
254
293
  version() {
255
294
  return `${this.name}/${this._version}`;
256
295
  }
257
- help(command) {
296
+ help(commands = []) {
258
297
  const output = [];
259
298
  const println = (msg) => output.push(msg);
260
299
  println(this.version());
261
- if (!command) {
300
+ if (commands.length === 0) {
262
301
  if (this.description) {
263
302
  println("");
264
303
  if (Array.isArray(this.description)) {
@@ -269,27 +308,26 @@ class Breadc {
269
308
  println(this.description);
270
309
  }
271
310
  }
272
- } else {
273
- if (command.description) {
274
- println("");
275
- println(command.description);
276
- }
277
- }
278
- if (!command) {
279
311
  if (this.defaultCommand) {
280
312
  println(``);
281
313
  println(`Usage:`);
282
- println(` $ ${this.name} ${this.defaultCommand.format.join(" ")}`);
314
+ println(` $ ${this.name} ${this.defaultCommand.format}`);
315
+ }
316
+ } else if (commands.length === 1) {
317
+ const command = commands[0];
318
+ if (command.description) {
319
+ println("");
320
+ println(command.description);
283
321
  }
284
- } else {
285
322
  println(``);
286
323
  println(`Usage:`);
287
- println(` $ ${this.name} ${command.format.join(" ")}`);
324
+ println(` $ ${this.name} ${command.format}`);
288
325
  }
289
- if (!command && this.commands.length > 2) {
326
+ if (commands.length !== 1) {
327
+ const cmdList = (commands.length === 0 ? this.commands : commands).filter((c) => !c.isInternal);
290
328
  println(``);
291
329
  println(`Commands:`);
292
- const commandHelps = this.commands.filter((c) => !c.hasConditionFn).map((c) => [` $ ${this.name} ${c.format.join(" ")}`, c.description]);
330
+ const commandHelps = cmdList.map((c) => [` $ ${this.name} ${c.format}`, c.description]);
293
331
  for (const line of twoColumn(commandHelps)) {
294
332
  println(line);
295
333
  }
@@ -297,7 +335,7 @@ class Breadc {
297
335
  println(``);
298
336
  println(`Options:`);
299
337
  const optionHelps = [].concat([
300
- ...command ? command.options.map((o) => [` ${o.format}`, o.description]) : [],
338
+ ...commands.length > 0 ? commands.flatMap((cmd) => cmd.options.map((o) => [` ${o.format}`, o.description])) : [],
301
339
  ...this.options.map((o) => [` ${o.format}`, o.description]),
302
340
  [` -h, --help`, `Display this message`],
303
341
  [` -v, --version`, `Display version number`]
@@ -335,15 +373,28 @@ class Breadc {
335
373
  ...this.options,
336
374
  ...this.commands.flatMap((c) => c.options)
337
375
  ];
376
+ {
377
+ const names = /* @__PURE__ */ new Map();
378
+ for (const option of allowOptions) {
379
+ if (names.has(option.name)) {
380
+ const otherOption = names.get(option.name);
381
+ if (otherOption.type !== option.type) {
382
+ this.logger.warn(`Option "${option.name}" encounters conflict`);
383
+ }
384
+ } else {
385
+ names.set(option.name, option);
386
+ }
387
+ }
388
+ }
338
389
  const alias = allowOptions.reduce((map, o) => {
339
390
  if (o.shortcut) {
340
391
  map[o.shortcut] = o.name;
341
392
  }
342
393
  return map;
343
- }, {});
394
+ }, { h: "help", v: "version" });
344
395
  const argv = minimist(args, {
345
396
  string: allowOptions.filter((o) => o.type === "string").map((o) => o.name),
346
- boolean: allowOptions.filter((o) => o.type === "boolean").map((o) => o.name),
397
+ boolean: allowOptions.filter((o) => o.type === "boolean").map((o) => o.name).concat(["help", "version"]),
347
398
  alias,
348
399
  unknown: (t) => {
349
400
  if (t[0] !== "-")
@@ -367,11 +418,14 @@ class Breadc {
367
418
  }
368
419
  }
369
420
  if (this.defaultCommand) {
421
+ this.defaultCommand.shouldRun(argv);
370
422
  return this.defaultCommand.parseArgs(argv, this.options);
371
423
  }
372
424
  const argumentss = argv["_"];
373
425
  const options = argv;
374
426
  delete options["_"];
427
+ delete options["help"];
428
+ delete options["version"];
375
429
  return {
376
430
  command: void 0,
377
431
  arguments: argumentss,
@@ -391,14 +445,6 @@ class Breadc {
391
445
  }
392
446
  }
393
447
  }
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
448
 
403
449
  function breadc(name, option = {}) {
404
450
  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.5.0",
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.2",
48
+ "vitest": "^0.18.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",