breadc 0.1.1 → 0.2.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
@@ -12,7 +12,7 @@ npm i breadc
12
12
 
13
13
  ## Usage
14
14
 
15
- See [./examples/echo.ts](./examples/echo.ts).
15
+ Try [./examples/echo.ts](./examples/echo.ts).
16
16
 
17
17
  ```ts
18
18
  import Breadc from 'breadc'
@@ -24,24 +24,23 @@ const cli = Breadc('echo', { version: '1.0.0' })
24
24
  cli
25
25
  .command('[message]')
26
26
  .action((message, option) => {
27
- console.log(message ?? 'You can say anything!')
28
- console.log(`Host: ${option.host}`)
29
- console.log(`Port: ${option.port}`)
27
+ const host = option.host;
28
+ const port = option.port;
29
+ console.log(`Host: ${host}`);
30
+ console.log(`Port: ${port}`);
30
31
  })
31
32
 
32
33
  cli.run(process.argv.slice(2))
33
34
  .catch(err => cli.logger.error(err.message))
34
35
  ```
35
36
 
36
- If you are using IDEs that support TypeScript (like [Visual Studio Code](https://code.visualstudio.com/)), move your cursor to the parameter `option` in this `dev` command, and then you will find the `option` is automatically typed with `{ host: string, port: string }` or `Record<'host' | 'port', string>`.
37
+ If you are using IDEs that support TypeScript (like [Visual Studio Code](https://code.visualstudio.com/)), move your cursor to the parameter `option` in the default command, and then you will find the `option` is automatically typed with `{ host: string | boolean, port: string | boolean }`.
37
38
 
38
- ![vscode1](./images/vscode1.png)
39
-
40
- ![vscode2](./images/vscode2.png)
39
+ ![vscode](./images/vscode.png)
41
40
 
42
41
  ### Limitation
43
42
 
44
- For the limitation of TypeScript, in the command format string, you can only write up to **4** pieces. That is to say, you can only write format string like `<p1> <p2> <p3> <p4>`, but `<p1> <p2> <p3> <p4> <p5>` does not work.
43
+ For the limitation of TypeScript, in the command format string, you can only write up to **5** pieces. That is to say, you can only write format string like `<p1> <p2> <p3> <p4> [p5]`, but `<p1> <p2> <p3> <p4> <p5> [p6]` does not work.
45
44
 
46
45
  You should always use method chaining when registering options and commands. The example below will fail to infer the option `--host`.
47
46
 
package/dist/index.cjs CHANGED
@@ -12,20 +12,24 @@ const kolorist__default = /*#__PURE__*/_interopDefaultLegacy(kolorist);
12
12
  const minimist__default = /*#__PURE__*/_interopDefaultLegacy(minimist);
13
13
  const createDebug__default = /*#__PURE__*/_interopDefaultLegacy(createDebug);
14
14
 
15
- function createDefaultLogger(name) {
15
+ function createDefaultLogger(name, logger) {
16
+ if (!!logger && typeof logger === "object") {
17
+ return logger;
18
+ }
16
19
  const debug = createDebug__default(name + ":breadc");
20
+ const println = !!logger && typeof logger === "function" ? logger : (message, ...args) => {
21
+ console.log(message, ...args);
22
+ };
17
23
  return {
18
- println(message) {
19
- console.log(message);
20
- },
24
+ println,
21
25
  info(message, ...args) {
22
- console.log(`${kolorist.blue("INFO")} ${message}`, ...args);
26
+ println(`${kolorist.blue("INFO")} ${message}`, ...args);
23
27
  },
24
28
  warn(message, ...args) {
25
- console.log(`${kolorist.yellow("WARN")} ${message}`, ...args);
29
+ println(`${kolorist.yellow("WARN")} ${message}`, ...args);
26
30
  },
27
31
  error(message, ...args) {
28
- console.log(`${kolorist.red("ERROR")} ${message}`, ...args);
32
+ println(`${kolorist.red("ERROR")} ${message}`, ...args);
29
33
  },
30
34
  debug(message, ...args) {
31
35
  debug(message, ...args);
@@ -35,6 +39,7 @@ function createDefaultLogger(name) {
35
39
 
36
40
  const _Option = class {
37
41
  constructor(format, config = {}) {
42
+ this.format = format;
38
43
  const match = _Option.OptionRE.exec(format);
39
44
  if (match) {
40
45
  if (match[3]) {
@@ -50,25 +55,28 @@ const _Option = class {
50
55
  throw new Error(`Can not parse option format from "${format}"`);
51
56
  }
52
57
  this.description = config.description ?? "";
58
+ this.required = format.indexOf("<") !== -1;
59
+ this.default = config.default;
53
60
  this.construct = config.construct ?? ((text) => text ?? config.default ?? void 0);
54
61
  }
55
62
  };
56
63
  let Option = _Option;
57
64
  Option.OptionRE = /^(-[a-zA-Z], )?--([a-zA-Z.]+)( \[[a-zA-Z]+\]| <[a-zA-Z]+>)?$/;
58
65
 
59
- class Command {
66
+ const _Command = class {
60
67
  constructor(format, config) {
61
68
  this.options = [];
62
69
  this.format = config.condition ? [format] : format.split(" ").map((t) => t.trim()).filter(Boolean);
70
+ this.default = this.format.length === 0 || this.format[0][0] === "[" || this.format[0][0] === "<";
63
71
  this.description = config.description ?? "";
64
72
  this.conditionFn = config.condition;
65
73
  this.logger = config.logger;
74
+ if (this.format.length > _Command.MaxDep) {
75
+ this.logger.warn(`Command format string "${format}" is too long`);
76
+ }
66
77
  }
67
78
  option(format, configOrDescription = "", otherConfig = {}) {
68
- const config = otherConfig;
69
- if (typeof configOrDescription === "string") {
70
- config.description = configOrDescription;
71
- }
79
+ const config = typeof configOrDescription === "object" ? configOrDescription : { ...otherConfig, description: configOrDescription };
72
80
  try {
73
81
  const option = new Option(format, config);
74
82
  this.options.push(option);
@@ -77,13 +85,18 @@ class Command {
77
85
  }
78
86
  return this;
79
87
  }
88
+ get hasConditionFn() {
89
+ return !!this.conditionFn;
90
+ }
80
91
  shouldRun(args) {
81
92
  if (this.conditionFn) {
82
93
  return this.conditionFn(args);
83
94
  } else {
84
- const isArg = (t) => t[0] !== "[" && t[0] !== "<";
95
+ if (this.default)
96
+ return true;
97
+ const isCmd = (t) => t[0] !== "[" && t[0] !== "<";
85
98
  for (let i = 0; i < this.format.length; i++) {
86
- if (isArg(this.format[i])) {
99
+ if (!isCmd(this.format[i])) {
87
100
  return true;
88
101
  }
89
102
  if (i >= args["_"].length || this.format[i] !== args["_"][i]) {
@@ -93,7 +106,7 @@ class Command {
93
106
  return true;
94
107
  }
95
108
  }
96
- parseArgs(args) {
109
+ parseArgs(args, globalOptions) {
97
110
  if (this.conditionFn) {
98
111
  const argumentss2 = args["_"];
99
112
  const options2 = args;
@@ -104,33 +117,55 @@ class Command {
104
117
  options: args
105
118
  };
106
119
  }
107
- const isArg = (t) => t[0] !== "[" && t[0] !== "<";
120
+ const isCmd = (t) => t[0] !== "[" && t[0] !== "<";
108
121
  const argumentss = [];
109
122
  for (let i = 0; i < this.format.length; i++) {
110
- if (isArg(this.format[i]))
123
+ if (isCmd(this.format[i]))
111
124
  continue;
112
125
  if (i < args["_"].length) {
113
126
  if (this.format[i].startsWith("[...")) {
114
- argumentss.push(args["_"].slice(i));
127
+ argumentss.push(args["_"].slice(i).map(String));
115
128
  } else {
116
- argumentss.push(args["_"][i]);
129
+ argumentss.push(String(args["_"][i]));
117
130
  }
118
131
  } else {
119
132
  if (this.format[i].startsWith("<")) {
133
+ this.logger.warn(`You should provide the argument "${this.format[i]}"`);
120
134
  argumentss.push(void 0);
121
135
  } else if (this.format[i].startsWith("[...")) {
122
136
  argumentss.push([]);
123
137
  } else if (this.format[i].startsWith("[")) {
124
138
  argumentss.push(void 0);
125
- } else ;
139
+ } else {
140
+ this.logger.warn(`unknown format string ("${this.format[i]}")`);
141
+ }
126
142
  }
127
143
  }
144
+ const fullOptions = globalOptions.concat(this.options).reduce((map, o) => {
145
+ map.set(o.name, o);
146
+ return map;
147
+ }, /* @__PURE__ */ new Map());
128
148
  const options = args;
129
149
  delete options["_"];
150
+ for (const [name, rawOption] of fullOptions) {
151
+ if (rawOption.required) {
152
+ if (options[name] === void 0) {
153
+ options[name] = false;
154
+ } else if (options[name] === "") {
155
+ options[name] = true;
156
+ }
157
+ } else {
158
+ if (options[name] === false) {
159
+ options[name] = void 0;
160
+ } else if (!(name in options)) {
161
+ options[name] = void 0;
162
+ }
163
+ }
164
+ }
130
165
  return {
131
166
  command: this,
132
167
  arguments: argumentss,
133
- options: args
168
+ options
134
169
  };
135
170
  }
136
171
  action(fn) {
@@ -138,17 +173,29 @@ class Command {
138
173
  return this;
139
174
  }
140
175
  async run(...args) {
141
- this.actionFn && this.actionFn(...args);
176
+ if (this.actionFn) {
177
+ this.actionFn(...args);
178
+ } else {
179
+ this.logger.warn(`You may miss action function in "${this.format}"`);
180
+ }
142
181
  }
143
- }
144
- Command.MaxDep = 4;
145
- function createVersionCommand(breadc) {
182
+ };
183
+ let Command = _Command;
184
+ Command.MaxDep = 5;
185
+ function createHelpCommand(breadc) {
186
+ let helpCommand = void 0;
146
187
  return new Command("-h, --help", {
147
188
  condition(args) {
148
- const isEmpty = !args["_"].length && !args["--"]?.length;
149
- if (args.help && isEmpty) {
150
- return true;
151
- } else if (args.h && isEmpty) {
189
+ const isEmpty = !args["--"]?.length;
190
+ if ((args.help || args.h) && isEmpty) {
191
+ if (args["_"].length > 0) {
192
+ for (const cmd of breadc.commands) {
193
+ if (!cmd.hasConditionFn && !cmd.default && cmd.shouldRun(args)) {
194
+ helpCommand = cmd;
195
+ return true;
196
+ }
197
+ }
198
+ }
152
199
  return true;
153
200
  } else {
154
201
  return false;
@@ -156,10 +203,12 @@ function createVersionCommand(breadc) {
156
203
  },
157
204
  logger: breadc.logger
158
205
  }).action(() => {
159
- breadc.logger.println("Help");
206
+ for (const line of breadc.help(helpCommand)) {
207
+ breadc.logger.println(line);
208
+ }
160
209
  });
161
210
  }
162
- function createHelpCommand(breadc) {
211
+ function createVersionCommand(breadc) {
163
212
  return new Command("-v, --version", {
164
213
  condition(args) {
165
214
  const isEmpty = !args["_"].length && !args["--"]?.length;
@@ -173,7 +222,7 @@ function createHelpCommand(breadc) {
173
222
  },
174
223
  logger: breadc.logger
175
224
  }).action(() => {
176
- breadc.logger.println(`${breadc.name}/${breadc.version}`);
225
+ breadc.logger.println(breadc.version());
177
226
  });
178
227
  }
179
228
 
@@ -182,22 +231,78 @@ class Breadc {
182
231
  this.options = [];
183
232
  this.commands = [];
184
233
  this.name = name;
185
- this.version = option.version ?? "unknown";
186
- this.logger = option.logger ?? createDefaultLogger(name);
234
+ this._version = option.version ?? "unknown";
235
+ this.description = option.description;
236
+ this.logger = createDefaultLogger(name, option.logger);
187
237
  const breadc = {
188
238
  name: this.name,
189
- version: this.version,
239
+ version: () => this.version.call(this),
240
+ help: (command) => this.help.call(this, command),
190
241
  logger: this.logger,
191
242
  options: this.options,
192
243
  commands: this.commands
193
244
  };
194
- this.commands = [createVersionCommand(breadc), createHelpCommand(breadc)];
245
+ this.commands.push(createVersionCommand(breadc), createHelpCommand(breadc));
195
246
  }
196
- option(format, configOrDescription = "", otherConfig = {}) {
197
- const config = otherConfig;
198
- if (typeof configOrDescription === "string") {
199
- config.description = configOrDescription;
247
+ version() {
248
+ return `${this.name}/${this._version}`;
249
+ }
250
+ help(command) {
251
+ const output = [];
252
+ const println = (msg) => output.push(msg);
253
+ println(this.version());
254
+ if (!command) {
255
+ if (this.description) {
256
+ println("");
257
+ if (Array.isArray(this.description)) {
258
+ for (const line of this.description) {
259
+ println(line);
260
+ }
261
+ } else {
262
+ println(this.description);
263
+ }
264
+ }
265
+ } else {
266
+ if (command.description) {
267
+ println("");
268
+ println(command.description);
269
+ }
270
+ }
271
+ if (!command) {
272
+ if (this.defaultCommand) {
273
+ println(``);
274
+ println(`Usage:`);
275
+ println(` $ ${this.name} ${this.defaultCommand.format.join(" ")}`);
276
+ }
277
+ } else {
278
+ println(``);
279
+ println(`Usage:`);
280
+ println(` $ ${this.name} ${command.format.join(" ")}`);
281
+ }
282
+ if (!command && this.commands.length > 2) {
283
+ println(``);
284
+ println(`Commands:`);
285
+ const commandHelps = this.commands.filter((c) => !c.hasConditionFn).map((c) => [` $ ${this.name} ${c.format.join(" ")}`, c.description]);
286
+ for (const line of twoColumn(commandHelps)) {
287
+ println(line);
288
+ }
289
+ }
290
+ println(``);
291
+ println(`Options:`);
292
+ const optionHelps = [].concat([
293
+ ...command ? command.options.map((o) => [` ${o.format}`, o.description]) : [],
294
+ ...this.options.map((o) => [` ${o.format}`, o.description]),
295
+ [` -h, --help`, `Display this message`],
296
+ [` -v, --version`, `Display version number`]
297
+ ]);
298
+ for (const line of twoColumn(optionHelps)) {
299
+ println(line);
200
300
  }
301
+ println(``);
302
+ return output;
303
+ }
304
+ option(format, configOrDescription = "", otherConfig = {}) {
305
+ const config = typeof configOrDescription === "object" ? configOrDescription : { ...otherConfig, description: configOrDescription };
201
306
  try {
202
307
  const option = new Option(format, config);
203
308
  this.options.push(option);
@@ -206,32 +311,64 @@ class Breadc {
206
311
  }
207
312
  return this;
208
313
  }
209
- command(format, config = {}) {
314
+ command(format, configOrDescription = "", otherConfig = {}) {
315
+ const config = typeof configOrDescription === "object" ? configOrDescription : { ...otherConfig, description: configOrDescription };
210
316
  const command = new Command(format, { ...config, logger: this.logger });
317
+ if (command.default) {
318
+ if (this.defaultCommand) {
319
+ this.logger.warn("You can not have two default commands.");
320
+ }
321
+ this.defaultCommand = command;
322
+ }
211
323
  this.commands.push(command);
212
324
  return command;
213
325
  }
214
326
  parse(args) {
215
- const allowOptions = [this.options, this.commands.map((c) => c.options)].flat();
327
+ const allowOptions = [
328
+ ...this.options,
329
+ ...this.commands.flatMap((c) => c.options)
330
+ ];
216
331
  const alias = allowOptions.reduce((map, o) => {
217
332
  if (o.shortcut) {
218
333
  map[o.shortcut] = o.name;
219
334
  }
220
335
  return map;
221
336
  }, {});
337
+ const defaults = allowOptions.reduce((map, o) => {
338
+ if (o.default) {
339
+ map[o.name] = o.default;
340
+ }
341
+ return map;
342
+ }, {});
222
343
  const argv = minimist__default(args, {
223
344
  string: allowOptions.filter((o) => o.type === "string").map((o) => o.name),
224
345
  boolean: allowOptions.filter((o) => o.type === "boolean").map((o) => o.name),
225
- alias
346
+ alias,
347
+ default: defaults,
348
+ unknown: (t) => {
349
+ if (t[0] !== "-")
350
+ return true;
351
+ else {
352
+ if (["--help", "-h", "--version", "-v"].includes(t)) {
353
+ return true;
354
+ } else {
355
+ this.logger.warn(`Find unknown flag "${t}"`);
356
+ return false;
357
+ }
358
+ }
359
+ }
226
360
  });
227
361
  for (const shortcut of Object.keys(alias)) {
228
362
  delete argv[shortcut];
229
363
  }
230
364
  for (const command of this.commands) {
231
- if (command.shouldRun(argv)) {
232
- return command.parseArgs(argv);
365
+ if (!command.default && command.shouldRun(argv)) {
366
+ return command.parseArgs(argv, this.options);
233
367
  }
234
368
  }
369
+ if (this.defaultCommand) {
370
+ return this.defaultCommand.parseArgs(argv, this.options);
371
+ }
235
372
  const argumentss = argv["_"];
236
373
  const options = argv;
237
374
  delete options["_"];
@@ -248,6 +385,14 @@ class Breadc {
248
385
  }
249
386
  }
250
387
  }
388
+ function twoColumn(texts, split = " ") {
389
+ const left = padRight(texts.map((t) => t[0]));
390
+ return left.map((l, idx) => l + split + texts[idx][1]);
391
+ }
392
+ function padRight(texts, fill = " ") {
393
+ const length = texts.map((t) => t.length).reduce((max, l) => Math.max(max, l), 0);
394
+ return texts.map((t) => t + fill.repeat(length - t.length));
395
+ }
251
396
 
252
397
  function breadc(name, option = {}) {
253
398
  return new Breadc(name, option);
package/dist/index.d.ts CHANGED
@@ -17,25 +17,29 @@ interface OptionConfig<T = string> {
17
17
  * + --option <arg>
18
18
  * + --option [arg]
19
19
  */
20
- declare class Option<T extends string = string, U = ExtractOption<T>> {
20
+ declare class Option<T extends string = string, F = string> {
21
21
  private static OptionRE;
22
22
  readonly name: string;
23
23
  readonly shortcut?: string;
24
+ readonly default?: F;
25
+ readonly format: string;
24
26
  readonly description: string;
25
27
  readonly type: 'string' | 'boolean';
28
+ readonly required: boolean;
26
29
  readonly construct: (rawText: string | undefined) => any;
27
- constructor(format: T, config?: OptionConfig);
30
+ constructor(format: T, config?: OptionConfig<F>);
28
31
  }
29
32
 
30
33
  declare type ConditionFn = (args: ParsedArgs) => boolean;
31
34
  interface CommandConfig {
32
35
  description?: string;
33
36
  }
34
- declare class Command<F extends string = string, GlobalOption extends string | never = never, CommandOption extends string | never = never> {
37
+ declare class Command<F extends string = string, CommandOption extends object = {}> {
35
38
  private static MaxDep;
36
39
  private readonly conditionFn?;
37
40
  private readonly logger;
38
41
  readonly format: string[];
42
+ readonly default: boolean;
39
43
  readonly description: string;
40
44
  readonly options: Option[];
41
45
  private actionFn?;
@@ -43,34 +47,37 @@ declare class Command<F extends string = string, GlobalOption extends string | n
43
47
  condition?: ConditionFn;
44
48
  logger: Logger;
45
49
  });
46
- option<OF extends string>(format: OF, description: string, config?: Omit<OptionConfig, 'description'>): Command<F, GlobalOption, CommandOption | ExtractOption<OF>>;
47
- option<OF extends string>(format: OF, config?: OptionConfig): Command<F, GlobalOption, CommandOption | ExtractOption<OF>>;
50
+ option<OF extends string, T = string>(format: OF, description: string, config?: Omit<OptionConfig<T>, 'description'>): Command<F, CommandOption & ExtractOption<OF>>;
51
+ option<OF extends string, T = string>(format: OF, config?: OptionConfig<T>): Command<F, CommandOption & ExtractOption<OF>>;
52
+ get hasConditionFn(): boolean;
48
53
  shouldRun(args: ParsedArgs): boolean;
49
- parseArgs(args: ParsedArgs): ParseResult;
50
- action(fn: ActionFn<ExtractCommand<F>, GlobalOption | CommandOption>): this;
54
+ parseArgs(args: ParsedArgs, globalOptions: Option[]): ParseResult;
55
+ action(fn: ActionFn<ExtractCommand<F>, CommandOption>): this;
51
56
  run(...args: any[]): Promise<void>;
52
57
  }
53
58
 
54
59
  interface AppOption {
55
60
  version?: string;
61
+ description?: string | string[];
56
62
  help?: string | string[] | (() => string | string[]);
57
- logger?: Logger;
63
+ logger?: Logger | LoggerFn;
58
64
  }
65
+ declare type LoggerFn = (message: string, ...args: any[]) => void;
59
66
  interface Logger {
60
- println: (message: string) => void;
61
- info: (message: string, ...args: any[]) => void;
62
- warn: (message: string, ...args: any[]) => void;
63
- error: (message: string, ...args: any[]) => void;
64
- debug: (message: string, ...args: any[]) => void;
67
+ println: LoggerFn;
68
+ info: LoggerFn;
69
+ warn: LoggerFn;
70
+ error: LoggerFn;
71
+ debug: LoggerFn;
65
72
  }
66
73
  interface ParseResult {
67
74
  command: Command | undefined;
68
75
  arguments: any[];
69
76
  options: Record<string, string>;
70
77
  }
71
- declare type Lowercase = 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i' | 'j' | 'k' | 'l' | 'm' | 'n' | 'o' | 'p' | 'q' | 'r' | 's' | 't' | 'u' | 'v' | 'w' | 'x' | 'y' | 'z';
72
- declare type Uppercase = 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H' | 'I' | 'J' | 'K' | 'L' | 'M' | 'N' | 'O' | 'P' | 'Q' | 'R' | 'S' | 'T' | 'U' | 'V' | 'W' | 'X' | 'Y' | 'Z';
73
- declare type Letter = Lowercase | Uppercase;
78
+ declare type ExtractOption<T extends string> = {
79
+ [k in ExtractOptionName<T>]: ExtractOptionType<T>;
80
+ };
74
81
  /**
75
82
  * Extract option name type
76
83
  *
@@ -78,30 +85,39 @@ declare type Letter = Lowercase | Uppercase;
78
85
  * + const t1: ExtractOption<'--option' | '--hello'> = 'hello'
79
86
  * + const t2: ExtractOption<'-r, --root'> = 'root'
80
87
  */
81
- declare type ExtractOption<T extends string> = T extends `-${Letter}, --${infer R} [${infer U}]` ? R : T extends `-${Letter}, --${infer R} <${infer U}>` ? R : T extends `-${Letter}, --${infer R}` ? R : T extends `--${infer R} [${infer U}]` ? R : T extends `--${infer R} <${infer U}>` ? R : T extends `--${infer R}` ? R : never;
88
+ declare type ExtractOptionName<T extends string> = T extends `-${Letter}, --${infer R} [${infer U}]` ? R : T extends `-${Letter}, --${infer R} <${infer U}>` ? R : T extends `-${Letter}, --${infer R}` ? R : T extends `--${infer R} [${infer U}]` ? R : T extends `--${infer R} <${infer U}>` ? R : T extends `--${infer R}` ? R : never;
89
+ declare type ExtractOptionType<T extends string> = T extends `-${Letter}, --${infer R} [${infer U}]` ? string | undefined : T extends `-${Letter}, --${infer R} <${infer U}>` ? string | boolean : T extends `-${Letter}, --${infer R}` ? boolean : T extends `--${infer R} [${infer U}]` ? string | undefined : T extends `--${infer R} <${infer U}>` ? string | boolean : T extends `--${infer R}` ? boolean : never;
90
+ declare type Lowercase = 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i' | 'j' | 'k' | 'l' | 'm' | 'n' | 'o' | 'p' | 'q' | 'r' | 's' | 't' | 'u' | 'v' | 'w' | 'x' | 'y' | 'z';
91
+ declare type Uppercase = 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H' | 'I' | 'J' | 'K' | 'L' | 'M' | 'N' | 'O' | 'P' | 'Q' | 'R' | 'S' | 'T' | 'U' | 'V' | 'W' | 'X' | 'Y' | 'Z';
92
+ declare type Letter = Lowercase | Uppercase;
82
93
  declare type Push<T extends any[], U> = [...T, U];
83
- declare type ActionFn<T extends any[], Option extends string = never> = (...arg: Push<T, Record<Option, string>>) => void;
94
+ declare type ActionFn<T extends any[], Option extends object = {}> = (...arg: Push<T, Option>) => void;
84
95
  /**
85
- * Max Dep: 4
96
+ * Max Dep: 5
86
97
  *
87
- * Generated by: npx tsx scripts/genType.ts 4
98
+ * Generated by: npx tsx scripts/genType.ts 5
88
99
  */
89
- declare type ExtractCommand<T extends string> = T extends `[${infer P1}] [${infer P2}] [${infer P3}] [...${infer P4}]` ? [string | undefined, string | undefined, string | undefined, string[]] : T extends `[${infer P1}] [${infer P2}] [${infer P3}] [${infer P4}]` ? [string | undefined, string | undefined, string | undefined, string | undefined] : T extends `<${infer P1}> [${infer P2}] [${infer P3}] [...${infer P4}]` ? [string, string | undefined, string | undefined, string[]] : T extends `<${infer P1}> [${infer P2}] [${infer P3}] [${infer P4}]` ? [string, string | undefined, string | undefined, string | undefined] : T extends `<${infer P1}> <${infer P2}> [${infer P3}] [...${infer P4}]` ? [string, string, string | undefined, string[]] : T extends `<${infer P1}> <${infer P2}> [${infer P3}] [${infer P4}]` ? [string, string, string | undefined, string | undefined] : T extends `<${infer P1}> <${infer P2}> <${infer P3}> [...${infer P4}]` ? [string, string, string, string[]] : T extends `<${infer P1}> <${infer P2}> <${infer P3}> [${infer P4}]` ? [string, string, string, string | undefined] : T extends `<${infer P1}> <${infer P2}> <${infer P3}> <${infer P4}>` ? [string, string, string, string] : T extends `${infer P1} [${infer P2}] [${infer P3}] [...${infer P4}]` ? [string | undefined, string | undefined, string[]] : T extends `${infer P1} [${infer P2}] [${infer P3}] [${infer P4}]` ? [string | undefined, string | undefined, string | undefined] : T extends `${infer P1} <${infer P2}> [${infer P3}] [...${infer P4}]` ? [string, string | undefined, string[]] : T extends `${infer P1} <${infer P2}> [${infer P3}] [${infer P4}]` ? [string, string | undefined, string | undefined] : T extends `${infer P1} <${infer P2}> <${infer P3}> [...${infer P4}]` ? [string, string, string[]] : T extends `${infer P1} <${infer P2}> <${infer P3}> [${infer P4}]` ? [string, string, string | undefined] : T extends `${infer P1} <${infer P2}> <${infer P3}> <${infer P4}>` ? [string, string, string] : T extends `${infer P1} ${infer P2} [${infer P3}] [...${infer P4}]` ? [string | undefined, string[]] : T extends `${infer P1} ${infer P2} [${infer P3}] [${infer P4}]` ? [string | undefined, string | undefined] : T extends `${infer P1} ${infer P2} <${infer P3}> [...${infer P4}]` ? [string, string[]] : T extends `${infer P1} ${infer P2} <${infer P3}> [${infer P4}]` ? [string, string | undefined] : T extends `${infer P1} ${infer P2} <${infer P3}> <${infer P4}>` ? [string, string] : T extends `${infer P1} ${infer P2} ${infer P3} [...${infer P4}]` ? [string[]] : T extends `${infer P1} ${infer P2} ${infer P3} [${infer P4}]` ? [string | undefined] : T extends `${infer P1} ${infer P2} ${infer P3} <${infer P4}>` ? [string] : T extends `[${infer P1}] [${infer P2}] [...${infer P3}]` ? [string | undefined, string | undefined, string[]] : T extends `[${infer P1}] [${infer P2}] [${infer P3}]` ? [string | undefined, string | undefined, string | undefined] : T extends `<${infer P1}> [${infer P2}] [...${infer P3}]` ? [string, string | undefined, string[]] : T extends `<${infer P1}> [${infer P2}] [${infer P3}]` ? [string, string | undefined, string | undefined] : T extends `<${infer P1}> <${infer P2}> [...${infer P3}]` ? [string, string, string[]] : T extends `<${infer P1}> <${infer P2}> [${infer P3}]` ? [string, string, string | undefined] : T extends `<${infer P1}> <${infer P2}> <${infer P3}>` ? [string, string, string] : T extends `${infer P1} [${infer P2}] [...${infer P3}]` ? [string | undefined, string[]] : T extends `${infer P1} [${infer P2}] [${infer P3}]` ? [string | undefined, string | undefined] : T extends `${infer P1} <${infer P2}> [...${infer P3}]` ? [string, string[]] : T extends `${infer P1} <${infer P2}> [${infer P3}]` ? [string, string | undefined] : T extends `${infer P1} <${infer P2}> <${infer P3}>` ? [string, string] : T extends `${infer P1} ${infer P2} [...${infer P3}]` ? [string[]] : T extends `${infer P1} ${infer P2} [${infer P3}]` ? [string | undefined] : T extends `${infer P1} ${infer P2} <${infer P3}>` ? [string] : T extends `${infer P1} ${infer P2} ${infer P3}` ? [] : T extends `[${infer P1}] [...${infer P2}]` ? [string | undefined, string[]] : T extends `[${infer P1}] [${infer P2}]` ? [string | undefined, string | undefined] : T extends `<${infer P1}> [...${infer P2}]` ? [string, string[]] : T extends `<${infer P1}> [${infer P2}]` ? [string, string | undefined] : T extends `<${infer P1}> <${infer P2}>` ? [string, string] : T extends `${infer P1} [...${infer P2}]` ? [string[]] : T extends `${infer P1} [${infer P2}]` ? [string | undefined] : T extends `${infer P1} <${infer P2}>` ? [string] : T extends `${infer P1} ${infer P2}` ? [] : T extends `[...${infer P1}]` ? [string[]] : T extends `[${infer P1}]` ? [string | undefined] : T extends `<${infer P1}>` ? [string] : T extends `${infer P1}` ? [] : T extends `` ? [] : never;
100
+ declare type ExtractCommand<T extends string> = T extends `<${infer P1}> <${infer P2}> <${infer P3}> <${infer P4}> [...${infer P5}]` ? [string, string, string, string, string[]] : T extends `<${infer P1}> <${infer P2}> <${infer P3}> <${infer P4}> [${infer P5}]` ? [string, string, string, string, string | undefined] : T extends `<${infer P1}> <${infer P2}> <${infer P3}> <${infer P4}> <${infer P5}>` ? [string, string, string, string, string] : T extends `${infer P1} <${infer P2}> <${infer P3}> <${infer P4}> [...${infer P5}]` ? [string, string, string, string[]] : T extends `${infer P1} <${infer P2}> <${infer P3}> <${infer P4}> [${infer P5}]` ? [string, string, string, string | undefined] : T extends `${infer P1} <${infer P2}> <${infer P3}> <${infer P4}> <${infer P5}>` ? [string, string, string, string] : T extends `${infer P1} ${infer P2} <${infer P3}> <${infer P4}> [...${infer P5}]` ? [string, string, string[]] : T extends `${infer P1} ${infer P2} <${infer P3}> <${infer P4}> [${infer P5}]` ? [string, string, string | undefined] : T extends `${infer P1} ${infer P2} <${infer P3}> <${infer P4}> <${infer P5}>` ? [string, string, string] : T extends `${infer P1} ${infer P2} ${infer P3} <${infer P4}> [...${infer P5}]` ? [string, string[]] : T extends `${infer P1} ${infer P2} ${infer P3} <${infer P4}> [${infer P5}]` ? [string, string | undefined] : T extends `${infer P1} ${infer P2} ${infer P3} <${infer P4}> <${infer P5}>` ? [string, string] : T extends `<${infer P1}> <${infer P2}> <${infer P3}> [...${infer P4}]` ? [string, string, string, string[]] : T extends `<${infer P1}> <${infer P2}> <${infer P3}> [${infer P4}]` ? [string, string, string, string | undefined] : T extends `<${infer P1}> <${infer P2}> <${infer P3}> <${infer P4}>` ? [string, string, string, string] : T extends `${infer P1} <${infer P2}> <${infer P3}> [...${infer P4}]` ? [string, string, string[]] : T extends `${infer P1} <${infer P2}> <${infer P3}> [${infer P4}]` ? [string, string, string | undefined] : T extends `${infer P1} <${infer P2}> <${infer P3}> <${infer P4}>` ? [string, string, string] : T extends `${infer P1} ${infer P2} <${infer P3}> [...${infer P4}]` ? [string, string[]] : T extends `${infer P1} ${infer P2} <${infer P3}> [${infer P4}]` ? [string, string | undefined] : T extends `${infer P1} ${infer P2} <${infer P3}> <${infer P4}>` ? [string, string] : T extends `${infer P1} ${infer P2} ${infer P3} [...${infer P4}]` ? [string[]] : T extends `${infer P1} ${infer P2} ${infer P3} [${infer P4}]` ? [string | undefined] : T extends `${infer P1} ${infer P2} ${infer P3} <${infer P4}>` ? [string] : T extends `<${infer P1}> <${infer P2}> [...${infer P3}]` ? [string, string, string[]] : T extends `<${infer P1}> <${infer P2}> [${infer P3}]` ? [string, string, string | undefined] : T extends `<${infer P1}> <${infer P2}> <${infer P3}>` ? [string, string, string] : T extends `${infer P1} <${infer P2}> [...${infer P3}]` ? [string, string[]] : T extends `${infer P1} <${infer P2}> [${infer P3}]` ? [string, string | undefined] : T extends `${infer P1} <${infer P2}> <${infer P3}>` ? [string, string] : T extends `${infer P1} ${infer P2} [...${infer P3}]` ? [string[]] : T extends `${infer P1} ${infer P2} [${infer P3}]` ? [string | undefined] : T extends `${infer P1} ${infer P2} <${infer P3}>` ? [string] : T extends `${infer P1} ${infer P2} ${infer P3}` ? [] : T extends `<${infer P1}> [...${infer P2}]` ? [string, string[]] : T extends `<${infer P1}> [${infer P2}]` ? [string, string | undefined] : T extends `<${infer P1}> <${infer P2}>` ? [string, string] : T extends `${infer P1} [...${infer P2}]` ? [string[]] : T extends `${infer P1} [${infer P2}]` ? [string | undefined] : T extends `${infer P1} <${infer P2}>` ? [string] : T extends `${infer P1} ${infer P2}` ? [] : T extends `[...${infer P1}]` ? [string[]] : T extends `[${infer P1}]` ? [string | undefined] : T extends `<${infer P1}>` ? [string] : T extends `${infer P1}` ? [] : T extends `` ? [] : never;
90
101
 
91
- declare class Breadc<GlobalOption extends string | never = never> {
102
+ declare class Breadc<GlobalOption extends object = {}> {
92
103
  private readonly name;
93
- private readonly version;
104
+ private readonly _version;
105
+ private readonly description?;
94
106
  private readonly options;
95
107
  private readonly commands;
108
+ private defaultCommand?;
96
109
  readonly logger: Logger;
97
110
  constructor(name: string, option: AppOption);
98
- option<F extends string>(format: F, description: string, config?: Omit<OptionConfig, 'description'>): Breadc<GlobalOption | ExtractOption<F>>;
99
- option<F extends string>(format: F, config?: OptionConfig): Breadc<GlobalOption | ExtractOption<F>>;
111
+ version(): string;
112
+ help(command?: Command): string[];
113
+ option<F extends string, T = string>(format: F, description: string, config?: Omit<OptionConfig<T>, 'description'>): Breadc<GlobalOption & ExtractOption<F>>;
114
+ option<F extends string, T = string>(format: F, config?: OptionConfig<T>): Breadc<GlobalOption & ExtractOption<F>>;
115
+ command<F extends string>(format: F, description: string, config?: Omit<CommandConfig, 'description'>): Command<F, GlobalOption>;
100
116
  command<F extends string>(format: F, config?: CommandConfig): Command<F, GlobalOption>;
101
117
  parse(args: string[]): ParseResult;
102
118
  run(args: string[]): Promise<void>;
103
119
  }
104
120
 
105
- declare function breadc(name: string, option?: AppOption): Breadc<never>;
121
+ declare function breadc(name: string, option?: AppOption): Breadc<{}>;
106
122
 
107
123
  export { breadc as default };
package/dist/index.mjs CHANGED
@@ -5,20 +5,24 @@ export { default as minimist } from 'minimist';
5
5
  import createDebug from 'debug';
6
6
  export { default as createDebug } from 'debug';
7
7
 
8
- function createDefaultLogger(name) {
8
+ function createDefaultLogger(name, logger) {
9
+ if (!!logger && typeof logger === "object") {
10
+ return logger;
11
+ }
9
12
  const debug = createDebug(name + ":breadc");
13
+ const println = !!logger && typeof logger === "function" ? logger : (message, ...args) => {
14
+ console.log(message, ...args);
15
+ };
10
16
  return {
11
- println(message) {
12
- console.log(message);
13
- },
17
+ println,
14
18
  info(message, ...args) {
15
- console.log(`${blue("INFO")} ${message}`, ...args);
19
+ println(`${blue("INFO")} ${message}`, ...args);
16
20
  },
17
21
  warn(message, ...args) {
18
- console.log(`${yellow("WARN")} ${message}`, ...args);
22
+ println(`${yellow("WARN")} ${message}`, ...args);
19
23
  },
20
24
  error(message, ...args) {
21
- console.log(`${red("ERROR")} ${message}`, ...args);
25
+ println(`${red("ERROR")} ${message}`, ...args);
22
26
  },
23
27
  debug(message, ...args) {
24
28
  debug(message, ...args);
@@ -28,6 +32,7 @@ function createDefaultLogger(name) {
28
32
 
29
33
  const _Option = class {
30
34
  constructor(format, config = {}) {
35
+ this.format = format;
31
36
  const match = _Option.OptionRE.exec(format);
32
37
  if (match) {
33
38
  if (match[3]) {
@@ -43,25 +48,28 @@ const _Option = class {
43
48
  throw new Error(`Can not parse option format from "${format}"`);
44
49
  }
45
50
  this.description = config.description ?? "";
51
+ this.required = format.indexOf("<") !== -1;
52
+ this.default = config.default;
46
53
  this.construct = config.construct ?? ((text) => text ?? config.default ?? void 0);
47
54
  }
48
55
  };
49
56
  let Option = _Option;
50
57
  Option.OptionRE = /^(-[a-zA-Z], )?--([a-zA-Z.]+)( \[[a-zA-Z]+\]| <[a-zA-Z]+>)?$/;
51
58
 
52
- class Command {
59
+ const _Command = class {
53
60
  constructor(format, config) {
54
61
  this.options = [];
55
62
  this.format = config.condition ? [format] : format.split(" ").map((t) => t.trim()).filter(Boolean);
63
+ this.default = this.format.length === 0 || this.format[0][0] === "[" || this.format[0][0] === "<";
56
64
  this.description = config.description ?? "";
57
65
  this.conditionFn = config.condition;
58
66
  this.logger = config.logger;
67
+ if (this.format.length > _Command.MaxDep) {
68
+ this.logger.warn(`Command format string "${format}" is too long`);
69
+ }
59
70
  }
60
71
  option(format, configOrDescription = "", otherConfig = {}) {
61
- const config = otherConfig;
62
- if (typeof configOrDescription === "string") {
63
- config.description = configOrDescription;
64
- }
72
+ const config = typeof configOrDescription === "object" ? configOrDescription : { ...otherConfig, description: configOrDescription };
65
73
  try {
66
74
  const option = new Option(format, config);
67
75
  this.options.push(option);
@@ -70,13 +78,18 @@ class Command {
70
78
  }
71
79
  return this;
72
80
  }
81
+ get hasConditionFn() {
82
+ return !!this.conditionFn;
83
+ }
73
84
  shouldRun(args) {
74
85
  if (this.conditionFn) {
75
86
  return this.conditionFn(args);
76
87
  } else {
77
- const isArg = (t) => t[0] !== "[" && t[0] !== "<";
88
+ if (this.default)
89
+ return true;
90
+ const isCmd = (t) => t[0] !== "[" && t[0] !== "<";
78
91
  for (let i = 0; i < this.format.length; i++) {
79
- if (isArg(this.format[i])) {
92
+ if (!isCmd(this.format[i])) {
80
93
  return true;
81
94
  }
82
95
  if (i >= args["_"].length || this.format[i] !== args["_"][i]) {
@@ -86,7 +99,7 @@ class Command {
86
99
  return true;
87
100
  }
88
101
  }
89
- parseArgs(args) {
102
+ parseArgs(args, globalOptions) {
90
103
  if (this.conditionFn) {
91
104
  const argumentss2 = args["_"];
92
105
  const options2 = args;
@@ -97,33 +110,55 @@ class Command {
97
110
  options: args
98
111
  };
99
112
  }
100
- const isArg = (t) => t[0] !== "[" && t[0] !== "<";
113
+ const isCmd = (t) => t[0] !== "[" && t[0] !== "<";
101
114
  const argumentss = [];
102
115
  for (let i = 0; i < this.format.length; i++) {
103
- if (isArg(this.format[i]))
116
+ if (isCmd(this.format[i]))
104
117
  continue;
105
118
  if (i < args["_"].length) {
106
119
  if (this.format[i].startsWith("[...")) {
107
- argumentss.push(args["_"].slice(i));
120
+ argumentss.push(args["_"].slice(i).map(String));
108
121
  } else {
109
- argumentss.push(args["_"][i]);
122
+ argumentss.push(String(args["_"][i]));
110
123
  }
111
124
  } else {
112
125
  if (this.format[i].startsWith("<")) {
126
+ this.logger.warn(`You should provide the argument "${this.format[i]}"`);
113
127
  argumentss.push(void 0);
114
128
  } else if (this.format[i].startsWith("[...")) {
115
129
  argumentss.push([]);
116
130
  } else if (this.format[i].startsWith("[")) {
117
131
  argumentss.push(void 0);
118
- } else ;
132
+ } else {
133
+ this.logger.warn(`unknown format string ("${this.format[i]}")`);
134
+ }
119
135
  }
120
136
  }
137
+ const fullOptions = globalOptions.concat(this.options).reduce((map, o) => {
138
+ map.set(o.name, o);
139
+ return map;
140
+ }, /* @__PURE__ */ new Map());
121
141
  const options = args;
122
142
  delete options["_"];
143
+ for (const [name, rawOption] of fullOptions) {
144
+ if (rawOption.required) {
145
+ if (options[name] === void 0) {
146
+ options[name] = false;
147
+ } else if (options[name] === "") {
148
+ options[name] = true;
149
+ }
150
+ } else {
151
+ if (options[name] === false) {
152
+ options[name] = void 0;
153
+ } else if (!(name in options)) {
154
+ options[name] = void 0;
155
+ }
156
+ }
157
+ }
123
158
  return {
124
159
  command: this,
125
160
  arguments: argumentss,
126
- options: args
161
+ options
127
162
  };
128
163
  }
129
164
  action(fn) {
@@ -131,17 +166,29 @@ class Command {
131
166
  return this;
132
167
  }
133
168
  async run(...args) {
134
- this.actionFn && this.actionFn(...args);
169
+ if (this.actionFn) {
170
+ this.actionFn(...args);
171
+ } else {
172
+ this.logger.warn(`You may miss action function in "${this.format}"`);
173
+ }
135
174
  }
136
- }
137
- Command.MaxDep = 4;
138
- function createVersionCommand(breadc) {
175
+ };
176
+ let Command = _Command;
177
+ Command.MaxDep = 5;
178
+ function createHelpCommand(breadc) {
179
+ let helpCommand = void 0;
139
180
  return new Command("-h, --help", {
140
181
  condition(args) {
141
- const isEmpty = !args["_"].length && !args["--"]?.length;
142
- if (args.help && isEmpty) {
143
- return true;
144
- } else if (args.h && isEmpty) {
182
+ const isEmpty = !args["--"]?.length;
183
+ if ((args.help || args.h) && isEmpty) {
184
+ if (args["_"].length > 0) {
185
+ for (const cmd of breadc.commands) {
186
+ if (!cmd.hasConditionFn && !cmd.default && cmd.shouldRun(args)) {
187
+ helpCommand = cmd;
188
+ return true;
189
+ }
190
+ }
191
+ }
145
192
  return true;
146
193
  } else {
147
194
  return false;
@@ -149,10 +196,12 @@ function createVersionCommand(breadc) {
149
196
  },
150
197
  logger: breadc.logger
151
198
  }).action(() => {
152
- breadc.logger.println("Help");
199
+ for (const line of breadc.help(helpCommand)) {
200
+ breadc.logger.println(line);
201
+ }
153
202
  });
154
203
  }
155
- function createHelpCommand(breadc) {
204
+ function createVersionCommand(breadc) {
156
205
  return new Command("-v, --version", {
157
206
  condition(args) {
158
207
  const isEmpty = !args["_"].length && !args["--"]?.length;
@@ -166,7 +215,7 @@ function createHelpCommand(breadc) {
166
215
  },
167
216
  logger: breadc.logger
168
217
  }).action(() => {
169
- breadc.logger.println(`${breadc.name}/${breadc.version}`);
218
+ breadc.logger.println(breadc.version());
170
219
  });
171
220
  }
172
221
 
@@ -175,22 +224,78 @@ class Breadc {
175
224
  this.options = [];
176
225
  this.commands = [];
177
226
  this.name = name;
178
- this.version = option.version ?? "unknown";
179
- this.logger = option.logger ?? createDefaultLogger(name);
227
+ this._version = option.version ?? "unknown";
228
+ this.description = option.description;
229
+ this.logger = createDefaultLogger(name, option.logger);
180
230
  const breadc = {
181
231
  name: this.name,
182
- version: this.version,
232
+ version: () => this.version.call(this),
233
+ help: (command) => this.help.call(this, command),
183
234
  logger: this.logger,
184
235
  options: this.options,
185
236
  commands: this.commands
186
237
  };
187
- this.commands = [createVersionCommand(breadc), createHelpCommand(breadc)];
238
+ this.commands.push(createVersionCommand(breadc), createHelpCommand(breadc));
188
239
  }
189
- option(format, configOrDescription = "", otherConfig = {}) {
190
- const config = otherConfig;
191
- if (typeof configOrDescription === "string") {
192
- config.description = configOrDescription;
240
+ version() {
241
+ return `${this.name}/${this._version}`;
242
+ }
243
+ help(command) {
244
+ const output = [];
245
+ const println = (msg) => output.push(msg);
246
+ println(this.version());
247
+ if (!command) {
248
+ if (this.description) {
249
+ println("");
250
+ if (Array.isArray(this.description)) {
251
+ for (const line of this.description) {
252
+ println(line);
253
+ }
254
+ } else {
255
+ println(this.description);
256
+ }
257
+ }
258
+ } else {
259
+ if (command.description) {
260
+ println("");
261
+ println(command.description);
262
+ }
263
+ }
264
+ if (!command) {
265
+ if (this.defaultCommand) {
266
+ println(``);
267
+ println(`Usage:`);
268
+ println(` $ ${this.name} ${this.defaultCommand.format.join(" ")}`);
269
+ }
270
+ } else {
271
+ println(``);
272
+ println(`Usage:`);
273
+ println(` $ ${this.name} ${command.format.join(" ")}`);
274
+ }
275
+ if (!command && this.commands.length > 2) {
276
+ println(``);
277
+ println(`Commands:`);
278
+ const commandHelps = this.commands.filter((c) => !c.hasConditionFn).map((c) => [` $ ${this.name} ${c.format.join(" ")}`, c.description]);
279
+ for (const line of twoColumn(commandHelps)) {
280
+ println(line);
281
+ }
282
+ }
283
+ println(``);
284
+ println(`Options:`);
285
+ const optionHelps = [].concat([
286
+ ...command ? command.options.map((o) => [` ${o.format}`, o.description]) : [],
287
+ ...this.options.map((o) => [` ${o.format}`, o.description]),
288
+ [` -h, --help`, `Display this message`],
289
+ [` -v, --version`, `Display version number`]
290
+ ]);
291
+ for (const line of twoColumn(optionHelps)) {
292
+ println(line);
193
293
  }
294
+ println(``);
295
+ return output;
296
+ }
297
+ option(format, configOrDescription = "", otherConfig = {}) {
298
+ const config = typeof configOrDescription === "object" ? configOrDescription : { ...otherConfig, description: configOrDescription };
194
299
  try {
195
300
  const option = new Option(format, config);
196
301
  this.options.push(option);
@@ -199,32 +304,64 @@ class Breadc {
199
304
  }
200
305
  return this;
201
306
  }
202
- command(format, config = {}) {
307
+ command(format, configOrDescription = "", otherConfig = {}) {
308
+ const config = typeof configOrDescription === "object" ? configOrDescription : { ...otherConfig, description: configOrDescription };
203
309
  const command = new Command(format, { ...config, logger: this.logger });
310
+ if (command.default) {
311
+ if (this.defaultCommand) {
312
+ this.logger.warn("You can not have two default commands.");
313
+ }
314
+ this.defaultCommand = command;
315
+ }
204
316
  this.commands.push(command);
205
317
  return command;
206
318
  }
207
319
  parse(args) {
208
- const allowOptions = [this.options, this.commands.map((c) => c.options)].flat();
320
+ const allowOptions = [
321
+ ...this.options,
322
+ ...this.commands.flatMap((c) => c.options)
323
+ ];
209
324
  const alias = allowOptions.reduce((map, o) => {
210
325
  if (o.shortcut) {
211
326
  map[o.shortcut] = o.name;
212
327
  }
213
328
  return map;
214
329
  }, {});
330
+ const defaults = allowOptions.reduce((map, o) => {
331
+ if (o.default) {
332
+ map[o.name] = o.default;
333
+ }
334
+ return map;
335
+ }, {});
215
336
  const argv = minimist(args, {
216
337
  string: allowOptions.filter((o) => o.type === "string").map((o) => o.name),
217
338
  boolean: allowOptions.filter((o) => o.type === "boolean").map((o) => o.name),
218
- alias
339
+ alias,
340
+ default: defaults,
341
+ unknown: (t) => {
342
+ if (t[0] !== "-")
343
+ return true;
344
+ else {
345
+ if (["--help", "-h", "--version", "-v"].includes(t)) {
346
+ return true;
347
+ } else {
348
+ this.logger.warn(`Find unknown flag "${t}"`);
349
+ return false;
350
+ }
351
+ }
352
+ }
219
353
  });
220
354
  for (const shortcut of Object.keys(alias)) {
221
355
  delete argv[shortcut];
222
356
  }
223
357
  for (const command of this.commands) {
224
- if (command.shouldRun(argv)) {
225
- return command.parseArgs(argv);
358
+ if (!command.default && command.shouldRun(argv)) {
359
+ return command.parseArgs(argv, this.options);
226
360
  }
227
361
  }
362
+ if (this.defaultCommand) {
363
+ return this.defaultCommand.parseArgs(argv, this.options);
364
+ }
228
365
  const argumentss = argv["_"];
229
366
  const options = argv;
230
367
  delete options["_"];
@@ -241,6 +378,14 @@ class Breadc {
241
378
  }
242
379
  }
243
380
  }
381
+ function twoColumn(texts, split = " ") {
382
+ const left = padRight(texts.map((t) => t[0]));
383
+ return left.map((l, idx) => l + split + texts[idx][1]);
384
+ }
385
+ function padRight(texts, fill = " ") {
386
+ const length = texts.map((t) => t.length).reduce((max, l) => Math.max(max, l), 0);
387
+ return texts.map((t) => t + fill.repeat(length - t.length));
388
+ }
244
389
 
245
390
  function breadc(name, option = {}) {
246
391
  return new Breadc(name, option);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "breadc",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Yet another Command Line Application Framework",
5
5
  "keywords": [
6
6
  "cli",
@@ -43,7 +43,6 @@
43
43
  "@types/node": "^17.0.43",
44
44
  "bumpp": "^8.2.1",
45
45
  "prettier": "^2.7.1",
46
- "tsx": "^3.4.3",
47
46
  "typescript": "^4.7.4",
48
47
  "unbuild": "^0.7.4",
49
48
  "vite": "^2.9.12",
@@ -52,7 +51,7 @@
52
51
  "packageManager": "pnpm@7.3.0",
53
52
  "scripts": {
54
53
  "build": "unbuild",
55
- "format": "prettier --write src/**/*.ts",
54
+ "format": "prettier --write src/**/*.ts test/*.ts examples/*.ts scripts/*.ts",
56
55
  "release": "bumpp --commit --push --tag && pnpm publish",
57
56
  "test": "vitest",
58
57
  "typecheck": "tsc --noEmit",