- POSIX-style short flag combining.
- Parsed, type-safe flags.
- Parsed, type-safe positional arguments.
- Support for required flags and required positional arguments
- Callbacks per command, flag and argument.
- Help output that isn't as ugly as sin.
Kingpin uses gopkgin.in for versioning.
-
2014-06-19 -- Stable v1.0.0 release.
- Support cumulative positional arguments.
- Return error rather than panic when there are fatal errors not caught by the type system. eg. when a default value is invalid.
- Use gokpg.in.
-
2014-06-10 -- Place-holder streamlining.
- Renamed
MetaVar
toPlaceHolder
. - Removed
MetaVarFromDefault
. Kingpin now uses heuristics to determine what to display.
- Renamed
Kingpin can be used for simple flag+arg applications like so:
$ ping --help
usage: ping [<flags>] <ip> [<count>]
Flags:
--debug Enable debug mode.
--help Show help.
-t, --timeout=5s Timeout waiting for ping.
Args:
<ip> IP address to ping.
[<count>] Number of packets to send
$ ping 1.2.3.4 5
Would ping: 1.2.3.4 with timeout 5s and count 0
From the following source:
package main
import (
"fmt"
"gopkg.in/alecthomas/kingpin.v1"
)
var (
debug = kingpin.Flag("debug", "Enable debug mode.").Bool()
timeout = kingpin.Flag("timeout", "Timeout waiting for ping.").Default("5s").OverrideDefaultFromEnvar("PING_TIMEOUT").Short('t').Duration()
ip = kingpin.Arg("ip", "IP address to ping.").Required().IP()
count = kingpin.Arg("count", "Number of packets to send").Int()
)
func main() {
kingpin.Version("0.0.1")
kingpin.Parse()
fmt.Printf("Would ping: %s with timeout %s and count %d", *ip, *timeout, *count)
}
Kingpin can also produce complex command-line applications with global flags, subcommands, and per-subcommand flags, like this:
$ chat
usage: chat [<flags>] <command> [<flags>] [<args> ...]
A command-line chat application.
Flags:
--debug enable debug mode
--help Show help.
--server=127.0.0.1 server address
Commands:
help <command>
Show help for a command.
post [<flags>] <channel>
Post a message to a channel.
register <nick> <name>
Register a new user.
$ chat help post
usage: chat [<flags>] post [<flags>] <channel> [<text>]
Post a message to a channel.
Flags:
--image=IMAGE image to post
Args:
<channel> channel to post to
[<text>] text to post
$ chat post --image=~/Downloads/owls.jpg pics
...
From this code:
package main
import (
"os"
"gopkg.in/alecthomas/kingpin.v1"
)
var (
app = kingpin.New("chat", "A command-line chat application.")
debug = app.Flag("debug", "Enable debug mode.").Bool()
serverIP = app.Flag("server", "Server address.").Default("127.0.0.1").IP()
register = app.Command("register", "Register a new user.")
registerNick = register.Arg("nick", "Nickname for user.").Required().String()
registerName = register.Arg("name", "Name of user.").Required().String()
post = app.Command("post", "Post a message to a channel.")
postImage = post.Flag("image", "Image to post.").File()
postChannel = post.Arg("channel", "Channel to post to.").Required().String()
postText = post.Arg("text", "Text to post.").Strings()
)
func main() {
switch kingpin.MustParse(app.Parse(os.Args[1:])) {
// Register user
case "register":
println(*registerNick)
// Post message
case "post":
if *postImage != nil {
}
text := strings.Join(*postText, " ")
}
}
Kingpin supports both flag and positional argument parsers for converting to
Go types. For example, some included parsers are Int()
, Float()
,
Duration()
and ExistingFile()
.
Parsers conform to Go's flag.Value
interface, so any existing implementations will work.
For example, a parser for accumulating HTTP header values might look like this:
type HTTPHeaderValue http.Header
func (h *HTTPHeaderValue) Set(value string) error {
parts := strings.SplitN(value, ":", 2)
if len(parts) != 2 {
return fmt.Errorf("expected HEADER:VALUE got '%s'", value)
}
(*http.Header)(h).Add(parts[0], parts[1])
return nil
}
func (h *HTTPHeaderValue) String() string {
return ""
}
As a convenience, I would recommend something like this:
func HTTPHeader(s Settings) (target *http.Header) {
target = new(http.Header)
s.SetValue((*HTTPHeaderValue)(target))
return
}
You would use it like so:
headers = HTTPHeader(kingpin.Flag("header", "Add a HTTP header to the request.").Short('H'))
The default value is the zero value for a type. This can be overridden with
the Default(value)
function on flags and arguments. This function accepts a
string, which is parsed by the value itself, so it must be compliant with
the format expected.
The place-holder value for a flag is the value used in the help to describe the value of a non-boolean flag.
The value provided to PlaceHolder() is used if provided, then the value provided by Default() if provided, then finally the capitalised flag name is used.
Here are some examples of flags with various permutations:
--name=NAME // Flag(...).String()
--name="Harry" // Flag(...).Default("Harry").String()
--name=FULL-NAME // flag(...).PlaceHolder("FULL-NAME").Default("Harry").String()
A common command-line idiom is to use all remaining arguments for some purpose. eg. The following command accepts an arbitrary number of IP addresses as positional arguments:
./cmd ping 10.1.1.1 192.168.1.1
Kingpin supports this by having Value
provide a IsCumulative() bool
function. If this function exists and returns true, the value parser will be
called repeatedly for every remaining argument.
Examples of this are the Strings()
and StringMap()
values.
To implement the above example we might do something like this:
type ipList []net.IP
func (i *ipList) Set(value string) error {
if ip := net.ParseIP(value); ip == nil {
return fmt.Errorf("'%s' is not an IP address", value)
} else {
*i = append(*i, ip)
return nil
}
}
func (i *ipList) String() string {
return ""
}
func (i *ipList) IsCumulative() bool {
return true
}
func IPList(s Settings) (target *[]net.IP) {
target = new([]net.IP)
s.SetValue((*ipList)(target))
return
}
And use it like so:
ips := IPList(kingpin.Arg("ips", "IP addresses to ping."))