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

Flag with optional value #134

Open
mremond opened this issue Jul 16, 2016 · 20 comments
Open

Flag with optional value #134

mremond opened this issue Jul 16, 2016 · 20 comments

Comments

@mremond
Copy link

mremond commented Jul 16, 2016

Is there already a way to have flag that can be empty, like "-p Password"?

If Password is not provided from the command-line, I would like to prompt user to enter it after launching command. This avoid showing the password in user bash history.

@alecthomas
Copy link
Owner

Only by checking If the value differs from the default. A useful trick is to do something like .Default("\0").Placeholder("PASSWORD") then if the password is still "\0" prompt.

@mremond
Copy link
Author

mremond commented Jul 17, 2016

Thanks Alec, that exactly what I need.

@headzoo
Copy link

headzoo commented Mar 17, 2017

Just leaving a note here in case anyone stumbles upon this issue looking for the same solution. @alecthomas solution either no longer works or he didn't understand the problem @mremond and I are trying to solve.

The MySQL command line client may be called in one of three ways.

mysql
Attempts to login the user without a password. MySQL does not prompt the user for a password if logging in without one fails. The command line client simply exits with an "access denied" error message.

mysql -pmypassword
Attempts to login the user with the password "mypassword". This is not secure because it leaves the password in your bash history.

mysql -p
Prompts the user for their password. This is the prefered method when logging in from the command line.

As far as I can tell this can't be accomplished with Kingpin.

pass := kingpin.Flag("password", "Password.").Default("\000").Short('p').String()

That leaves the end user with two possibilities: don't use the -p flag or use it with a value, but using the -p flag without a value isn't possible. Even when a default has been set.

error: expected argument for flag '-p', try --help

The user should not be prompted for a password when -p isn't used. The user should only be prompted when using the -p flag without a value. Kingpin would need the option to treat a flag as both a boolean and a string.

Here's the solution I'm using, which perfectly recreates the MySQL behavior.

for i, a := range os.Args {
	if a == "-p" || a == "--password" {
		os.Args[i] = "--password=\000"
	}
}

pass := kingpin.Flag("password", "Password.").Short('p').String()

if *pass == "\000" {
    // Prompt for the user's password.
}

Note: MySQL doesn't allow a space character after the -p or --password flags, e.g. -p mypassword, so my solution works in this case.

@alecthomas
Copy link
Owner

@mremond 's problem wasn't the same as yours, afaik.

You are correct, there is no builtin way to do what you're asking for.

@headzoo
Copy link

headzoo commented Mar 18, 2017

It's a bit of an edge case for sure. One simple fix which may be useful outside of this one situation would be having an error handler. The handler could return nil to disregard the error, or return a different error to customize the message shown to the user. For example:

Kingpin.OnError(func(flag string, err error) error {
    if flag == "--password" {
        // prompt for password
        return nil
    }
    // Return the original error, or a new one.
    // return errors.New("Are you crazy!")
    return err
})

Either way, Kingpin is great and pretty flexible!

@codyaray
Copy link

codyaray commented Jun 5, 2019

For what its worth, @headzoo described exactly what i'm looking to do as well. Thanks!

@dakyskye
Copy link

@headzoo thanks, but I still do not understand what the point of Kingpin's .Default method is. What we want from it is to just set that default value for that FlagClause when the flag is present without arguments. kingpin.Flag("foo", "it defaults to bar").Default("bar").String()) should just set bar as the value when --foo is passed.

@alecthomas
Copy link
Owner

It sets bar as the value when the flag is not present. It's identical to the behaviour of the stdlib's flag package.

Flags with optional values, ie. that can either take --flag or --flag foo are not supported.

@ilius
Copy link

ilius commented Sep 12, 2022

Compatibility with some standard GNU/Linux tools requires this, for example ls --color is a shortcut to ls --color=always or just ls --color= (while ls --color=never and ls --color=auto is also possible).

   --color[=WHEN]
          colorize the output; WHEN can be 'always' (default if omitted), 'auto', or 'never'; more info below

@alecthomas
Copy link
Owner

Ah interesting. If there's a default value, there's no ambiguity because Kong could just set it to that value and pass it through transparently.

Seems reasonable, PRs welcome.

@alecthomas alecthomas reopened this Sep 12, 2022
@ilius
Copy link

ilius commented Sep 12, 2022

Ah interesting. If there's a default value, there's no ambiguity because Kong could just set it to that value and pass it through transparently.

When user reads the manual where it mentions default value for a flag, it generally means default when flag is not passed.
So I'm not really sure what you want to do here.

@alecthomas
Copy link
Owner

It definitely does set the default value when the value is not passed.

@ilius
Copy link

ilius commented Sep 12, 2022

Sorry, you are right.
Then we need a second default?

@ilius
Copy link

ilius commented Sep 13, 2022

Something like .AllowEmpty(value any) because it's not a very common case.

@alecthomas
Copy link
Owner

Something like that could work, yeah. I think the complication is that eg. in the GNU ls --color case, there's no way through the Kong API to tell if a value was set by Default(), or set because the user passed --color. I think you're right that it would have to replace Default(), and only populate the default value if the bare flag is passed. Does that sound about right?

@alecthomas
Copy link
Owner

It would probably have to be an error to use Default() and AllowEmpty() together.

@ilius
Copy link

ilius commented Sep 13, 2022

No actually in this case both are needed. --color means --color=always which enables colors unconditionally. But if flag is missing, auto is assumed (enables colors if output is connected to a terminal, not piped or redirected)

@alecthomas
Copy link
Owner

Ah yes, good point 👍

@ilius
Copy link

ilius commented Sep 13, 2022

My main concern is the ambiguity it causes when parsing and separating flags and their values from standalone arguments. For example, you can have a file named "always" in current directory, and run ls -l --color always which can either mean ls -l --color=always . (list current directory) or ls -l --color=always always (show file named always).
In case of ls only the second one works because ls only recognizes --color=MODE and not --color MODE with space. While for other / normal flags with value, both = and space work (--format long or --format=long).

@ilius
Copy link

ilius commented Sep 13, 2022

We can still have both Default() and AllowEmpty() and use them for the same flag. But the only catch is when you use AllowEmpty(), space can no longer be used to separate this flag from its value (only =). That could solve the problem.

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

No branches or pull requests

6 participants