diff --git a/src/app/command-processor.js b/src/app/command-processor.js index f6d719068..4b26cb8d3 100644 --- a/src/app/command-processor.js +++ b/src/app/command-processor.js @@ -109,6 +109,7 @@ class CLICommandItem { configure(yargs, { options, setup, examples, version, epilogue }=this.buildOptions()) { if (options) { this.fetchAliases(options); + this.configureOptions(options); // avoid converting positional arguments to numbers by default const optionsWithDefaults = Object.assign({ '_': { string: true } }, options); yargs.options(optionsWithDefaults); @@ -146,6 +147,26 @@ class CLICommandItem { }); } + configureOptions(options) { + Object.keys(options).forEach((key) => { + const option = options[key]; + if (!option.hasOwnProperty('nargs') && + !option.boolean && + !option.count && + !option.array) { + option.nargs = 1; + } + + if (!option.hasOwnProperty('number') && + !option.hasOwnProperty('boolean') && + !option.hasOwnProperty('string') && + !option.hasOwnProperty('count') && + !option.hasOwnProperty('array')) { + option.string = true; + } + }); + } + /** * Finds the original name of an option given a possible alias. * @param {string} name The option name to unalias. @@ -428,7 +449,7 @@ function consoleErrorLogger(console, yargs, exit, err) { // Adapted from: https://github.com/bcoe/yargs/blob/master/lib/validation.js#L83-L110 function checkForUnknownArguments(yargs, argv, command) { const aliasLookup = {}; - const descriptions = yargs.getUsageInstance().getDescriptions(); + const flags = yargs.getOptions().key; const demanded = yargs.getDemanded(); const unknown = []; @@ -440,8 +461,8 @@ function checkForUnknownArguments(yargs, argv, command) { function isUnknown(key) { return (key !== '$0' && key !== '_' && key !== 'params' && - !descriptions.hasOwnProperty(key) && !demanded.hasOwnProperty(key) && + !flags.hasOwnProperty(key) && !aliasLookup.hasOwnProperty('no-' + key) && !aliasLookup.hasOwnProperty(key)); } diff --git a/src/cli/cloud.js b/src/cli/cloud.js index ba35fe9a1..ca5279574 100644 --- a/src/cli/cloud.js +++ b/src/cli/cloud.js @@ -3,8 +3,7 @@ export default ({ commandProcessor, root }) => { const compileOptions = { 'target': { - description: 'The firmware version to compile against. Defaults to latest version, or version on device for cellular.', - nargs: 1 + description: 'The firmware version to compile against. Defaults to latest version, or version on device for cellular.' } }; @@ -80,8 +79,7 @@ export default ({ commandProcessor, root }) => { params: ' [files...]', options: Object.assign({}, compileOptions, { 'saveTo': { - description: 'Filename for the compiled binary', - nargs: 1 + description: 'Filename for the compiled binary' } }), handler: (args) => { @@ -113,22 +111,18 @@ export default ({ commandProcessor, root }) => { options: { u: { description: 'your username', - alias: 'username', - nargs: 1 + alias: 'username' }, p: { description: 'your password', - alias: 'password', - nargs: 1 + alias: 'password' }, t: { description: 'an existing Particle access token to use', - alias: 'token', - nargs: 1 + alias: 'token' }, otp: { - description: 'the login code if two-step authentication is enabled', - nargs: 1 + description: 'the login code if two-step authentication is enabled' } }, handler: (args) => { diff --git a/src/cli/config.js b/src/cli/config.js index d1463fc92..b5c4ba281 100644 --- a/src/cli/config.js +++ b/src/cli/config.js @@ -3,6 +3,7 @@ export default ({ commandProcessor, root }) => { params: '[profile] [setting] [value]', options: { 'list': { + boolean: true, description: 'Display available configurations' } }, diff --git a/src/cli/flash.js b/src/cli/flash.js index c8995e0ab..5b2679a36 100644 --- a/src/cli/flash.js +++ b/src/cli/flash.js @@ -27,12 +27,10 @@ export default ({ commandProcessor, root }) => { description: 'Answer yes to all questions' }, 'target': { - description: 'The firmware version to compile against. Defaults to latest version, or version on device for cellular.', - nargs: 1 + description: 'The firmware version to compile against. Defaults to latest version, or version on device for cellular.' }, 'port': { - describe: 'Use this serial port instead of auto-detecting. Useful if there are more than 1 connected device. Only available for serial', - nargs: 1 + describe: 'Use this serial port instead of auto-detecting. Useful if there are more than 1 connected device. Only available for serial' } }, handler: (args) => { diff --git a/src/cli/keys.js b/src/cli/keys.js index 6f3a1ae72..0c8b6aeec 100644 --- a/src/cli/keys.js +++ b/src/cli/keys.js @@ -3,8 +3,7 @@ export default ({ commandProcessor, root }) => { const protocolOption = { 'protocol': { - description: 'Communication protocol for the device using the key. tcp or udp', - nargs: 1 + description: 'Communication protocol for the device using the key. tcp or udp' } }; @@ -45,8 +44,7 @@ export default ({ commandProcessor, root }) => { options: { 'product_id': { number: true, - description: 'The product ID to use when provisioning a new device', - nargs: 1 + description: 'The product ID to use when provisioning a new device' } }, handler: (args) => { @@ -69,12 +67,11 @@ export default ({ commandProcessor, root }) => { params: '[filename]', options: Object.assign({}, protocolOption, { 'host': { - description: 'Hostname or IP address of the server to add to the key', - nargs: 1 + description: 'Hostname or IP address of the server to add to the key' }, 'port': { - description: 'Port number of the server to add to the key', - nargs: 1 + number: true, + description: 'Port number of the server to add to the key' } }), handler: (args) => { diff --git a/src/cli/library.js b/src/cli/library.js index eb10ddd9c..30ffaf5fa 100644 --- a/src/cli/library.js +++ b/src/cli/library.js @@ -16,7 +16,6 @@ export default ({ commandProcessor, root }) => { const lib = commandProcessor.createCategory(root, 'library', 'Manage firmware libraries', { alias: 'libraries' }); commandProcessor.createCommand(lib, 'add', 'Add a library to the current project.', { - options: {}, params: '', handler: (...args) => require('./library_add').command(api(), ...args), examples: { @@ -27,16 +26,13 @@ export default ({ commandProcessor, root }) => { commandProcessor.createCommand(lib, 'create', 'Create a new library in the specified or current directory', { options: { 'name': { - description: 'The name of the library to create.', - nargs: 1 + description: 'The name of the library to create.' }, 'version': { - description: 'The initial version of the library to create.', - nargs: 1 + description: 'The initial version of the library to create.' }, 'author': { - description: 'The author of the library.', - nargs: 1 + description: 'The author of the library.' } }, handler: (...args) => require('./library_init').command(...args) @@ -58,7 +54,6 @@ export default ({ commandProcessor, root }) => { alias: 'y' }, 'dest': { - boolean: false, description: 'the directory to install to' } }, @@ -74,20 +69,19 @@ export default ({ commandProcessor, root }) => { commandProcessor.createCommand(lib, 'list', 'List libraries available', { options: { 'filter': { - description: 'filters libraries not matching the text', - nargs: 1 + description: 'filters libraries not matching the text' }, 'non-interactive': { boolean: true, description: 'Prints a single page of libraries without prompting' }, 'page': { - description: 'Start the listing at the given page number', - nargs: 1 + number: true, + description: 'Start the listing at the given page number' }, 'limit': { - description: 'The number of items to show per page', - nargs: 1 + number: true, + description: 'The number of items to show per page' } }, params: '[sections...]', @@ -128,7 +122,6 @@ export default ({ commandProcessor, root }) => { }); commandProcessor.createCommand(lib, 'publish', 'Publish a private library, making it public', { - options: {}, params: '[name]', handler: (...args) => require('./library_publish').command(api(), ...args) }); diff --git a/src/cli/project.js b/src/cli/project.js index bd1570d97..94764daa4 100644 --- a/src/cli/project.js +++ b/src/cli/project.js @@ -6,8 +6,7 @@ export default ({ commandProcessor, root }) => { commandProcessor.createCommand(project, 'create', 'Create a new project in the current or specified directory', { options: { 'name' : { - description: 'provide a name for the project', - nargs: 1 + description: 'provide a name for the project' } }, params: '[dir]', diff --git a/src/cli/serial.js b/src/cli/serial.js index 52262e655..1706b3dd4 100644 --- a/src/cli/serial.js +++ b/src/cli/serial.js @@ -5,8 +5,7 @@ export default ({ commandProcessor, root }) => { const portOption = { 'port': { - describe: 'Use this serial port instead of auto-detecting. Useful if there are more than 1 connected device', - nargs: 1 + describe: 'Use this serial port instead of auto-detecting. Useful if there are more than 1 connected device' } }; @@ -41,8 +40,7 @@ export default ({ commandProcessor, root }) => { commandProcessor.createCommand(serial, 'wifi', 'Configure Wi-Fi credentials over serial', { options: Object.assign({ 'file': { - description: 'Take the credentials from a JSON file instead of prompting for them', - nargs: 1 + description: 'Take the credentials from a JSON file instead of prompting for them' } }, portOption), handler: (args) => { diff --git a/src/cli/subscribe.js b/src/cli/subscribe.js index 219d49f0f..a42579799 100644 --- a/src/cli/subscribe.js +++ b/src/cli/subscribe.js @@ -7,8 +7,7 @@ export default ({ commandProcessor, root }) => { description: 'Listen to all events instead of just those from my devices' }, 'device': { - describe: 'Listen to events from this device only', - nargs: 1 + describe: 'Listen to events from this device only' } }, handler: (args) => { diff --git a/test/app/command-processor.spec.js b/test/app/command-processor.spec.js index 807861ac9..5ebed650f 100644 --- a/test/app/command-processor.spec.js +++ b/test/app/command-processor.spec.js @@ -167,7 +167,7 @@ describe('command-line parsing', () => { it('can accept options', () => { // see '.choices()` https://www.npmjs.com/package/yargs const app = commandProcessor.createAppCategory({ options: { - cm: { alias: 'chundermonkey' } + cm: { alias: 'chundermonkey', boolean: true } }}); expect(commandProcessor.parse(app, ['--cm'])).to.have.property('cm').equal(true); @@ -177,10 +177,10 @@ describe('command-line parsing', () => { it('refuses options meant for other commands', () => { const app = commandProcessor.createAppCategory(); const one = commandProcessor.createCommand(app, 'one', 'first', { options: { - one: { alias: '1', description: ''} + one: { alias: '1', description: '', boolean: true } }}); const two = commandProcessor.createCommand(app, 'two', 'second', { options: { - two: { alias: '2', description: ''} + two: { alias: '2', description: '', boolean: true } }}); // sanity test @@ -203,6 +203,17 @@ describe('command-line parsing', () => { test: { alias: 'flag', boolean: true + }, + string: { + }, + multipleStrings: { + nargs: 2 + }, + number: { + number: true, + }, + count: { + count: true } } }); @@ -312,6 +323,30 @@ describe('command-line parsing', () => { .deep.equal(commandProcessor.errors.unknownParametersError(['stragglers', 'here'])); }); + it('flags default to strings', () => { + const result = paramsCommand('', ['--string', '42']); + expect(result).to.have.property('string').equal('42'); + }); + + it('flags require one argument by default', () => { + expect(() => paramsCommand('', ['--string'])).to.throw(/Not enough arguments following: string/); + }); + + it('flags can require multiple arguments', () => { + const result = paramsCommand('', ['--multipleStrings', '1', '2']); + expect(result).to.have.property('multipleStrings').deep.equal(['1', '2']); + }); + + it('number flag get converted', () => { + const result = paramsCommand('', ['--number', '42']); + expect(result).to.have.property('number').equal(42); + }); + + it('count flag get counted', () => { + const result = paramsCommand('', ['--count', '--count']); + expect(result).to.have.property('count').equal(2); + }); + it('keeps device ids as strings', () => { const result = paramsCommand('', ['500000000000000000000000']).params; expect(result).to.have.property('deviceid').equal('500000000000000000000000');