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

Choices for arguments #248

Open
zekroTJA opened this issue May 23, 2024 · 2 comments
Open

Choices for arguments #248

zekroTJA opened this issue May 23, 2024 · 2 comments

Comments

@zekroTJA
Copy link

It would be awesome if you could specify choices (like possible/allowed values) for arguments and flags.

I could imagine it in two ways.

a) Via struct tags

var args struct {
    LogLevel string `arg:"-l,--log-level" choices:"panic,error,warn,info,debug"`
}

b) Via a method

In example, a parameter type could implement a Method - i.e. - called ValueChoises, which returns a list of allowed values for the argument.

type Level string

func (t Level) ValueChoises() []string {
	return []string{"panic", "error", "warn", "info", "debug"}
}

var args struct {
    LogLevel Level `arg:"-l,--log-level"`
}

I think I would prefer the second way, because it allows for more flexibility with the definition of choices.

Then, the choice values could be represented in the help text something like as following.

Usage: main [--level LEVEL]

Options:
  --level LEVEL, -l LEVEL
                         log level
                         choices: panic, error, warn, info, debug
  --help, -h             display this help and exit

Let me know what you think of the idea. If you think it would fit in the project, I would be pleased to submit an implementation for it as well. :)

@alexflint
Copy link
Owner

alexflint commented May 24, 2024

Yes, I've considered this feature in the past, and I think you're right that the dynamic approach makes more sense. Even if the choices themselves do not change in any dynamic, one generally would want to define those choices in one place -- as a set of constants, for example -- and populate the help text from there.

One very simple way to get multiple-choice behavior is using UnmarshalText like this:

type myEnum int

const (
  Choice1 myEnum = iota
  Choice2
  Choice3
)

func (e *myEnum) UnmarshalText(b []byte) error {
  switch string(b) {
    case "Choice1": *e = Choice1
    case "Choice2": *e = Choice2
    case "Choice3": *e = Choice3
    default:   return fmt.Errorf("invalid for myEnum: %q: options are Choice1, Choice2, Choice3", string(b))
  }
  return nil
}

One of the good things about this approach is that the user of the library gets to choose details such as whether the choices are case-sensitive. There are some use-cases where you really need case-sensitive choices, and others where you'd really prefer case-insensitive choices. It also leaves the door open to write "did you mean xyz?" in the error message and such.

Unfortunately this approach won't populate the help text with the choices. You would have to manually put the choices into the help text and keep them up to date there.

So then the appropriate feature is to add a dynamic help text function to go-arg, so that any type that implements, say, Help() string gets that as its help text. Then the type above would have

func (e *myEnum) Help() string {
  return "one of: " + strings.Join(", ", possibleChoices)
}

@zekroTJA
Copy link
Author

Well, that makes well more sense and allows for a way more flexible implementation. I love this package and would really like to contribute an approach for an implementation of such a dynamic help text, when I find the time to do so. :)

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

2 participants