breadc 0.0.0 → 0.1.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
@@ -1,8 +1,8 @@
1
1
  # Breadc
2
2
 
3
- [![CI](https://github.com/yjl9903/Breadc/actions/workflows/ci.yml/badge.svg)](https://github.com/yjl9903/Breadc/actions/workflows/ci.yml)
3
+ [![version](https://img.shields.io/npm/v/breadc?color=rgb%2850%2C203%2C86%29&label=Breadc)](https://www.npmjs.com/package/breadc) [![CI](https://github.com/yjl9903/Breadc/actions/workflows/ci.yml/badge.svg)](https://github.com/yjl9903/Breadc/actions/workflows/ci.yml)
4
4
 
5
- Yet another Command Line Application Framework powered by [minimist](https://www.npmjs.com/package/minimist).
5
+ Yet another Command Line Application Framework powered by [minimist](https://www.npmjs.com/package/minimist), but with fully [TypeScript](https://www.typescriptlang.org/) support.
6
6
 
7
7
  ## Installation
8
8
 
@@ -15,11 +15,27 @@ npm i breadc
15
15
  ```ts
16
16
  import Breadc from 'breadc'
17
17
 
18
- const cli = Breadc('cli', { version: '1.0.0' })
18
+ const cli = Breadc('vite', { version: '1.0.0' })
19
+ .option('--host <host>')
20
+ .option('--port <port>')
19
21
 
20
- cli.parse(process.argv.slice(2))
22
+ cli.command('dev')
23
+ .action((option) => {
24
+ console.log(`Host: ${option.host}`);
25
+ console.log(`Port: ${option.port}`);
26
+ })
27
+
28
+ cli.run(process.argv.slice(2))
29
+ .catch(err => cli.logger.error(err.message))
21
30
  ```
22
31
 
32
+ 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>`.
33
+
34
+ ## Inspiration
35
+
36
+ + [cac](https://github.com/cacjs/cac): Simple yet powerful framework for building command-line apps.
37
+ + [TypeScript: Documentation - Template Literal Types](https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html)
38
+
23
39
  ## License
24
40
 
25
41
  MIT License © 2021 [XLor](https://github.com/yjl9903)
package/dist/index.cjs CHANGED
@@ -1,62 +1,259 @@
1
1
  'use strict';
2
2
 
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ const kolorist = require('kolorist');
3
6
  const minimist = require('minimist');
7
+ const createDebug = require('debug');
4
8
 
5
9
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e["default"] : e; }
6
10
 
11
+ const kolorist__default = /*#__PURE__*/_interopDefaultLegacy(kolorist);
7
12
  const minimist__default = /*#__PURE__*/_interopDefaultLegacy(minimist);
13
+ const createDebug__default = /*#__PURE__*/_interopDefaultLegacy(createDebug);
14
+
15
+ function createDefaultLogger(name) {
16
+ const debug = createDebug__default(name + ":breadc");
17
+ return {
18
+ println(message) {
19
+ console.log(message);
20
+ },
21
+ info(message, ...args) {
22
+ console.log(`${kolorist.blue("INFO")} ${message}`, ...args);
23
+ },
24
+ warn(message, ...args) {
25
+ console.log(`${kolorist.yellow("WARN")} ${message}`, ...args);
26
+ },
27
+ error(message, ...args) {
28
+ console.log(`${kolorist.red("ERROR")} ${message}`, ...args);
29
+ },
30
+ debug(message, ...args) {
31
+ debug(message, ...args);
32
+ }
33
+ };
34
+ }
35
+
36
+ const _Option = class {
37
+ constructor(format, config = {}) {
38
+ const match = _Option.OptionRE.exec(format);
39
+ if (match) {
40
+ if (match[3]) {
41
+ this.type = "string";
42
+ } else {
43
+ this.type = "boolean";
44
+ }
45
+ this.name = match[2];
46
+ if (match[1]) {
47
+ this.shortcut = match[1][1];
48
+ }
49
+ } else {
50
+ throw new Error(`Can not parse option format from "${format}"`);
51
+ }
52
+ this.description = config.description ?? "";
53
+ this.construct = config.construct ?? ((text) => text ?? config.default ?? void 0);
54
+ }
55
+ };
56
+ let Option = _Option;
57
+ Option.OptionRE = /^(-[a-zA-Z], )?--([a-zA-Z.]+)( \[[a-zA-Z]+\]| <[a-zA-Z]+>)?$/;
58
+
59
+ class Command {
60
+ constructor(format, config) {
61
+ this.options = [];
62
+ this.format = config.condition ? [format] : format.split(" ").map((t) => t.trim()).filter(Boolean);
63
+ this.description = config.description ?? "";
64
+ this.conditionFn = config.condition;
65
+ this.logger = config.logger;
66
+ }
67
+ option(format, configOrDescription = "", otherConfig = {}) {
68
+ const config = otherConfig;
69
+ if (typeof configOrDescription === "string") {
70
+ config.description = configOrDescription;
71
+ }
72
+ try {
73
+ const option = new Option(format, config);
74
+ this.options.push(option);
75
+ } catch (error) {
76
+ this.logger.warn(error.message);
77
+ }
78
+ return this;
79
+ }
80
+ shouldRun(args) {
81
+ if (this.conditionFn) {
82
+ return this.conditionFn(args);
83
+ } else {
84
+ const isArg = (t) => t[0] !== "[" && t[0] !== "<";
85
+ for (let i = 0; i < this.format.length; i++) {
86
+ if (isArg(this.format[i])) {
87
+ return true;
88
+ }
89
+ if (i >= args["_"].length || this.format[i] !== args["_"][i]) {
90
+ return false;
91
+ }
92
+ }
93
+ return true;
94
+ }
95
+ }
96
+ parseArgs(args) {
97
+ if (this.conditionFn) {
98
+ const argumentss2 = args["_"];
99
+ const options2 = args;
100
+ delete options2["_"];
101
+ return {
102
+ command: this,
103
+ arguments: argumentss2,
104
+ options: args
105
+ };
106
+ }
107
+ const isArg = (t) => t[0] !== "[" && t[0] !== "<";
108
+ const argumentss = [];
109
+ for (let i = 0; i < this.format.length; i++) {
110
+ if (isArg(this.format[i]))
111
+ continue;
112
+ if (i < args["_"].length) {
113
+ if (this.format[i].startsWith("[...")) {
114
+ argumentss.push(args["_"].slice(i));
115
+ } else {
116
+ argumentss.push(args["_"][i]);
117
+ }
118
+ } else {
119
+ if (this.format[i].startsWith("<")) {
120
+ argumentss.push(void 0);
121
+ } else if (this.format[i].startsWith("[...")) {
122
+ argumentss.push([]);
123
+ } else if (this.format[i].startsWith("[")) {
124
+ argumentss.push(void 0);
125
+ } else ;
126
+ }
127
+ }
128
+ const options = args;
129
+ delete options["_"];
130
+ return {
131
+ command: this,
132
+ arguments: argumentss,
133
+ options: args
134
+ };
135
+ }
136
+ action(fn) {
137
+ this.actionFn = fn;
138
+ return this;
139
+ }
140
+ async run(...args) {
141
+ this.actionFn && this.actionFn(...args);
142
+ }
143
+ }
144
+ Command.MaxDep = 4;
145
+ function createVersionCommand(breadc) {
146
+ return new Command("-h, --help", {
147
+ condition(args) {
148
+ const isEmpty = !args["_"].length && !args["--"]?.length;
149
+ if (args.help && isEmpty) {
150
+ return true;
151
+ } else if (args.h && isEmpty) {
152
+ return true;
153
+ } else {
154
+ return false;
155
+ }
156
+ },
157
+ logger: breadc.logger
158
+ }).action(() => {
159
+ breadc.logger.println("Help");
160
+ });
161
+ }
162
+ function createHelpCommand(breadc) {
163
+ return new Command("-v, --version", {
164
+ condition(args) {
165
+ const isEmpty = !args["_"].length && !args["--"]?.length;
166
+ if (args.version && isEmpty) {
167
+ return true;
168
+ } else if (args.v && isEmpty) {
169
+ return true;
170
+ } else {
171
+ return false;
172
+ }
173
+ },
174
+ logger: breadc.logger
175
+ }).action(() => {
176
+ breadc.logger.println(`${breadc.name}/${breadc.version}`);
177
+ });
178
+ }
8
179
 
9
180
  class Breadc {
10
181
  constructor(name, option) {
11
182
  this.options = [];
183
+ this.commands = [];
12
184
  this.name = name;
13
- this.version = option.version;
185
+ this.version = option.version ?? "unknown";
186
+ this.logger = option.logger ?? createDefaultLogger(name);
187
+ const breadc = {
188
+ name: this.name,
189
+ version: this.version,
190
+ logger: this.logger,
191
+ options: this.options,
192
+ commands: this.commands
193
+ };
194
+ this.commands = [createVersionCommand(breadc), createHelpCommand(breadc)];
14
195
  }
15
- option(text) {
196
+ option(format, configOrDescription = "", otherConfig = {}) {
197
+ const config = otherConfig;
198
+ if (typeof configOrDescription === "string") {
199
+ config.description = configOrDescription;
200
+ }
16
201
  try {
17
- const option = new BreadcOption(text);
202
+ const option = new Option(format, config);
18
203
  this.options.push(option);
19
204
  } catch (error) {
205
+ this.logger.warn(error.message);
20
206
  }
21
207
  return this;
22
208
  }
23
- command(text) {
24
- return new Breadcommand(this, text);
209
+ command(format, config = {}) {
210
+ const command = new Command(format, { ...config, logger: this.logger });
211
+ this.commands.push(command);
212
+ return command;
25
213
  }
26
214
  parse(args) {
215
+ const allowOptions = [this.options, this.commands.map((c) => c.options)].flat();
216
+ const alias = allowOptions.reduce((map, o) => {
217
+ if (o.shortcut) {
218
+ map[o.shortcut] = o.name;
219
+ }
220
+ return map;
221
+ }, {});
27
222
  const argv = minimist__default(args, {
28
- string: this.options.filter((o) => o.type === "string").map((o) => o.name),
29
- boolean: this.options.filter((o) => o.type === "boolean").map((o) => o.name)
223
+ string: allowOptions.filter((o) => o.type === "string").map((o) => o.name),
224
+ boolean: allowOptions.filter((o) => o.type === "boolean").map((o) => o.name),
225
+ alias
30
226
  });
31
- return argv;
32
- }
33
- }
34
- class Breadcommand {
35
- constructor(breadc, text) {
36
- this.breadc = breadc;
37
- }
38
- }
39
- const _BreadcOption = class {
40
- constructor(text) {
41
- if (_BreadcOption.BooleanRE.test(text)) {
42
- this.type = "boolean";
43
- } else {
44
- this.type = "string";
227
+ for (const shortcut of Object.keys(alias)) {
228
+ delete argv[shortcut];
45
229
  }
46
- const match = _BreadcOption.NameRE.exec(text);
47
- if (match) {
48
- this.name = match[1];
49
- } else {
50
- throw new Error(`Can not extract option name from "${text}"`);
230
+ for (const command of this.commands) {
231
+ if (command.shouldRun(argv)) {
232
+ return command.parseArgs(argv);
233
+ }
51
234
  }
235
+ const argumentss = argv["_"];
236
+ const options = argv;
237
+ delete options["_"];
238
+ return {
239
+ command: void 0,
240
+ arguments: argumentss,
241
+ options
242
+ };
52
243
  }
53
- };
54
- let BreadcOption = _BreadcOption;
55
- BreadcOption.BooleanRE = /^--[a-zA-Z.]+$/;
56
- BreadcOption.NameRE = /--([a-zA-Z.]+)/;
244
+ async run(args) {
245
+ const parsed = this.parse(args);
246
+ if (parsed.command) {
247
+ parsed.command.run(...parsed.arguments, parsed.options);
248
+ }
249
+ }
250
+ }
57
251
 
58
252
  function breadc(name, option = {}) {
59
- return new Breadc(name, { version: option.version ?? "unknown" });
253
+ return new Breadc(name, option);
60
254
  }
61
255
 
62
- module.exports = breadc;
256
+ exports.kolorist = kolorist__default;
257
+ exports.minimist = minimist__default;
258
+ exports.createDebug = createDebug__default;
259
+ exports["default"] = breadc;
package/dist/index.d.ts CHANGED
@@ -1,24 +1,107 @@
1
- import minimist from 'minimist';
1
+ import { ParsedArgs } from 'minimist';
2
+ export { default as minimist } from 'minimist';
3
+ export { default as kolorist } from 'kolorist';
4
+ export { default as createDebug } from 'debug';
2
5
 
3
- declare class Breadc {
4
- private readonly name;
5
- private readonly version;
6
- private readonly options;
7
- constructor(name: string, option: {
8
- version: string;
9
- });
10
- option(text: string): this;
11
- command(text: string): Breadcommand;
12
- parse(args: string[]): minimist.ParsedArgs;
6
+ interface OptionConfig<T = string> {
7
+ description?: string;
8
+ default?: T;
9
+ construct?: (rawText?: string) => T;
13
10
  }
14
- declare class Breadcommand {
15
- private readonly breadc;
16
- constructor(breadc: Breadc, text: string);
11
+ /**
12
+ * Option
13
+ *
14
+ * Option format must follow:
15
+ * + --option
16
+ * + -o, --option
17
+ * + --option <arg>
18
+ * + --option [arg]
19
+ */
20
+ declare class Option<T extends string = string, U = ExtractOption<T>> {
21
+ private static OptionRE;
22
+ readonly name: string;
23
+ readonly shortcut?: string;
24
+ readonly description: string;
25
+ readonly type: 'string' | 'boolean';
26
+ readonly construct: (rawText: string | undefined) => any;
27
+ constructor(format: T, config?: OptionConfig);
28
+ }
29
+
30
+ declare type ConditionFn = (args: ParsedArgs) => boolean;
31
+ interface CommandConfig {
32
+ description?: string;
33
+ }
34
+ declare class Command<F extends string = string, GlobalOption extends string | never = never, CommandOption extends string | never = never> {
35
+ private static MaxDep;
36
+ private readonly conditionFn?;
37
+ private readonly logger;
38
+ readonly format: string[];
39
+ readonly description: string;
40
+ readonly options: Option[];
41
+ private actionFn?;
42
+ constructor(format: F, config: CommandConfig & {
43
+ condition?: ConditionFn;
44
+ logger: Logger;
45
+ });
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>>;
48
+ shouldRun(args: ParsedArgs): boolean;
49
+ parseArgs(args: ParsedArgs): ParseResult;
50
+ action(fn: ActionFn<ExtractCommand<F>, GlobalOption | CommandOption>): this;
51
+ run(...args: any[]): Promise<void>;
17
52
  }
18
53
 
19
- interface Option {
54
+ interface AppOption {
20
55
  version?: string;
56
+ help?: string | string[] | (() => string | string[]);
57
+ logger?: Logger;
21
58
  }
22
- declare function breadc(name: string, option?: Option): Breadc;
59
+ 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;
65
+ }
66
+ interface ParseResult {
67
+ command: Command | undefined;
68
+ arguments: any[];
69
+ options: Record<string, string>;
70
+ }
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;
74
+ /**
75
+ * Extract option name type
76
+ *
77
+ * Examples:
78
+ * + const t1: ExtractOption<'--option' | '--hello'> = 'hello'
79
+ * + const t2: ExtractOption<'-r, --root'> = 'root'
80
+ */
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;
82
+ 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;
84
+ /**
85
+ * Max Dep: 4
86
+ *
87
+ * Generated by: npx tsx scripts/genType.ts 4
88
+ */
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}` ? [] : never;
90
+
91
+ declare class Breadc<GlobalOption extends string | never = never> {
92
+ private readonly name;
93
+ private readonly version;
94
+ private readonly options;
95
+ private readonly commands;
96
+ readonly logger: Logger;
97
+ 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>>;
100
+ command<F extends string>(format: F, config?: CommandConfig): Command<F, GlobalOption>;
101
+ parse(args: string[]): ParseResult;
102
+ run(args: string[]): Promise<void>;
103
+ }
104
+
105
+ declare function breadc(name: string, option?: AppOption): Breadc<never>;
23
106
 
24
107
  export { breadc as default };
package/dist/index.mjs CHANGED
@@ -1,56 +1,249 @@
1
+ import { blue, yellow, red } from 'kolorist';
2
+ export { default as kolorist } from 'kolorist';
1
3
  import minimist from 'minimist';
4
+ export { default as minimist } from 'minimist';
5
+ import createDebug from 'debug';
6
+ export { default as createDebug } from 'debug';
7
+
8
+ function createDefaultLogger(name) {
9
+ const debug = createDebug(name + ":breadc");
10
+ return {
11
+ println(message) {
12
+ console.log(message);
13
+ },
14
+ info(message, ...args) {
15
+ console.log(`${blue("INFO")} ${message}`, ...args);
16
+ },
17
+ warn(message, ...args) {
18
+ console.log(`${yellow("WARN")} ${message}`, ...args);
19
+ },
20
+ error(message, ...args) {
21
+ console.log(`${red("ERROR")} ${message}`, ...args);
22
+ },
23
+ debug(message, ...args) {
24
+ debug(message, ...args);
25
+ }
26
+ };
27
+ }
28
+
29
+ const _Option = class {
30
+ constructor(format, config = {}) {
31
+ const match = _Option.OptionRE.exec(format);
32
+ if (match) {
33
+ if (match[3]) {
34
+ this.type = "string";
35
+ } else {
36
+ this.type = "boolean";
37
+ }
38
+ this.name = match[2];
39
+ if (match[1]) {
40
+ this.shortcut = match[1][1];
41
+ }
42
+ } else {
43
+ throw new Error(`Can not parse option format from "${format}"`);
44
+ }
45
+ this.description = config.description ?? "";
46
+ this.construct = config.construct ?? ((text) => text ?? config.default ?? void 0);
47
+ }
48
+ };
49
+ let Option = _Option;
50
+ Option.OptionRE = /^(-[a-zA-Z], )?--([a-zA-Z.]+)( \[[a-zA-Z]+\]| <[a-zA-Z]+>)?$/;
51
+
52
+ class Command {
53
+ constructor(format, config) {
54
+ this.options = [];
55
+ this.format = config.condition ? [format] : format.split(" ").map((t) => t.trim()).filter(Boolean);
56
+ this.description = config.description ?? "";
57
+ this.conditionFn = config.condition;
58
+ this.logger = config.logger;
59
+ }
60
+ option(format, configOrDescription = "", otherConfig = {}) {
61
+ const config = otherConfig;
62
+ if (typeof configOrDescription === "string") {
63
+ config.description = configOrDescription;
64
+ }
65
+ try {
66
+ const option = new Option(format, config);
67
+ this.options.push(option);
68
+ } catch (error) {
69
+ this.logger.warn(error.message);
70
+ }
71
+ return this;
72
+ }
73
+ shouldRun(args) {
74
+ if (this.conditionFn) {
75
+ return this.conditionFn(args);
76
+ } else {
77
+ const isArg = (t) => t[0] !== "[" && t[0] !== "<";
78
+ for (let i = 0; i < this.format.length; i++) {
79
+ if (isArg(this.format[i])) {
80
+ return true;
81
+ }
82
+ if (i >= args["_"].length || this.format[i] !== args["_"][i]) {
83
+ return false;
84
+ }
85
+ }
86
+ return true;
87
+ }
88
+ }
89
+ parseArgs(args) {
90
+ if (this.conditionFn) {
91
+ const argumentss2 = args["_"];
92
+ const options2 = args;
93
+ delete options2["_"];
94
+ return {
95
+ command: this,
96
+ arguments: argumentss2,
97
+ options: args
98
+ };
99
+ }
100
+ const isArg = (t) => t[0] !== "[" && t[0] !== "<";
101
+ const argumentss = [];
102
+ for (let i = 0; i < this.format.length; i++) {
103
+ if (isArg(this.format[i]))
104
+ continue;
105
+ if (i < args["_"].length) {
106
+ if (this.format[i].startsWith("[...")) {
107
+ argumentss.push(args["_"].slice(i));
108
+ } else {
109
+ argumentss.push(args["_"][i]);
110
+ }
111
+ } else {
112
+ if (this.format[i].startsWith("<")) {
113
+ argumentss.push(void 0);
114
+ } else if (this.format[i].startsWith("[...")) {
115
+ argumentss.push([]);
116
+ } else if (this.format[i].startsWith("[")) {
117
+ argumentss.push(void 0);
118
+ } else ;
119
+ }
120
+ }
121
+ const options = args;
122
+ delete options["_"];
123
+ return {
124
+ command: this,
125
+ arguments: argumentss,
126
+ options: args
127
+ };
128
+ }
129
+ action(fn) {
130
+ this.actionFn = fn;
131
+ return this;
132
+ }
133
+ async run(...args) {
134
+ this.actionFn && this.actionFn(...args);
135
+ }
136
+ }
137
+ Command.MaxDep = 4;
138
+ function createVersionCommand(breadc) {
139
+ return new Command("-h, --help", {
140
+ condition(args) {
141
+ const isEmpty = !args["_"].length && !args["--"]?.length;
142
+ if (args.help && isEmpty) {
143
+ return true;
144
+ } else if (args.h && isEmpty) {
145
+ return true;
146
+ } else {
147
+ return false;
148
+ }
149
+ },
150
+ logger: breadc.logger
151
+ }).action(() => {
152
+ breadc.logger.println("Help");
153
+ });
154
+ }
155
+ function createHelpCommand(breadc) {
156
+ return new Command("-v, --version", {
157
+ condition(args) {
158
+ const isEmpty = !args["_"].length && !args["--"]?.length;
159
+ if (args.version && isEmpty) {
160
+ return true;
161
+ } else if (args.v && isEmpty) {
162
+ return true;
163
+ } else {
164
+ return false;
165
+ }
166
+ },
167
+ logger: breadc.logger
168
+ }).action(() => {
169
+ breadc.logger.println(`${breadc.name}/${breadc.version}`);
170
+ });
171
+ }
2
172
 
3
173
  class Breadc {
4
174
  constructor(name, option) {
5
175
  this.options = [];
176
+ this.commands = [];
6
177
  this.name = name;
7
- this.version = option.version;
178
+ this.version = option.version ?? "unknown";
179
+ this.logger = option.logger ?? createDefaultLogger(name);
180
+ const breadc = {
181
+ name: this.name,
182
+ version: this.version,
183
+ logger: this.logger,
184
+ options: this.options,
185
+ commands: this.commands
186
+ };
187
+ this.commands = [createVersionCommand(breadc), createHelpCommand(breadc)];
8
188
  }
9
- option(text) {
189
+ option(format, configOrDescription = "", otherConfig = {}) {
190
+ const config = otherConfig;
191
+ if (typeof configOrDescription === "string") {
192
+ config.description = configOrDescription;
193
+ }
10
194
  try {
11
- const option = new BreadcOption(text);
195
+ const option = new Option(format, config);
12
196
  this.options.push(option);
13
197
  } catch (error) {
198
+ this.logger.warn(error.message);
14
199
  }
15
200
  return this;
16
201
  }
17
- command(text) {
18
- return new Breadcommand(this, text);
202
+ command(format, config = {}) {
203
+ const command = new Command(format, { ...config, logger: this.logger });
204
+ this.commands.push(command);
205
+ return command;
19
206
  }
20
207
  parse(args) {
208
+ const allowOptions = [this.options, this.commands.map((c) => c.options)].flat();
209
+ const alias = allowOptions.reduce((map, o) => {
210
+ if (o.shortcut) {
211
+ map[o.shortcut] = o.name;
212
+ }
213
+ return map;
214
+ }, {});
21
215
  const argv = minimist(args, {
22
- string: this.options.filter((o) => o.type === "string").map((o) => o.name),
23
- boolean: this.options.filter((o) => o.type === "boolean").map((o) => o.name)
216
+ string: allowOptions.filter((o) => o.type === "string").map((o) => o.name),
217
+ boolean: allowOptions.filter((o) => o.type === "boolean").map((o) => o.name),
218
+ alias
24
219
  });
25
- return argv;
26
- }
27
- }
28
- class Breadcommand {
29
- constructor(breadc, text) {
30
- this.breadc = breadc;
31
- }
32
- }
33
- const _BreadcOption = class {
34
- constructor(text) {
35
- if (_BreadcOption.BooleanRE.test(text)) {
36
- this.type = "boolean";
37
- } else {
38
- this.type = "string";
220
+ for (const shortcut of Object.keys(alias)) {
221
+ delete argv[shortcut];
39
222
  }
40
- const match = _BreadcOption.NameRE.exec(text);
41
- if (match) {
42
- this.name = match[1];
43
- } else {
44
- throw new Error(`Can not extract option name from "${text}"`);
223
+ for (const command of this.commands) {
224
+ if (command.shouldRun(argv)) {
225
+ return command.parseArgs(argv);
226
+ }
45
227
  }
228
+ const argumentss = argv["_"];
229
+ const options = argv;
230
+ delete options["_"];
231
+ return {
232
+ command: void 0,
233
+ arguments: argumentss,
234
+ options
235
+ };
46
236
  }
47
- };
48
- let BreadcOption = _BreadcOption;
49
- BreadcOption.BooleanRE = /^--[a-zA-Z.]+$/;
50
- BreadcOption.NameRE = /--([a-zA-Z.]+)/;
237
+ async run(args) {
238
+ const parsed = this.parse(args);
239
+ if (parsed.command) {
240
+ parsed.command.run(...parsed.arguments, parsed.options);
241
+ }
242
+ }
243
+ }
51
244
 
52
245
  function breadc(name, option = {}) {
53
- return new Breadc(name, { version: option.version ?? "unknown" });
246
+ return new Breadc(name, option);
54
247
  }
55
248
 
56
249
  export { breadc as default };
package/package.json CHANGED
@@ -1,9 +1,12 @@
1
1
  {
2
2
  "name": "breadc",
3
- "version": "0.0.0",
4
- "description": "",
3
+ "version": "0.1.0",
4
+ "description": "Yet another Command Line Application Framework",
5
5
  "keywords": [
6
- "cli"
6
+ "cli",
7
+ "framework",
8
+ "command-line",
9
+ "minimist"
7
10
  ],
8
11
  "homepage": "https://github.com/yjl9903/Breadc#readme",
9
12
  "bugs": {
@@ -30,13 +33,17 @@
30
33
  "dist"
31
34
  ],
32
35
  "dependencies": {
36
+ "debug": "^4.3.4",
37
+ "kolorist": "^1.5.1",
33
38
  "minimist": "^1.2.6"
34
39
  },
35
40
  "devDependencies": {
41
+ "@types/debug": "^4.1.7",
36
42
  "@types/minimist": "^1.2.2",
37
43
  "@types/node": "^17.0.43",
38
44
  "bumpp": "^8.2.1",
39
45
  "prettier": "^2.7.1",
46
+ "tsx": "^3.4.3",
40
47
  "typescript": "^4.7.4",
41
48
  "unbuild": "^0.7.4",
42
49
  "vite": "^2.9.12",