From 94b7f1b2962515b3c14b64ef828682bd09ea9518 Mon Sep 17 00:00:00 2001 From: heyjiawei Date: Tue, 18 Aug 2020 23:49:48 +0800 Subject: [PATCH 01/11] Add documentation on tricks and traps of using optional options --- optional-options-docs.md | 72 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 optional-options-docs.md diff --git a/optional-options-docs.md b/optional-options-docs.md new file mode 100644 index 000000000..eb62d1038 --- /dev/null +++ b/optional-options-docs.md @@ -0,0 +1,72 @@ +# Tricks and traps when using options with optional values + +There potential challenges using options with optional values. They seem quite attractive and the README used to use them more than options with require values but in practice, they are a bit tricky and aren't a free choice. + +## Parsing ambiguity + +There is parsing ambiguity when using option as boolean flag and also having it accept operands and subcommands. + +``` +program.command('example') + .option("-o, --option [optionalValue]") + .command('brew') + .action(() => { + console.log("example brew"); + }) + +program.parse(process.argv); + +if (program.option) console.log(`option: ${program.option}`); +``` + +``` +$ example -o +option: true +$ example -o thisValueIsPassedToOption +option: thisValueIsPassedToOption +$ example -o brew +option: brew +$ example -o nextArg +option: nextArg +``` + +For example, the user may intend `brew` to be passed as a subcommand. Instead, it will be read as the passed in value for `-o`. Likewise, the user may intend `nextArg` to be passed as an argument but it too, will be read as the passed in value for `-o` + +### Possible workarounds + +To reduce such ambiguity, you can do the following: + +1. always use `--` before operands +2. add your options after operands +3. convert arguments into options! Options work pretty nicely together. + +## Combining short flags with optional values + +``` +program + .option("-a, --any [count]", "any servings") + .option("-v, --vegan [count]", "vegan servings") + .option("-l, --halal [count]", "halal servings"); +program.parse(process.argv); + +if (program.any) console.log(`any servings: ${program.any}`); +if (program.vegan) console.log(`vegan servings: ${program.vegan}`); +if (program.halal) console.log(`halal servings: ${program.halal}`); + +``` + +In this example, you have to take note that optional options consume the value after the short flag. + +``` +$ collect -avl +any servings: vl +``` + +If you wish to use optional options as boolean options, you need to explicity list them as individual options. + +``` +$ collect -a -v -l +any servings: true +vegan servings: true +halal servings: true +``` From 0588e305e6c69729d1c812f743b38e382ac96104 Mon Sep 17 00:00:00 2001 From: heyjiawei Date: Thu, 20 Aug 2020 00:11:18 +0800 Subject: [PATCH 02/11] Fix grammar and example. Add future works --- optional-options-docs.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/optional-options-docs.md b/optional-options-docs.md index eb62d1038..470ed50e5 100644 --- a/optional-options-docs.md +++ b/optional-options-docs.md @@ -1,6 +1,6 @@ # Tricks and traps when using options with optional values -There potential challenges using options with optional values. They seem quite attractive and the README used to use them more than options with require values but in practice, they are a bit tricky and aren't a free choice. +There are potential challenges using options with optional values. They seem quite attractive and the README used to use them more than options with require values but in practice, they are a bit tricky and aren't a free choice. ## Parsing ambiguity @@ -30,7 +30,7 @@ $ example -o nextArg option: nextArg ``` -For example, the user may intend `brew` to be passed as a subcommand. Instead, it will be read as the passed in value for `-o`. Likewise, the user may intend `nextArg` to be passed as an argument but it too, will be read as the passed in value for `-o` +For example, you may intend `brew` to be passed as a subcommand. Instead, it will be read as the passed in value for `-o`. Likewise, you may intend `nextArg` to be passed as an argument but it too, will be read as the passed in value for `-o` ### Possible workarounds @@ -44,12 +44,12 @@ To reduce such ambiguity, you can do the following: ``` program - .option("-a, --any [count]", "any servings") + .option("-o, --others [count]", "others servings") .option("-v, --vegan [count]", "vegan servings") .option("-l, --halal [count]", "halal servings"); program.parse(process.argv); -if (program.any) console.log(`any servings: ${program.any}`); +if (program.others) console.log(`others servings: ${program.others}`); if (program.vegan) console.log(`vegan servings: ${program.vegan}`); if (program.halal) console.log(`halal servings: ${program.halal}`); @@ -70,3 +70,9 @@ any servings: true vegan servings: true halal servings: true ``` + +### Behaviour change from v5 to v6 + +Before Commander v5, `-ob` expanded to `-o -b`, which is different from the current behaviour in Commander v6 as explained above. + +This new behaviour may be an issue for people upgrading from older versions of Commander but we do have plans to prioritise combining flags over combining flag-and-value in the future. From 3f8623e5eb63e07643f5214559c633939f613414 Mon Sep 17 00:00:00 2001 From: heyjiawei Date: Mon, 24 Aug 2020 22:21:53 +0800 Subject: [PATCH 03/11] Update variadic optional options --- optional-options-docs.md | 53 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/optional-options-docs.md b/optional-options-docs.md index 470ed50e5..1e9e70083 100644 --- a/optional-options-docs.md +++ b/optional-options-docs.md @@ -62,7 +62,7 @@ $ collect -avl any servings: vl ``` -If you wish to use optional options as boolean options, you need to explicity list them as individual options. +If you wish to use optional options as boolean options, you need to explicitly list them as individual options. ``` $ collect -a -v -l @@ -71,6 +71,57 @@ vegan servings: true halal servings: true ``` +Likewise for variadic options. While options can have a single optional value, variadic options can take in multiple optional values and have the same parsing complications. + +``` +program + .option("-c, --citizenship ", "countries you hold passport of") // 1 or more value(s) + .option("-i, --illness [illness...]", "known illness before travel") // 0 or more value(s) + .option("-s, --visa-approved [status]", "status of visa if not approved"); // 0 ir 1 value + +program.parse(); + +console.log(`Citizen of: `, program.citizenship); +console.log(`Known illness(es): `, program.illness); +console.log(`visa approved: ${program.visaApproved}`); +``` + +Optional options consume the value after the short flag and you will experience the following behaviour: + +``` +$ node opt.js -si +Known illness(es): undefined +visa approved: i + +$ node opt.js -is +Known illness(es): [ 's' ] +visa approved: undefined +``` + +If you wish to use variadic optional options as booleans, you will need to state them explicitly as follows: + +``` +$ node opt.js -i -s +Known illness(es): true +visa approved: true +``` + +You should also be careful when you mix variadic optional options with variadic required options. A required option **always** consumes a value and so, you will not get any errors when the first value passed to it contains a '-' like so: + +``` +$ node opt.js -c -si +Citizen of: [ '-si' ] // Does not throw error +``` + +``` +$ node opt.js -c -si -x +error: unknown option '-x' + +$ node opt.js -c -si -i +Citizen of: [ '-si' ] +Known illness(es): true +``` + ### Behaviour change from v5 to v6 Before Commander v5, `-ob` expanded to `-o -b`, which is different from the current behaviour in Commander v6 as explained above. From 57467045cb0a66304bd474ce61664dd8502e5413 Mon Sep 17 00:00:00 2001 From: heyjiawei Date: Mon, 24 Aug 2020 23:53:29 +0800 Subject: [PATCH 04/11] Add terminology section --- optional-options-docs.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/optional-options-docs.md b/optional-options-docs.md index 1e9e70083..0557f9025 100644 --- a/optional-options-docs.md +++ b/optional-options-docs.md @@ -2,6 +2,15 @@ There are potential challenges using options with optional values. They seem quite attractive and the README used to use them more than options with require values but in practice, they are a bit tricky and aren't a free choice. +## Terminology + +| Term(s) | Explanation | code example (if any) | +| ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- | +| option(s), flags, non-positional arguments | The term options consist of hyphen-minus characters
(that is ‘-’) followed by letters or digits.
options can take an argument or choose not to.
options that do not take an argument are term boolean flag(s) or boolean option(s) | `.option('-s, --small', 'small pizza size')` | +| optional value(s), option argument(s) | options are followed by an option argument.
If they are enclosed with square brackets `[]`, these option arguments are optional. | `.option('-o, --option [optionalValue]')` | +| operand(s), non-option argument(s) | arguments following the last options and option-arguments are named “operands” | `.arguments('[file]')` | +| optional options | flags that take in optional argument | + ## Parsing ambiguity There is parsing ambiguity when using option as boolean flag and also having it accept operands and subcommands. @@ -38,7 +47,7 @@ To reduce such ambiguity, you can do the following: 1. always use `--` before operands 2. add your options after operands -3. convert arguments into options! Options work pretty nicely together. +3. convert operands into options! Options work pretty nicely together. ## Combining short flags with optional values From 9812ee71eecfa911188f29d95fcd0b347a118991 Mon Sep 17 00:00:00 2001 From: heyjiawei Date: Tue, 25 Aug 2020 22:40:40 +0800 Subject: [PATCH 05/11] Add that commander follows GNU utility convention --- optional-options-docs.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/optional-options-docs.md b/optional-options-docs.md index 0557f9025..31051fe76 100644 --- a/optional-options-docs.md +++ b/optional-options-docs.md @@ -49,6 +49,8 @@ To reduce such ambiguity, you can do the following: 2. add your options after operands 3. convert operands into options! Options work pretty nicely together. +The POSIX convention is that options always come before operands. The GNU utility convention allows options to come before or after the operands. Commander follows the GNU convention and allows options before or after the operands. So by putting the options last, the option values do not get confused with the operands. + ## Combining short flags with optional values ``` From c686ee325f3303ddfbd6590066e25329b76c8209 Mon Sep 17 00:00:00 2001 From: heyjiawei Date: Tue, 25 Aug 2020 22:57:04 +0800 Subject: [PATCH 06/11] Update terminology; remove our terms from official terms --- optional-options-docs.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/optional-options-docs.md b/optional-options-docs.md index 31051fe76..89774f293 100644 --- a/optional-options-docs.md +++ b/optional-options-docs.md @@ -7,13 +7,12 @@ There are potential challenges using options with optional values. They seem qui | Term(s) | Explanation | code example (if any) | | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- | | option(s), flags, non-positional arguments | The term options consist of hyphen-minus characters
(that is ‘-’) followed by letters or digits.
options can take an argument or choose not to.
options that do not take an argument are term boolean flag(s) or boolean option(s) | `.option('-s, --small', 'small pizza size')` | -| optional value(s), option argument(s) | options are followed by an option argument.
If they are enclosed with square brackets `[]`, these option arguments are optional. | `.option('-o, --option [optionalValue]')` | +| option argument(s) | options are followed by an option argument.
If they are enclosed with square brackets `[]`, these option arguments are optional. | `.option('-o, --option [optionalValue]')` | | operand(s), non-option argument(s) | arguments following the last options and option-arguments are named “operands” | `.arguments('[file]')` | -| optional options | flags that take in optional argument | ## Parsing ambiguity -There is parsing ambiguity when using option as boolean flag and also having it accept operands and subcommands. +There is parsing ambiguity when using option as boolean flag and also having it accept operands(sometimes called a positional argument or command argument, referring to `Command.arguments()`) and subcommands. ``` program.command('example') @@ -53,6 +52,10 @@ The POSIX convention is that options always come before operands. The GNU utilit ## Combining short flags with optional values +optional options are option(s) which functions as a flag but may also take a value (declared using square brackets). + +optional values (sometimes called option arguments) are values of these optional flag. + ``` program .option("-o, --others [count]", "others servings") From 3e29566b4b0ebaad9108d768f734083e39ceddec Mon Sep 17 00:00:00 2001 From: heyjiawei Date: Fri, 28 Aug 2020 23:58:45 +0800 Subject: [PATCH 07/11] Update example using option as boolean flag and also having it accept operands --- optional-options-docs.md | 44 +++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/optional-options-docs.md b/optional-options-docs.md index 89774f293..4a6ca13ee 100644 --- a/optional-options-docs.md +++ b/optional-options-docs.md @@ -12,33 +12,39 @@ There are potential challenges using options with optional values. They seem qui ## Parsing ambiguity -There is parsing ambiguity when using option as boolean flag and also having it accept operands(sometimes called a positional argument or command argument, referring to `Command.arguments()`) and subcommands. +There is parsing ambiguity when using option as boolean flag and also having it accept operands (sometimes called a positional argument or command argument, referring to `Command.arguments()`) and subcommands. ``` -program.command('example') - .option("-o, --option [optionalValue]") - .command('brew') - .action(() => { - console.log("example brew"); - }) - -program.parse(process.argv); +program + .arguments("[technique]") + .option("-i, --ingredient [ingredient]") + .action((args, cmdObj) => { + console.log(args); + console.log(cmdObj.opts()); + }); -if (program.option) console.log(`option: ${program.option}`); +program.parse(); ``` ``` -$ example -o -option: true -$ example -o thisValueIsPassedToOption -option: thisValueIsPassedToOption -$ example -o brew -option: brew -$ example -o nextArg -option: nextArg +$ cook scrambled +scrambled +{ ingredient: undefined } + +$ cook -i +undefined +{ ingredient: true } + +$ cook -i egg +undefined +{ ingredient: egg } + +$ cook -i scrambled +undefined +{ ingredient: scrambled } ``` -For example, you may intend `brew` to be passed as a subcommand. Instead, it will be read as the passed in value for `-o`. Likewise, you may intend `nextArg` to be passed as an argument but it too, will be read as the passed in value for `-o` +For example, you may intend `scrambled` to be passed as a non-option argument. Instead, it will be read as the passed in value for ingredient. ### Possible workarounds From 5a2b953f5c2d9c3612ec63af0d730819aff68ea6 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sun, 30 Aug 2020 19:55:01 +1200 Subject: [PATCH 08/11] Refactor and expand intro and parse ambiguity --- optional-options-docs.md | 172 ++++++++++++++++++++++++++++++++------- 1 file changed, 141 insertions(+), 31 deletions(-) diff --git a/optional-options-docs.md b/optional-options-docs.md index 4a6ca13ee..9944dc02a 100644 --- a/optional-options-docs.md +++ b/optional-options-docs.md @@ -1,60 +1,170 @@ -# Tricks and traps when using options with optional values +# More About Options -There are potential challenges using options with optional values. They seem quite attractive and the README used to use them more than options with require values but in practice, they are a bit tricky and aren't a free choice. +The README covers declaring and using options, and mostly parsing will work the way you and your users expect. This page covers some special cases +and subtle issues in depth. + +- [More About Options](#more-about-options) + - [Terminology](#terminology) + - [Options taking varying numbers of option-arguments](#options-taking-varying-numbers-of-option-arguments) + - [Parsing ambiguity](#parsing-ambiguity) + - [Alternative: Make `--` part of your syntax](#alternative-make----part-of-your-syntax) + - [Alternative: Put options last](#alternative-put-options-last) + - [Alternative: Use options instead of command-arguments](#alternative-use-options-instead-of-command-arguments) + - [Combining short flags with optional values](#combining-short-flags-with-optional-values) + - [Behaviour change from v5 to v6](#behaviour-change-from-v5-to-v6) ## Terminology -| Term(s) | Explanation | code example (if any) | -| ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- | -| option(s), flags, non-positional arguments | The term options consist of hyphen-minus characters
(that is ‘-’) followed by letters or digits.
options can take an argument or choose not to.
options that do not take an argument are term boolean flag(s) or boolean option(s) | `.option('-s, --small', 'small pizza size')` | -| option argument(s) | options are followed by an option argument.
If they are enclosed with square brackets `[]`, these option arguments are optional. | `.option('-o, --option [optionalValue]')` | -| operand(s), non-option argument(s) | arguments following the last options and option-arguments are named “operands” | `.arguments('[file]')` | +_Work in progress: this section may move to the main README, or a page of its own._ + +The command line arguments are made up of options, option-arguments, commands, and command-arguments. -## Parsing ambiguity +| Term | Explanation | +| --- | --- | +| option | an argument which begins with a dash and a character, or a double-dash and a word | +| option-argument| some options can take an argument | +| command | a program or command can have subcommands | +| command-argument | argument for the command (and not an option or option-argument) | -There is parsing ambiguity when using option as boolean flag and also having it accept operands (sometimes called a positional argument or command argument, referring to `Command.arguments()`) and subcommands. +For example: +```sh +my-utility command -o --option option-argument command-argument-1 command-argument-2 ``` + +In other references options are sometimes called flags, and command-arguments are sometimes called positional arguments. + +## Options taking varying numbers of option-arguments + +Certain options take a varying number of option-arguments: + +```js program - .arguments("[technique]") - .option("-i, --ingredient [ingredient]") - .action((args, cmdObj) => { - console.log(args); - console.log(cmdObj.opts()); + .option('-c, --compress [percentage]') // 0 or 1 + .option('--preprocess ') // 1 or more + .option('--test [name...]') // 0 or more +``` + +### Parsing ambiguity + +There is a potential downside to be aware of. If a command has both +command-arguments and options with varying option-arguments, this introduces a parsing ambiguity which may affect the user of your program. +Commander looks for option-arguments first, but the user may +intend the argument following the option as a command or command-argument. + +```js +program + .name('cook') + .arguments('[technique]') + .option('-i, --ingredient [ingredient]', 'add cheese or given ingredient') + .action((technique, options) => { + console.log(`technique: ${technique}`); + const ingredient = (options.ingredient === true) ? 'cheese' : options.ingredient; + console.log(`ingredient: ${ingredient}`); }); program.parse(); ``` -``` +```sh $ cook scrambled -scrambled -{ ingredient: undefined } +technique: scrambled +ingredient: undefined $ cook -i -undefined -{ ingredient: true } +technique: undefined +ingredient: cheese $ cook -i egg -undefined -{ ingredient: egg } +technique: undefined +ingredient: egg + +$ cook -i scrambled # oops +technique: undefined +ingredient: scrambled +``` + +The explicit way to resolve this is use `--` to indicate the end of the options and option-arguments: -$ cook -i scrambled -undefined -{ ingredient: scrambled } +```sh +$ node cook.js -i -- egg +technique: egg +ingredient: cheese ``` -For example, you may intend `scrambled` to be passed as a non-option argument. Instead, it will be read as the passed in value for ingredient. +If you want to avoid your users needing to learn when to use `--`, there are a few approaches you could take. + +### Alternative: Make `--` part of your syntax + +Rather than trying to teach your users what `--` does, you could just make it part of your syntax. + +```js +program.usage('[options] -- [technique]'); +``` -### Possible workarounds +```sh +$ cook --help +Usage: cook [options] -- [technique] -To reduce such ambiguity, you can do the following: +Options: + -i, --ingredient [ingredient] add cheese or given ingredient + -h, --help display help for command -1. always use `--` before operands -2. add your options after operands -3. convert operands into options! Options work pretty nicely together. +$ cook -- scrambled +technique: scrambled +ingredient: undefined + +$ cook -i -- scrambled +technique: scrambled +ingredient: cheese +``` + +### Alternative: Put options last + +Commander follows the GNU convention for parsing and allows options before or after the command-arguments, or intermingled. +So by putting the options last, the command-arguments do not get confused with the option-arguments. + +```js +program.usage('[technique] [options]'); +``` + +```sh +$ cook --help +Usage: cook [technique] [options] + +Options: + -i, --ingredient [ingredient] add cheese or given ingredient + -h, --help display help for command + +$ node cook.js scrambled -i +technique: scrambled +ingredient: cheese +``` + +### Alternative: Use options instead of command-arguments + +This is a bit more radical, but completely avoids the parsing ambiguity! + +```js +program + .name('cook') + .option('-t, --technique ', 'cooking technique') + .option('-i, --ingredient [ingredient]', 'add cheese or given ingredient') + .action((options) => { + console.log(`technique: ${options.technique}`); + const ingredient = (options.ingredient === true) ? 'cheese' : options.ingredient; + console.log(`ingredient: ${ingredient}`); + }); +``` + +```sh +$ cook -i -t scrambled +technique: scrambled +ingredient: cheese +``` -The POSIX convention is that options always come before operands. The GNU utility convention allows options to come before or after the operands. Commander follows the GNU convention and allows options before or after the operands. So by putting the options last, the option values do not get confused with the operands. +------ +_Work in progress: unreviewed from here down._ ## Combining short flags with optional values From 53cf35be1edf69df8f09f5ff36f0eaa16b48c85e Mon Sep 17 00:00:00 2001 From: John Gee Date: Sun, 30 Aug 2020 20:18:07 +1200 Subject: [PATCH 09/11] Expand fragments a little, reword GNU reference --- optional-options-docs.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/optional-options-docs.md b/optional-options-docs.md index 9944dc02a..b407ecd9e 100644 --- a/optional-options-docs.md +++ b/optional-options-docs.md @@ -99,7 +99,9 @@ If you want to avoid your users needing to learn when to use `--`, there are a f Rather than trying to teach your users what `--` does, you could just make it part of your syntax. ```js +// ... program.usage('[options] -- [technique]'); +program.parse(); ``` ```sh @@ -121,11 +123,13 @@ ingredient: cheese ### Alternative: Put options last -Commander follows the GNU convention for parsing and allows options before or after the command-arguments, or intermingled. +Commander allows options before or after the command-arguments, or even intermixed (like GNU program argument conventions). So by putting the options last, the command-arguments do not get confused with the option-arguments. ```js +// ... program.usage('[technique] [options]'); +program.parse(); ``` ```sh @@ -155,6 +159,8 @@ program const ingredient = (options.ingredient === true) ? 'cheese' : options.ingredient; console.log(`ingredient: ${ingredient}`); }); + +program.parse(); ``` ```sh From 8113f41a910b3740f2fa2b92c60eae8167d45a0a Mon Sep 17 00:00:00 2001 From: John Gee Date: Sun, 30 Aug 2020 20:25:01 +1200 Subject: [PATCH 10/11] Some tweaks, and add GNU link --- optional-options-docs.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/optional-options-docs.md b/optional-options-docs.md index b407ecd9e..eeb1bbb0a 100644 --- a/optional-options-docs.md +++ b/optional-options-docs.md @@ -21,7 +21,7 @@ The command line arguments are made up of options, option-arguments, commands, a | Term | Explanation | | --- | --- | -| option | an argument which begins with a dash and a character, or a double-dash and a word | +| option | an argument which begins with a dash and a character, or a double-dash and a string | | option-argument| some options can take an argument | | command | a program or command can have subcommands | | command-argument | argument for the command (and not an option or option-argument) | @@ -123,7 +123,7 @@ ingredient: cheese ### Alternative: Put options last -Commander allows options before or after the command-arguments, or even intermixed (like GNU program argument conventions). +Commander allows options before or after the command-arguments, or even intermixed (like [GNU program argument conventions](https://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html)). So by putting the options last, the command-arguments do not get confused with the option-arguments. ```js From 3434f4d2773e51481c84925e776fe4678b58a4cd Mon Sep 17 00:00:00 2001 From: John Gee Date: Fri, 4 Sep 2020 20:54:36 +1200 Subject: [PATCH 11/11] Rework, and move file --- .../options-taking-varying-arguments.md | 163 +++++++----------- 1 file changed, 59 insertions(+), 104 deletions(-) rename optional-options-docs.md => docs/options-taking-varying-arguments.md (53%) diff --git a/optional-options-docs.md b/docs/options-taking-varying-arguments.md similarity index 53% rename from optional-options-docs.md rename to docs/options-taking-varying-arguments.md index eeb1bbb0a..566216f15 100644 --- a/optional-options-docs.md +++ b/docs/options-taking-varying-arguments.md @@ -1,42 +1,18 @@ -# More About Options +# Options taking varying numbers of option-arguments The README covers declaring and using options, and mostly parsing will work the way you and your users expect. This page covers some special cases and subtle issues in depth. -- [More About Options](#more-about-options) - - [Terminology](#terminology) - - [Options taking varying numbers of option-arguments](#options-taking-varying-numbers-of-option-arguments) - - [Parsing ambiguity](#parsing-ambiguity) +- [Options taking varying numbers of option-arguments](#options-taking-varying-numbers-of-option-arguments) + - [Parsing ambiguity](#parsing-ambiguity) - [Alternative: Make `--` part of your syntax](#alternative-make----part-of-your-syntax) - [Alternative: Put options last](#alternative-put-options-last) - [Alternative: Use options instead of command-arguments](#alternative-use-options-instead-of-command-arguments) - - [Combining short flags with optional values](#combining-short-flags-with-optional-values) - - [Behaviour change from v5 to v6](#behaviour-change-from-v5-to-v6) - -## Terminology - -_Work in progress: this section may move to the main README, or a page of its own._ - -The command line arguments are made up of options, option-arguments, commands, and command-arguments. - -| Term | Explanation | -| --- | --- | -| option | an argument which begins with a dash and a character, or a double-dash and a string | -| option-argument| some options can take an argument | -| command | a program or command can have subcommands | -| command-argument | argument for the command (and not an option or option-argument) | - -For example: - -```sh -my-utility command -o --option option-argument command-argument-1 command-argument-2 -``` - -In other references options are sometimes called flags, and command-arguments are sometimes called positional arguments. - -## Options taking varying numbers of option-arguments + - [Combining short options, and options taking arguments](#combining-short-options-and-options-taking-arguments) + - [Combining short options as if boolean](#combining-short-options-as-if-boolean) + - [Terminology](#terminology) -Certain options take a varying number of option-arguments: +Certain options take a varying number of arguments: ```js program @@ -45,7 +21,9 @@ program .option('--test [name...]') // 0 or more ``` -### Parsing ambiguity +This page uses examples with options taking 0 or 1 arguments, but the discussions also apply to variadic options taking more arguments. + +## Parsing ambiguity There is a potential downside to be aware of. If a command has both command-arguments and options with varying option-arguments, this introduces a parsing ambiguity which may affect the user of your program. @@ -79,7 +57,7 @@ $ cook -i egg technique: undefined ingredient: egg -$ cook -i scrambled # oops +$ cook -i scrambled # oops technique: undefined ingredient: scrambled ``` @@ -99,9 +77,7 @@ If you want to avoid your users needing to learn when to use `--`, there are a f Rather than trying to teach your users what `--` does, you could just make it part of your syntax. ```js -// ... program.usage('[options] -- [technique]'); -program.parse(); ``` ```sh @@ -123,13 +99,11 @@ ingredient: cheese ### Alternative: Put options last -Commander allows options before or after the command-arguments, or even intermixed (like [GNU program argument conventions](https://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html)). +Commander follows the GNU convention for parsing and allows options before or after the command-arguments, or intermingled. So by putting the options last, the command-arguments do not get confused with the option-arguments. ```js -// ... program.usage('[technique] [options]'); -program.parse(); ``` ```sh @@ -159,8 +133,6 @@ program const ingredient = (options.ingredient === true) ? 'cheese' : options.ingredient; console.log(`ingredient: ${ingredient}`); }); - -program.parse(); ``` ```sh @@ -168,37 +140,41 @@ $ cook -i -t scrambled technique: scrambled ingredient: cheese ``` +## Combining short options, and options taking arguments ------- -_Work in progress: unreviewed from here down._ - -## Combining short flags with optional values +Multiple boolean short options can be combined after a single `-`, like `ls -al`. You can also include just +a single short option which might take a value, as any following characters will +be taken as the value. -optional options are option(s) which functions as a flag but may also take a value (declared using square brackets). +This means that by default you can not combine short options which may take an argument. -optional values (sometimes called option arguments) are values of these optional flag. - -``` +```js program - .option("-o, --others [count]", "others servings") - .option("-v, --vegan [count]", "vegan servings") - .option("-l, --halal [count]", "halal servings"); + .name('collect') + .option("-o, --other [count]", "other serving(s)") + .option("-v, --vegan [count]", "vegan serving(s)") + .option("-l, --halal [count]", "halal serving(s)"); program.parse(process.argv); -if (program.others) console.log(`others servings: ${program.others}`); -if (program.vegan) console.log(`vegan servings: ${program.vegan}`); -if (program.halal) console.log(`halal servings: ${program.halal}`); - +const opts = program.opts(); +if (opts.other) console.log(`other servings: ${opts.other}`); +if (opts.vegan) console.log(`vegan servings: ${opts.vegan}`); +if (opts.halal) console.log(`halal servings: ${opts.halal}`); ``` -In this example, you have to take note that optional options consume the value after the short flag. - -``` -$ collect -avl -any servings: vl +```sh +$ collect -o 3 +other servings: 3 +$ collect -o3 +other servings: 3 +$ collect -l -v +vegan servings: true +halal servings: true +$ collect -lv # oops +halal servings: v ``` -If you wish to use optional options as boolean options, you need to explicitly list them as individual options. +If you wish to use options taking varying arguments as boolean options, you need to specify them separately. ``` $ collect -a -v -l @@ -207,59 +183,38 @@ vegan servings: true halal servings: true ``` -Likewise for variadic options. While options can have a single optional value, variadic options can take in multiple optional values and have the same parsing complications. - -``` -program - .option("-c, --citizenship ", "countries you hold passport of") // 1 or more value(s) - .option("-i, --illness [illness...]", "known illness before travel") // 0 or more value(s) - .option("-s, --visa-approved [status]", "status of visa if not approved"); // 0 ir 1 value - -program.parse(); +### Combining short options as if boolean -console.log(`Citizen of: `, program.citizenship); -console.log(`Known illness(es): `, program.illness); -console.log(`visa approved: ${program.visaApproved}`); -``` +Before Commander v5, combining a short option and the value was not supported, and combined short flags were always expanded. +So `-avl` expanded to `-a -v -l`. -Optional options consume the value after the short flag and you will experience the following behaviour: +If you want backwards compatible behaviour, or prefer combining short options as booleans to combining short option and value, +you may change the behavior. -``` -$ node opt.js -si -Known illness(es): undefined -visa approved: i +To modify the parsing of options taking an optional value: -$ node opt.js -is -Known illness(es): [ 's' ] -visa approved: undefined +```js +.combineFlagAndOptionalValue(true) // `-v45` is treated like `--vegan=45`, this is the default behaviour +.combineFlagAndOptionalValue(false) // `-vl` is treated like `-v -l` ``` -If you wish to use variadic optional options as booleans, you will need to state them explicitly as follows: +## Terminology -``` -$ node opt.js -i -s -Known illness(es): true -visa approved: true -``` +_Work in progress: this section may move to the main README, or a page of its own._ -You should also be careful when you mix variadic optional options with variadic required options. A required option **always** consumes a value and so, you will not get any errors when the first value passed to it contains a '-' like so: +The command line arguments are made up of options, option-arguments, commands, and command-arguments. -``` -$ node opt.js -c -si -Citizen of: [ '-si' ] // Does not throw error -``` +| Term | Explanation | +| --- | --- | +| option | an argument which is a `-` followed by a character, or `--` followed by a word (or hyphenated words), like `-s` or `--short` | +| option-argument| some options can take an argument | +| command | a program or command can have subcommands | +| command-argument | argument for the command (and not an option or option-argument) | -``` -$ node opt.js -c -si -x -error: unknown option '-x' +For example: -$ node opt.js -c -si -i -Citizen of: [ '-si' ] -Known illness(es): true +```sh +my-utility command -o --option option-argument command-argument-1 command-argument-2 ``` -### Behaviour change from v5 to v6 - -Before Commander v5, `-ob` expanded to `-o -b`, which is different from the current behaviour in Commander v6 as explained above. - -This new behaviour may be an issue for people upgrading from older versions of Commander but we do have plans to prioritise combining flags over combining flag-and-value in the future. +In other references options are sometimes called flags, and command-arguments are sometimes called positional arguments or operands.