claptrap

package module
v1.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 30, 2022 License: BSD-3-Clause Imports: 5 Imported by: 0

README

claptrap

logo

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.

Build statusAPI DocumentationGo Report Card

The project home is here. File bugs here.

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 (
  "ser1.net/claptrap"
  "os"
  "fmt"
  "time"
)

func main() {

  reg := claptrap.NewRegistry()

  root := reg.Register("")
  root.AddFlag("!count", "c", []int{1,2,3})
  root.AddFlag("prob...", "p", []float64{1.5, 2.5, 0.5})
  root.AddFlag("delay...", "d", []time.Duration{10*time.Second, 20*time.Minute, time.Hour})
  root.AddFlag("verbose", "v", false)
  root.AddFlag("fuzzy", "f", true)             // A flag with a default true value
  root.AddArg("commestibles...", []string{"carrots", "cabbage", "ceyanne"})
  
  info := reg.Register("info")
  info.AddArg("first", true)                   // The first optional argument must be a boolean (default: true)
  info.AddArg("second", 99)                             // The second optional argument is an int (default: 99)
  info.AddArg("third...", []string{"a", "b", "c", "d"}) // The rest of the arguments must each be a letter, a-d
  info.AddFlag("verbose", "v", true)                      // A completely different verbose, defaulting to true

  c, e := reg.Parse(os.Args[1:])               // exclude the command!

  if e != nil {
    fmt.Printf("%+v\n", e)
  } else {
    switch c.Name {
    case "":
      fmt.Printf("%s %v\n", "commestibles", c.Args["commestibles"].Strings())
      fmt.Printf("%s %t\n", "verbose", c.Flags["verbose"].Bool())
      fmt.Printf("%s %t\n", "fuzzy", c.Flags["fuzzy"].Bool())
      fmt.Printf("%s %+v\n", "count", c.Flags["count"].Ints())
      fmt.Printf("%s %+v\n", "prob", c.Flags["prob"].Floats())
      fmt.Printf("%s %+v\n", "delay", c.Flags["delay"].Durations())
    case "info":
      fmt.Printf("info: %s %t\n", "first", c.Args["first"].Bool())
      fmt.Printf("info: %s %+v\n", "second", c.Args["second"].Ints())
      fmt.Printf("info: %s %+v\n", "third", c.Args["third"].Strings())
      fmt.Printf("info: %s %t\n", "verbose", c.Flags["verbose"].Bool())
    }
  }
}

Rules

There are commands, flags, and args. A parse scope is created with claptrap.NewRegistry().

package main
import "ser1.net/claptrap"
func main() {
  reg := 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.

  root := reg.Register("")
  info := reg.Register("info")

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.

  root.AddArg("first", "")
  root.AddArg("second", "")
  root.AddArg("third", "")

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.

$ claptest x y z
# root.Args["first"]  => "x"
# root.Args["second"] => "y"
# root.Args["third"]  => "z"

More arguments would result in a parse error. The last Arg registered can be made variadic by adding ... to the name:

  root.AddArg("fourth...", "")

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 !:

  root.AddArg("!second")

Note that this has the effect of also making any previous arguments also mandatory, because claptrap fills arguments in order.

$ claptest x

would fail, because first would get x, and second would get nothing. Required variadic arguments need at least one argument.

claptrap supports strong typing; types are derived from the default value:

  root.AddArg("first", 1)            // ints
  root.AddArg("second", true)        // bools
  root.AddArg("third", 1.0)          // float64
  root.AddArg("fourth", "")          // strings
  root.AddArg("third", time.Now())   // time.Time
  root.AddArg("fourth", time.Second) // time.Duration

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 AddArg():

  root.AddArg("first", []int{1, 2, 3, 5, 8, 13})

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()
  • Times()
  • Durations()
package main
import "ser1.net/claptrap"
import "os"
import "fmt"
func main() {
  reg := claptrap.NewRegistry()
  reg.Register("").AddArg("!first...", []string{"carrots", "cabbage", "ceyanne"})
  c, e := reg.Parse(os.Args[1:])  // exclude the command!
  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 .
count is required

# 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 [carrots cabbage ceyanne]
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 [carrots cabbage ceyanne]
verbose true
fuzzy true
count [1]
prob [1.5 2.5 0.5]
delay [10s 20m0s 1h0m0s]

# Automatic --no-longflag is always false, and args can be anywhere
$ go run . carrots -c 2 --no-fuzzy --no-verbose ceyanne
commestibles [carrots ceyanne]
verbose false
fuzzy false
count [2]
prob [1.5 2.5 0.5]
delay [10s 20m0s 1h0m0s]

# 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 [carrots cabbage ceyanne]
verbose true
fuzzy true
count [3]
prob [1.5 2.5 0.5]
delay [10s 20m0s 1h0m0s]

# Flags with the same name can be given for different commands, and have different types and defaults
$ go run . info
info: first true
info: second [99]
info: third [a b c d]
info: verbose true

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. For example:

  reg := claptrap.NewRegistry()
  root := reg.Register("")
  root.AddArg("foo", "")
  root.AddFlag("ex", "x", false)
  info := reg.Register("info")
  root.AddFlag("ex", "x", false)
  reg.Parse([]string{"info", "-x"})
  // the -x will go to root, not info
  root.Flags["ex"].Bool() == true
  root.Args["foo"].Strings() == []string{"info"}
  info.Flags["ex"].Bool() == false
  info.IsSet == false

UIs that have global flags should limit themselves to using Flags and not Args in the root command.

Caveats and future

  • Times are really limited right now; you have to provide them in full YYYY-mm-ddThh:mm:ss format. There will a a smarter parser, eventually.
  • There's (not yet) auto-generated help/USAGE text.

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
claptrap Y Y Y Y N Y Y Y Y Y Y N
clapper Y Y N Y N N N Y Y Y N
droundy Y Y Y N Y Y Y N
sircmpwn Y N Y N N N N N Y Y Y N
opflag Y Y Y N N Y N N Y Y N N
namsral Y Y Y Y Y Y Y
integrii Y Y y Y Y Y Y
jessevdk Y Y Y N N Y Y Y Y N Y N

claptrap supports the following data types:

  • bool
  • string
  • int
  • float64
  • time.Time
  • 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.

Library LOC Deps ABC Score
clapper 303 0 210
cosiner-argv 462 0 207
claptrap 490 0 490
sircmpwn-getopt 501 0 256
droundy-goopt 595 0 243
namsral-flag 764 0 629
ogier-pflag 1112 0 676
opflag 1161 0 700
integrii-flaggy 1726 0 1340
jessevdk-go-flags 3473 0 2540
spf13-pflag 3974 0 3525
dmulholland-args 437 1 539
thatisuday-commando 627 1 343
mwitkow-go-flagz 1454 1 1213
cosiner-flag 1821 1 763
moogar0880-venom 2000 2 1581
stevenroose-gonfig 1169 5 819
peterbourgon-ff 1060 6 502
cobra 4298 6 4315
dc0d-argify 348 14 139

LOC -- no tests, no comments
ABC Score -- ABC complexity for the project (excluding dependencies)

Elevator Pitch (toot-sized)

Every few months I go searching around for a flags library that isn't bigger than my program, conforms to getopt, and has basic validation features. I never find it, so I wrote one.

claptrap: very small, much features, getoptish https://sr.ht/~ser/claptrap/

claptrap has long & short, combined short, inverted, typed, mandatory, global, and variadic flags. It has subcommands and arguments, and partial command matching. And it's 490 non-comment, non- unit-test lines of code, and zero dependencies.

#golang #library #flags

Documentation

Overview

Package claptrap processes the command-line arguments of getopt(3) syntax. This package provides the ability to process the root command, sub commands, command-line arguments and command-line flags.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Arg

type Arg struct {
	// name of the argument
	Name string
	// contains filtered or unexported fields
}

Arg holds the structured information about an argument.

func (Arg) Bool

func (a Arg) Bool() bool

Bool returns the Arg value as a bool, or the default if no argument was given. It returns false anif the Arg was declared as a different type.

func (Arg) Durations added in v1.2.0

func (a Arg) Durations() []time.Duration

Durations returns the Arg value as an array of type duration, or the defaults if no argument was given. It returns an empty array if the Arg was declared as a different type.

func (Arg) Floats added in v1.2.0

func (a Arg) Floats() []float64

Floats returns the Arg value as an array of type float, or the defaults if no argument was given. It returns an empty array if the Arg was declared as a different type.

func (Arg) Ints added in v1.2.0

func (a Arg) Ints() []int

Ints returns the Arg value as an array of type int, or the defaults if no argument was given. It returns an empty array if the Arg was declared as a different type.

func (Arg) IsSet added in v1.4.0

func (a Arg) IsSet() bool

IsSet returns true if the Argument was provided with a value in the Parsed data.

func (Arg) Strings added in v1.2.0

func (a Arg) Strings() []string

Strings returns the Arg value as an array of type string, or the defaults if no argument was given. It returns an empty array if the Arg was declared as a different type.

func (Arg) Times added in v1.2.0

func (a Arg) Times() []time.Time

Times returns the Arg value as an array of type time, or the defaults if no argument was given. It returns an empty array if the Arg was declared as a different type.

type BadArgument

type BadArgument struct {
	Arg     *Arg
	Message string
}

BadArguments are returned when the command line arguments are invalid

func (BadArgument) Error

func (e BadArgument) Error() string

type CommandConfig

type CommandConfig struct {

	// name of the sub-command ("" for the root command)
	Name string

	// command-line flags
	Flags map[string]*Flag

	// registered command argument values
	Args map[string]*Arg

	// list of the argument names (for ordered iteration)
	ArgNames []string

	// was the command provided on the command line?
	IsSet bool
	// contains filtered or unexported fields
}

CommandConfig type holds the structure and values of the command-line arguments of command.

func (*CommandConfig) AddArg

func (commandConfig *CommandConfig) AddArg(name string, defaultValue interface{}) *Arg

AddArg creates a named argument. Args are placeholders for non-flagged arguments; command-line elements are consumed into Args, in the order they are defined. The very last Arg created (on a command) can be variadic, which means it consumes all of the remaining arguments.

If more elements are provided than there are Args, and the last Arg is *not* variadic, a parse error will result.

Arguments can be made mandatory with a ! prefix in the name. Because Args are consumed in order, this has the effect of making _all_ arguments before the mandatory argument required.

Arguments are strongly typed. The type of an argument is derived from the default value. The supported type

  • int
  • string
  • float64
  • bool
  • time.Time
  • time.Duration

If the provided defaultValue is an array of one of the above types, then that array defines a set of legal values; any provided parameter that doesn't match a value in the array will result in a parse error.

Boolean arguments differ from other types in that:

- boolean Args may *not* be variadic, and will produce an error - the getter, `Bool()`, always returns a single value -- not an array, like the rest - boolean Args may not have array defaults; the options are always `true` and `false`

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.

func (*CommandConfig) AddFlag

func (commandConfig *CommandConfig) AddFlag(name string, shortName string, defaultValue interface{}) (*Flag, error)

AddFlag creates a flag of a given type and defaults. Do not provide the dashes in the names.

The rules for flags are the same as for args, but in addition:

  • The `name` argument is the long-name of the flag. The long name can be prefixed with `!`, in which case it is mandatory. It can also be suffixed with `...`, in which case it is variadic.
  • Unlike Args, any flag can be variadic.
  • The `shortName` argument should be a single character.
  • Boolean Flags don't accept arguments; if the flagis provided on the command line, it is true; otherwise, it is the default.
  • Boolean flags automatically get a `no-<longname>` option, which if provided, returns `false' for the flag.
  • Regardless of the default, boolean flags are true if provided, and false if inverted

On the command line, Flags are prefixed with dashes. Unlike Args, Flag names are always provided on input. With the exceptions above, Flags follow all the rules and features of arguments: mandatory, variadic, defaults, choices, and types.

type Flag

type Flag struct {
	Arg
	// short name of the flag
	ShortName string
}

Flag type holds the structured information about a flag. Because a Flag is an Arg, all of the Arg getters apply and are used to get Flag values.

Note that Flags differ slightly from args, as boolean Flags take no arguments (and Args _are_ arguments): `Bool()` returns `true` if the flag was in the Parsed array, `false` if `no-<longname>` was provided, and the default otherwise.

type MissingMandatoryOption

type MissingMandatoryOption struct {
	Name string
}

func (MissingMandatoryOption) Error

func (e MissingMandatoryOption) Error() string

type Registry

type Registry map[string]*CommandConfig

Registry holds the configuration of the registered commands.

func NewRegistry

func NewRegistry() Registry

NewRegistry returns new instance of the "Registry"

func (Registry) Parse

func (registry Registry) Parse(values []string) (*CommandConfig, error)

Parse parses an array of arguments and returns a command object, or an error if any of the parameter validations fail. If no root command is registered, the first array item must be a registered command.

See the rules for `AddArg()` and `AddFlag()` for specific parse rules.

func (Registry) Register

func (registry Registry) Register(name string) *CommandConfig

Register creates a command. If the command name is an empty string, the command created is the root command. If a command is already registered, already created command is returned. Example ``` r := NewRegistry() a := r.Register("").AddArg("X", "") c := r.Parse([]string{"abc"}) // No explicit command, just an arg if c.Args["X"].Strings() != []string{"abc"} { panic() } ````

type UnknownCommand

type UnknownCommand struct {
	Name string
}

UnknownCommand represents errors when command-line arguments contain an unregistered command.

func (UnknownCommand) Error

func (e UnknownCommand) Error() string

type UnknownFlag

type UnknownFlag struct {
	Name string
}

UnknownFlag represents an error when command-line arguments contain an unregistered flag.

func (UnknownFlag) Error

func (e UnknownFlag) Error() string

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL