Skip to content

tdewolff/argp

Repository files navigation

GNU command line argument parser

Command line argument parser following the GNU standard.

./test -vo out.png --size 256 input.txt

with the following features:

  • build-in help (-h and --help) message
  • scan arguments into struct fields with configuration in tags
  • scan into composite field types (arrays, slices, structs)
  • allow for nested sub commands

GNU command line argument rules:

  • arguments are options when they begin with a hyphen -
  • multiple options can be combined: -abc is the same as -a -b -c
  • long options start with two hyphens: --abc is one option
  • option names are alphanumeric characters
  • options can have a value: -a 1 means that a has value 1
  • option values can be separated by a space, equal sign, or nothing: -a1 -a=1 -a 1 are all equal
  • options and non-options can be interleaved
  • the argument -- terminates all options so that all following arguments are treated as non-options
  • a single - argument is a non-option usually used to mean standard in or out streams
  • options may be specified multiple times, only the last one determines its value
  • options can have multiple values: -a 1 2 3 means that a is an array/slice/struct of three numbers of value [1,2,3]

See also github.com/tdewolff/prompt for a command line prompter.

Installation

Make sure you have Git and Go (1.22 or higher) installed, run

mkdir Project
cd Project
go mod init
go get -u github.com/tdewolff/argp

Then add the following import

import (
    "github.com/tdewolff/argp"
)

Examples

Default usage

A regular command with short and long options. See cmd/test/main.go.

package main

import "github.com/tdewolff/argp"

func main() {
    var verbose int
    var input string
    var output string
    var files []string
    size := 512 // default value

    cmd := argp.New("CLI tool description")
    cmd.AddOpt(argp.Count{&verbose}, "v", "verbose", "Increase verbosity, eg. -vvv")
    cmd.AddOpt(&output, "o", "output", "Output file name")
    cmd.AddOpt(&size, "", "size", "Image size")
    cmd.AddArg(&input, "input", "Input file name")
    cmd.AddRest(&files, "files", "Additional files")
    cmd.Parse()

    // ...
}

with help output

Usage: test [options] input files...

Options:
  -h, --help          Help
  -o, --output string Output file name
      --size=512 int  Image size
  -v, --verbose int   Increase verbosity, eg. -vvv

Arguments:
  input     Input file name
  files     Additional files

Sub commands

Example with sub commands using a main command for when no sub command is used, and a sub command named "cmd". For the main command we can also use New and AddOpt instead and process the command after argp.Parse().

package main

import "github.com/tdewolff/argp"

func main() {
    cmd := argp.NewCmd(&Main{}, "CLI tool description")
    cmd.AddCmd(&Command{}, "cmd", "Sub command")
    cmd.Parse()
}

type Main struct {
    Version bool `short:"v"`
}

func (cmd *Main) Run() error {
    // ...
}

type Command struct {
    Verbose bool `short:"v" name:""`
    Output string `short:"o" desc:"Output file name"`
    Size int `default:"512" desc:"Image size"`
}

func (cmd *Command) Run() error {
    // ...
}

Arguments

var input string
cmd.AddArg(&input, "input", "Input file name")

var files []string
cmd.AddRest(&files, "files", "Additional input files")

Options

Basic types

var v string = "default"
cmd.AddOpt(&v, "v", "var", "description")

var v bool = true
cmd.AddOpt(&v, "v", "var", "description")

var v int = 42 // also: int8, int16, int32, int64
cmd.AddOpt(&v, "v", "var", "description")

var v uint = 42 // also: uint8, uint16, uint32, uint64
cmd.AddOpt(&v, "v", "var", "description")

var v float64 = 4.2 // also: float32
cmd.AddOpt(&v, "v", "var", "description")

Composite types

v := [2]int{4, 2} // element can be any valid basic or composite type
cmd.AddOpt(&v, "v", "var", "description")
// --var [4 2]  =>  [2]int{4, 2}
// or: --var 4,2  =>  [2]int{4, 2}

v := []int{4, 2, 1} // element can be any valid basic or composite type
cmd.AddOpt(&v, "v", "var", "description")
// --var [4 2 1]  =>  []int{4, 2, 1}
// or: --var 4,2,1  =>  []int{4, 2, 1}

v := map[int]string{1:"one", 2:"two"} // key and value can be any valid basic or composite type
cmd.AddOpt(&v, "v", "var", "description")
// --var {1:one 2:two}  =>  map[int]string{1:"one", 2:"two"}

v := struct { // fields can be any valid basic or composite type
    S string
    I int
    B [2]bool
}{"string", 42, [2]bool{0, 1}}
cmd.AddOpt(&v, "v", "var", "description")
// --var {string 42 [0 1]}  =>  struct{S string, I int, B [2]bool}{"string", 42, false, true}

Count

Count the number of time a flag has been passed.

var c int
cmd.AddOpt(argp.Count{&c}, "c", "count", "Count")
// Count the number of times flag is present
// -c -c / -cc / --count --count  =>  2
// or: -c 5  =>  5

Append

Append each flag to a list.

var v []int
cmd.AddOpt(argp.Append{&v}, "v", "value", "Values")
// Append values for each flag
// -v 1 -v 2  =>  [1 2]

Config

Load all arguments from a configuration file. Currently only TOML is supported.

cmd.AddOpt(&argp.Config{cmd, "config.toml"}, "", "config", "Configuration file")

List

Use a list source specified as type:list. Default supported types are: inline.

  • Inline takes a []string, e.g. inline:[foo bar]
list := argp.NewList(il)
defer list.Close()

cmd.AddOpt(&list, "", "list", "List")

You can add a MySQL source:

type mysqlList struct {
	Hosts    string
	User     string
	Password string
	Dbname   string
	Query    string
}

func newMySQLList(s []string) (argp.ListSource, error) {
	if len(s) != 1 {
		return nil, fmt.Errorf("invalid path")
	}

	t := mysqlList{}
	if err := argp.LoadConfigFile(&t, s[0]); err != nil {
		return nil, err
	}

	uri := fmt.Sprintf("%s:%s@%s/%s", t.User, t.Password, t.Hosts, t.Dbname)
	db, err := sqlx.Open("mysql", uri)
	if err != nil {
		return nil, err
	}
	db.SetConnMaxLifetime(time.Minute)
	db.SetConnMaxIdleTime(time.Minute)
	db.SetMaxOpenConns(10)
	db.SetMaxIdleConns(10)
	return argp.NewSQLList(db, t.Query, "")
}

// ...
list.AddSource("mysql", newMySQLList)
// ...

Use as ./bin -list mysql:list-config.toml.

Dict

Use a dict source specified as type:dict. Default supported types are: static and inline.

  • Static takes a string and will return that as a value for all keys, e.g. static:foobar
  • Inline takes a map[string]string, e.g. inline:{foo:1 bar:2}
dict := argp.NewDict([]string{"static:value"})
defer dict.Close()

cmd.AddOpt(&dict, "", "dict", "Dict")

You can add custom sources must like the mysqlList example above.

Option tags

The following struct will accept the following options and arguments:

  • -v or --var with a default value of 42
  • The first argument called first with a default value of 4.2
  • The other arguments called rest
type Command struct {
    Var1 int `short:"v" name:"var" default:"42" desc:"Description"`
    Var2 float64 `name:"first" index:"0" default:"4.2"`
    Var3 []string `name:"rest" index:"*"`
}

func (cmd *Command) Run() error {
    // run command
    return nil
}

License

Released under the MIT license.

About

GNU command line argument parser

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages