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

Support click.option with default values / optional arguments #549

Closed
mbrancato opened this issue Apr 1, 2016 · 24 comments · Fixed by #1618
Closed

Support click.option with default values / optional arguments #549

mbrancato opened this issue Apr 1, 2016 · 24 comments · Fixed by #1618
Milestone

Comments

@mbrancato
Copy link

Since the default value always applies, there should be a way to differentiate between the following options usage:

file.py
file.py --foo
file.py --foo 2

where '2' is an argument to --foo. This should produce 3 different values for the destination of 'foo'. This is implemented in argparse with nargs='?' but has no functional equivalent in click (that I can identify). I've created a pull request that implements a potential solution. I'm not asking for a specific way of implementing the solution, and what I provided is mostly for a functional example. It does need test cases.
#548

@untitaker
Copy link
Contributor

How would you know if 2 is an argument to file.py or --foo?

@ZenithDK
Copy link
Contributor

I had a similar issue.
I want to check that the input values are different from the defaults, and I ended up with this (in case others want the same):

@cli.command()
@click.option('--usb')
@click.option('--adk')
@click.pass_context
def local(ctx, usb, adk):
....
    default_dict = dict(zip([command.name for command in ctx.command.params], [command.default for command in ctx.command.params]))

Then you can look up the defaults in default_dict - and compare to what you received.

@anarcat
Copy link

anarcat commented Nov 12, 2016

@untitaker ordering. first comes first served.

@anarcat
Copy link

anarcat commented Sep 10, 2017

could this be merged if unit tests are added to #548? this is a feature i'm often missing from click...

is there a way to monkeypatch our way around this limitation?

@pombredanne
Copy link
Contributor

pombredanne commented Feb 1, 2018

The thing here is that there is no way to tell if an option was entered by a user or if the default value has been used. A user-entered option that has the same value as the default is the same as a non user-entered option with a default. As a result, the simple nargs='?' pattern in argparse cannot be implemented and instead two options must be used: a flag and a value. See https://gist.github.com/pombredanne/aeab7f5b4783919dfe5265b2c553cdb8 for some examples.

Not having this capability makes a CLI bloated with more options for no good reason IMHO when wanting to use this approach

@Evidlo
Copy link

Evidlo commented Feb 6, 2018

@pombredanne argparse provides a const option in addition to default.

add_parser = subparsers.add_parser('add')
add_parser.add_argument('-w', '--words', metavar='length', type=int, nargs='?', const=5, default=None)

So my_prog add gives args.words == None, my_prog add -w gives args.words == 5, and my_prog add -w 10 gives args.words == 10.

@untitaker I honestly don't think its an issue (do what @anarcat said), but you can get around this by not having a space after short options (e.g. -n3) and by using an equals sign for long arguments (e.g. --number=3). Here's an excerpt from the manpage for man:

-X[dpi], --gxditview[=dpi]
   This option displays the output of groff in a graphical window using the gxditview program.  The dpi (dots per inch) may be 75, 75-12, 100, or 100-12, defaulting  to  75;  the  -12
   variants use a 12-point base font.  This option implies -T with the X75, X75-12, X100, or X100-12 device respectively.

This is a feature I'm missing badly too. It's keeping me from switching my CLI program from argparse.

@Evidlo Evidlo mentioned this issue Feb 6, 2018
25 tasks
@pombredanne
Copy link
Contributor

@untitaker would you like to see this in Click or not?

@mbrancato
Copy link
Author

How does Click conform to say the POSIX command line argument syntax standard without supporting optional option-arguments?

@anarcat
Copy link

anarcat commented Feb 28, 2018

@mbrancato which POSIX standard? could you link to or quote the relevant section? oh no, you can't, because IEEE standards are behind paywalls... :p

i mean i'm all for implementing this feature, but heralding POSIX here does not further the discussion.. I think the only thing that needs to happen is to complete unit tests in #548 and make sure they suffice for this issue to be fixed.

@mbrancato you wrote that code, i guess it was to resolve this issue?

how does your code resolve the following ambiguities?

--foo --bar
--foo 2 --bar
--foo -- 2 --bar
--foo --bar 2

@Evidlo
Copy link

Evidlo commented Feb 28, 2018

@untitaker
Copy link
Contributor

Excuse me, how does it not conform to those conventions?

@Evidlo
Copy link

Evidlo commented Feb 28, 2018

Click doesn't support the [-f[option_argument]] bit.

@untitaker
Copy link
Contributor

Conforming to a convention does not mean that it has to fully exercise all rights. I can conform to a protocol without using all of its features

@Evidlo
Copy link

Evidlo commented Feb 28, 2018

The document specifies a well defined behavior for [-f[option_argument]]. (no space, single letter)

If the SYNOPSIS shows an optional option-argument (as with [ -f[ option_argument]] in the example), 
a conforming application shall place any option-argument for that option directly adjacent to the option 
in the same argument string, without intervening <blank> characters. If the utility receives an 
argument containing only the option, it shall behave as specified in its description for an omitted 
option-argument; it shall not treat the next argument (if any) as the option-argument for that option.

I don't understand the resistance.

Also, to clear up any confusion as what 'conform' means:

The utilities in the Shell and Utilities volume of POSIX.1-2008 that claim conformance to these 
guidelines shall conform completely to these guidelines as if these guidelines contained the term 
"shall" instead of "should". On some implementations, the utilities accept usage in violation of these 
guidelines for backwards-compatibility as well as accepting the required form.

But I guess such utilities could use a subset of the features and still comply/conform/whatever. I personally don't care whether it conforms or not. It's just a useful feature that I would like to have.

@pombredanne
Copy link
Contributor

I am willing to chip in a PR. I had to create quirky option subclasses to handle this in a brittle way ;) And I options with a default that is activated only if the option is selected is a bit more than nice to have for me (FWIW, this is how one of my more fleshed out CLI help looks like: https://github.com/nexB/scancode-toolkit/blob/develop/tests/scancode/data/help/help.txt )

@untitaker
Copy link
Contributor

untitaker commented Feb 28, 2018

Click conforms because the synopsis will never show this, because click doesn't support the feature. Your second quote does not support your argument.

The suggestions in both the guideline and in this thread to resolve the parsing ambiguity are not good UX imo. I don't really know why anybody needs this feature. Why not have two options?

@anarcat
Copy link

anarcat commented Feb 28, 2018

I don't really know why anybody needs this feature. Why not have two options?

Here are two examples of how I use this feature with the argparse module. I have a --syslog flag that when specified alone, sends logs with the INFO level to syslog, but an optional argument can also be specified to tweak the logging level (--syslog WARNING). i could make a --syslog-level level, but that makes the whole commandline a little verbose (e.g. --syslog --syslog-level WARNING). Also, what happens if --syslog-level WARNING is specified alone? i am guessing that it will not enable the --syslog flag, so this is rather error prone.

another example is a --force flag: by default, it forces "everything" to happen, but I'd like to be able to restrict to some subsystem (e.g. --force=metadata).

@pombredanne
Copy link
Contributor

pombredanne commented Feb 28, 2018

My use cases are different but resort to the same. For instance I have plugins that contribute various output format options. If one such option is selected (e.g. --json) and no output files is specified I would like this to default to stdout e.g. -
Today this is really hard todo even custom: how do you distinguish from a non-selected option with a default value vs. an option that was selected? (irrespective of the other discussions here, this is something rather difficult)

@Evidlo
Copy link

Evidlo commented Feb 28, 2018

Here is an example.

I have a password generator that can be used for generating symbolic (default) or word-based passwords of varying length.

With argparse, it looks like this

usage: ph add [-h] [-w [length]] PATH

positional arguments:
  PATH                  path to entry (e.g. 'foo') or group (e.g. 'foo/')

optional arguments:
  ...
  -w [length], --words [length]
                        generate 'correct horse battery staple' style password
                        when creating entry
  ...

In Click, the synopsis would be a bit more cluttered and you have to code in that length requires -w (not sure if that is built in).

usage: ph add [-h] [-w] PATH

positional arguments:
  PATH                  path to entry (e.g. 'foo') or group (e.g. 'foo/')

optional arguments:
  ...
  -w, --words    generate 'correct horse battery staple' style password
                 when creating entry (use --length to specify password length)
  --length       specify the length of a password (requires argument -w)
  ...

@pombredanne
Copy link
Contributor

@untitaker re

Why not have two options?

When you start to have two options for most options and have quite a few options, this is doubling the number of options and gets you an ugly UX & help very quickly, guaranteed to confuse your users together with less explicit, and more verbose code with a lot of boilerplate for no good reasons IMHO.

Now leaving aside POSIX, the doc example is to build a git clone. This would not actually be possible with Click as it is: for instance $ git branch --column , $ git clone --recurse-submodules , $ git init --shared and other options such as --log, --gpg-sign , --rebase have a default value or can accept a value. The default only applies if the option is selected (And FWIW, I am not saying that git is a model of UX ;) )

@mbrancato
Copy link
Author

regarding @anarcat #549 (comment) - The POSIX thing was mostly off the top of my head, just thinking about other drivers / compatibility. But my code was a simple implementation, it needs significant work to resolve ambiguities. I think there are some ways to solve ambiguities by defining an order preference.

Ignoring the space issue causing ambiguities between options and optional arguments, does click support something like the following with three separate results to provide similar functionality as getopt_long?

--bar
--foo --bar
--foo=4 --bar

@sblancov
Copy link

sblancov commented May 6, 2018

I really need this feature.

I'm developing a CLI and I want to write results to stdout by default and use an option to write a file instead of write to stdout, but I want to specify a path or use a default one when no path is specified. For example:

cli -> stdout
cli -f -> default_file
cli -f path -> specific file

I have found a workaround:
https://stackoverflow.com/questions/40753999/python-click-make-option-value-optional
But is not as fast as just an annotation.

@rollnlearn

This comment has been minimized.

jpassaro added a commit to jpassaro/click that referenced this issue Feb 13, 2019
Prior to now, the logic for assigning an option value based on the
remaining arguments on the command line has been handled directly by
OptionParser. This refactor accomplishes two things:

1: Bringing two very similar procedures into a single method;
2: Placing them in an Option class that can be easily inherited.

This clears the way for users or plugins to easily address pallets#549 without
affecting core code.
jpassaro added a commit to jpassaro/click that referenced this issue Feb 13, 2019
Prior to now, the logic for assigning an option value based on the
remaining arguments on the command line has been handled directly by
OptionParser. This refactor accomplishes two things:

1: Bringing two very similar procedures into a single method;
2: Placing them in an Option class that can be easily inherited.

This clears the way for users or plugins to easily address pallets#549 without
affecting core code.
jpassaro added a commit to jpassaro/click that referenced this issue Feb 13, 2019
Add a new attribute/argument `explicit_only` to `Option` class. When set
to true, this option will cause Options with nargs=1 to only accept
input that is explicitly associated to it.

This makes the following valid:

    command
    command --option
    command --option=value

This addresses pallets#549 and pallets#764.
@azzamsa

This comment has been minimized.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet