breadc 0.0.0 → 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 +47 -4
- package/dist/index.cjs +374 -32
- package/dist/index.d.ts +115 -16
- package/dist/index.mjs +369 -31
- package/package.json +10 -4
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
|
|
|
@@ -12,14 +12,57 @@ npm i breadc
|
|
|
12
12
|
|
|
13
13
|
## Usage
|
|
14
14
|
|
|
15
|
+
Try [./examples/echo.ts](./examples/echo.ts).
|
|
16
|
+
|
|
15
17
|
```ts
|
|
16
18
|
import Breadc from 'breadc'
|
|
17
19
|
|
|
18
|
-
const cli = Breadc('
|
|
20
|
+
const cli = Breadc('echo', { version: '1.0.0' })
|
|
21
|
+
.option('--host <host>')
|
|
22
|
+
.option('--port <port>')
|
|
23
|
+
|
|
24
|
+
cli
|
|
25
|
+
.command('[message]')
|
|
26
|
+
.action((message, option) => {
|
|
27
|
+
const host = option.host;
|
|
28
|
+
const port = option.port;
|
|
29
|
+
console.log(`Host: ${host}`);
|
|
30
|
+
console.log(`Port: ${port}`);
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
cli.run(process.argv.slice(2))
|
|
34
|
+
.catch(err => cli.logger.error(err.message))
|
|
35
|
+
```
|
|
36
|
+
|
|
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 }`.
|
|
38
|
+
|
|
39
|
+

|
|
40
|
+
|
|
41
|
+
### Limitation
|
|
19
42
|
|
|
20
|
-
|
|
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.
|
|
44
|
+
|
|
45
|
+
You should always use method chaining when registering options and commands. The example below will fail to infer the option `--host`.
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
const cli = Breadc('cli')
|
|
49
|
+
|
|
50
|
+
cli
|
|
51
|
+
.option('--host')
|
|
52
|
+
|
|
53
|
+
cli
|
|
54
|
+
.option('--port')
|
|
55
|
+
.command('')
|
|
56
|
+
.action((option) => {
|
|
57
|
+
// The type of option is Record<'port', string>
|
|
58
|
+
})
|
|
21
59
|
```
|
|
22
60
|
|
|
61
|
+
## Inspiration
|
|
62
|
+
|
|
63
|
+
+ [cac](https://github.com/cacjs/cac): Simple yet powerful framework for building command-line apps.
|
|
64
|
+
+ [TypeScript: Documentation - Template Literal Types](https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html)
|
|
65
|
+
|
|
23
66
|
## License
|
|
24
67
|
|
|
25
68
|
MIT License © 2021 [XLor](https://github.com/yjl9903)
|
package/dist/index.cjs
CHANGED
|
@@ -1,62 +1,404 @@
|
|
|
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, logger) {
|
|
16
|
+
if (!!logger && typeof logger === "object") {
|
|
17
|
+
return logger;
|
|
18
|
+
}
|
|
19
|
+
const debug = createDebug__default(name + ":breadc");
|
|
20
|
+
const println = !!logger && typeof logger === "function" ? logger : (message, ...args) => {
|
|
21
|
+
console.log(message, ...args);
|
|
22
|
+
};
|
|
23
|
+
return {
|
|
24
|
+
println,
|
|
25
|
+
info(message, ...args) {
|
|
26
|
+
println(`${kolorist.blue("INFO")} ${message}`, ...args);
|
|
27
|
+
},
|
|
28
|
+
warn(message, ...args) {
|
|
29
|
+
println(`${kolorist.yellow("WARN")} ${message}`, ...args);
|
|
30
|
+
},
|
|
31
|
+
error(message, ...args) {
|
|
32
|
+
println(`${kolorist.red("ERROR")} ${message}`, ...args);
|
|
33
|
+
},
|
|
34
|
+
debug(message, ...args) {
|
|
35
|
+
debug(message, ...args);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const _Option = class {
|
|
41
|
+
constructor(format, config = {}) {
|
|
42
|
+
this.format = format;
|
|
43
|
+
const match = _Option.OptionRE.exec(format);
|
|
44
|
+
if (match) {
|
|
45
|
+
if (match[3]) {
|
|
46
|
+
this.type = "string";
|
|
47
|
+
} else {
|
|
48
|
+
this.type = "boolean";
|
|
49
|
+
}
|
|
50
|
+
this.name = match[2];
|
|
51
|
+
if (match[1]) {
|
|
52
|
+
this.shortcut = match[1][1];
|
|
53
|
+
}
|
|
54
|
+
} else {
|
|
55
|
+
throw new Error(`Can not parse option format from "${format}"`);
|
|
56
|
+
}
|
|
57
|
+
this.description = config.description ?? "";
|
|
58
|
+
this.required = format.indexOf("<") !== -1;
|
|
59
|
+
this.default = config.default;
|
|
60
|
+
this.construct = config.construct ?? ((text) => text ?? config.default ?? void 0);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
let Option = _Option;
|
|
64
|
+
Option.OptionRE = /^(-[a-zA-Z], )?--([a-zA-Z.]+)( \[[a-zA-Z]+\]| <[a-zA-Z]+>)?$/;
|
|
65
|
+
|
|
66
|
+
const _Command = class {
|
|
67
|
+
constructor(format, config) {
|
|
68
|
+
this.options = [];
|
|
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] === "<";
|
|
71
|
+
this.description = config.description ?? "";
|
|
72
|
+
this.conditionFn = config.condition;
|
|
73
|
+
this.logger = config.logger;
|
|
74
|
+
if (this.format.length > _Command.MaxDep) {
|
|
75
|
+
this.logger.warn(`Command format string "${format}" is too long`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
option(format, configOrDescription = "", otherConfig = {}) {
|
|
79
|
+
const config = typeof configOrDescription === "object" ? configOrDescription : { ...otherConfig, description: configOrDescription };
|
|
80
|
+
try {
|
|
81
|
+
const option = new Option(format, config);
|
|
82
|
+
this.options.push(option);
|
|
83
|
+
} catch (error) {
|
|
84
|
+
this.logger.warn(error.message);
|
|
85
|
+
}
|
|
86
|
+
return this;
|
|
87
|
+
}
|
|
88
|
+
get hasConditionFn() {
|
|
89
|
+
return !!this.conditionFn;
|
|
90
|
+
}
|
|
91
|
+
shouldRun(args) {
|
|
92
|
+
if (this.conditionFn) {
|
|
93
|
+
return this.conditionFn(args);
|
|
94
|
+
} else {
|
|
95
|
+
if (this.default)
|
|
96
|
+
return true;
|
|
97
|
+
const isCmd = (t) => t[0] !== "[" && t[0] !== "<";
|
|
98
|
+
for (let i = 0; i < this.format.length; i++) {
|
|
99
|
+
if (!isCmd(this.format[i])) {
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
if (i >= args["_"].length || this.format[i] !== args["_"][i]) {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
parseArgs(args, globalOptions) {
|
|
110
|
+
if (this.conditionFn) {
|
|
111
|
+
const argumentss2 = args["_"];
|
|
112
|
+
const options2 = args;
|
|
113
|
+
delete options2["_"];
|
|
114
|
+
return {
|
|
115
|
+
command: this,
|
|
116
|
+
arguments: argumentss2,
|
|
117
|
+
options: args
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
const isCmd = (t) => t[0] !== "[" && t[0] !== "<";
|
|
121
|
+
const argumentss = [];
|
|
122
|
+
for (let i = 0; i < this.format.length; i++) {
|
|
123
|
+
if (isCmd(this.format[i]))
|
|
124
|
+
continue;
|
|
125
|
+
if (i < args["_"].length) {
|
|
126
|
+
if (this.format[i].startsWith("[...")) {
|
|
127
|
+
argumentss.push(args["_"].slice(i).map(String));
|
|
128
|
+
} else {
|
|
129
|
+
argumentss.push(String(args["_"][i]));
|
|
130
|
+
}
|
|
131
|
+
} else {
|
|
132
|
+
if (this.format[i].startsWith("<")) {
|
|
133
|
+
this.logger.warn(`You should provide the argument "${this.format[i]}"`);
|
|
134
|
+
argumentss.push(void 0);
|
|
135
|
+
} else if (this.format[i].startsWith("[...")) {
|
|
136
|
+
argumentss.push([]);
|
|
137
|
+
} else if (this.format[i].startsWith("[")) {
|
|
138
|
+
argumentss.push(void 0);
|
|
139
|
+
} else {
|
|
140
|
+
this.logger.warn(`unknown format string ("${this.format[i]}")`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
const fullOptions = globalOptions.concat(this.options).reduce((map, o) => {
|
|
145
|
+
map.set(o.name, o);
|
|
146
|
+
return map;
|
|
147
|
+
}, /* @__PURE__ */ new Map());
|
|
148
|
+
const options = args;
|
|
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
|
+
}
|
|
165
|
+
return {
|
|
166
|
+
command: this,
|
|
167
|
+
arguments: argumentss,
|
|
168
|
+
options
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
action(fn) {
|
|
172
|
+
this.actionFn = fn;
|
|
173
|
+
return this;
|
|
174
|
+
}
|
|
175
|
+
async run(...args) {
|
|
176
|
+
if (this.actionFn) {
|
|
177
|
+
this.actionFn(...args);
|
|
178
|
+
} else {
|
|
179
|
+
this.logger.warn(`You may miss action function in "${this.format}"`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
let Command = _Command;
|
|
184
|
+
Command.MaxDep = 5;
|
|
185
|
+
function createHelpCommand(breadc) {
|
|
186
|
+
let helpCommand = void 0;
|
|
187
|
+
return new Command("-h, --help", {
|
|
188
|
+
condition(args) {
|
|
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
|
+
}
|
|
199
|
+
return true;
|
|
200
|
+
} else {
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
logger: breadc.logger
|
|
205
|
+
}).action(() => {
|
|
206
|
+
for (const line of breadc.help(helpCommand)) {
|
|
207
|
+
breadc.logger.println(line);
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
function createVersionCommand(breadc) {
|
|
212
|
+
return new Command("-v, --version", {
|
|
213
|
+
condition(args) {
|
|
214
|
+
const isEmpty = !args["_"].length && !args["--"]?.length;
|
|
215
|
+
if (args.version && isEmpty) {
|
|
216
|
+
return true;
|
|
217
|
+
} else if (args.v && isEmpty) {
|
|
218
|
+
return true;
|
|
219
|
+
} else {
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
logger: breadc.logger
|
|
224
|
+
}).action(() => {
|
|
225
|
+
breadc.logger.println(breadc.version());
|
|
226
|
+
});
|
|
227
|
+
}
|
|
8
228
|
|
|
9
229
|
class Breadc {
|
|
10
230
|
constructor(name, option) {
|
|
11
231
|
this.options = [];
|
|
232
|
+
this.commands = [];
|
|
12
233
|
this.name = name;
|
|
13
|
-
this.
|
|
234
|
+
this._version = option.version ?? "unknown";
|
|
235
|
+
this.description = option.description;
|
|
236
|
+
this.logger = createDefaultLogger(name, option.logger);
|
|
237
|
+
const breadc = {
|
|
238
|
+
name: this.name,
|
|
239
|
+
version: () => this.version.call(this),
|
|
240
|
+
help: (command) => this.help.call(this, command),
|
|
241
|
+
logger: this.logger,
|
|
242
|
+
options: this.options,
|
|
243
|
+
commands: this.commands
|
|
244
|
+
};
|
|
245
|
+
this.commands.push(createVersionCommand(breadc), createHelpCommand(breadc));
|
|
246
|
+
}
|
|
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);
|
|
300
|
+
}
|
|
301
|
+
println(``);
|
|
302
|
+
return output;
|
|
14
303
|
}
|
|
15
|
-
option(
|
|
304
|
+
option(format, configOrDescription = "", otherConfig = {}) {
|
|
305
|
+
const config = typeof configOrDescription === "object" ? configOrDescription : { ...otherConfig, description: configOrDescription };
|
|
16
306
|
try {
|
|
17
|
-
const option = new
|
|
307
|
+
const option = new Option(format, config);
|
|
18
308
|
this.options.push(option);
|
|
19
309
|
} catch (error) {
|
|
310
|
+
this.logger.warn(error.message);
|
|
20
311
|
}
|
|
21
312
|
return this;
|
|
22
313
|
}
|
|
23
|
-
command(
|
|
24
|
-
|
|
314
|
+
command(format, configOrDescription = "", otherConfig = {}) {
|
|
315
|
+
const config = typeof configOrDescription === "object" ? configOrDescription : { ...otherConfig, description: configOrDescription };
|
|
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
|
+
}
|
|
323
|
+
this.commands.push(command);
|
|
324
|
+
return command;
|
|
25
325
|
}
|
|
26
326
|
parse(args) {
|
|
327
|
+
const allowOptions = [
|
|
328
|
+
...this.options,
|
|
329
|
+
...this.commands.flatMap((c) => c.options)
|
|
330
|
+
];
|
|
331
|
+
const alias = allowOptions.reduce((map, o) => {
|
|
332
|
+
if (o.shortcut) {
|
|
333
|
+
map[o.shortcut] = o.name;
|
|
334
|
+
}
|
|
335
|
+
return map;
|
|
336
|
+
}, {});
|
|
337
|
+
const defaults = allowOptions.reduce((map, o) => {
|
|
338
|
+
if (o.default) {
|
|
339
|
+
map[o.name] = o.default;
|
|
340
|
+
}
|
|
341
|
+
return map;
|
|
342
|
+
}, {});
|
|
27
343
|
const argv = minimist__default(args, {
|
|
28
|
-
string:
|
|
29
|
-
boolean:
|
|
344
|
+
string: allowOptions.filter((o) => o.type === "string").map((o) => o.name),
|
|
345
|
+
boolean: allowOptions.filter((o) => o.type === "boolean").map((o) => o.name),
|
|
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
|
+
}
|
|
30
360
|
});
|
|
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";
|
|
361
|
+
for (const shortcut of Object.keys(alias)) {
|
|
362
|
+
delete argv[shortcut];
|
|
45
363
|
}
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
throw new Error(`Can not extract option name from "${text}"`);
|
|
364
|
+
for (const command of this.commands) {
|
|
365
|
+
if (!command.default && command.shouldRun(argv)) {
|
|
366
|
+
return command.parseArgs(argv, this.options);
|
|
367
|
+
}
|
|
51
368
|
}
|
|
369
|
+
if (this.defaultCommand) {
|
|
370
|
+
return this.defaultCommand.parseArgs(argv, this.options);
|
|
371
|
+
}
|
|
372
|
+
const argumentss = argv["_"];
|
|
373
|
+
const options = argv;
|
|
374
|
+
delete options["_"];
|
|
375
|
+
return {
|
|
376
|
+
command: void 0,
|
|
377
|
+
arguments: argumentss,
|
|
378
|
+
options
|
|
379
|
+
};
|
|
52
380
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
381
|
+
async run(args) {
|
|
382
|
+
const parsed = this.parse(args);
|
|
383
|
+
if (parsed.command) {
|
|
384
|
+
parsed.command.run(...parsed.arguments, parsed.options);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
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
|
+
}
|
|
57
396
|
|
|
58
397
|
function breadc(name, option = {}) {
|
|
59
|
-
return new Breadc(name,
|
|
398
|
+
return new Breadc(name, option);
|
|
60
399
|
}
|
|
61
400
|
|
|
62
|
-
|
|
401
|
+
exports.kolorist = kolorist__default;
|
|
402
|
+
exports.minimist = minimist__default;
|
|
403
|
+
exports.createDebug = createDebug__default;
|
|
404
|
+
exports["default"] = breadc;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,24 +1,123 @@
|
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
6
|
+
interface OptionConfig<T = string> {
|
|
7
|
+
description?: string;
|
|
8
|
+
default?: T;
|
|
9
|
+
construct?: (rawText?: string) => T;
|
|
10
|
+
}
|
|
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, F = string> {
|
|
21
|
+
private static OptionRE;
|
|
22
|
+
readonly name: string;
|
|
23
|
+
readonly shortcut?: string;
|
|
24
|
+
readonly default?: F;
|
|
25
|
+
readonly format: string;
|
|
26
|
+
readonly description: string;
|
|
27
|
+
readonly type: 'string' | 'boolean';
|
|
28
|
+
readonly required: boolean;
|
|
29
|
+
readonly construct: (rawText: string | undefined) => any;
|
|
30
|
+
constructor(format: T, config?: OptionConfig<F>);
|
|
13
31
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
32
|
+
|
|
33
|
+
declare type ConditionFn = (args: ParsedArgs) => boolean;
|
|
34
|
+
interface CommandConfig {
|
|
35
|
+
description?: string;
|
|
36
|
+
}
|
|
37
|
+
declare class Command<F extends string = string, CommandOption extends object = {}> {
|
|
38
|
+
private static MaxDep;
|
|
39
|
+
private readonly conditionFn?;
|
|
40
|
+
private readonly logger;
|
|
41
|
+
readonly format: string[];
|
|
42
|
+
readonly default: boolean;
|
|
43
|
+
readonly description: string;
|
|
44
|
+
readonly options: Option[];
|
|
45
|
+
private actionFn?;
|
|
46
|
+
constructor(format: F, config: CommandConfig & {
|
|
47
|
+
condition?: ConditionFn;
|
|
48
|
+
logger: Logger;
|
|
49
|
+
});
|
|
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;
|
|
53
|
+
shouldRun(args: ParsedArgs): boolean;
|
|
54
|
+
parseArgs(args: ParsedArgs, globalOptions: Option[]): ParseResult;
|
|
55
|
+
action(fn: ActionFn<ExtractCommand<F>, CommandOption>): this;
|
|
56
|
+
run(...args: any[]): Promise<void>;
|
|
17
57
|
}
|
|
18
58
|
|
|
19
|
-
interface
|
|
59
|
+
interface AppOption {
|
|
20
60
|
version?: string;
|
|
61
|
+
description?: string | string[];
|
|
62
|
+
help?: string | string[] | (() => string | string[]);
|
|
63
|
+
logger?: Logger | LoggerFn;
|
|
21
64
|
}
|
|
22
|
-
declare
|
|
65
|
+
declare type LoggerFn = (message: string, ...args: any[]) => void;
|
|
66
|
+
interface Logger {
|
|
67
|
+
println: LoggerFn;
|
|
68
|
+
info: LoggerFn;
|
|
69
|
+
warn: LoggerFn;
|
|
70
|
+
error: LoggerFn;
|
|
71
|
+
debug: LoggerFn;
|
|
72
|
+
}
|
|
73
|
+
interface ParseResult {
|
|
74
|
+
command: Command | undefined;
|
|
75
|
+
arguments: any[];
|
|
76
|
+
options: Record<string, string>;
|
|
77
|
+
}
|
|
78
|
+
declare type ExtractOption<T extends string> = {
|
|
79
|
+
[k in ExtractOptionName<T>]: ExtractOptionType<T>;
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* Extract option name type
|
|
83
|
+
*
|
|
84
|
+
* Examples:
|
|
85
|
+
* + const t1: ExtractOption<'--option' | '--hello'> = 'hello'
|
|
86
|
+
* + const t2: ExtractOption<'-r, --root'> = 'root'
|
|
87
|
+
*/
|
|
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;
|
|
93
|
+
declare type Push<T extends any[], U> = [...T, U];
|
|
94
|
+
declare type ActionFn<T extends any[], Option extends object = {}> = (...arg: Push<T, Option>) => void;
|
|
95
|
+
/**
|
|
96
|
+
* Max Dep: 5
|
|
97
|
+
*
|
|
98
|
+
* Generated by: npx tsx scripts/genType.ts 5
|
|
99
|
+
*/
|
|
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;
|
|
101
|
+
|
|
102
|
+
declare class Breadc<GlobalOption extends object = {}> {
|
|
103
|
+
private readonly name;
|
|
104
|
+
private readonly _version;
|
|
105
|
+
private readonly description?;
|
|
106
|
+
private readonly options;
|
|
107
|
+
private readonly commands;
|
|
108
|
+
private defaultCommand?;
|
|
109
|
+
readonly logger: Logger;
|
|
110
|
+
constructor(name: string, option: AppOption);
|
|
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>;
|
|
116
|
+
command<F extends string>(format: F, config?: CommandConfig): Command<F, GlobalOption>;
|
|
117
|
+
parse(args: string[]): ParseResult;
|
|
118
|
+
run(args: string[]): Promise<void>;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
declare function breadc(name: string, option?: AppOption): Breadc<{}>;
|
|
23
122
|
|
|
24
123
|
export { breadc as default };
|
package/dist/index.mjs
CHANGED
|
@@ -1,56 +1,394 @@
|
|
|
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, logger) {
|
|
9
|
+
if (!!logger && typeof logger === "object") {
|
|
10
|
+
return logger;
|
|
11
|
+
}
|
|
12
|
+
const debug = createDebug(name + ":breadc");
|
|
13
|
+
const println = !!logger && typeof logger === "function" ? logger : (message, ...args) => {
|
|
14
|
+
console.log(message, ...args);
|
|
15
|
+
};
|
|
16
|
+
return {
|
|
17
|
+
println,
|
|
18
|
+
info(message, ...args) {
|
|
19
|
+
println(`${blue("INFO")} ${message}`, ...args);
|
|
20
|
+
},
|
|
21
|
+
warn(message, ...args) {
|
|
22
|
+
println(`${yellow("WARN")} ${message}`, ...args);
|
|
23
|
+
},
|
|
24
|
+
error(message, ...args) {
|
|
25
|
+
println(`${red("ERROR")} ${message}`, ...args);
|
|
26
|
+
},
|
|
27
|
+
debug(message, ...args) {
|
|
28
|
+
debug(message, ...args);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const _Option = class {
|
|
34
|
+
constructor(format, config = {}) {
|
|
35
|
+
this.format = format;
|
|
36
|
+
const match = _Option.OptionRE.exec(format);
|
|
37
|
+
if (match) {
|
|
38
|
+
if (match[3]) {
|
|
39
|
+
this.type = "string";
|
|
40
|
+
} else {
|
|
41
|
+
this.type = "boolean";
|
|
42
|
+
}
|
|
43
|
+
this.name = match[2];
|
|
44
|
+
if (match[1]) {
|
|
45
|
+
this.shortcut = match[1][1];
|
|
46
|
+
}
|
|
47
|
+
} else {
|
|
48
|
+
throw new Error(`Can not parse option format from "${format}"`);
|
|
49
|
+
}
|
|
50
|
+
this.description = config.description ?? "";
|
|
51
|
+
this.required = format.indexOf("<") !== -1;
|
|
52
|
+
this.default = config.default;
|
|
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
|
+
const _Command = class {
|
|
60
|
+
constructor(format, config) {
|
|
61
|
+
this.options = [];
|
|
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] === "<";
|
|
64
|
+
this.description = config.description ?? "";
|
|
65
|
+
this.conditionFn = config.condition;
|
|
66
|
+
this.logger = config.logger;
|
|
67
|
+
if (this.format.length > _Command.MaxDep) {
|
|
68
|
+
this.logger.warn(`Command format string "${format}" is too long`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
option(format, configOrDescription = "", otherConfig = {}) {
|
|
72
|
+
const config = typeof configOrDescription === "object" ? configOrDescription : { ...otherConfig, description: configOrDescription };
|
|
73
|
+
try {
|
|
74
|
+
const option = new Option(format, config);
|
|
75
|
+
this.options.push(option);
|
|
76
|
+
} catch (error) {
|
|
77
|
+
this.logger.warn(error.message);
|
|
78
|
+
}
|
|
79
|
+
return this;
|
|
80
|
+
}
|
|
81
|
+
get hasConditionFn() {
|
|
82
|
+
return !!this.conditionFn;
|
|
83
|
+
}
|
|
84
|
+
shouldRun(args) {
|
|
85
|
+
if (this.conditionFn) {
|
|
86
|
+
return this.conditionFn(args);
|
|
87
|
+
} else {
|
|
88
|
+
if (this.default)
|
|
89
|
+
return true;
|
|
90
|
+
const isCmd = (t) => t[0] !== "[" && t[0] !== "<";
|
|
91
|
+
for (let i = 0; i < this.format.length; i++) {
|
|
92
|
+
if (!isCmd(this.format[i])) {
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
if (i >= args["_"].length || this.format[i] !== args["_"][i]) {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
parseArgs(args, globalOptions) {
|
|
103
|
+
if (this.conditionFn) {
|
|
104
|
+
const argumentss2 = args["_"];
|
|
105
|
+
const options2 = args;
|
|
106
|
+
delete options2["_"];
|
|
107
|
+
return {
|
|
108
|
+
command: this,
|
|
109
|
+
arguments: argumentss2,
|
|
110
|
+
options: args
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
const isCmd = (t) => t[0] !== "[" && t[0] !== "<";
|
|
114
|
+
const argumentss = [];
|
|
115
|
+
for (let i = 0; i < this.format.length; i++) {
|
|
116
|
+
if (isCmd(this.format[i]))
|
|
117
|
+
continue;
|
|
118
|
+
if (i < args["_"].length) {
|
|
119
|
+
if (this.format[i].startsWith("[...")) {
|
|
120
|
+
argumentss.push(args["_"].slice(i).map(String));
|
|
121
|
+
} else {
|
|
122
|
+
argumentss.push(String(args["_"][i]));
|
|
123
|
+
}
|
|
124
|
+
} else {
|
|
125
|
+
if (this.format[i].startsWith("<")) {
|
|
126
|
+
this.logger.warn(`You should provide the argument "${this.format[i]}"`);
|
|
127
|
+
argumentss.push(void 0);
|
|
128
|
+
} else if (this.format[i].startsWith("[...")) {
|
|
129
|
+
argumentss.push([]);
|
|
130
|
+
} else if (this.format[i].startsWith("[")) {
|
|
131
|
+
argumentss.push(void 0);
|
|
132
|
+
} else {
|
|
133
|
+
this.logger.warn(`unknown format string ("${this.format[i]}")`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
const fullOptions = globalOptions.concat(this.options).reduce((map, o) => {
|
|
138
|
+
map.set(o.name, o);
|
|
139
|
+
return map;
|
|
140
|
+
}, /* @__PURE__ */ new Map());
|
|
141
|
+
const options = args;
|
|
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
|
+
}
|
|
158
|
+
return {
|
|
159
|
+
command: this,
|
|
160
|
+
arguments: argumentss,
|
|
161
|
+
options
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
action(fn) {
|
|
165
|
+
this.actionFn = fn;
|
|
166
|
+
return this;
|
|
167
|
+
}
|
|
168
|
+
async run(...args) {
|
|
169
|
+
if (this.actionFn) {
|
|
170
|
+
this.actionFn(...args);
|
|
171
|
+
} else {
|
|
172
|
+
this.logger.warn(`You may miss action function in "${this.format}"`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
let Command = _Command;
|
|
177
|
+
Command.MaxDep = 5;
|
|
178
|
+
function createHelpCommand(breadc) {
|
|
179
|
+
let helpCommand = void 0;
|
|
180
|
+
return new Command("-h, --help", {
|
|
181
|
+
condition(args) {
|
|
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
|
+
}
|
|
192
|
+
return true;
|
|
193
|
+
} else {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
logger: breadc.logger
|
|
198
|
+
}).action(() => {
|
|
199
|
+
for (const line of breadc.help(helpCommand)) {
|
|
200
|
+
breadc.logger.println(line);
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
function createVersionCommand(breadc) {
|
|
205
|
+
return new Command("-v, --version", {
|
|
206
|
+
condition(args) {
|
|
207
|
+
const isEmpty = !args["_"].length && !args["--"]?.length;
|
|
208
|
+
if (args.version && isEmpty) {
|
|
209
|
+
return true;
|
|
210
|
+
} else if (args.v && isEmpty) {
|
|
211
|
+
return true;
|
|
212
|
+
} else {
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
logger: breadc.logger
|
|
217
|
+
}).action(() => {
|
|
218
|
+
breadc.logger.println(breadc.version());
|
|
219
|
+
});
|
|
220
|
+
}
|
|
2
221
|
|
|
3
222
|
class Breadc {
|
|
4
223
|
constructor(name, option) {
|
|
5
224
|
this.options = [];
|
|
225
|
+
this.commands = [];
|
|
6
226
|
this.name = name;
|
|
7
|
-
this.
|
|
227
|
+
this._version = option.version ?? "unknown";
|
|
228
|
+
this.description = option.description;
|
|
229
|
+
this.logger = createDefaultLogger(name, option.logger);
|
|
230
|
+
const breadc = {
|
|
231
|
+
name: this.name,
|
|
232
|
+
version: () => this.version.call(this),
|
|
233
|
+
help: (command) => this.help.call(this, command),
|
|
234
|
+
logger: this.logger,
|
|
235
|
+
options: this.options,
|
|
236
|
+
commands: this.commands
|
|
237
|
+
};
|
|
238
|
+
this.commands.push(createVersionCommand(breadc), createHelpCommand(breadc));
|
|
239
|
+
}
|
|
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);
|
|
293
|
+
}
|
|
294
|
+
println(``);
|
|
295
|
+
return output;
|
|
8
296
|
}
|
|
9
|
-
option(
|
|
297
|
+
option(format, configOrDescription = "", otherConfig = {}) {
|
|
298
|
+
const config = typeof configOrDescription === "object" ? configOrDescription : { ...otherConfig, description: configOrDescription };
|
|
10
299
|
try {
|
|
11
|
-
const option = new
|
|
300
|
+
const option = new Option(format, config);
|
|
12
301
|
this.options.push(option);
|
|
13
302
|
} catch (error) {
|
|
303
|
+
this.logger.warn(error.message);
|
|
14
304
|
}
|
|
15
305
|
return this;
|
|
16
306
|
}
|
|
17
|
-
command(
|
|
18
|
-
|
|
307
|
+
command(format, configOrDescription = "", otherConfig = {}) {
|
|
308
|
+
const config = typeof configOrDescription === "object" ? configOrDescription : { ...otherConfig, description: configOrDescription };
|
|
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
|
+
}
|
|
316
|
+
this.commands.push(command);
|
|
317
|
+
return command;
|
|
19
318
|
}
|
|
20
319
|
parse(args) {
|
|
320
|
+
const allowOptions = [
|
|
321
|
+
...this.options,
|
|
322
|
+
...this.commands.flatMap((c) => c.options)
|
|
323
|
+
];
|
|
324
|
+
const alias = allowOptions.reduce((map, o) => {
|
|
325
|
+
if (o.shortcut) {
|
|
326
|
+
map[o.shortcut] = o.name;
|
|
327
|
+
}
|
|
328
|
+
return map;
|
|
329
|
+
}, {});
|
|
330
|
+
const defaults = allowOptions.reduce((map, o) => {
|
|
331
|
+
if (o.default) {
|
|
332
|
+
map[o.name] = o.default;
|
|
333
|
+
}
|
|
334
|
+
return map;
|
|
335
|
+
}, {});
|
|
21
336
|
const argv = minimist(args, {
|
|
22
|
-
string:
|
|
23
|
-
boolean:
|
|
337
|
+
string: allowOptions.filter((o) => o.type === "string").map((o) => o.name),
|
|
338
|
+
boolean: allowOptions.filter((o) => o.type === "boolean").map((o) => o.name),
|
|
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
|
+
}
|
|
24
353
|
});
|
|
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";
|
|
354
|
+
for (const shortcut of Object.keys(alias)) {
|
|
355
|
+
delete argv[shortcut];
|
|
39
356
|
}
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
throw new Error(`Can not extract option name from "${text}"`);
|
|
357
|
+
for (const command of this.commands) {
|
|
358
|
+
if (!command.default && command.shouldRun(argv)) {
|
|
359
|
+
return command.parseArgs(argv, this.options);
|
|
360
|
+
}
|
|
45
361
|
}
|
|
362
|
+
if (this.defaultCommand) {
|
|
363
|
+
return this.defaultCommand.parseArgs(argv, this.options);
|
|
364
|
+
}
|
|
365
|
+
const argumentss = argv["_"];
|
|
366
|
+
const options = argv;
|
|
367
|
+
delete options["_"];
|
|
368
|
+
return {
|
|
369
|
+
command: void 0,
|
|
370
|
+
arguments: argumentss,
|
|
371
|
+
options
|
|
372
|
+
};
|
|
46
373
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
374
|
+
async run(args) {
|
|
375
|
+
const parsed = this.parse(args);
|
|
376
|
+
if (parsed.command) {
|
|
377
|
+
parsed.command.run(...parsed.arguments, parsed.options);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
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
|
+
}
|
|
51
389
|
|
|
52
390
|
function breadc(name, option = {}) {
|
|
53
|
-
return new Breadc(name,
|
|
391
|
+
return new Breadc(name, option);
|
|
54
392
|
}
|
|
55
393
|
|
|
56
394
|
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.2.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,9 +33,12 @@
|
|
|
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",
|
|
@@ -45,7 +51,7 @@
|
|
|
45
51
|
"packageManager": "pnpm@7.3.0",
|
|
46
52
|
"scripts": {
|
|
47
53
|
"build": "unbuild",
|
|
48
|
-
"format": "prettier --write src/**/*.ts",
|
|
54
|
+
"format": "prettier --write src/**/*.ts test/*.ts examples/*.ts scripts/*.ts",
|
|
49
55
|
"release": "bumpp --commit --push --tag && pnpm publish",
|
|
50
56
|
"test": "vitest",
|
|
51
57
|
"typecheck": "tsc --noEmit",
|