Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

ser1.net/claptrap/v3

Package Overview
Dependencies
Alerts
File Explorer
Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

ser1.net/claptrap/v3

  • v3.4.0
  • Go
  • Socket score

Version published
Created
Source

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. 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") // A flag with a default true value
	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")                            // The first optional argument must be a boolean (default: true)
	info.AddArg("second", 99, "the second argument")                            // The second optional argument is an int (default: 99)
	info.AddArg("third...", []string{"a", "b", "c", "d"}, "the third argument") // The rest of the arguments must each be a letter, a-d
	info.AddFlag("verbose", "v", 0, "info verbosity flag")                      // A completely different verbose, defaulting to true
	info.AddFlag("fuzzy", "f", true, "fuzzilischiousity!")                      // A completely different verbose, defaulting to true

	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:]) // exclude the command!

	if e != nil {
		fmt.Printf("%+v\n", e)
	} else {
		// Looping like this is probably only good if the commands are unique
		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"})  // exclude the command!
  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:

  1. 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.
  2. 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.
  3. If the value is a choice, the default value of the type is returned. Choices do not have default values.
  4. 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.

libshortlongcombinedinvertedenv varstypeschoicescommandsvarargsgood apimand/optfilesusage
claptrapYYYYNYYYYYYNY
clapperYYNYNNNYYYNN
droundyYYYNYYYNY
sircmpwnYNYNNNNNYYYNY
opflagYYYNNYNNYYNNY
namsralYYYYYYYY
integriiYYyYYYYY
jessevdkYYYNNYYYYNYNY

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.

LibraryLOCDepsABC ScoreComplexity
clapper303011976
sircmpwn-getopt501015460
cosiner-argv462015594
claptrap4940243167
droundy-goopt5960243162
namsral-flag7640299162
ogier-pflag1112043897
opflag11610461118
integrii-flaggy17320659303
spf13-pflag385601464583
jessevdk-go-flags3529016041045
dmulholland-args437119997
thatisuday-commando6401213110
mwitkow-go-flagz14611487265
cosiner-flag18211713463
moogar0880-venom20292604303
stevenroose-gonfig11695540375
peterbourgon-ff10606308231
cobra452961507808
dc0d-argify3481413996

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.

FAQs

Package last updated on 14 Oct 2022

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc