akamensky / argparse

Argparse for golang. Just because `flag` sucks
MIT License
604 stars 62 forks source link

Mutually exclusive groups #23

Closed henryx closed 3 years ago

henryx commented 6 years ago

As Python's argparse implementation, is possible to have a mutually exclusive groups for arguments

akamensky commented 6 years ago

And you do have them via commands. if you use command "a" then only arguments of this group will be available.

Is this what you meant?

henryx commented 6 years ago

Consider this python code:

import argparse

parser = argparse.ArgumentParser(prog='PROG')
group = parser.add_mutually_exclusive_group()
group.add_argument('--foo', action='store_true')
group.add_argument('--bar', action='store_false')

# result is Namespace(bar=True, foo=True)
parser.parse_args(['--foo'])

# result is Namespace(bar=False, foo=False)
parser.parse_args(['--bar'])

parser.parse_args(['--foo', '--bar'])

Result of the last line is:

usage: PROG [-h] [--foo | --bar]
PROG: error: argument --bar: not allowed with argument --foo

So, is possible to have a similar feature in argparse implementation?

akamensky commented 6 years ago

Okay, I see what you mean, I never used this functionality in Python's argparse.

I feel this is a bit of corner case, meaning it most likely wouldn't be used very often and could easily be implemented in the code that follows the parser (just check if both are provided and print usage and error).

If you feel this library would benefit from this functionality then more than welcome to provide a PR for this issue.

On the other hand, I hope to keep this lib as lightweight and simple as possible (hopefully can even find time to throw away fmt usage for it doubling size of helloworld example), so if you can find away to implement this in a simple way -- I would gladly take that. At the moment I feel adding this would require rewriting this lib from scratch.

Gui13 commented 4 years ago

Hello @akamensky I have a practical use case for that:

If I want a --version argument but also have required parameters, argparse will fail if I invoke my program with ./myprogram --version with an error stating that I am missing required arguments.

See this snippet that fails:


// Create new parser object
parser := argparse.NewParser("ldapschemamigrate", "Migrates a LDAP to a specified schema file passed in argument.")
// Create string flag
host := parser.String("H", "host", &argparse.Options{Required: true, Help: "the ldap host, in the form ldap[s]://{host}:{port}"})
cn := parser.String("u", "user", &argparse.Options{Required: true, Help: "the CN with which to bind"})
password := parser.String("p", "password", &argparse.Options{Required: false, Help: "the password to use to bind. Also possible to pass as a LDAP_PASSWORD environment variable"})
ldif := parser.String("f", "schema", &argparse.Options{Required: true, Help: "the schema file containing the new schema to apply"})
verbose = parser.Flag("v", "verbose", &argparse.Options{Required: false, Help: "Verbose mode"})
dryRun = parser.Flag("n", "dry-run", &argparse.Options{Required: false, Help: "Perform a dry run"})
exitVersion := parser.Flag("", "version", &argparse.Options{Required: false, Help: "Show the program version"})

// Parse input
err := parser.Parse(os.Args) // will fail if I invoke ./myprogram --version
if err != nil {
    log.Fatalf(parser.Usage(err))
}

// never reached
if *exitVersion {
    fmt.Printf("Program version %v\n", version)
    os.Exit(0)
}

Solution would be to have multiple independent groups of args, one of which would only have --version, and the other the rest.

akamensky commented 4 years ago

@Gui13 you can implement that in code that uses this library. It doesn't have to be part of library for your application to work exactly the way you want it.

Gui13 commented 4 years ago

Ah I think I got you:

Am I right?

The drawback is that this isn't 'pure' in terms of I parse the args twice, and have a big ugly "if '--version' in os.Args" before all the code.

akamensky commented 4 years ago

@Gui13 Not sure if I understood approach you meant. The one I suggested is something like below:

  1. Setup arguments
  2. Parse the arguments from os.Args
  3. Validate arguments (if ... then to check for presence of exclusive arguments would be here)
  4. If 3 fails -- exit with error, if 3 succeeds -- proceed further
  5. Your application logic

As mentioned in one of the comments above -- this library is for parsing arguments. While Python's argparse does indeed have mutually exclusive arguments, implementing this would be adding quite a lot to this library, and while I am not opposed to having that, I myself would not have time to implement that at the moment. So while there is no relevant PR for this -- you can put this logic into your code.

stale[bot] commented 3 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed soon if no further activity occurs. Thank you for your contributions. Feel free to comment or otherwise update to keep it alive.

stale[bot] commented 3 years ago

Closing due to old age. Feel free to re-open or ping maintainers.