Command line argument parser following the GNU standard.
./test -vo out.png --size 256 input.txt
with the following features:
- build-in help (
-h
and--help
) message - scan arguments into struct fields with configuration in tags
- scan into composite field types (arrays, slices, structs)
- allow for nested sub commands
GNU command line argument rules:
- arguments are options when they begin with a hyphen
-
- multiple options can be combined:
-abc
is the same as-a -b -c
- long options start with two hyphens:
--abc
is one option - option names are alphanumeric characters
- options can have a value:
-a 1
means thata
has value1
- option values can be separated by a space, equal sign, or nothing:
-a1 -a=1 -a 1
are all equal - options and non-options can be interleaved
- the argument
--
terminates all options so that all following arguments are treated as non-options - a single
-
argument is a non-option usually used to mean standard in or out streams - options may be specified multiple times, only the last one determines its value
- options can have multiple values:
-a 1 2 3
means thata
is an array/slice/struct of three numbers of value[1,2,3]
See also github.com/tdewolff/prompt for a command line prompter.
Make sure you have Git and Go (1.22 or higher) installed, run
mkdir Project
cd Project
go mod init
go get -u github.com/tdewolff/argp
Then add the following import
import ( "github.com/tdewolff/argp" )
A regular command with short and long options.
See cmd/test/main.go
.
package main import "github.com/tdewolff/argp" func main() { var verbose int var input string var output string var files []string size := 512 // default value cmd := argp.New("CLI tool description") cmd.AddOpt(argp.Count{&verbose}, "v", "verbose", "Increase verbosity, eg. -vvv") cmd.AddOpt(&output, "o", "output", "Output file name") cmd.AddOpt(&size, "", "size", "Image size") cmd.AddArg(&input, "input", "Input file name") cmd.AddRest(&files, "files", "Additional files") cmd.Parse() // ... }
with help output
Usage: test [options] input files...
Options:
-h, --help Help
-o, --output string Output file name
--size=512 int Image size
-v, --verbose int Increase verbosity, eg. -vvv
Arguments:
input Input file name
files Additional files
Example with sub commands using a main command for when no sub command is used, and a sub command named “cmd”. For the main command we can also use New
and AddOpt
instead and process the command after argp.Parse()
.
package main import "github.com/tdewolff/argp" func main() { cmd := argp.NewCmd(&Main{}, "CLI tool description") cmd.AddCmd(&Command{}, "cmd", "Sub command") cmd.Parse() } type Main struct { Version bool `short:"v"` } func (cmd *Main) Run() error { // ... } type Command struct { Verbose bool `short:"v" name:""` Output string `short:"o" desc:"Output file name"` Size int `default:"512" desc:"Image size"` } func (cmd *Command) Run() error { // ... }
var input string cmd.AddArg(&input, "input", "Input file name") var files []string cmd.AddRest(&files, "files", "Additional input files")
Basic types
var v string = "default" cmd.AddOpt(&v, "v", "var", "description") var v bool = true cmd.AddOpt(&v, "v", "var", "description") var v int = 42 // also: int8, int16, int32, int64 cmd.AddOpt(&v, "v", "var", "description") var v uint = 42 // also: uint8, uint16, uint32, uint64 cmd.AddOpt(&v, "v", "var", "description") var v float64 = 4.2 // also: float32 cmd.AddOpt(&v, "v", "var", "description")
Composite types
// or: –var 4,2 => [2]int{4, 2}
v := []int{4, 2, 1} // element can be any valid basic or composite type
cmd.AddOpt(&v, “v”, “var”, “description”)
// –var [4 2 1] => []int{4, 2, 1}
// or: –var 4,2,1 => []int{4, 2, 1}
v := map[int]string{1:”one”, 2:”two”} // key and value can be any valid basic or composite type
cmd.AddOpt(&v, “v”, “var”, “description”)
// –var {1:one 2:two} => map[int]string{1:”one”, 2:”two”}
v := struct { // fields can be any valid basic or composite type
S string
I int
B [2]bool
}{“string”, 42, [2]bool{0, 1}}
cmd.AddOpt(&v, “v”, “var”, “description”)
// –var {string 42 [0 1]} => struct{S string, I int, B [2]bool}{“string”, 42, false, true}” dir=”auto”>
v := [2]int{4, 2} // element can be any valid basic or composite type cmd.AddOpt(&v, "v", "var", "description") // --var [4 2] => [2]int{4, 2} // or: --var 4,2 => [2]int{4, 2} v := []int{4, 2, 1} // element can be any valid basic or composite type cmd.AddOpt(&v, "v", "var", "description") // --var [4 2 1] => []int{4, 2, 1} // or: --var 4,2,1 => []int{4, 2, 1} v := map[int]string{1:"one", 2:"two"} // key and value can be any valid basic or composite type cmd.AddOpt(&v, "v", "var", "description") // --var {1:one 2:two} => map[int]string{1:"one", 2:"two
13 Comments
arccy
> * multiple options can be combined: -abc is the same as -a -b -c
> * option values can be separated by a space, equal sign, or nothing: -a1 -a=1 -a 1 are all equal
> * options can have multiple values: -a 1 2 3 means that a is an array/slice/struct of three numbers of value [1,2,3]
while convenient, one of the reasons I dislike gnu style flags is just how inconsistent they can be and annoyingly hard to parse by humans
acmj
In general, command-line argument parsers should just follow the GNU style. No more, no less. Deviations confuse users as it is not immediately obvious to them what rules a parser is imposing.
> options can have multiple values: -a 1 2 3 means that a is an array/slice/struct of three numbers of value [1,2,3]
Allowing multiple values is inconsistent because you can't tell in "./cmd -a 1 2 3" whether 2 and 3 are positional arguments or arguments for -a. This is not a GNU style. The GNU way is "./cmd -a 1 -a 2 -a 3" (or "./cmd -a 1,2,3"). This package supports that, which is good.
> option values can be separated by a space, equal sign, or nothing: -a1 -a=1 -a 1 are all equal
"–a=1" is a GNU style but "-a=1" is not. This is a minor issue, though.
Also, does this package support "–"? Everything following "–" should be treated as positional arguments.
amelius
Does it throw an exception when it fails to parse?
latchkey
Cobra https://github.com/spf13/cobra uses https://github.com/spf13/pflag, which supports GNU style flags. Cobra has been developed for many years now and has a ton of additional features, like automatically generating the autocompletions and has tons of unit tests.
I'd just use cobra.
jmclnx
If you need long options because you are running out of letters, time for a rewrite :)
dotemacs
I'm glad that this package exists.
I know of UNIX parameters, with a single hyphen & a single letter, and GNU parameters, with a double parameter and a word. But a single hyphen and then a word: *mind broken*.
When I first saw that on Terraform, I was upset & conflicted.
OK, I know that Java also has mental parameter options like that ('-version', note the *single* hyphen followed by, gasp, a word!), but I just dismissed it as weird enterprise-ism.
Then I saw that this 'single hyphen + word' was an accepted convention for parameters, in the Go world & I was disappointed.
Glad that this package is fighting the good fight.
edoceo
I use Kong https://github.com/alecthomas/kong
I does everything, including too complicated options.
d_burfoot
I might be the only person in this camp, but I find the "standard" command line arguments style absolutely repulsive. I write tons of CL code, and I always use easy key=value notation (sometimes it's flag=true, which I consider to be a minor sacrifice of conciseness in favor of consistency and readability).
sandreas
Interesting. I used
https://github.com/urfave/cli
in the past and was pretty happy with it. However sometimes it's good to try something New :-)
thrill
Sanity!
CamouflagedKiwi
I've become a fan of https://github.com/jessevdk/go-flags (also enjoyed structopt which is similar for Rust). I much prefer defining the flags statically on a type which stores them after rather than building it all up programmatically.
Otherwise, I'm not really sure what this offers that alternatives don't, apart from the -a 1 2 3 -> [1,2,3] business which seems highly confusing and undesirable to me so I'd think of it as more of an anti-feature.
reactordev
Aside from the other comments, an ick I have is that struct tags are being abused here. Instead of providing a grouping like json, yaml, mapstructure, gorm, etc – it just goes willy nilly with struct tags like it owns the whole space. I’d like to see it grouped like…
This would follow go struct tag standards.
braggerxyz
I really favor posix style, which is why I use pflags package for my cli Tools.