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

Positional options #1427

Merged
merged 26 commits into from
Jan 4, 2021
Merged
Changes from 1 commit
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
0f74c8b
First cut at optionsBeforeArguments
shadowspawn Dec 27, 2020
3de2bde
Different to mix global options and subcommands, and options and argu…
shadowspawn Dec 27, 2020
bc1f8f7
Different to mix global options and subcommands, and options and argu…
shadowspawn Dec 27, 2020
c93da58
Add _parseOptionsFollowingArguments
shadowspawn Dec 27, 2020
69458b4
Use allow wording
shadowspawn Dec 27, 2020
304ad64
Another try at naming
shadowspawn Dec 28, 2020
ee98c44
Exclude options from special processing, which fixes help
shadowspawn Dec 28, 2020
e155f3a
Add help checks for new option configuration
shadowspawn Dec 28, 2020
fe29f6e
Rename after discovering needed for any positional options
shadowspawn Dec 29, 2020
d781d77
Rework logic to hopefully cope with default commands.
shadowspawn Dec 29, 2020
eef4d52
Expand basic tests. Positional options are tricky!
shadowspawn Dec 29, 2020
f20aff0
Add first default command tests
shadowspawn Dec 30, 2020
91468d3
Fill out more tests
shadowspawn Dec 31, 2020
3b6cd3f
Add setters, and throw when passThrough without enabling positional
shadowspawn Jan 1, 2021
bb95593
Rename test file
shadowspawn Jan 1, 2021
9c5b77a
Add TypeScript
shadowspawn Jan 1, 2021
6a4524b
Add tests. Fix help handling by making explicit.
shadowspawn Jan 2, 2021
4d26a15
Reorder tests
shadowspawn Jan 2, 2021
4136ce8
Use usual indentation
shadowspawn Jan 2, 2021
17472b9
Make _enablePositionalOptions inherited to simpify nested commands
shadowspawn Jan 2, 2021
2c9373f
Add examples
shadowspawn Jan 2, 2021
ca47d95
Add tests for some less common setups
shadowspawn Jan 2, 2021
a021878
Test the boring true/false parameters
shadowspawn Jan 2, 2021
7ab4d6d
Fix typo
shadowspawn Jan 2, 2021
71d92e1
Add new section to README with parsing configuration.
shadowspawn Jan 3, 2021
6f7ffcb
Tweak wording in README
shadowspawn Jan 3, 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
Fill out more tests
  • Loading branch information
shadowspawn committed Dec 31, 2020
commit 91468d3493e96fa56176f030cea9ebc19113cfae
172 changes: 103 additions & 69 deletions tests/command.optionsBeforeArguments.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,13 @@ describe('program with positionalOptions and subcommand', () => {
expect(program.opts().shared).toBeUndefined();
});

test('when shared option before and after subcommand then both parsed', () => {
const { program, sub } = makeProgram();
program.parse(['--shared', 'sub', '--shared'], { from: 'user' });
expect(program.opts().shared).toBe(true);
expect(sub.opts().shared).toBe(true);
});

test.each([
[[], 1, 0],
[['sub'], 0, 0],
Expand Down Expand Up @@ -136,7 +143,7 @@ describe('program with positionalOptions and subcommand', () => {

// ---------------------------------------------------------------

describe('program with positionalOptions and default subcommand called sub', () => {
describe('program with positionalOptions and default subcommand (called sub)', () => {
function makeProgram() {
const program = new commander.Command();
program._enablePositionalOptions = true;
Expand All @@ -159,7 +166,7 @@ describe('program with positionalOptions and default subcommand called sub', ()
expect(program.opts().global).toBe(true);
});

test('when global option before sub option then sub option read by sub', () => {
test('when program option before sub option then sub option read by sub', () => {
const { program, sub } = makeProgram();
program.parse(['--global', '--default'], { from: 'user' });
expect(sub.opts().default).toBe(true);
Expand Down Expand Up @@ -201,88 +208,115 @@ describe('program with positionalOptions and default subcommand called sub', ()
});
});

// ---------------------------------------------------------------------------
// WIP from here
// ---------------------------------------------------------------------------
// ------------------------------------------------------------------------------

// --------- subcommand passThrough
describe('subcommand with passThrough', () => {
function makeProgram() {
const program = new commander.Command();
program._enablePositionalOptions = true;
program
.option('-s, --shared')
.arguments('<args...>');
const sub = program.command('sub')
.arguments('[args...]')
.option('-s, --shared')
.option('-d, --debug')
.action(() => {}); // Not used, but normal to have action handler on subcommand.
sub._passThroughOptions = true;
return { program, sub };
}

// --------- subcommand passThrough with default ????
test('when option before command-argument then option parsed', () => {
const { program, sub } = makeProgram();
program.parse(['sub', '--debug', 'arg'], { from: 'user' });
expect(sub.args).toEqual(['arg']);
expect(sub.opts().debug).toBe(true);
});

test('when global option after subcommand then global option parsed', () => {
const mockAction = jest.fn();
const program = new commander.Command();
program
.option('-d, --debug');
const sub = program.command('sub')
.arguments('[arg]')
.action(mockAction);
program.parse(['sub', '--debug', 'arg'], { from: 'user' });
expect(program.opts().debug).toBe(true);
expect(mockAction).toBeCalledWith('arg', sub.opts(), sub);
});
test('when known option after command-argument then option passed through', () => {
const { program, sub } = makeProgram();
program.parse(['sub', 'arg', '--debug'], { from: 'user' });
expect(sub.args).toEqual(['arg', '--debug']);
expect(sub.opts().debug).toBeUndefined();
});

test('when global option after subcommand and _enablePositionalOptions=true then global option not parsed', () => {
const mockAction = jest.fn();
const program = new commander.Command();
program._enablePositionalOptions = true;
program
.option('-d, --debug');
const sub = program.command('sub')
.arguments('[arg]')
.allowUnknownOption()
.action(mockAction);
program.parse(['sub', '--debug'], { from: 'user' });
expect(program.opts().debug).toBeUndefined();
expect(mockAction).toBeCalledWith('--debug', sub.opts(), sub);
});
test('when unknown option after command-argument then option passed through', () => {
const { program, sub } = makeProgram();
program.parse(['sub', 'arg', '--pass'], { from: 'user' });
expect(sub.args).toEqual(['arg', '--pass']);
});

test('when action handler and unknown option after command-argument then option passed through', () => {
const { program, sub } = makeProgram();
const mockAction = jest.fn();
sub.action(mockAction);
program.parse(['sub', 'arg', '--pass'], { from: 'user' });
expect(mockAction).toHaveBeenCalledWith(['arg', '--pass'], sub.opts(), sub);
});

test('when help option after command-argument then option passed through', () => {
const { program, sub } = makeProgram();
program.parse(['sub', 'arg', '--help'], { from: 'user' });
expect(sub.args).toEqual(['arg', '--help']);
});

test('when option after subcommand is global and local and _enablePositionalOptions=true then option parsed as local', () => {
const mockAction = jest.fn();
const program = new commander.Command();
program._enablePositionalOptions = true;
program
.option('-d, --debug');
const sub = program.command('sub')
.option('-d, --debug')
.action(mockAction);
program.parse(['sub', '--debug'], { from: 'user' });
expect(program.opts().debug).toBeUndefined();
expect(sub.opts().debug).toBe(true);
test('when version option after command-argument then option passed through', () => {
const { program, sub } = makeProgram();
program.version('1.2.3');
program.parse(['sub', 'arg', '--version'], { from: 'user' });
expect(sub.args).toEqual(['arg', '--version']);
});

test('when shared option before sub and after sub and after sub parameter then all three parsed', () => {
const { program, sub } = makeProgram();
program.version('1.2.3');
program.parse(['--shared', 'sub', '--shared', 'arg', '--shared'], { from: 'user' });
expect(program.opts().shared).toBe(true);
expect(sub.opts().shared).toBe(true);
expect(sub.args).toEqual(['arg', '--shared']);
});
});

describe('program with _enablePositionalOptions=true and subcommand with _passThroughOptions=true', () => {
test.each([
[[], true],
[['help'], true],
[['--help'], true],
[['sub'], false],
[['sub', '--help'], true],
[['sub', 'foo', '--help'], false]
])('when user args %p then help called is %p', (userArgs, expectHelpCalled) => {
// also check which command calls help ????
// ------------------------------------------------------------------------------

describe('program with action handler and positionalOptions and subcommand', () => {
function makeProgram() {
const program = new commander.Command();
program._enablePositionalOptions = true;
program
.exitOverride()
.configureHelp({ formatHelp: () => '' });
.option('-g, --global')
.arguments('<args...>')
.action(() => {});
const sub = program.command('sub')
.exitOverride()
.configureHelp({ formatHelp: () => '' })
.action(() => { });
sub._passThroughOptions = true;
.arguments('[arg]')
.action(() => {});
return { program, sub };
}

let helpCalled = false;
try {
program.parse(userArgs, { from: 'user' });
} catch (err) {
helpCalled = err.code === 'commander.helpDisplayed' || err.code === 'commander.help';
}
expect(helpCalled).toEqual(expectHelpCalled);
test('when global option before parameter then global option parsed', () => {
const { program } = makeProgram();
program.parse(['--global', 'foo'], { from: 'user' });
expect(program.opts().global).toBe(true);
});

test('when global option after parameter then global option parsed', () => {
const { program } = makeProgram();
program.parse(['foo', '--global'], { from: 'user' });
expect(program.opts().global).toBe(true);
});

test('when global option after parameter with same name as subcommand then global option parsed', () => {
const { program } = makeProgram();
program.parse(['foo', 'sub', '--global'], { from: 'user' });
expect(program.opts().global).toBe(true);
});
});

// test for "foo sub" where sub is a parameter and not a command ????
// ---------------------------------------------------------------------------
// WIP from here
// ---------------------------------------------------------------------------

// --------- subcommand passThrough with default ????

// Interaction of unknowns with passThrough ????

Expand Down