Skip to content

Commit

Permalink
Change argv pipeline to init -> applyConfig -> prepareContext
Browse files Browse the repository at this point in the history
  • Loading branch information
lahmatiy committed Dec 31, 2019
1 parent 1d8f5e8 commit e591b3f
Show file tree
Hide file tree
Showing 8 changed files with 89 additions and 70 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
- Removed `Command#normalize()` method (use `createOptionValues()` instead)
- Changed `Option` to store params info as `Option#params`, it always an object even if no params
- Allowed a number for options's short name
- Changed argv parse handlers to [`init()` -> `applyConfig()` -> `prepareContext()`]+ -> `action()`
- Changed exports
- Added `getCommandHelp()` function
- Added `Params` class
Expand Down
29 changes: 17 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,25 +107,30 @@ Where `options`:
value: any, // default value
normalize: (value, oldValue) => { ... }, // any value for option is passing through this function and its result stores as option value
shortcut: (value, oldValue) => { ... }, // for shortcut options, the handler is executed after the value is set, and its result (an object) is used as a source of values for other options
action: () => { ... } // for an action option, which breaks regular args processing and preform and action (e.g. show help or version)
action: () => { ... }, // for an action option, which breaks regular args processing and preform and action (e.g. show help or version)
config: boolean // mark option is about config and should be applied before `applyConfig()`
}
```

### Args processing
### Argv processing

- init(command, context) // before arguments parsing
- invoke action option and exit if any
- apply option values
- prepare(context) // after arguments parsing
- `init(command, context)` // before arguments parsing
- invoke action option and exit if any
- apply **config** options
- `applyConfig(context)`
- apply all the rest options
- `prepareContext(context)` // after arguments parsing
- switch to next command -> command is prescending
- init(command, context)
- invoke action option and exit if any
- apply option values
- prepare(context) // after arguments parsing
- `init(command, context)`
- invoke action option and exit if any
- apply **config** options
- `applyConfig(context)`
- apply all the rest options
- `prepareContext(context)` // after arguments parsing
- switch to next command
- ...
- action(context) -> command is target
- action(context) -> command is target
- `action(context)` -> command is target
- `action(context)` -> command is target

## License

Expand Down
2 changes: 1 addition & 1 deletion lib/command.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const defaultHelpAction = (instance, _, { commandPath }) => instance.outputHelp(
const defaultVersionAction = instance => console.log(instance.meta.version);
const lastCommandHost = new WeakMap();

const handlers = ['init', 'prepare', 'action'].reduce((res, name) => {
const handlers = ['init', 'applyConfig', 'finishContext', 'action'].reduce((res, name) => {
res.initial[name] = name === 'action' ? self : noop;
res.setters[name] = function(fn) {
this.handlers[name] = fn.bind(null);
Expand Down
3 changes: 2 additions & 1 deletion lib/option.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ module.exports = class Option {
defValue: !ensureFunction(raw.action) ? raw.value : undefined,
normalize: ensureFunction(raw.normalize, self),
shortcut: ensureFunction(raw.shortcut),
action: ensureFunction(raw.action)
action: ensureFunction(raw.action),
config: Boolean(raw.config)
};
}

Expand Down
50 changes: 32 additions & 18 deletions lib/parse-argv.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ function consumeOptionParams(option, rawOptions, argv, index, suggestPoint) {
module.exports = function parseArgv(command, argv, context, suggestMode) {
const suggestPoint = suggestMode ? argv.length - 1 : -1;
const rawOptions = [];
const resultChunk = {
const result = {
context,
action: null,
next: null
Expand Down Expand Up @@ -124,8 +124,8 @@ module.exports = function parseArgv(command, argv, context, suggestMode) {

if (subcommand !== null &&
context.args.length >= command.params.minCount) {
// finalize command
resultChunk.next = {
// set next command and rest argv
result.next = {
command: subcommand,
argv: argv.slice(i + 1)
};
Expand All @@ -142,34 +142,48 @@ module.exports = function parseArgv(command, argv, context, suggestMode) {
}

// final checks
if (suggestMode && !resultChunk.next) {
if (suggestMode && !result.next) {
return findVariants(command, '');
} else if (context.args.length < command.params.minCount) {
throw new CliError(`Missed required argument(s) for command "${command.name}"`);
}

// create new option values storage
context.options = command.createOptionValues();

// process action option
const firstActionOption = rawOptions.find(({ option }) => option.action);
if (firstActionOption) {
const { option, value } = firstActionOption;
resultChunk.action = (() => option.action(command, value, context));
resultChunk.next = null;
return resultChunk;
const actionOption = rawOptions.find(({ option }) => option.action);
if (actionOption) {
const { option, value } = actionOption;
result.action = () => option.action(command, value, context);
result.next = null;
return result;
}

// apply options
context.options = command.createOptionValues();
// apply config options
for (const { option, value } of rawOptions) {
context.options[option.name] = value;
if (option.config) {
context.options[option.name] = value;
}
}

// run apply config handler
command.handlers.applyConfig(context);

// apply regular options
for (const { option, value } of rawOptions) {
if (!option.config) {
context.options[option.name] = value;
}
}

// run prepare handler
command.handlers.prepare(context);
// run context finish handler
command.handlers.finishContext(context);

// set action if no rest argv
if (!resultChunk.next) {
resultChunk.action = command.handlers.action;
if (!result.next) {
result.action = command.handlers.action;
}

return resultChunk;
return result;
};
32 changes: 0 additions & 32 deletions test/command-init-prepare.js

This file was deleted.

34 changes: 34 additions & 0 deletions test/command-parse-handlers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
const assert = require('assert');
const cli = require('../lib');

describe('init()/applyConfig()/finishContext()', function() {
let command;
let calls;

beforeEach(function() {
calls = [];
command = cli.command('test', '[arg1]')
.init(() => calls.push('init'))
.applyConfig(() => calls.push('applyConfig'))
.finishContext(() => calls.push('finishContext'));
command.command('nested', '[arg2] [arg3]')
.init(() => calls.push('nested init'))
.applyConfig(() => calls.push('nested applyConfig'))
.finishContext(() => calls.push('nested finishContext'));
});

it('with no arguments should init/finishContext top level command only', function() {
command.run([]);
assert.deepEqual(calls, ['init', 'applyConfig', 'finishContext']);
});

it('with one argument should init and finishContext top level command', function() {
command.run(['foo']);
assert.deepEqual(calls, ['init', 'applyConfig', 'finishContext']);
});

it('with first argument as command should init/finishContext both commands', function() {
command.run(['nested']);
assert.deepEqual(calls, ['init', 'applyConfig', 'finishContext', 'nested init', 'nested applyConfig', 'nested finishContext']);
});
});
8 changes: 2 additions & 6 deletions test/option-one-arg.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,7 @@ describe('one arg options', function() {

const command = cli.command()
.option('--option <arg>', 'description')
.prepare(function({ options }) {
values = options;
})
.finishContext(({ options }) => values = options)
.command('test')
.action(() => ok = true)
.end();
Expand Down Expand Up @@ -144,9 +142,7 @@ describe('one arg options', function() {

const command = cli.command()
.option('--option [arg]', 'description')
.prepare(function({ options }) {
values = options;
})
.finishContext(({ options }) => values = options)
.command('test')
.action(() => ok = true)
.end();
Expand Down

0 comments on commit e591b3f

Please sign in to comment.