Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More consistent way of adding command arguments #1490

Merged
merged 36 commits into from
Apr 8, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
39a47ed
added addArguments method
Niryo Feb 6, 2021
88a4cc5
fix undefined args
Niryo Feb 6, 2021
e8415ed
added tests, docs and typings
Niryo Feb 13, 2021
782f705
code review fixes
Niryo Feb 19, 2021
d106ca0
throw error on bad arg
Niryo Mar 6, 2021
39cb757
Parse command-argument details in constructor
shadowspawn Mar 13, 2021
8c4d8d0
Handle argument descriptions separately for legacy and new support
shadowspawn Mar 13, 2021
c2e0a5a
Add text to distinguish test names with .each
shadowspawn Mar 13, 2021
32f001b
Match nameAndArgs into two parts, rather than split on spaces.
shadowspawn Mar 14, 2021
4a8e323
Merge pull request #1 from shadowspawn/Niryo-addArgument
Niryo Mar 14, 2021
ad20e0b
Update release date post-release to be more accurate
shadowspawn Mar 22, 2021
9a77be4
Merge pull request #1467 from Niryo/addArgument
shadowspawn Mar 27, 2021
da37436
Fix test naming
shadowspawn Mar 27, 2021
81248e2
Simplify and tidy argument example
shadowspawn Mar 27, 2021
6052c14
Typing and typings test for .argument
shadowspawn Mar 27, 2021
0dd3547
Expand argument section to include existing multiple-argument approac…
shadowspawn Mar 27, 2021
0c43cd9
Add name method and improve Argument typings and tests
shadowspawn Mar 27, 2021
0dea8ac
Fix copy-and-paste JSDoc error
shadowspawn Mar 27, 2021
82ff5bd
Update example to match new method and README
shadowspawn Mar 27, 2021
a20a8fe
Deprecate old way of adding command argument descriptions
shadowspawn Mar 27, 2021
df73a19
Be lenient about Argument construction to allow lazy building
shadowspawn Mar 27, 2021
43ce351
Call first param to .argument "name", and expand jsdoc
shadowspawn Mar 28, 2021
af08a2b
Add low-level check that get same Argument from multiple ways of spec…
shadowspawn Mar 28, 2021
bac5ea9
Minor wording tweaks
shadowspawn Mar 28, 2021
0e505be
Add low-level tests for multiple arg variations
shadowspawn Mar 28, 2021
dac9656
Simplify test. Use .argument now.
shadowspawn Mar 28, 2021
3745659
Restore simple test, use .argument
shadowspawn Mar 28, 2021
a76f831
Big switch from .arguments to .argument in tests
shadowspawn Mar 28, 2021
5a08571
Expand help to explain argument variations
shadowspawn Mar 28, 2021
747d6ef
Keep Argument properties private for now (like Command, unlike Option)
shadowspawn Mar 29, 2021
b7def8a
Argument should follow Option for properties, make public again
shadowspawn Mar 30, 2021
f54d1ed
Generate help for arguments using same methods as for option and subc…
shadowspawn Mar 30, 2021
0879a1f
Simplify Argument .name(), just getter
shadowspawn Mar 30, 2021
1e66821
Expand test coverage for visibleArguments
shadowspawn Mar 30, 2021
ed7a1c8
Rework the multiple ways of specifying command-arguments
shadowspawn Apr 5, 2021
819616f
Add Argument to esm exports (and createOption)
shadowspawn Apr 7, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
code review fixes
  • Loading branch information
Niryo committed Feb 19, 2021
commit 782f7055a1bb911a0f4e8cd4f061d28554b2e09a
23 changes: 1 addition & 22 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -456,27 +456,6 @@ program
```

The variadic argument is passed to the action handler as an array.


You can supply all the arguments at once using `.arguments()`, and optionally
describe the arguments in the help by supplying a hash as second parameter to `.description()`.

Example file: [arguments.js](./examples/arguments.js)

```js
program
.version('0.1.0')
.arguments('<username> [password]')
.description('test command', {
username: 'user to login',
password: 'password for user, if required'
})
.action((username, password) => {
console.log('username:', username);
console.log('environment:', password || 'no password given');
});
```

### Action handler

The action handler gets passed a parameter for each command-argument you declared, and two additional parameters
Expand All @@ -486,7 +465,7 @@ Example file: [thank.js](./examples/thank.js)

```js
program
.arguments('<name>')
.argument('<name>')
.option('-t, --title <honorific>', 'title to use before name')
.option('-d, --debug', 'display some debugging')
.action((name, options, command) => {
Expand Down
9 changes: 6 additions & 3 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,10 @@ class Argument {
*/

constructor(arg, description) {
this.argDetails = parseArg(arg);
const argDetails = parseArg(arg);
this.name = argDetails.name;
this.required = argDetails.required;
this.variadic = argDetails.variadic;
this.description = description || '';
}
shadowspawn marked this conversation as resolved.
Show resolved Hide resolved
}
Expand Down Expand Up @@ -774,11 +777,11 @@ class Command extends EventEmitter {
* @param {Argument} argument
*/
addArgument(argument) {
this._args.push(argument.argDetails);
this._args.push(argument);
if (!this._argsDescription) {
this._argsDescription = {};
}
this._argsDescription[argument.argDetails.name] = argument.description;
this._argsDescription[argument.name] = argument.description;
this._validateArgs();
return this;
}
Expand Down
77 changes: 27 additions & 50 deletions tests/command.action.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,27 +23,18 @@ test('when .action called then program.args only contains args', () => {
expect(program.args).toEqual(['info', 'my-file']);
});

test('when .action on program with required argument and argument supplied then action called', () => {
test.each(getTestCases('<file>'))('when .action on program with required argument and argument supplied then action called', (program) => {
const actionMock = jest.fn();
const program = new commander.Command();
program
.arguments('<file>')
.argument('<second>')
.addArgument(new commander.Argument('<last>'))
.action(actionMock);
program.parse(['node', 'test', 'my-file', 'second', 'last']);
expect(actionMock).toHaveBeenCalledWith('my-file', 'second', 'last', program.opts(), program);
program.action(actionMock);
program.parse(['node', 'test', 'my-file']);
expect(actionMock).toHaveBeenCalledWith('my-file', program.opts(), program);
});

test('when .action on program with required argument and argument not supplied then action not called', () => {
test.each(getTestCases('<file>'))('when .action on program with required argument and argument not supplied then action not called', (program) => {
const actionMock = jest.fn();
const program = new commander.Command();
program
.exitOverride()
.configureOutput({ writeErr: () => {} })
.arguments('<file>')
.argument('<second>')
.addArgument(new commander.Argument('<last>'))
.action(actionMock);
expect(() => {
program.parse(['node', 'test']);
Expand All @@ -61,61 +52,40 @@ test('when .action on program and no arguments then action called', () => {
expect(actionMock).toHaveBeenCalledWith(program.opts(), program);
});

test('when .action on program with optional argument supplied then action called', () => {
test.each(getTestCases('[file]'))('when .action on program with optional argument supplied then action called', (program) => {
const actionMock = jest.fn();
const program = new commander.Command();
program
.arguments('[file]')
.argument('[second]')
.addArgument(new commander.Argument('[last]'))
.action(actionMock);
program.parse(['node', 'test', 'my-file', 'second', 'last']);
expect(actionMock).toHaveBeenCalledWith('my-file', 'second', 'last', program.opts(), program);
program.action(actionMock);
program.parse(['node', 'test', 'my-file']);
expect(actionMock).toHaveBeenCalledWith('my-file', program.opts(), program);
});

test('when .action on program without optional argument supplied then action called', () => {
test.each(getTestCases('[file]'))('when .action on program without optional argument supplied then action called', (program) => {
const actionMock = jest.fn();
const program = new commander.Command();
program
.arguments('[file]')
.argument('[second]')
.addArgument(new commander.Argument('[last]'))
.action(actionMock);
program.action(actionMock);
program.parse(['node', 'test']);
expect(actionMock).toHaveBeenCalledWith(undefined, undefined, undefined, program.opts(), program);
expect(actionMock).toHaveBeenCalledWith(undefined, program.opts(), program);
});

test('when .action on program with optional argument and subcommand and program argument then program action called', () => {
test.each(getTestCases('[file]'))('when .action on program with optional argument and subcommand and program argument then program action called', (program) => {
const actionMock = jest.fn();
const program = new commander.Command();
program
.arguments('[file]')
.argument('[second]')
.addArgument(new commander.Argument('[last]'))
.action(actionMock);
program.action(actionMock);
program
.command('subcommand');

program.parse(['node', 'test', 'a', 'second', 'last']);
program.parse(['node', 'test', 'a']);

expect(actionMock).toHaveBeenCalledWith('a', 'second', 'last', program.opts(), program);
expect(actionMock).toHaveBeenCalledWith('a', program.opts(), program);
});

// Changes made in #1062 to allow this case
test('when .action on program with optional argument and subcommand and no program argument then program action called', () => {
test.each(getTestCases('[file]'))('when .action on program with optional argument and subcommand and no program argument then program action called', (program) => {
const actionMock = jest.fn();
const program = new commander.Command();
program
.arguments('[file]')
.argument('[second]')
.addArgument(new commander.Argument('[last]'))
.action(actionMock);
program
.command('subcommand');
program.action(actionMock);
program.command('subcommand');

program.parse(['node', 'test']);

expect(actionMock).toHaveBeenCalledWith(undefined, undefined, undefined, program.opts(), program);
expect(actionMock).toHaveBeenCalledWith(undefined, program.opts(), program);
});

test('when action is async then can await parseAsync', async() => {
Expand All @@ -133,3 +103,10 @@ test('when action is async then can await parseAsync', async() => {
await later;
expect(asyncFinished).toBe(true);
});

function getTestCases(arg) {
const withArguments = new commander.Command().arguments(arg);
const withArgument = new commander.Command().argument(arg);
const withAddArgument = new commander.Command().addArgument(new commander.Argument(arg));
return [withArguments, withArgument, withAddArgument];
}