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 +20 -4
- package/dist/index.cjs +229 -32
- package/dist/index.d.ts +99 -16
- package/dist/index.mjs +224 -31
- package/package.json +10 -3
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Breadc
|
|
2
2
|
|
|
3
|
-
[](https://github.com/yjl9903/Breadc/actions/workflows/ci.yml)
|
|
3
|
+
[](https://www.npmjs.com/package/breadc) [](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('
|
|
18
|
+
const cli = Breadc('vite', { version: '1.0.0' })
|
|
19
|
+
.option('--host <host>')
|
|
20
|
+
.option('--port <port>')
|
|
19
21
|
|
|
20
|
-
cli.
|
|
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(
|
|
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
|
|
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(
|
|
24
|
-
|
|
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:
|
|
29
|
-
boolean:
|
|
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
|
-
|
|
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
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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,
|
|
253
|
+
return new Breadc(name, option);
|
|
60
254
|
}
|
|
61
255
|
|
|
62
|
-
|
|
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
|
|
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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
|
54
|
+
interface AppOption {
|
|
20
55
|
version?: string;
|
|
56
|
+
help?: string | string[] | (() => string | string[]);
|
|
57
|
+
logger?: Logger;
|
|
21
58
|
}
|
|
22
|
-
|
|
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(
|
|
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
|
|
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(
|
|
18
|
-
|
|
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:
|
|
23
|
-
boolean:
|
|
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
|
-
|
|
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
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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,
|
|
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.
|
|
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",
|