bob-core 0.9.0 → 0.9.1
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 +6 -6
- package/dist/Cli.d.ts +6 -2
- package/dist/Cli.js +23 -6
- package/dist/Command.d.ts +4 -2
- package/dist/Command.js +19 -1
- package/dist/Command.test.d.ts +1 -0
- package/dist/Command.test.js +52 -0
- package/dist/CommandHelper.d.ts +0 -2
- package/dist/CommandHelper.js +2 -13
- package/dist/CommandParser.d.ts +11 -4
- package/dist/CommandParser.js +116 -57
- package/dist/CommandParser.test.d.ts +1 -0
- package/dist/CommandParser.test.js +144 -0
- package/dist/errors/InvalidOption.d.ts +8 -0
- package/dist/errors/InvalidOption.js +32 -0
- package/dist/errors/MissingSignatureArgument.d.ts +8 -0
- package/dist/errors/MissingSignatureArgument.js +31 -0
- package/dist/errors/MissingSignatureOption.d.ts +8 -0
- package/dist/errors/MissingSignatureOption.js +31 -0
- package/package.json +7 -3
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ import { CLI } from 'bob-core';
|
|
|
17
17
|
|
|
18
18
|
const cli = new CLI();
|
|
19
19
|
|
|
20
|
-
await cli.
|
|
20
|
+
await cli.withCommands('./commands');
|
|
21
21
|
|
|
22
22
|
cli.run('commandName', 'arg1', 'arg2', 'arg3');
|
|
23
23
|
```
|
|
@@ -108,11 +108,11 @@ Arguments:
|
|
|
108
108
|
test2 [default: []] (variadic)
|
|
109
109
|
|
|
110
110
|
Options:
|
|
111
|
-
--option, -o, -b option description (boolean) [default: false]
|
|
112
|
-
--flag flag description (string) [default: null]
|
|
113
|
-
--arr arr description (array) [default: null]
|
|
114
|
-
--flag2 flag2 description (string) [default: 2]
|
|
115
|
-
--help, -h Display help for the given command. When no command is given display help for the list command (boolean)
|
|
111
|
+
--option, -o, -b option description (boolean) [default: false]
|
|
112
|
+
--flag flag description (string) [default: null]
|
|
113
|
+
--arr arr description (array) [default: null]
|
|
114
|
+
--flag2 flag2 description (string) [default: 2]
|
|
115
|
+
--help, -h Display help for the given command. When no command is given display help for the list command (boolean)
|
|
116
116
|
|
|
117
117
|
Examples:
|
|
118
118
|
Example description 1
|
package/dist/Cli.d.ts
CHANGED
|
@@ -6,12 +6,16 @@ export declare class Cli<C> {
|
|
|
6
6
|
readonly commandRegistry: CommandRegistry;
|
|
7
7
|
private readonly exceptionHandler;
|
|
8
8
|
private readonly ctx?;
|
|
9
|
+
private readonly helpCommand;
|
|
9
10
|
get CommandRegistryClass(): typeof CommandRegistry;
|
|
10
11
|
get HelpCommandClass(): typeof HelpCommand;
|
|
11
12
|
get ExceptionHandlerClass(): typeof ExceptionHandler;
|
|
12
13
|
constructor(ctx?: C);
|
|
13
14
|
setCommandResolver(resolver: (path: string) => Promise<Command>): void;
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
withCommands(...commands: Array<Command | {
|
|
16
|
+
new (): Command;
|
|
17
|
+
} | string>): Promise<void>;
|
|
16
18
|
runCommand(command: string, ...args: any[]): Promise<number>;
|
|
19
|
+
runHelpCommand(): Promise<number>;
|
|
20
|
+
protected registerCommand(command: Command): void;
|
|
17
21
|
}
|
package/dist/Cli.js
CHANGED
|
@@ -11,6 +11,7 @@ class Cli {
|
|
|
11
11
|
commandRegistry;
|
|
12
12
|
exceptionHandler;
|
|
13
13
|
ctx;
|
|
14
|
+
helpCommand;
|
|
14
15
|
get CommandRegistryClass() {
|
|
15
16
|
return CommandRegistry_1.CommandRegistry;
|
|
16
17
|
}
|
|
@@ -24,20 +25,36 @@ class Cli {
|
|
|
24
25
|
this.ctx = ctx;
|
|
25
26
|
this.commandRegistry = new this.CommandRegistryClass();
|
|
26
27
|
this.exceptionHandler = new this.ExceptionHandlerClass();
|
|
27
|
-
this.
|
|
28
|
+
this.helpCommand = new this.HelpCommandClass(this.commandRegistry);
|
|
29
|
+
this.registerCommand(this.helpCommand);
|
|
28
30
|
}
|
|
29
31
|
setCommandResolver(resolver) {
|
|
30
32
|
this.commandRegistry.setCommandResolver(resolver);
|
|
31
33
|
}
|
|
32
|
-
async
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
async withCommands(...commands) {
|
|
35
|
+
for (const command of commands) {
|
|
36
|
+
if (typeof command === 'string') {
|
|
37
|
+
await this.commandRegistry.loadCommandsPath(command);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
if (typeof command === 'function') {
|
|
41
|
+
this.registerCommand(new command());
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
this.registerCommand(command);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
37
48
|
}
|
|
38
49
|
async runCommand(command, ...args) {
|
|
39
50
|
return await this.commandRegistry.runCommand(this.ctx, command, ...args)
|
|
40
51
|
.catch(this.exceptionHandler.handle);
|
|
41
52
|
}
|
|
53
|
+
async runHelpCommand() {
|
|
54
|
+
return await this.runCommand(this.helpCommand.command);
|
|
55
|
+
}
|
|
56
|
+
registerCommand(command) {
|
|
57
|
+
this.commandRegistry.registerCommand(command);
|
|
58
|
+
}
|
|
42
59
|
}
|
|
43
60
|
exports.Cli = Cli;
|
package/dist/Command.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CommandHelper } from "./CommandHelper";
|
|
2
|
-
import { CommandParser } from "./CommandParser";
|
|
2
|
+
import { ArgSignature, CommandParser } from "./CommandParser";
|
|
3
3
|
export type CommandExample = {
|
|
4
4
|
description: string;
|
|
5
5
|
command: string;
|
|
@@ -13,8 +13,10 @@ export declare abstract class Command<C = undefined> extends CommandHelper {
|
|
|
13
13
|
};
|
|
14
14
|
protected commandsExamples: CommandExample[];
|
|
15
15
|
protected parser: CommandParser;
|
|
16
|
-
get command(): string;
|
|
17
16
|
protected abstract handle(): Promise<void | number>;
|
|
17
|
+
private get CommandParserClass();
|
|
18
|
+
protected get defaultOptions(): ArgSignature[];
|
|
19
|
+
get command(): string;
|
|
18
20
|
run(ctx: C, ...args: any[]): Promise<number>;
|
|
19
21
|
protected setOption(name: string, value: any): void;
|
|
20
22
|
protected setArgument(name: string, value: any): void;
|
package/dist/Command.js
CHANGED
|
@@ -1,13 +1,31 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.Command = void 0;
|
|
4
7
|
const CommandHelper_1 = require("./CommandHelper");
|
|
5
8
|
const CommandParser_1 = require("./CommandParser");
|
|
9
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
6
10
|
class Command extends CommandHelper_1.CommandHelper {
|
|
7
11
|
ctx;
|
|
8
12
|
helperDefinitions = {};
|
|
9
13
|
commandsExamples = [];
|
|
10
14
|
parser;
|
|
15
|
+
get CommandParserClass() {
|
|
16
|
+
return CommandParser_1.CommandParser;
|
|
17
|
+
}
|
|
18
|
+
get defaultOptions() {
|
|
19
|
+
return [
|
|
20
|
+
{
|
|
21
|
+
name: 'help',
|
|
22
|
+
optional: true,
|
|
23
|
+
type: 'boolean',
|
|
24
|
+
help: (0, chalk_1.default) `Display help for the given command. When no command is given display help for the {green list} command`,
|
|
25
|
+
alias: ['h']
|
|
26
|
+
}
|
|
27
|
+
];
|
|
28
|
+
}
|
|
11
29
|
get command() {
|
|
12
30
|
if (this.parser) {
|
|
13
31
|
return this.parser.command;
|
|
@@ -16,7 +34,7 @@ class Command extends CommandHelper_1.CommandHelper {
|
|
|
16
34
|
}
|
|
17
35
|
async run(ctx, ...args) {
|
|
18
36
|
this.ctx = ctx;
|
|
19
|
-
this.parser = new
|
|
37
|
+
this.parser = new this.CommandParserClass(this.signature, this.helperDefinitions, this.defaultOptions, ...args);
|
|
20
38
|
if (args.includes('--help') || args.includes('-h')) {
|
|
21
39
|
return this.help.call(this);
|
|
22
40
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const Command_1 = require("./Command");
|
|
4
|
+
const MissingRequiredArgumentValue_1 = require("./errors/MissingRequiredArgumentValue");
|
|
5
|
+
class MockCommand extends Command_1.Command {
|
|
6
|
+
signature = 'mockCommand {argument} {--option}';
|
|
7
|
+
description = 'This is a mock command for testing';
|
|
8
|
+
handle() {
|
|
9
|
+
const opts = this.option('option');
|
|
10
|
+
const arg = this.argument('argument');
|
|
11
|
+
if (opts) {
|
|
12
|
+
return Promise.resolve(11);
|
|
13
|
+
}
|
|
14
|
+
if (arg === 'value') {
|
|
15
|
+
return Promise.resolve(1);
|
|
16
|
+
}
|
|
17
|
+
else if (arg) {
|
|
18
|
+
return Promise.resolve(-1);
|
|
19
|
+
}
|
|
20
|
+
return Promise.resolve(0);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
describe('Command', () => {
|
|
24
|
+
let command;
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
command = new MockCommand();
|
|
27
|
+
});
|
|
28
|
+
it('should have a command', () => {
|
|
29
|
+
expect(command.command).toBe('mockCommand');
|
|
30
|
+
});
|
|
31
|
+
it('should have a signature', () => {
|
|
32
|
+
expect(command.signature).toBe('mockCommand {argument} {--option}');
|
|
33
|
+
});
|
|
34
|
+
it('should have a description', () => {
|
|
35
|
+
expect(command.description).toBe('This is a mock command for testing');
|
|
36
|
+
});
|
|
37
|
+
it('should handle command with argument', async () => {
|
|
38
|
+
const result = await command.run(undefined, 'value');
|
|
39
|
+
expect(result).toBe(1);
|
|
40
|
+
});
|
|
41
|
+
it('should handle command with argument', async () => {
|
|
42
|
+
const result = await command.run(undefined, 'badValue');
|
|
43
|
+
expect(result).toBe(-1);
|
|
44
|
+
});
|
|
45
|
+
it('should throw error if argument is missing', async () => {
|
|
46
|
+
await expect(command.run(undefined)).rejects.toThrowError(MissingRequiredArgumentValue_1.MissingRequiredArgumentValue);
|
|
47
|
+
});
|
|
48
|
+
it('should handle command with option', async () => {
|
|
49
|
+
const result = await command.run(undefined, 'value', '--option');
|
|
50
|
+
expect(result).toBe(11);
|
|
51
|
+
});
|
|
52
|
+
});
|
package/dist/CommandHelper.d.ts
CHANGED
package/dist/CommandHelper.js
CHANGED
|
@@ -8,21 +8,10 @@ const chalk_1 = __importDefault(require("chalk"));
|
|
|
8
8
|
const lodash_1 = require("lodash");
|
|
9
9
|
const string_1 = require("./lib/string");
|
|
10
10
|
class CommandHelper {
|
|
11
|
-
get defaultOptions() {
|
|
12
|
-
return [
|
|
13
|
-
{
|
|
14
|
-
name: 'help',
|
|
15
|
-
optional: true,
|
|
16
|
-
type: 'boolean',
|
|
17
|
-
help: (0, chalk_1.default) `Display help for the given command. When no command is given display help for the {green list} command`,
|
|
18
|
-
alias: ['h']
|
|
19
|
-
}
|
|
20
|
-
];
|
|
21
|
-
}
|
|
22
11
|
help() {
|
|
23
12
|
const log = console.log;
|
|
24
|
-
const availableArguments = Object.values(this.parser.
|
|
25
|
-
const availableOptions =
|
|
13
|
+
const availableArguments = Object.values(this.parser.getArgumentSignatures());
|
|
14
|
+
const availableOptions = Object.values(this.parser.getOptionSignatures())
|
|
26
15
|
.map((signature) => ({
|
|
27
16
|
...signature,
|
|
28
17
|
optionWithAlias: `--${signature.name}${signature.alias?.map(a => `, -${a}`).join('') ?? ''}`
|
package/dist/CommandParser.d.ts
CHANGED
|
@@ -13,24 +13,31 @@ export declare class CommandParser {
|
|
|
13
13
|
protected readonly helperDefinitions: {
|
|
14
14
|
[key: string]: string;
|
|
15
15
|
};
|
|
16
|
+
protected readonly defaultOptions: ArgSignature[];
|
|
16
17
|
command: string;
|
|
17
18
|
private arguments;
|
|
18
19
|
private options;
|
|
19
20
|
private argumentsSignature;
|
|
20
|
-
private
|
|
21
|
+
private optionSignatures;
|
|
22
|
+
private optionAliases;
|
|
21
23
|
option(name: string): any;
|
|
22
24
|
setOption(name: string, value: any): void;
|
|
25
|
+
optionHelp(name: string): string | undefined;
|
|
23
26
|
argument(name: string): any;
|
|
24
27
|
setArgument(name: string, value: any): void;
|
|
25
|
-
|
|
28
|
+
argumentHelp(name: string): string | undefined;
|
|
29
|
+
getArgumentSignatures(): {
|
|
26
30
|
[argument: string]: ArgSignature;
|
|
27
31
|
};
|
|
28
|
-
|
|
32
|
+
getOptionSignatures(): {
|
|
29
33
|
[option: string]: ArgSignature;
|
|
30
34
|
};
|
|
31
35
|
constructor(signature: string, helperDefinitions: {
|
|
32
36
|
[key: string]: string;
|
|
33
|
-
}, ...args: any[]);
|
|
37
|
+
}, defaultOptions?: ArgSignature[], ...args: any[]);
|
|
38
|
+
private getParamValue;
|
|
39
|
+
private parseArguments;
|
|
40
|
+
private parseOptions;
|
|
34
41
|
private parseSignature;
|
|
35
42
|
private parseParamSignature;
|
|
36
43
|
validate(): void;
|
package/dist/CommandParser.js
CHANGED
|
@@ -6,105 +6,154 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.CommandParser = void 0;
|
|
7
7
|
const minimist_1 = __importDefault(require("minimist"));
|
|
8
8
|
const MissingRequiredArgumentValue_1 = require("./errors/MissingRequiredArgumentValue");
|
|
9
|
+
const MissingSignatureOption_1 = require("./errors/MissingSignatureOption");
|
|
10
|
+
const MissingSignatureArgument_1 = require("./errors/MissingSignatureArgument");
|
|
11
|
+
const InvalidOption_1 = require("./errors/InvalidOption");
|
|
9
12
|
class CommandParser {
|
|
10
13
|
signature;
|
|
11
14
|
helperDefinitions;
|
|
15
|
+
defaultOptions;
|
|
12
16
|
command;
|
|
13
17
|
arguments = {};
|
|
14
18
|
options = {};
|
|
15
19
|
argumentsSignature = {};
|
|
16
|
-
|
|
20
|
+
optionSignatures = {};
|
|
21
|
+
optionAliases = {};
|
|
17
22
|
option(name) {
|
|
18
|
-
if (!this.
|
|
19
|
-
throw new
|
|
20
|
-
}
|
|
21
|
-
const signature = this.optionsSignature[name];
|
|
22
|
-
if (signature.type === 'boolean') {
|
|
23
|
-
if (this.options[name] === 'true' || this.options[name] === '1') {
|
|
24
|
-
return true;
|
|
25
|
-
}
|
|
26
|
-
else if (this.options[name] === 'false' || this.options[name] === '0') {
|
|
27
|
-
return false;
|
|
28
|
-
}
|
|
29
|
-
return Boolean(this.options[name]);
|
|
30
|
-
}
|
|
31
|
-
if (signature.type === 'array') {
|
|
32
|
-
if (!this.options[name]) {
|
|
33
|
-
return [];
|
|
34
|
-
}
|
|
35
|
-
return Array.isArray(this.options[name]) ? this.options[name] : [this.options[name]];
|
|
23
|
+
if (!this.optionSignatures[name]) {
|
|
24
|
+
throw new MissingSignatureOption_1.MissingSignatureOption(name, Object.values(this.optionSignatures));
|
|
36
25
|
}
|
|
37
26
|
return this.options[name];
|
|
38
27
|
}
|
|
39
28
|
setOption(name, value) {
|
|
29
|
+
if (!this.optionSignatures[name]) {
|
|
30
|
+
throw new MissingSignatureOption_1.MissingSignatureOption(name, Object.values(this.optionSignatures));
|
|
31
|
+
}
|
|
40
32
|
this.options[name] = value;
|
|
41
33
|
}
|
|
34
|
+
optionHelp(name) {
|
|
35
|
+
const optionSignature = this.optionSignatures[name];
|
|
36
|
+
if (!optionSignature) {
|
|
37
|
+
throw new MissingSignatureOption_1.MissingSignatureOption(name, Object.values(this.optionSignatures));
|
|
38
|
+
}
|
|
39
|
+
return this.optionSignatures[name].help;
|
|
40
|
+
}
|
|
42
41
|
argument(name) {
|
|
42
|
+
if (!this.argumentsSignature[name]) {
|
|
43
|
+
throw new MissingSignatureArgument_1.MissingSignatureArgument(name, Object.values(this.argumentsSignature));
|
|
44
|
+
}
|
|
43
45
|
return this.arguments[name];
|
|
44
46
|
}
|
|
45
47
|
setArgument(name, value) {
|
|
48
|
+
if (!this.argumentsSignature[name]) {
|
|
49
|
+
throw new MissingSignatureArgument_1.MissingSignatureArgument(name, Object.values(this.argumentsSignature));
|
|
50
|
+
}
|
|
46
51
|
this.arguments[name] = value;
|
|
47
52
|
}
|
|
48
|
-
|
|
53
|
+
argumentHelp(name) {
|
|
54
|
+
const argumentSignature = this.argumentsSignature[name];
|
|
55
|
+
if (!argumentSignature) {
|
|
56
|
+
throw new MissingSignatureArgument_1.MissingSignatureArgument(name, Object.values(this.argumentsSignature));
|
|
57
|
+
}
|
|
58
|
+
return this.argumentsSignature[name].help;
|
|
59
|
+
}
|
|
60
|
+
getArgumentSignatures() {
|
|
49
61
|
return this.argumentsSignature;
|
|
50
62
|
}
|
|
51
|
-
|
|
52
|
-
return this.
|
|
63
|
+
getOptionSignatures() {
|
|
64
|
+
return this.optionSignatures;
|
|
53
65
|
}
|
|
54
|
-
constructor(signature, helperDefinitions, ...args) {
|
|
66
|
+
constructor(signature, helperDefinitions, defaultOptions = [], ...args) {
|
|
55
67
|
this.signature = signature;
|
|
56
68
|
this.helperDefinitions = helperDefinitions;
|
|
57
|
-
|
|
69
|
+
this.defaultOptions = defaultOptions;
|
|
70
|
+
const [command, ...signatureParams] = signature.split(/\{(.*?)\}/g).map(param => param.trim()).filter(Boolean);
|
|
58
71
|
const { _: paramValues, ...optionValues } = (0, minimist_1.default)(args);
|
|
72
|
+
if (defaultOptions.length) {
|
|
73
|
+
for (const option of defaultOptions) {
|
|
74
|
+
this.optionSignatures[option.name] = option;
|
|
75
|
+
this.options[option.name] = option.defaultValue;
|
|
76
|
+
if (option.alias) {
|
|
77
|
+
for (const alias of option.alias) {
|
|
78
|
+
this.optionAliases[alias] = option.name;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
59
83
|
this.command = command;
|
|
60
|
-
this.parseSignature(
|
|
84
|
+
this.parseSignature(signatureParams);
|
|
85
|
+
this.parseArguments(paramValues);
|
|
86
|
+
this.parseOptions(optionValues);
|
|
87
|
+
}
|
|
88
|
+
getParamValue(value, signature) {
|
|
89
|
+
if (signature.type === 'boolean') {
|
|
90
|
+
if (value === 'true' || value === '1') {
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
else if (value === 'false' || value === '0') {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
return Boolean(value);
|
|
97
|
+
}
|
|
98
|
+
if (signature.type === 'array') {
|
|
99
|
+
if (!value) {
|
|
100
|
+
return [];
|
|
101
|
+
}
|
|
102
|
+
return Array.isArray(value) ? value : [value];
|
|
103
|
+
}
|
|
104
|
+
return value ?? signature.defaultValue;
|
|
105
|
+
}
|
|
106
|
+
parseArguments(paramValues) {
|
|
107
|
+
for (const [argument, value] of Object.entries(this.arguments)) {
|
|
108
|
+
const argSignature = this.argumentsSignature[argument];
|
|
109
|
+
if (argSignature.variadic) {
|
|
110
|
+
this.arguments[argument] = paramValues;
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
const paramValue = paramValues.shift();
|
|
114
|
+
this.arguments[argument] = this.getParamValue(paramValue, argSignature);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
61
117
|
}
|
|
62
|
-
|
|
118
|
+
parseOptions(optionValues) {
|
|
119
|
+
for (const [option, value] of Object.entries(optionValues)) {
|
|
120
|
+
const optionAlias = this.optionAliases[option];
|
|
121
|
+
const optionSignature = this.optionSignatures[option] ?? this.optionSignatures[optionAlias];
|
|
122
|
+
if (!optionSignature) {
|
|
123
|
+
throw new InvalidOption_1.InvalidOption(option, Object.values(this.optionSignatures));
|
|
124
|
+
}
|
|
125
|
+
this.options[option] = this.getParamValue(value, optionSignature);
|
|
126
|
+
for (const alias of optionSignature.alias ?? []) {
|
|
127
|
+
if (optionValues[alias]) {
|
|
128
|
+
this.options[optionSignature.name] = optionValues[alias];
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
parseSignature(params) {
|
|
63
134
|
for (const paramSignature of params) {
|
|
64
135
|
const param = this.parseParamSignature(paramSignature);
|
|
65
136
|
if (param.isOption) {
|
|
66
|
-
|
|
67
|
-
this.
|
|
68
|
-
this.optionsSignature[param.name] = param;
|
|
137
|
+
this.options[param.name] = param.defaultValue ?? null;
|
|
138
|
+
this.optionSignatures[param.name] = param;
|
|
69
139
|
for (const alias of param.alias ?? []) {
|
|
70
|
-
|
|
71
|
-
this.options[param.name] = optionValues[alias];
|
|
72
|
-
this.optionsSignature[param.name] = param;
|
|
73
|
-
}
|
|
140
|
+
this.optionAliases[alias] = param.name;
|
|
74
141
|
}
|
|
75
142
|
}
|
|
76
143
|
else {
|
|
77
|
-
|
|
78
|
-
const paramValue = paramValues.splice(0, paramValues.length);
|
|
79
|
-
this.arguments[param.name] = paramValue ?? [];
|
|
80
|
-
}
|
|
81
|
-
else {
|
|
82
|
-
const paramValue = paramValues.shift();
|
|
83
|
-
this.arguments[param.name] = paramValue ?? param.defaultValue ?? null;
|
|
84
|
-
}
|
|
144
|
+
this.arguments[param.name] = param.defaultValue ?? null;
|
|
85
145
|
this.argumentsSignature[param.name] = param;
|
|
86
146
|
}
|
|
87
147
|
}
|
|
88
148
|
}
|
|
89
149
|
parseParamSignature(argument) {
|
|
90
|
-
let cleanedArgs = argument;
|
|
91
|
-
let isOptional = false;
|
|
92
|
-
let isVariadic = false;
|
|
93
|
-
if (cleanedArgs.endsWith('?')) {
|
|
94
|
-
cleanedArgs = cleanedArgs.slice(0, -1);
|
|
95
|
-
isOptional = true;
|
|
96
|
-
}
|
|
97
|
-
if (cleanedArgs.endsWith('*')) {
|
|
98
|
-
cleanedArgs = cleanedArgs.slice(0, -1);
|
|
99
|
-
isVariadic = true;
|
|
100
|
-
}
|
|
101
150
|
const arg = {
|
|
102
|
-
name:
|
|
103
|
-
optional:
|
|
104
|
-
type:
|
|
151
|
+
name: argument,
|
|
152
|
+
optional: false,
|
|
153
|
+
type: 'string',
|
|
105
154
|
help: undefined,
|
|
106
|
-
defaultValue:
|
|
107
|
-
variadic:
|
|
155
|
+
defaultValue: null,
|
|
156
|
+
variadic: false,
|
|
108
157
|
isOption: false
|
|
109
158
|
};
|
|
110
159
|
if (arg.name.includes(':')) {
|
|
@@ -149,6 +198,16 @@ class CommandParser {
|
|
|
149
198
|
arg.defaultValue = [];
|
|
150
199
|
arg.type = 'array';
|
|
151
200
|
}
|
|
201
|
+
if (arg.name.endsWith('?')) {
|
|
202
|
+
arg.optional = true;
|
|
203
|
+
arg.name = arg.name.slice(0, -1);
|
|
204
|
+
}
|
|
205
|
+
if (arg.name.endsWith('*')) {
|
|
206
|
+
arg.type = 'array';
|
|
207
|
+
arg.variadic = true;
|
|
208
|
+
arg.defaultValue = [];
|
|
209
|
+
arg.name = arg.name.slice(0, -1);
|
|
210
|
+
}
|
|
152
211
|
arg.help = arg.help ?? this.helperDefinitions[arg.name] ?? this.helperDefinitions[`--${arg.name}`];
|
|
153
212
|
return arg;
|
|
154
213
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const CommandParser_1 = require("./CommandParser");
|
|
4
|
+
const MissingRequiredArgumentValue_1 = require("./errors/MissingRequiredArgumentValue");
|
|
5
|
+
describe('CommandParser', () => {
|
|
6
|
+
let commandParser;
|
|
7
|
+
const parseCommand = (signature, args, helperDefinition = {}) => {
|
|
8
|
+
return new CommandParser_1.CommandParser(signature, helperDefinition, [], ...args);
|
|
9
|
+
};
|
|
10
|
+
it('should parse signature without arguments & options', () => {
|
|
11
|
+
commandParser = parseCommand('test', []);
|
|
12
|
+
expect(commandParser.command).toBe('test');
|
|
13
|
+
});
|
|
14
|
+
describe('Arguments', () => {
|
|
15
|
+
it('should parse signature with arguments', () => {
|
|
16
|
+
commandParser = parseCommand('test {arg1} {arg2}', ['value1', 'value2']);
|
|
17
|
+
expect(commandParser.argument('arg1')).toBe('value1');
|
|
18
|
+
expect(commandParser.argument('arg2')).toBe('value2');
|
|
19
|
+
});
|
|
20
|
+
it('should parse signature with optional arguments', () => {
|
|
21
|
+
commandParser = parseCommand('test {arg1?} {arg2?}', ['value1']);
|
|
22
|
+
expect(commandParser.argument('arg1')).toBe('value1');
|
|
23
|
+
expect(commandParser.argument('arg2')).toBeNull();
|
|
24
|
+
});
|
|
25
|
+
it('should parse signature with optional arguments with default value', () => {
|
|
26
|
+
commandParser = parseCommand('test {arg1?} {arg2=defaultValue1}', ['value1']);
|
|
27
|
+
expect(commandParser.argument('arg1')).toBe('value1');
|
|
28
|
+
expect(commandParser.argument('arg2')).toBe('defaultValue1');
|
|
29
|
+
});
|
|
30
|
+
it('should parse signature with variadic arguments', () => {
|
|
31
|
+
commandParser = parseCommand('test {arg1*}', ['value1', 'value2']);
|
|
32
|
+
expect(commandParser.argument('arg1')).toEqual(['value1', 'value2']);
|
|
33
|
+
});
|
|
34
|
+
it('should parse signature with optional variadic arguments', () => {
|
|
35
|
+
commandParser = parseCommand('test {arg1*?}', ['value1', 'value2']);
|
|
36
|
+
expect(commandParser.argument('arg1')).toEqual(['value1', 'value2']);
|
|
37
|
+
});
|
|
38
|
+
it('should parse signature with optional variadic arguments without value', () => {
|
|
39
|
+
commandParser = parseCommand('test {arg1*?}', []);
|
|
40
|
+
expect(commandParser.argument('arg1')).toEqual([]);
|
|
41
|
+
});
|
|
42
|
+
it('should set argument value', () => {
|
|
43
|
+
commandParser = parseCommand('test {arg1}', ['value1']);
|
|
44
|
+
commandParser.setArgument('arg1', 'newValue');
|
|
45
|
+
expect(commandParser.argument('arg1')).toBe('newValue');
|
|
46
|
+
});
|
|
47
|
+
it('should throw error when argument is missing with setArgument', () => {
|
|
48
|
+
commandParser = parseCommand('test {arg1}', []);
|
|
49
|
+
expect(() => commandParser.setArgument('arg2', 'newValue')).toThrowError(Error);
|
|
50
|
+
});
|
|
51
|
+
it('calling validate method should throw error when argument is missing', () => {
|
|
52
|
+
commandParser = parseCommand('test {arg1}', []);
|
|
53
|
+
expect(() => commandParser.validate()).toThrowError(MissingRequiredArgumentValue_1.MissingRequiredArgumentValue);
|
|
54
|
+
});
|
|
55
|
+
it('calling validate should throw with variadic argument is missing', () => {
|
|
56
|
+
commandParser = parseCommand('test {arg1*}', []);
|
|
57
|
+
expect(() => commandParser.validate()).toThrowError(MissingRequiredArgumentValue_1.MissingRequiredArgumentValue);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
describe('Options', () => {
|
|
61
|
+
it('boolean option should be false when not provided', () => {
|
|
62
|
+
commandParser = parseCommand('test {--option}', []);
|
|
63
|
+
expect(commandParser.option('option')).toBeFalsy();
|
|
64
|
+
});
|
|
65
|
+
it('boolean option should be true when provided', () => {
|
|
66
|
+
commandParser = parseCommand('test {--option}', ['--option']);
|
|
67
|
+
expect(commandParser.option('option')).toBeTruthy();
|
|
68
|
+
});
|
|
69
|
+
it('boolean option should be true when provided with value', () => {
|
|
70
|
+
commandParser = parseCommand('test {--option}', ['--option=true']);
|
|
71
|
+
expect(commandParser.option('option')).toBeTruthy();
|
|
72
|
+
});
|
|
73
|
+
it('boolean option should be false when provided with value', () => {
|
|
74
|
+
commandParser = parseCommand('test {--option}', ['--option=false']);
|
|
75
|
+
expect(commandParser.option('option')).toBeFalsy();
|
|
76
|
+
});
|
|
77
|
+
it('string option should be null when not provided', () => {
|
|
78
|
+
commandParser = parseCommand('test {--option=}', []);
|
|
79
|
+
expect(commandParser.option('option')).toBeNull();
|
|
80
|
+
});
|
|
81
|
+
it('string option should be value when provided', () => {
|
|
82
|
+
commandParser = parseCommand('test {--option=}', ['--option=value']);
|
|
83
|
+
expect(commandParser.option('option')).toBe('value');
|
|
84
|
+
});
|
|
85
|
+
it('string option should take the default value when not provided', () => {
|
|
86
|
+
commandParser = parseCommand('test {--option=default}', []);
|
|
87
|
+
expect(commandParser.option('option')).toBe('default');
|
|
88
|
+
});
|
|
89
|
+
it('string option should take the provided value with default value', () => {
|
|
90
|
+
commandParser = parseCommand('test {--option=default}', ['--option=value']);
|
|
91
|
+
expect(commandParser.option('option')).toBe('value');
|
|
92
|
+
});
|
|
93
|
+
it('array option should be empty when not provided', () => {
|
|
94
|
+
commandParser = parseCommand('test {--option=*}', []);
|
|
95
|
+
expect(commandParser.option('option')).toEqual([]);
|
|
96
|
+
});
|
|
97
|
+
it('array option should be value when provided', () => {
|
|
98
|
+
commandParser = parseCommand('test {--option=*}', ['--option=value1', '--option=value2']);
|
|
99
|
+
expect(commandParser.option('option')).toEqual(['value1', 'value2']);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
describe('Mixed', () => {
|
|
103
|
+
it('should parse signature with arguments and options', () => {
|
|
104
|
+
commandParser = parseCommand('test {arg1} {arg2} {--option}', ['value1', 'value2', '--option']);
|
|
105
|
+
expect(commandParser.argument('arg1')).toBe('value1');
|
|
106
|
+
expect(commandParser.argument('arg2')).toBe('value2');
|
|
107
|
+
expect(commandParser.option('option')).toBeTruthy();
|
|
108
|
+
});
|
|
109
|
+
it('should parse signature with optional arguments and options', () => {
|
|
110
|
+
commandParser = parseCommand('test {arg1?} {arg2?} {--option}', ['value1', '--option']);
|
|
111
|
+
expect(commandParser.argument('arg1')).toBe('value1');
|
|
112
|
+
expect(commandParser.argument('arg2')).toBeNull();
|
|
113
|
+
expect(commandParser.option('option')).toBeTruthy();
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
describe('Helper', () => {
|
|
117
|
+
it('should parse help signature', () => {
|
|
118
|
+
commandParser = parseCommand('test {arg1} {arg2:help 1} {--option : option help 2 }', ['value1', 'value2', '--option']);
|
|
119
|
+
expect(commandParser.argumentHelp('arg1')).toBeUndefined();
|
|
120
|
+
expect(commandParser.argumentHelp('arg2')).toBe('help 1');
|
|
121
|
+
expect(commandParser.optionHelp('option')).toBe('option help 2');
|
|
122
|
+
});
|
|
123
|
+
it('should define help with helperDefinition', () => {
|
|
124
|
+
commandParser = parseCommand('test {arg1} {arg2} {--option} {--option2}', ['value1', 'value2', '--option'], {
|
|
125
|
+
arg1: 'arg1 help',
|
|
126
|
+
arg2: 'arg2 help',
|
|
127
|
+
'--option': 'option help'
|
|
128
|
+
});
|
|
129
|
+
expect(commandParser.argumentHelp('arg1')).toBe('arg1 help');
|
|
130
|
+
expect(commandParser.argumentHelp('arg2')).toBe('arg2 help');
|
|
131
|
+
expect(commandParser.optionHelp('option')).toBe('option help');
|
|
132
|
+
expect(commandParser.optionHelp('option2')).toBeUndefined();
|
|
133
|
+
});
|
|
134
|
+
it('should define help with helperDefinition with default value', () => {
|
|
135
|
+
commandParser = parseCommand('test {arg1:arg1 help} {arg2} {--option=default}', ['value1', 'value2'], {
|
|
136
|
+
arg2: 'arg2 help',
|
|
137
|
+
'--option': 'option help'
|
|
138
|
+
});
|
|
139
|
+
expect(commandParser.argumentHelp('arg1')).toBe('arg1 help');
|
|
140
|
+
expect(commandParser.argumentHelp('arg2')).toBe('arg2 help');
|
|
141
|
+
expect(commandParser.optionHelp('option')).toBe('option help');
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { BobError } from "./BobError";
|
|
2
|
+
import { ArgSignature } from "../CommandParser";
|
|
3
|
+
export declare class InvalidOption extends BobError {
|
|
4
|
+
private option;
|
|
5
|
+
private optionsSignature;
|
|
6
|
+
constructor(option: string, optionsSignature: ArgSignature[]);
|
|
7
|
+
pretty(): void;
|
|
8
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.InvalidOption = void 0;
|
|
7
|
+
const BobError_1 = require("./BobError");
|
|
8
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
+
class InvalidOption extends BobError_1.BobError {
|
|
10
|
+
option;
|
|
11
|
+
optionsSignature;
|
|
12
|
+
constructor(option, optionsSignature) {
|
|
13
|
+
super(`Invalid option ${option} in not recognized`);
|
|
14
|
+
this.option = option;
|
|
15
|
+
this.optionsSignature = optionsSignature;
|
|
16
|
+
}
|
|
17
|
+
pretty() {
|
|
18
|
+
const log = console.log;
|
|
19
|
+
if (this.optionsSignature.length > 0) {
|
|
20
|
+
log((0, chalk_1.default) `\n{yellow Available options}:`);
|
|
21
|
+
for (const option of this.optionsSignature) {
|
|
22
|
+
const type = option.type ? (0, chalk_1.default) `{white (${option.type})}` : '';
|
|
23
|
+
const nameWithAlias = `--${option.name}${option.alias?.map(a => `, -${a}`).join('') ?? ''}`;
|
|
24
|
+
const spaces = ' '.repeat(30 - nameWithAlias.length);
|
|
25
|
+
log((0, chalk_1.default) ` {green ${nameWithAlias}} ${spaces} ${option.help ?? '\b'} ${type}`);
|
|
26
|
+
}
|
|
27
|
+
log('');
|
|
28
|
+
}
|
|
29
|
+
log((0, chalk_1.default) ` {white.bgRed ERROR } Option "{yellow ${this.option}}" is not recognized.`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
exports.InvalidOption = InvalidOption;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { BobError } from "./BobError";
|
|
2
|
+
import { ArgSignature } from "../CommandParser";
|
|
3
|
+
export declare class MissingSignatureArgument extends BobError {
|
|
4
|
+
private argument;
|
|
5
|
+
private argumentSignatures;
|
|
6
|
+
constructor(argument: string, argumentSignatures: ArgSignature[]);
|
|
7
|
+
pretty(): void;
|
|
8
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.MissingSignatureArgument = void 0;
|
|
7
|
+
const BobError_1 = require("./BobError");
|
|
8
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
+
class MissingSignatureArgument extends BobError_1.BobError {
|
|
10
|
+
argument;
|
|
11
|
+
argumentSignatures;
|
|
12
|
+
constructor(argument, argumentSignatures) {
|
|
13
|
+
super(`Missing ${argument} in the command signature`);
|
|
14
|
+
this.argument = argument;
|
|
15
|
+
this.argumentSignatures = argumentSignatures;
|
|
16
|
+
}
|
|
17
|
+
pretty() {
|
|
18
|
+
const log = console.log;
|
|
19
|
+
if (this.argumentSignatures.length) {
|
|
20
|
+
log((0, chalk_1.default) `\n{yellow Available arguments}:`);
|
|
21
|
+
for (const argument of this.argumentSignatures) {
|
|
22
|
+
const type = argument.type ? (0, chalk_1.default) `{white (${argument.type})}` : '';
|
|
23
|
+
const spaces = ' '.repeat(20 - argument.name.length);
|
|
24
|
+
log((0, chalk_1.default) ` {green ${argument.name}} ${spaces} ${argument.help ?? '\b'} ${type}`);
|
|
25
|
+
}
|
|
26
|
+
log('');
|
|
27
|
+
}
|
|
28
|
+
log((0, chalk_1.default) ` {white.bgRed ERROR } Argument "{yellow ${this.argument}}" is missing in the signature.`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
exports.MissingSignatureArgument = MissingSignatureArgument;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { BobError } from "./BobError";
|
|
2
|
+
import { ArgSignature } from "../CommandParser";
|
|
3
|
+
export declare class MissingSignatureOption extends BobError {
|
|
4
|
+
private option;
|
|
5
|
+
private optionsSignature;
|
|
6
|
+
constructor(option: string, optionsSignature: ArgSignature[]);
|
|
7
|
+
pretty(): void;
|
|
8
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.MissingSignatureOption = void 0;
|
|
7
|
+
const BobError_1 = require("./BobError");
|
|
8
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
+
class MissingSignatureOption extends BobError_1.BobError {
|
|
10
|
+
option;
|
|
11
|
+
optionsSignature;
|
|
12
|
+
constructor(option, optionsSignature) {
|
|
13
|
+
super(`Missing ${option} in the command signature`);
|
|
14
|
+
this.option = option;
|
|
15
|
+
this.optionsSignature = optionsSignature;
|
|
16
|
+
}
|
|
17
|
+
pretty() {
|
|
18
|
+
const log = console.log;
|
|
19
|
+
if (this.optionsSignature.length) {
|
|
20
|
+
log((0, chalk_1.default) `{yellow Available options}:`);
|
|
21
|
+
for (const option of this.optionsSignature) {
|
|
22
|
+
const type = option.type ? (0, chalk_1.default) `{white (${option.type})}` : '';
|
|
23
|
+
const spaces = ' '.repeat(20 - option.name.length);
|
|
24
|
+
log((0, chalk_1.default) ` {green ${option.name}} ${spaces} ${option.help ?? '\b'} ${type}`);
|
|
25
|
+
}
|
|
26
|
+
log('');
|
|
27
|
+
}
|
|
28
|
+
log((0, chalk_1.default) ` {white.bgRed ERROR } Option "{yellow ${this.option}}" is missing in the signature.`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
exports.MissingSignatureOption = MissingSignatureOption;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bob-core",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"description": "BOB Core",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -8,18 +8,22 @@
|
|
|
8
8
|
"/dist"
|
|
9
9
|
],
|
|
10
10
|
"scripts": {
|
|
11
|
-
"start": "node -r @swc-node/register
|
|
11
|
+
"start": "node -r @swc-node/register debug/main.ts",
|
|
12
12
|
"build": "tsc",
|
|
13
|
-
"prepare": "npm run build"
|
|
13
|
+
"prepare": "npm run build",
|
|
14
|
+
"test": "jest"
|
|
14
15
|
},
|
|
15
16
|
"author": "Léo Hubert",
|
|
16
17
|
"license": "ISC",
|
|
17
18
|
"devDependencies": {
|
|
18
19
|
"@swc-node/register": "^1.9.2",
|
|
20
|
+
"@types/jest": "^29.5.12",
|
|
19
21
|
"@types/lodash": "^4.17.5",
|
|
20
22
|
"@types/minimist": "^1.2.5",
|
|
21
23
|
"@types/node": "^20.14.5",
|
|
22
24
|
"@types/string-similarity": "^4.0.2",
|
|
25
|
+
"jest": "^29.7.0",
|
|
26
|
+
"ts-jest": "^29.1.5",
|
|
23
27
|
"typescript": "^5.4.5"
|
|
24
28
|
},
|
|
25
29
|
"dependencies": {
|