breadc 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -9
- package/dist/index.cjs +190 -45
- package/dist/index.d.ts +42 -26
- package/dist/index.mjs +190 -45
- package/package.json +2 -3
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ npm i breadc
|
|
|
12
12
|
|
|
13
13
|
## Usage
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
Try [./examples/echo.ts](./examples/echo.ts).
|
|
16
16
|
|
|
17
17
|
```ts
|
|
18
18
|
import Breadc from 'breadc'
|
|
@@ -24,24 +24,23 @@ const cli = Breadc('echo', { version: '1.0.0' })
|
|
|
24
24
|
cli
|
|
25
25
|
.command('[message]')
|
|
26
26
|
.action((message, option) => {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
console.log(`
|
|
27
|
+
const host = option.host;
|
|
28
|
+
const port = option.port;
|
|
29
|
+
console.log(`Host: ${host}`);
|
|
30
|
+
console.log(`Port: ${port}`);
|
|
30
31
|
})
|
|
31
32
|
|
|
32
33
|
cli.run(process.argv.slice(2))
|
|
33
34
|
.catch(err => cli.logger.error(err.message))
|
|
34
35
|
```
|
|
35
36
|
|
|
36
|
-
If you are using IDEs that support TypeScript (like [Visual Studio Code](https://code.visualstudio.com/)), move your cursor to the parameter `option` in
|
|
37
|
+
If you are using IDEs that support TypeScript (like [Visual Studio Code](https://code.visualstudio.com/)), move your cursor to the parameter `option` in the default command, and then you will find the `option` is automatically typed with `{ host: string | boolean, port: string | boolean }`.
|
|
37
38
|
|
|
38
|
-

|
|
39
|
+

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