Skip to content
Go (golang) command line option parser inspired on the flexibility of Perl’s GetOpt::Long.
Branch: master
Clone or download

README.adoc

go-getoptions

Quick overview

  1. Define your command line specification:

    package main
    
    import (
    	"fmt"
    	"io/ioutil"
    	"log"
    	"os"
    
    	"github.com/DavidGamba/go-getoptions"
    )
    
    var logger = log.New(os.Stderr, "DEBUG: ", log.LstdFlags)
    
    func main() {
    	// Declare the variables you want your options to update
    	var debug bool
    	var greetCount int
    
    	// Declare the GetOptions object
    	opt := getoptions.New()
    
    	// Options definition
    	opt.Bool("help", false, opt.Alias("h", "?")) // Aliases can be defined
    	opt.BoolVar(&debug, "debug", false)
    	opt.IntVar(&greetCount, "greet", 0,
    		opt.Required(), // Mark option as required
    		opt.Description("Number of times to greet."), // Set the automated help description
    		opt.ArgName("number"),                        // Change the help synopsis arg from <int> to <number>
    	)
    	greetings := opt.StringMap("list", 1, 99,
    		opt.Description("Greeting list by language."),
    		opt.ArgName("lang=msg"), // Change the help synopsis arg from <key=value> to <lang=msg>
    	)
    
    	// Parse cmdline arguments or any provided []string
    	remaining, err := opt.Parse(os.Args[1:])
    
    	// Handle help before handling user errors
    	if opt.Called("help") {
    		fmt.Fprintf(os.Stderr, opt.Help())
    		os.Exit(1)
    	}
    
    	// Handle user errors
    	if err != nil {
    		fmt.Fprintf(os.Stderr, "ERROR: %s\n\n", err)
    		fmt.Fprintf(os.Stderr, opt.HelpSynopsis())
    		os.Exit(1)
    	}
    	if !debug {
    		logger.SetOutput(ioutil.Discard)
    	}
    	logger.Printf("Remaining: %v\n", remaining)
    
    	for i := 0; i < greetCount; i++ {
    		fmt.Println("Hello World, from go-getoptions!")
    	}
    	if len(greetings) > 0 {
    		fmt.Printf("Greeting List:\n")
    		for k, v := range greetings {
    			fmt.Printf("\t%s=%s\n", k, v)
    		}
    	}
    }
  2. Call it:

    $ ./myscript --help
    SYNOPSIS:
    myscript --greet <number> [--debug] [--help|-h|-?] [--list <lang=msg>...]...
    
    REQUIRED PARAMETERS:
        --greet <number>             Number of times to greet.
    
    OPTIONS:
        --debug                      (default: false)
    
        --help|-h|-?                 (default: false)
    
        --list <lang=msg>...         Greeting list by language. (default: {})
    $ ./myscript
    ERROR: Missing required option 'greet'!
    
    SYNOPSIS:
    myscript --greet <number> [--debug] [--help|-h|-?] [--list <lang=msg>...]...
    $ ./myscript -g
    ERROR: Missing argument for option 'greet'!
    
    SYNOPSIS:
    myscript --greet <number> [--debug] [--help|-h|-?] [--list <lang=msg>...]...
    $ ./myscript -g 3
    Hello World, from go-getoptions!
    Hello World, from go-getoptions!
    Hello World, from go-getoptions!
    $ ./myscript --debug -g 1 other stuff
    DEBUG: 2019/03/02 10:27:48 Remaining: [other stuff]
    Hello World, from go-getoptions!
    ./myscript -g 0 -l en='Hello World' es='Hola Mundo'
    Greeting List:
            en=Hello World
            es=Hola Mundo

Features

  • Allow passing options and non-options in any order.

  • Support for --long options.

  • Support for short (-s) options with flexible behaviour (see the Operation Modes section for details):

    • Normal (default)

    • Bundling

    • SingleDash

  • Called() method indicates if the option was passed on the command line.

  • Multiple aliases for the same option. e.g. help, man.

  • CalledAs() method indicates what alias was used to call the option on the command line.

  • Simple synopsis and option list automated help.

  • Boolean, String, Int and Float64 type options.

  • Negatable Boolean options.

    For example: --verbose, --no-verbose or --noverbose.

  • Options with Array arguments. The same option can be used multiple times with different arguments. The list of arguments will be saved into an Array like structure inside the program.

  • Options with array arguments and multiple entries.

    For example, instead of writing: color --r 10 --g 20 --b 30 --next-option or color --rgb 10 --rgb 20 --rgb 30 --next-option the input could be: color --rgb 10 20 30 --next-option

  • When using integer array options with multiple arguments, positive integer ranges are allowed.

    For example, Instead of writing: csv --columns 1 2 3 or csv --columns 1 --columns 2 --columns 3 The input could be: csv --columns 1..3

  • Options with key value arguments and multiple entries.

    For example, instead of writing: connection --server hostname=serverIP --server port=123 --client hostname=localhost --client port=456 the input could be: connection --server hostname=serverIP port=123 --client hostname=localhost port=456

  • Options with Key Value arguments. This allows the same option to be used multiple times with arguments of key value type.

    For example: rpmbuild --define name=myrpm --define version=123

  • Supports passing -- to stop parsing arguments (everything after will be left in the remaining []string).

  • Supports command line options with '='.

    For example: You can use --string=mystring and --string mystring.

  • Allows passing arguments to options that start with dash - when passed after equal.

    For example: --string=--hello and --int=-123.

  • Options with optional arguments. If the default argument is not passed the default is set.

    For example: You can call --int 123 which yields 123 or --int which yields the given default.

  • Allows abbreviations when the provided option is not ambiguous.

    For example: An option called build can be called with --b, --bu, --bui, --buil and --build as long as there is no ambiguity. In the case of ambiguity, the shortest non ambiguous combination is required.

  • Support for the lonesome dash "-". To indicate, for example, when to read input from STDIO.

  • Incremental options. Allows the same option to be called multiple times to increment a counter.

  • Supports case sensitive options. For example, you can use v to define verbose and V to define Version.

  • Support indicating if an option is required and allows overriding default error message.

  • Errors exposed as public variables to allow overriding them for internationalization.

  • Supports subcommands (stop parsing arguments when non option is passed).

  • Multiple ways of managing unknown options:

    • Fail on unknown (default).

    • Warn on unknown.

    • Pass through, allows for subcommands and can be combined with Require Order.

  • Require order: Allows for subcommands. Stop parsing arguments when the first non-option is found. When mixed with Pass through, it also stops parsing arguments when the first unmatched option is found.

How to install it

  1. Get it from github:

    go get github.com/DavidGamba/go-getoptions

  2. Then import it:

    import "github.com/DavidGamba/go-getoptions" // As getoptions

  3. Enjoy!

Dependencies

Go 1.5+

ROADMAP

  • Create new error description for errors when parsing integer ranges (1..3).

  • Option that runs a function?

  • Case insensitive matching.

  • Option values in the bundle: -h1024w800-h 1024 -w 800

  • prefix and prefix_pattern. The string that starts options. Defaults to "--" and "-" but could include "/" to support Win32 style argument handling.

  • Supports argument dividers other than '='. For example: You could define ':' and use --string=mystring, --string:mystring and --string mystring.

  • All other Perl’s Getopt::Long goodies that seem reasonable to add!

Introduction

Note
For a Quick overview, jump to that section in the TOC or review the GoDoc Documentation.

Option parsing is the act of taking command line arguments and converting them into meaningful structures within the program.

An option parser should support, at least, the following:

Boolean options

True when passed on the command line. For example:

ls --all

In go-getoptions this is accomplished with:

  • ptr := opt.Bool(name, default_value).

  • opt.BoolVar(&ptr, name, default_value).

  • Additionally, if all you want to know is if the option was passed you can use: opt.Bool(name, default_value) (without capturing its return value) and then check opt.Called(name).

Options with String arguments

The option will accept a string argument. For example:

grepp --ignore .txt

Additionally, arguments to options can be passed with the = symbol.

grepp --ignore=.txt

In go-getoptions this is accomplished with:

  • ptr := opt.String(name, default_value).

  • opt.StringVar(&ptr, name, default_value).

The features listed above are enough to create basic programs but an option parser should do better:

Options with Integer arguments

Parse an option string argument into an Integer and provide an user error if the string provided is not an integer. For example:

grepp --contex-lines 3

and:

grepp --context-lines string

Error: 'string' is not a valid integer.

In go-getoptions this is accomplished with:

  • ptr := opt.Int(name, default_value).

  • opt.IntVar(&ptr, name, default_value).

Options with Floating point arguments

Parse an option string argument into a Floating point value and provide an user error if the string provided is not a valid floating point. For example:

command --approximation 3.5

and:

command --approximation string

Error: 'string' is not a valid floating point value.

In go-getoptions this is accomplished with:

  • ptr := opt.Float64(name, default_value).

  • opt.Float64Var(&ptr, name, default_value).

The features listed above relieve the programmer from the cumbersome task of converting the option argument into the expected type.

Another feature a better option parser should have is the ability to set a flag to False.

Negatable boolean options

True when passed on the command line without any modifier and False when the --no- modifier is prefixed. For example:

command --verbose

and:

command --no-verbose, or command --noverbose

In go-getoptions this is accomplished with:

  • ptr := opt.NBool(name, default_value) which automatically defines no-name and noname.

  • opt.NBoolVar(&ptr, name, default_value) which automatically defines no-name and noname.

That covers the most basic set of features, but still it is not enough to get past a basic program. The following features will allow for a more complete interface.

Options with array arguments

This allows the same option to be used multiple times with different arguments. The list of arguments will be saved into a Slice inside the program. For example:

list-files --exclude .txt --exclude .html --exclude .pdf

In go-getoptions this is accomplished with:

  • ptr := opt.StringSlice(name, 1, 1).

Options with Key Value arguments

This allows the same option to be used multiple times with arguments of key value type. For example:

rpmbuild --define name=myrpm --define version=123

In go-getoptions this is accomplished with:

  • strMap := opt.StringMap(name, 1, 1).

Both features above should support the basic types listed before: string, integer and floating point.

go-getoptions has only implemented these two features for string.

The features above are useful when you have a variable amount of arguments, but it becomes cumbersome for the user when the number of entries is always the same. The features described below are meant to handle the cases when each option has a known number of multiple entries.

Options with array arguments and multiple entries

This allows the user to save typing. For example:

Instead of writing: color --r 10 --g 20 --b 30 --next-option or color --rgb 10 --rgb 20 --rgb 30 --next-option

The input could be: color --rgb 10 20 30 --next-option.

The setup for this feature should allow for the user to continue using both versions of the input, that is passing one argument at a time or passing the 3 arguments at once, or allow the setup to force the user to have to use the 3 arguments at once version. This is accomplished with the minimum and maximum setup parameters.

The minimum setup parameter indicates the minimum amount of parameters the user can pass at a time. For the example above, the parameter could be set to 3 to force the user to have to pass the 3 arguments at once. When set to 1, the user will be able to pass a single parameter per option call.

The maximum setup parameter indicates the maximum amount of parameters the user can pass at a time. The option parser will leave any non option argument after the maximum in the remaining slice.

In go-getoptions this is accomplished with:

  • strSlice := opt.StringSlice(name, minArgs, maxArgs).

  • intSlice := opt.IntSlice(name, minArgs, maxArgs).

    Additionally, in the case of integers, positive integer ranges are allowed. For example:

    Instead of writing: csv --columns 1 2 3 or csv --columns 1 --columns 2 --columns 3

    The input could be: csv --columns 1..3.

    In go-getoptions this is currently enabled by default when using:

    intSlice := opt.IntSlice(name, minArgs, maxArgs)

Options with key value arguments and multiple entries

This allows the user to save typing. For example:

Instead of writing: connection --server hostname=serverIP --server port=123 --client hostname=localhost --client port=456

The input could be: connection --server hostname=serverIP port=123 --client hostname=localhost port=456

In go-getoptions this is accomplished with:

  • strMap := opt.StringMap(name, minArgs, maxArgs).

That covers a complete user interface that is flexible enough to accommodate most programs. The following are advanced features:

Stop parsing options when -- is passed

Useful when arguments start with dash - and you don’t want them interpreted as options.

In go-getoptions this is the default behaviour.

Stop parsing options when a subcommand is passed

A subcommand is assumed to be the first argument that is not an option or an argument to an option. When a subcommand is found, stop parsing arguments and let a subcommand handler handle the remaining arguments. For example:

command --opt arg subcommand --subopt subarg

In the example above, --opt is an option and arg is an argument to an option, making subcommand the first non option argument.

This method is useful when both the command and the subcommand have option handlers for the same option.

For example, with:

command --help

--help is handled by command, and with:

command subcommand --help

--help is not handled by command since there was a subcommand that caused the parsing to stop.

Additionally, when mixed with pass through, it will also stop parsing arguments when it finds the first unmatched option.

In go-getoptions this is accomplished with:

  • opt.SetRequireOrder().

    And can be combined with:

  • opt.SetUnknownMode("pass").

Allow passing options and non-options in any order

Some option parsers force you to put the options before or after the arguments. That is really annoying!

In go-getoptions this is the default behaviour.

Allow pass through

Have an option to pass through unmatched options. Useful when writing programs with multiple options depending on the main arguments. The initial parser will only capture the help or global options and pass through everything else. Additional argument parsing calls are invoked on the remaining arguments based on the initial input.

In go-getoptions this is accomplished with:

  • opt.SetUnknownMode("pass").

Fail on unknown

The opposite of the above option. Useful if you want to ensure there are no input mistakes and force the application to stop.

In go-getoptions this is the default behaviour.

Warn on unknown

Less strict parsing of options. This will warn the user that the option used is not a valid option but it will not stop the rest of the program.

In go-getoptions this is accomplished with:

  • opt.SetUnknownMode("warn").

Option aliases

Options should be allowed to have different aliases. For example, the same option could be invoked with --address or --hostname.

In go-getoptions, pass opt.Alias("my-alias") to any option. For example:

opt.BoolVar(&flag, "flag", false, opt.Alias("alias", "alias-2"))

Finally, to know with what alias an option was called with used opt.CalledAs(<name>).

Required options

Mark an option as required. Return an error if the option is not called.

In go-getoptions, pass opt.Required() to any option. For example:

opt.BoolVar(&flag, "flag", false, opt.Required())

Optionally, override the default error message with opt.Required(msg). For example:

opt.BoolVar(&flag, "flag", false, opt.Required("Missing --flag!"))

Incremental option

Some options can be passed more than once to increment an internal counter. For example:

command --v --v --v

Could increase the verbosity level each time the option is passed.

In go-getoptions this is accomplished with:

  • ptr := opt.Increment(name, default_value).

  • opt.IncrementVar(&ptr, name, default_value).

Additional types

The option parser could provide converters to additional types. The disadvantage of providing non basic types is that the option parser grows in size.

Not yet implemented in go-getoptions.

Options with optional arguments

With regular options, when the argument is not passed (for example: --level instead of --level=debug) you will get a Missing argument error. When using options with optional arguments, If the argument is not passed, the option will set the default value for the option type. For this feature to be fully effective in strong typed languages where types have defaults, there must be a means to query the option parser to determine whether or not the option was called or not.

In go-getoptions this is accomplished with:

  • ptr := opt.StringOptional(name, default_value).

  • ptr := opt.IntOptional(name, default_value).

  • Not yet implemented for float64.

  • The above should be used in combination with opt.Called(name).

For example, for the following definition:

ptr := opt.StringOptional("level", "info")

  • If the option level is called with just --level, the value of *ptr is the default "info" and querying opt.Called("level") returns true.

  • If the option level is called with --level=debug, the value of *ptr is "debug" and querying opt.Called("level") returns true.

  • Finally, If the option level is not called, the value of *ptr is the default "info" and querying opt.Called("level") returns false.

Option flags that call a method internally

If all the flag is doing is call a method or function when present, then having a way to call that function directly saves the programmer some time.

Not yet implemented in go-getoptions.

Notice how so far only long options (options starting with double dash --) have been mentioned. There are 3 main ways to handle short options (options starting with only one dash -), see the Operation Modes section for details.

Operation Modes

The behaviour for long options (options starting with double dash --) is consistent across operation modes. The behaviour for short options (options starting with only one dash -) depends on the operation mode. The sections below show the different operation modes.

Normal Mode (default)

Given argument Interpretation

--opt

option: "opt", argument: nil

--opt=arg

option: "opt", argument: "arg" [1]


1. Argument gets type casted depending on option definition.

-opt

option: "opt", argument: nil

-opt=arg

option: "opt", argument: "arg" [2]


2. Argument gets type casted depending on option definition.

Bundling Mode

Set by defining opt.SetMode("bundling").

Given option Interpretation

--opt

option: "opt", argument: nil

--opt=arg

option: "opt", argument: "arg" [3]


3. Argument gets type casted depending on option definition.

-opt

option: "o", argument: nil
option: "p", argument: nil
option: "t", argument: nil

-opt=arg

option: "o", argument: nil
option: "p", argument: nil
option: "t", argument: "arg" [4]


4. Argument gets type casted depending on option definition.

Enforce Single Dash Mode

Set by defining opt.SetMode("singleDash").

Given option Interpretation

--opt

option: "opt", argument: nil

--opt=arg

option: "opt", argument: "arg" [5]


5. Argument gets type casted depending on option definition.

-opt

option: "o", argument: "pt" [6]


6. Argument gets type casted depending on option definition.

-opt=arg

option: "o", argument: "pt=arg" [7]


7. Argument gets type casted depending on option definition.

Biggest option parser misfeature - Automatically generate help

The biggest misfeature an option parser can have is to automatically generate the help message for the programmer. This seemingly helpful feature has caused most tools not to have proper man pages anymore and to have all options descriptions mixed in the help synopsis.

If you are writing a mid to large tool, don’t be lazy, write a man page for your program and create the program’s synopsis yourself. If you are looking for options, asciidoctor has a manpage backend that can generate manpages written in the Asciidoc markup.

For simple stuff, however, use the automated help.

License

This file is part of go-getoptions.

Copyright © 2015-2019 David Gamba Rios

This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.

You can’t perform that action at this time.