The lightweight, non-intrusive Command Line Argument Parser
The most famous command line parser is undoubtly Cobra. Cobra is very nice and has tons of features, however, I was not quite happy with it. First, it does way too many things for my liking, and also, it is pretty intrusive, i.e. you need to build your code around Cobra, rather than having Cobra silently collaborating with your code.
clap is a non intrusive Command Line Argument Parser, that will use struct tags to understand
what you want to parse. You don't have to implement interface or use a CLI to scaffold your project:
you just call clap.Parse(args, &YourConfig)
and you are done. You are nonetheless responsible for
handling potential commands and subcommands, but clap will fill up your CLI configuration struct
with the values passed on the command line for those commands / subcommands.
- lightweight
- non intrusive
- obvious defaults
- no dependencies
- easy to configure
- no CLI nor scaffolding
As a bonus, clap is about 350 LoC (excluding comments and tests)
go get github.com/fred1268/go-clap
Very easy to start with:
- declare a struct containing your configuration
- add struct tags as hints for clap
- call
clap.Parse(args, &config)
type config struct {
Cookie string `clap:"--cookie"`
HTTPOnly bool `clap:"--httpOnly"`
Secure bool `clap:"--secure"`
Origins [4]string `clap:"--origins,-O,mandatory"`
Port int `clap:",-P,mandatory"`
ConfigFiles []string `clap:"trailing"`
}
Please note that this automatically generates a
--no-secure
and--no-httpOnly
flags that you can use on the command line to set the corresponding booleans to thefalse
value. This is useful when you want to give a boolean atrue
default value.
A clap struct tag has the following structure:
Name Type `clap:"longName[,shortName][,mandatory]"`
longName is a... well... long name, like --recursive
or --credentials
shortName is a single letter name, like -R
or -c
mandatory can be added to make the non-optional parameters
In your main, just make a call to clap.Parse()
:
func main() {
var err error
var results *clap.Results
// define your defaults
cfg := &config{Secure: true}
// note you may want to skip the first few
// parameters (like command and subcommand)
// by passing args[2:] instead of args
if results, err = clap.Parse(args, cfg); err != nil {
// results contains a list of arguments in error
// can be used for user friendly error handling
return err
}
// results contains the list of arguments being ignored
// can be used for user friendly error handling
}
Assuming the command line looks like:
-P 8080 --cookie clapcookie --httpOnly --origins http://localhost:5137 \
https://localhost:5173 http://localhost:3000 https://localhost:3000 \
config-db.json config-log.json
You will get the following struct:
config{
Cookie: "clapcookie",
HTTPOnly: true,
Secure: true, // comes from your default (not in the command line)
Origins: []string{
"http://localhost:5137", "https://localhost:5173",
"http://localhost:3000", "https://localhost:3000",
},
Port: 8080,
// trailing parameters
ConfigFiles: []string{"config-db.json", "config-log.json"},
}
Note that it is important to use arrays rather than slices when you can, since arrays will consume, at maximum the requested number of parameters, whereas slices will consume all possible parameters. Thus, a slice parameter should not be used just before the trailing otherwise it will consume the trailing parameters.
The following parameter types are supported by clap:
- bool:
--param
or--no-param
- string:
--param test
- int:
--param 10
- float:
--param 12.3
- string array of any size (here 3):
--param a b c
- int array of any size (here 2):
--param 80 443
- string slice:
--param a b c
- int slice:
--param 80 443
clap doesn't have explicit support for commands and subcommands because it doesn't really need it. The way to have commands is simple:
- create a parameter struct per command
- switch on
os.Args[1]
(the command) - call
clap.Parse(os.Args[2:])
Here is an example:
type RunParams struct {
ForceRebuild bool `clap:",-a"`
PrintOnly bool `clap:",-n"`
// ...
}
type TestParams struct {
Binary string `clap:",-o"`
CompileOnly bool `clap:",-c"`
// ...
}
func main() {
if len(os.Args) < 2 {
os.Exit(1)
}
var runParams RunParams
var testParams TestParams
switch os.Args[1] {
case "run":
clap.Parse(os.Args[2:], &runParams)
case "test":
clap.Parse(os.Args[2:], &testParams)
}
fmt.Printf("%v\n%v\n", runParams, testParams)
}
Working with subcommands involves exactly the same steps, except that you will consume the first two args and give
os.Args[3:]
toclap.Parse()
.
clap is licensed under the MIT license (see LICENSE).
Issues or PR are welcome.