alexflint / go-arg

Struct-based argument parsing in Go
https://pkg.go.dev/github.com/alexflint/go-arg
BSD 2-Clause "Simplified" License
2.04k stars 100 forks source link

[Feature Request] combined arguments like python argparse #187

Open lonnywong opened 2 years ago

lonnywong commented 2 years ago

Combined arguments are convenience to use. e.g., ps -ef, rm -rf.

go-arg test code

package main

import (
    "fmt"
    "github.com/alexflint/go-arg"
)

func main() {
    var args struct {
        Foo  bool `arg:"-f"`
        Bar  bool `arg:"-b"`
        Time int  `arg:"-t"`
    }
    arg.MustParse(&args)
    fmt.Printf("Foo:  %v\nBar:  %v\nTime: %v\n", args.Foo, args.Bar, args.Time)
}

python argparse test code

import argparse

parser = argparse.ArgumentParser()
parser.add_argument('-f', '--foo', action='store_true')
parser.add_argument('-b', '--bar', action='store_true')
parser.add_argument('-t', '--time', type=int, default=0)

args = parser.parse_args()
print("Foo:  %s\nBar:  %s\nTime: %d" % (args.foo, args.bar, args.time))

test cases

test case go-arg python argparse
-f -b -t 1
Foo:  true
Bar: true
Time: 1
Foo:  True
Bar: True
Time: 1
-fb -t 2
Usage: goargtest [--foo] [--bar] [--time TIME]
error: unknown argument -fb
exit status 255
Foo:  True
Bar: True
Time: 2
-t3
Usage: goargtest [--foo] [--bar] [--time TIME]
error: unknown argument -t3
exit status 255
Foo:  False
Bar: False
Time: 3
-ft4
Usage: goargtest [--foo] [--bar] [--time TIME]
error: unknown argument -ft4
exit status 255
Foo:  True
Bar: False
Time: 4
-fbt5
Usage: goargtest [--foo] [--bar] [--time TIME]
error: unknown argument -fbt5
exit status 255
Foo:  True
Bar: True
Time: 5
-fbt 6
Usage: goargtest [--foo] [--bar] [--time TIME]
error: unknown argument -fbt
exit status 255
Foo:  True
Bar: True
Time: 6
-ft7b
Usage: goargtest [--foo] [--bar] [--time TIME]
error: unknown argument -ft7b
exit status 255
usage: pyargtest.py [-h] [-f] [-b] [-t TIME]
pyargtest.py: error: argument -t/--time: invalid int value: '7b'
alexflint commented 2 years ago

Thanks for putting this comprehensive feature request together @lonnywong. I think it would be great to implement this.

Currently, go-arg allows "short" arguments that are more than one character long. However, to implement this I think only single-character short arguments should be considered for combining in this way. This means that it'll still be okay to have multi-character short arguments, but those arguments just can't be combined together.

I don't think we should allow things like -fbt5 or -ft7b. That seems too complicated for now.

It may happen that somebody defines several single-character short arguments, and also a conflicting multiple-character short argument such as:

var args struct {
  X bool `arg:'-x"`
  Y bool `arg:'-y"`
  Z bool `arg:'-z"`
  XYZ bool `arg:'-xyz"`
}

In this case, if the user writes -xyz on the command line then there will be an ambiguity. I think a reasonable solution would be to first check for an argument that exactly matches the string on the command line (in this case args.XYZ), and then if one doesn't exist then try looking for the individual characters (X, Y, and Z).

I will try to fine time to look into this.

lonnywong commented 2 years ago

Thanks. We could do the exactly matches first. If it doesn't match any arguments, then try the combined parsing. If -ft7b doesn't match exactly, the f matches one and it's boolean, then go on, the t matches one and it's int, then the treats the left 7b as int, e.g., -ft5. If nothing left, treats the next argument as int, e.g., -ft 6.

lonnywong commented 2 years ago

I have a solution, but probably not a good one.

https://github.com/trzsz/go-arg/blob/ac5a9f75703f3e4186ade7da860f1a675e8765b2/parse.go#L568-L594