claptrap
A simple but powerful Go package to parse command-line arguments getopt(3) style. Designed especially for making CLI based libraries with ease. It has built-in support for sub-commands, long and short flag name combination (for example --version
<==> -v
), --flag=<value>
syntax, inverted flag (for example --no-clean
), variadic arguments, global flags, and partial command matching.
★ ★
The project home is here. File bugs here. Send patches, or any other comments or questions, to ~ser/claptrap@lists.sr.ht
Installation
claptrap
is a library, and is added to your Go modules like any other:
$ go get ser1.net/claptrap@latest@latest
Usage
package main
import (
"fmt"
"os"
"ser1.net/claptrap/v3"
"time"
)
func main() {
reg := claptrap.Command("claptest", "This is the claptest")
reg.AddFlag("!count", "c", []int{1, 2, 3}, "count flag")
reg.AddFlag("prob...", "p", []float64{1.5, 2.5, 0.5}, "probability flag")
reg.AddFlag("delay...", "d", []time.Duration{10 * time.Second, 20 * time.Minute, time.Hour}, "duration flag")
reg.AddFlag("verbose", "v", false, "verbosity flag")
reg.AddFlag("fuzzy", "f", true, "fuzzy match flag")
reg.AddFlag("name", "", "Mr. Biggles", "your name")
reg.AddArg("commestibles...", []string{"carrots", "cabbage", "ceyanne"}, "food to eat")
reg.AddCommand("make-man", "make a manpage")
info := reg.AddCommand("info", "the info command about information")
info.AddArg("first", true, "the first argument")
info.AddArg("second", 99, "the second argument")
info.AddArg("third...", []string{"a", "b", "c", "d"}, "the third argument")
info.AddFlag("verbose", "v", 0, "info verbosity flag")
info.AddFlag("fuzzy", "f", true, "fuzzilischiousity!")
subinfo := info.AddCommand("subcommand", "a subcommand for the info command")
subinfo.AddFlag("verbose", "V", []string{"quiet", "loud"}, "how verbose the subcommand is")
help := reg.AddCommand("help", "print the help")
help.AddArg("subcommand...", []string{"help", "info"}, "Get help about a subcommand")
e := reg.Parse(os.Args[1:])
if e != nil {
fmt.Printf("%+v\n", e)
} else {
for c := reg; c != nil; c = c.Command {
switch c.Name {
case "make-man":
claptrap.HelpTemplate = claptrap.ManTempl
claptrap.Usage()
os.Exit(0)
case "claptest":
fmt.Printf("%s %v\n", "commestibles", c.Args["commestibles"].Strings())
fmt.Printf("%s %t\n", "verbose", c.Bool("verbose"))
fmt.Printf("%s %t\n", "fuzzy", c.Bool("fuzzy"))
fmt.Printf("%s %+v\n", "count", c.Args["count"].Ints())
fmt.Printf("%s %+v\n", "prob", c.Args["prob"].Floats())
fmt.Printf("%s %+v\n", "delay", c.Args["delay"].Durations())
case "info":
fmt.Printf("info: %s %t\n", "first", c.Bool("first"))
fmt.Printf("info: %s %+v\n", "second", c.Int("second"))
fmt.Printf("info: %s %+v\n", "third", c.Args["third"].Strings())
fmt.Printf("info: %s %d\n", "verbose", c.Int("verbose"))
fmt.Printf("info: %s %t\n", "fuzzy", c.Bool("fuzzy"))
case "subcommand":
fmt.Printf("Heeeyaw, subcommand, with verbose %q\n", c.String("verbose"))
case "help":
fmt.Printf("%+v\n", claptrap.RootCommand)
claptrap.Usage()
os.Exit(0)
}
}
}
}
Rules
There are commands, flags, and args. A parse scope is created with claptrap.NewRegistry()
.
Commands
Commands are specified without dashes, and are like flag sets: each command can have its own set of flags and args. Commands are registered with the `Registry.Register()`` function. Commands usually have a name, but there is a special root command specified by the empty string; args and flags created on this root command don't require a command.
The parser will match abbreviated strings to commands, so "lis", "ls", and "l" will match "list", as long as it is a unique match. If both "less" and "list" are registered, the user could supply "lss", "le", or "lt" for example; "ls" is ambiguous and would be a parse error.
Args
Args are placeholders for non-flagged arguments, and are created with CommandConfig.AddArg()
; they consume variables in the order they're created. The very last Arg created (on a command) can be variadic, which means it consumes all of the remaining arguments.
Arg names are just that: references to use in your program. They do not appear in the command line. In the above example, the program would take up to three arguments; it'd place the first into first
, the second into second
, and the third into third
.
More arguments would result in a parse error. The last Arg registered can be made variadic by adding ...
to the name.
Now, the program will take any number of arguments, with the fourth and greater being placed in fourth
. Arguments are optional by default, but can be made mandatory by prefixing the name with !
.
Note that this has the effect of also making any previous arguments also mandatory, because claptrap
fills arguments in order. In:
claptrap
supports strong typing; types are derived from the default value.
claptrap
will validate the input data and return a parse error if the argument is not of the required type.
Choices are also supported. Choices limit the allowed arguments, and are set by passing an array of the choices to default value parameter of AddArg()
.
This works with all types except bools. Booleans are always only ever true or false, and boolean arguments can not be variadic. With the exception of booleans, all of these features can be combined:
Values are retrieved using one of the type getters. If the wrong type is retrieved, an empty array is provided. If the argument was not provided by the user, the default is provided. An argument can be tested whether it is a default, or was provided by the user, with the Arg.IsSet()
function.
Bool()
(the only getter that provides a single value)Strings()
Ints()
Floats()
Durations()
package main
import "ser1.net/claptrap/v3"
import "os"
import "fmt"
func main() {
reg := claptrap.Command("test", "description")
reg.AddArg("!first...", []string{"carrots", "cabbage", "ceyanne"}, "")
c, e := reg.Parse([]string{"a"})
if e != nil {
fmt.Printf("%+v\n", e)
} else {
fmt.Printf("%+v\n", c.Args["first"].Strings())
}
c, e = reg.Parse([]string{"carrots"})
if e != nil {
fmt.Printf("%+v\n", e)
} else {
fmt.Printf("%+v\n", c.Args["first"].Strings())
}
c, e = reg.Parse([]string{"carrots", "cabbage"})
if e != nil {
fmt.Printf("%+v\n", e)
} else {
fmt.Printf("%+v\n", c.Args["first"].Strings())
}
}
$ ./claptest a
first: illegal value a, must be [carrots cabbage ceyanne]
$ ./claptest carrots
[carrots]
$ ./claptest cabbage carrots
[cabbage carrots]
Durations can be any string parsable by Go time.ParseDuration()
. Date/times can be in any one of the following formats: time.RFC3339, 2006-01-02T03:04:05, 2006-01-02T03:04, 2006-01-02, 01-02, 03:04:05 (full time), 03:04 (hour and minute), :04:05 (minute and second), 03: (just an hour), 04 (just the minute), :05 (just the second), 2006 (year only), 01- (month only), -02 (day only).
Flags
Flags are prefixed with dashes. Unlike Args, flags are always provided on input. Flags can have long and short forms; like getopt
, long forms are specified with a double-dash --
and short forms with a single dash -
. With a couple of exceptions, Flags follow all the rules and features of arguments: mandatory, variadic, defaults, choices, and types.
- All flags may be variadic, not only the last. Flags and Args can be combined, and do not interfere with each other.
- Mandatory and variadic syntax is specified on the long form.
The biggest differences are in boolean flags:
- Boolean flags take no arguments. If they're supplied, they're true; if they are not supplied, they get the default.
- Boolean long Flags automatically get a
no-
version that returns false if supplied.
For demonstration, if the example code at the top of this readme is run, it will produce the following outputs for the given inputs.
# Flag can be required
$ go run .
claptest -- missing mandatory option: count
# Flag values can be constrained
$ go run . -c 5
count -- illegal value 5, must be [1 2 3]
# Illegal variadic
$ go run . -c 1 --count 2
count -- 2 values provided for non-variadic argument count
# Defaults for booleans can be true or false, and multiple flags can be variadic
$ go run . -c 1 -p 1.5 -p 2.5 --delay 10s -d 1h
commestibles []
verbose false
fuzzy true
count [1]
prob [1.5 2.5]
delay [10s 1h0m0s]
# Supplying a boolean Flag is always true
$ go run . -c 1 -v -f
commestibles []
verbose true
fuzzy true
count [1]
prob []
delay []
# Automatic --no-longflag is always false, and args can be anywhere
$ go run . carrots -c 2 --no-fuzzy --no-verbose ceyanne
commestibles [ceyanne]
verbose false
fuzzy false
count [2]
prob []
delay []
# Short boolean arguments can be combined. A non-boolean argument can be included, but
# it must be at the end:
$ go run . -vfc 3
commestibles []
verbose true
fuzzy true
count [3]
prob []
delay []
# Flags with the same name can be given for different commands, and have different types and defaults
$ go run ./main.go -c 1 -v --fuzzy info -v 5 --no-fuzzy
commestibles []
verbose true
fuzzy true
count [1]
prob []
delay []
info: first true
info: second 99
info: third []
info: verbose 5
info: fuzzy false
Important
Root arguments and flags are global: they can be provided on the command line before other arguments. However, if the root command has any arguments, they will consume commands before the commands are identified.
UIs that have global flags should limit themselves to using Flags and not Args in the root command.
Commands can have subcommands. You can easily construct interfaces that have unexpected results if you mix arguments and subcommands. claptrap will process arguments as subcommands first, then as arguments.
Args and flags must not have the same names.
For shorthand commands ("ls" for "list"), the first letter must match, and the match must be unique. For example:
- list, less -- ls bad
- config, configs -- config *good
- list, config, help -- l, c, h good
But -- again -- if you have arguments defined claptrap will choose commands over arguments. Keep this in mind when creating your UI.
Convenience Rules
The short version is that the convenience functions only work at the command level; they do not reach into the global parameters, for instance. To get to those, you need to get the global command from the Registry and inspect it. The code will look in both Args and Flags, and return either a value, a default value, or a default value for the type, based on the rules below.
The long version is:
- If neither an Arg nor a Flag is found; or if the found item is not the correct type; or the default is a choice; then the default value of the type requested is returned.
- If the value is variadic, the first element of the user values is returned. It's best to not use the convenience functions with variadic args, because you throw away user input.
- If the value is a choice, the default value of the type is returned. Choices do not have default values.
- If the type is wrong, the default value for the type is returned
Style
The examples so far use a style of setting everything within and with reference to a registry. An alternative style is more Go stdlib flags
-like, creating pointers to value containers:
reg := claptrap.NewRegistry()
root := reg.Register("", "")
username := root.AddFlag("user", "u", "", "")
password := root.AddFlag("password", "p", "", "")
server := root.AddFlag("server", "s", "", "")
item := root.AddFlag("!get", "g", 0, "")
reg.Parse(os.Args[1:])
service.Login(server.Strings()[0], username.Strings()[0], password.Strings()[0])
i := service.Get(item.Ints()[0])
Help Text
claptrap generates help text output.
The program at the top of this readme will generate the following Usage output:
USAGE: claptest [args] [commands]
This is the claptest
Commands:
help print the help
info the info command about information
make-man make a manpage
Arguments:
commestibles <string>... food to eat ([carrots cabbage ceyanne])
Flags:
--count / -c <int> count flag (![1 2 3])
--delay / -d <Duration>... duration flag ([10s 20m0s 1h0m0s])
--fuzzy / -f fuzzy match flag (true)
--name / - <string> your name (Mr. Biggles)
--prob / -p <float64>... probability flag ([1.5 2.5 0.5])
--verbose / -v verbosity flag (false)
help [args]
print the help
Commands:
Arguments:
subcommand <string>... Get help about a subcommand ([help info])
Flags:
info [args] [commands]
the info command about information
Commands:
subcommand a subcommand for the info command
Arguments:
first the first argument (true)
second <int> the second argument (99)
third <string>... the third argument ([a b c d])
Flags:
--fuzzy / -f fuzzilischiousity! (true)
--verbose / -v <int> info verbosity flag (0)
make-man
make a manpage
Commands:
Arguments:
Flags:
You can replace the Usage
function with your own. The default usage function uses templates, so you can also write your own templates and use the default function.
To generate a man page, change the help template to the provided manpage template, and call the default Usage function:
claptrap.HelpTemplate = claptrap.ManTempl
claptrap.Usage()
Credits
This library was based initially on clapper; it adds several features missing from clapper, but the main reason for the fork was that the several behaviors were changed in ways that differ fundamentally with how clapper treats arguments.
Features
The primary feature is general POSIX getopt
compatability, so that the tools built with the library have a familiar interface for the largest popular command-line-using population in the world (hint: it isn't Plan 9). On top of that, I wanted the library to support some validation, to reduce runtime errors easily caused by repetitive validation code.
The objective of claptrap is to be small but provide general getopt
compatability and validation features. The most popular libraries are large, either directly or indirectly through dependencies. claptrap has 0 dependencies outside of the Go stdlib.
lib | short | long | combined | inverted | env vars | types | choices | commands | varargs | good api | mand/opt | files | usage |
---|
claptrap | Y | Y | Y | Y | N | Y | Y | Y | Y | Y | Y | N | Y |
clapper | Y | Y | N | Y | N | N | N | Y | Y | Y | | N | N |
droundy | Y | Y | Y | | N | Y | Y | | | Y | | N | Y |
sircmpwn | Y | N | Y | N | N | N | N | N | Y | Y | Y | N | Y |
opflag | Y | Y | Y | N | N | Y | N | N | Y | Y | N | N | Y |
namsral | | Y | | | Y | Y | | Y | Y | Y | | Y | Y |
integrii | Y | Y | | | y | Y | | Y | Y | Y | | | Y |
jessevdk | Y | Y | Y | N | N | Y | Y | Y | Y | N | Y | N | Y |
claptrap supports the following data types:
- bool
- string
- int
- float64
- time.Duration
Env vars would be nice, to be able to support 12-Factor apps, but that starts to feel like feature envy to me, and it's something that is easily added by a library such as GoBike. The biggest missing feature is synchronization with configuration files -- this often accounts for a significant amount of error-prone code; I'm hoping to provide (or find) support through a seperate library, such as go-ini.
Compared to other flags libraries, claptrap is among the smallest, and it pulls in no other dependencies.
LOC -- no tests, no comments scc -i go -M _test --no-cocomo
ABC Score -- ABC complexity for the project (excluding dependencies) abcgo -format summary -path . -no-test
ABC is a better prediction of the amount of binary size a library will add to any give program using it. By that metric, claptrap is third in this list.
Related projects I'd like to provide for claptrap:
- GoBike's envflag project is fantastic, and works with the core Go library.
- subpop's go-ini works similarly, for integration with config files.
- Man page generation, but as a separate program
Elevator Pitch (toot-sized)
claptrap opts lib: very small, much features, getoptish
https://sr.ht/~ser/claptrap/
- getopt long & short flags (--bool, -b)
- combined short (-b -e == -be)
- inverted bools (--bool => --no-bool)
- typed params (int, bool, Duration, string, float)
- mandatory flags and arguments
- positional arguments
- global flags
- subcommands
- variadic flags (-a 1 -a 2 -a 3)
- partial command matching (list =~ ls)
- usage()
And it's 455 lines of code, and 0 dependencies.