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

fix: --long= should not consume the next argument #139

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

telemachus
Copy link

No description provided.

@telemachus
Copy link
Author

telemachus commented Jan 17, 2025

I added a couple of tests to confirm that (1) for a boolean, --long= parses as true, (2) for other flags --long= parses as --long="", and (3) --long= does not consume the next argument from args.

In the parsing code, I tried to minimize changes, but while I was there I updated two comments to reflect that only long flags were at issue, and I changed len(args) <= 0 to len(args) == 0 since I don't think it's ever possible for a slice to have a negative length.

I considered adding a further test (like below) for --long="" with non-string flags, but I wasn't sure whether testing for such a specific error was too fiddly. Let me know if you think more tests are needed.

{
	Name:         "--flt= -a",
	Constructors: []fftest.Constructor{fftest.CoreConstructor},
	Args:         []string{`--flt=`, `-a`},
	Want:         fftest.Vars{WantParseErrorIs: strconv.ErrSyntax},
},

flag_set.go Outdated
Comment on lines 263 to 278
if value == "" && !eqFound {
switch {
case f.isBoolFlag:
value = "true" // `-b` or `--foo` default to true
value = "true" // `--foo` defaults to true
if len(args) > 0 {
if _, err := strconv.ParseBool(args[0]); err == nil {
value = args[0] // `-b true` or `--foo false` should also work
value = args[0] // `--foo false` should also work
args = args[1:]
}
}
case !f.isBoolFlag && len(args) > 0:
value, args = args[0], args[1:]
case !f.isBoolFlag && len(args) <= 0:
case !f.isBoolFlag && len(args) == 0:
return nil, fmt.Errorf("missing value")
default:
panic("unreachable")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The complexity of this code is outstanding

	if value == "" && !eqFound {
		switch {
		case f.isBoolFlag:
			value = "true" // `--foo` defaults to true
			if len(args) > 0 {
				if _, err := strconv.ParseBool(args[0]); err == nil {
					value = args[0] // `--foo false` should also work
					args = args[1:]
				}
			}
		case !f.isBoolFlag && len(args) > 0:
			value, args = args[0], args[1:]
		case !f.isBoolFlag && len(args) == 0:
			return nil, fmt.Errorf("missing value")
		default:
			panic("unreachable")
		}
	}

Unless I'm wrong, it could be this

	if value == "" && !eqFound {
		switch {
		case f.isBoolFlag:
			value = "true" // `--foo` defaults to true
			if len(args) > 0 {
				if _, err := strconv.ParseBool(args[0]); err == nil {
					value = args[0] // `--foo false` should also work
					args = args[1:]
				}
			}
		case len(args) > 0:
			value, args = args[0], args[1:]
		default:
			return nil, fmt.Errorf("missing value")
		}
	}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are not wrong. Thanks.

Comment on lines 267 to 272
if len(args) > 0 {
if _, err := strconv.ParseBool(args[0]); err == nil {
value = args[0] // `-b true` or `--foo false` should also work
value = args[0] // `--foo false` should also work
args = args[1:]
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is also something strange with the code (the code that already exists)

Let's assume --foo expects a string

--foo leads to an error
--foo true leads to "true"
--foo=1 leads to "1"
--foo=false leads to "false"
--foo= leads to ""

But if --foo is a boolean

--foo false
--foo whatever
--foo 0
--foo 2
--foo=2

Leads to this

--foo=false
--foo whatever
--foo=false
--foo=true 2
an error

This behavior is strange to me, but I'm unsure how other libraries parsing flags do

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The difference between --foo 2 and --foo=2 is...not great. That is probably why flag in Go's standard library restricts the form --flag arg to non-boolean flags.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exactly

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would be in favor of that here as well, but I think @peterbourgon wants to keep boolean parsing as is. (Also, that would probably be a very breaking API change now. Don't know how that plays with v4 being in beta.)

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

Successfully merging this pull request may close these issues.

2 participants