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 CHANGED
@@ -17,7 +17,7 @@ import { CLI } from 'bob-core';
17
17
 
18
18
  const cli = new CLI();
19
19
 
20
- await cli.loadCommandsPath('./commands');
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
- loadCommandsPath(commandsPath: string): Promise<void>;
15
- registerCommand(command: Command): void;
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.registerCommand(new this.HelpCommandClass(this.commandRegistry));
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 loadCommandsPath(commandsPath) {
33
- await this.commandRegistry.loadCommandsPath(commandsPath);
34
- }
35
- registerCommand(command) {
36
- this.commandRegistry.registerCommand(command);
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 CommandParser_1.CommandParser(this.signature, this.helperDefinitions, ...args);
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
+ });
@@ -1,6 +1,4 @@
1
1
  import { Command } from "./Command";
2
- import { ArgSignature } from "./CommandParser";
3
2
  export declare class CommandHelper {
4
- get defaultOptions(): ArgSignature[];
5
3
  help(this: Command<any>): number;
6
4
  }
@@ -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.argumentsSignatures());
25
- const availableOptions = [...Object.values(this.parser.optionsSignatures()), ...this.defaultOptions]
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('') ?? ''}`
@@ -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 optionsSignature;
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
- argumentsSignatures(): {
28
+ argumentHelp(name: string): string | undefined;
29
+ getArgumentSignatures(): {
26
30
  [argument: string]: ArgSignature;
27
31
  };
28
- optionsSignatures(): {
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;
@@ -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
- optionsSignature = {};
20
+ optionSignatures = {};
21
+ optionAliases = {};
17
22
  option(name) {
18
- if (!this.optionsSignature[name]) {
19
- throw new Error(`Option ${name} not found`);
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
- argumentsSignatures() {
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
- optionsSignatures() {
52
- return this.optionsSignature;
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
- const [command, ...params] = signature.split(/\{(.*?)\}/g).map(param => param.trim()).filter(Boolean);
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(params, optionValues, paramValues);
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
- parseSignature(params, optionValues, paramValues) {
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
- const optionValue = optionValues[param.name];
67
- this.options[param.name] = optionValue ?? param.defaultValue ?? null;
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
- if (optionValues[alias]) {
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
- if (param.variadic) {
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: cleanedArgs,
103
- optional: isOptional,
104
- type: isVariadic ? 'array' : 'string',
151
+ name: argument,
152
+ optional: false,
153
+ type: 'string',
105
154
  help: undefined,
106
- defaultValue: isVariadic ? [] : null,
107
- variadic: isVariadic,
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.0",
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 ./tests/main.ts",
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": {