kingpin
kingpin copied to clipboard
Flag with optional value
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.
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.
Thanks Alec, that exactly what I need.
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.
@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.
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!
For what its worth, @headzoo described exactly what i'm looking to do as well. Thanks!
@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.
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.
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
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.
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.
It definitely does set the default value when the value is not passed.
Sorry, you are right. Then we need a second default?
Something like .AllowEmpty(value any) because it's not a very common case.
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?
It would probably have to be an error to use Default() and AllowEmpty() together.
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)
Ah yes, good point 👍
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).
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.