📚 go-getoptions - Awesome Go Library for Command Line
Go option parser inspired by the flexibility of Perl’s GetOpt::Long.
Detailed Description of go-getoptions
= go-getoptions David Gamba, https://github.com/DavidGamba :idprefix: :name: go-getoptions :toc: macro :toclevels: 4
Fully featured Go (golang) command line option parser with built-in auto-completion support.
image:https://pkg.go.dev/badge/github.com/DavidGamba/go-getoptions.svg["Go Reference", link="https://pkg.go.dev/github.com/DavidGamba/go-getoptions"] image:https://github.com/DavidGamba/go-getoptions/actions/workflows/test.yml/badge.svg?branch=master["Build Status", link="https://github.com/DavidGamba/go-getoptions/actions/workflows/test.yml?query=branch:master"] image:https://codecov.io/github/DavidGamba/go-getoptions/coverage.svg?branch=master["Coverage via Codecov", link="https://codecov.io/github/DavidGamba/go-getoptions?branch=release"]
Video Demo: https://youtu.be/1ZGyIkC5shM
toc::[]
[[quick_overview]] == Quick overview
. See a detailed video demonstration: https://youtu.be/1ZGyIkC5shM
. Define your command line specification: + [source,go]
package main
import ( "context" "errors" "fmt" "io" "log" "os"
"github.com/DavidGamba/go-getoptions"
)
var Logger = log.New(os.Stderr, "", log.LstdFlags)
func main() { os.Exit(program(os.Args)) }
func program(args []string) int { ctx, cancel, done := getoptions.InterruptContext() defer func() { cancel(); <-done }()
opt := getoptions.New()
opt.Self("myscript", "Simple demo script")
opt.Bool("debug", false, opt.GetEnv("DEBUG"))
opt.Int("greet", 0, opt.Required(), opt.Description("Number of times to greet."))
opt.StringMap("list", 1, 99, opt.Description("Greeting list by language."))
opt.Bool("quiet", false, opt.GetEnv("QUIET"))
opt.HelpSynopsisArg("<name>", "Name to greet.")
opt.SetCommandFn(Run)
opt.HelpCommand("help", opt.Alias("?"))
remaining, err := opt.Parse(args[1:])
if err != nil {
fmt.Fprintf(os.Stderr, "ERROR: %s\n", err)
return 1
}
if opt.Called("quiet") {
Logger.SetOutput(io.Discard)
}
err = opt.Dispatch(ctx, remaining)
if err != nil {
if errors.Is(err, getoptions.ErrorHelpCalled) {
return 1
}
fmt.Fprintf(os.Stderr, "ERROR: %s\n", err)
if errors.Is(err, getoptions.ErrorParsing) {
fmt.Fprintf(os.Stderr, "\n"+opt.Help())
}
return 1
}
return 0
}
func Run(ctx context.Context, opt *getoptions.GetOpt, args []string) error { // Get arguments and options name, _, err := opt.GetRequiredArg(args) if err != nil { return err } greetCount := opt.Value("greet").(int) list := opt.Value("list").(map[string]string)
Logger.Printf("Running: %v", args)
// Use the int variable
for i := 0; i < greetCount; i++ {
fmt.Printf("Hello %s, from go-getoptions!\n", name)
}
// Use the map[string]string variable
if len(list) > 0 {
fmt.Printf("Greeting List:\n")
for k, v := range list {
fmt.Printf("\t%s=%s\n", k, v)
}
}
return nil
}
. Call it: + .Show help
$ ./myscript help NAME: myscript - Simple demo script
SYNOPSIS: myscript --greet [--debug] [--help|-?] [--list <key=value>...]... [--quiet]
ARGUMENTS: Name to greet.
REQUIRED PARAMETERS: --greet Number of times to greet.
OPTIONS: --debug (default: false, env: DEBUG)
--help|-? (default: false)
--list <key=value>... Greeting list by language. (default: {})
--quiet (default: false, env: QUIET)
.Show errors
$ ./myscript ERROR: Missing required parameter 'greet'
.Show errors
$ ./myscript -g ERROR: Missing argument for option 'greet'!
.Show errors
$ ./myscript -g 3 ERROR: Missing SYNOPSIS: myscript --greet [--debug] [--help|-?] [--list <key=value>...]... [--quiet]
.Use of int option
$ ./myscript -g 3 David 2024/01/04 23:25:14 Running: [David] Hello David, from go-getoptions! Hello David, from go-getoptions! Hello David, from go-getoptions!
.Use of bool option
$ ./myscript -g 1 David --quiet Hello David, from go-getoptions!
.Use of map option
$ ./myscript -g 0 David -l en='Hello World' es='Hola Mundo' 2024/01/04 23:27:00 Running: [David] Greeting List: en=Hello World es=Hola Mundo
NOTE: If you are starting a new project, instead of copying the example code from above, use the code from the link:./docs/new-project-templates.adoc[New Project Templates].
== Examples
=== Simple script
A simple script link:./examples/myscript/main.go[]
To use the autocompletion, cd to the link:./examples/myscript[] dir and run: source sourceme.bash
The run go build
and ./myscript
.
Tab completion for this script is triggered for options only, so you need to have a dash -
to trigger it: ./myscript -<tab><tab>
=== Program with multiple commands separated in multiple packages
This is the other extreme, a large program that can separate each command in a separate go package.
The base is located at link:./examples/complex/main.go[]
The commands are located at:
- link:./examples/complex/greet/greet.go[]
- link:./examples/complex/log/log.go[]
- link:./examples/complex/show/show.go[]
- link:./examples/complex/slow/slow.go[]
To use the autocompletion, cd to the link:./examples/complex[] dir and run: source sourceme.bash
The run go build
and ./complex
.
Tab completion without arguments triggers completion for commands, for option completion add a dash -
and trigger it: ./complex -<tab><tab>
The link:./examples/complex/slow/slow.go[slow] command shows an example of an slow command that can be cancelled with Ctrl+C
.
The cancellation is passed to the command through context.Context
and it is handled at the command to stop taking new work and trigger a cleanup routine.
Running Ctrl+C
twice cancels the cancellation routine and fully cancels the program.
The link:./examples/complex/greet/greet.go[greet] command shows an example of using commands and subcommands.
=== Directed Acyclic Graph Build System
This example shows task dependency orchestration and parallelization link:./examples/dag/main.go[].
To use the autocompletion, cd to the link:./examples/dag[] dir and run: source sourceme.bash
The run go build
and ./dag
.
Tab completion without arguments triggers completion for commands, for option completion add a dash -
and trigger it: ./dag -<tab><tab>
== DAG Build System
For an overview of the Directed Acyclic Graph Build System see link:./dag/README.adoc[]
NOTE: The DAG code is in a separate package so it is not pulled in by default.
== Features
• Built in auto completion. A single line of bash is all it takes. + [source,bash]
complete -o default -C my-go-program my-go-program
Zshell is also supported, by exporting ZSHELL=true
in your environment and using bashcompinit
.
• Allow passing options and non-options (arguments) 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.
• Synopsis and option list automated help.
• Boolean, String, Int, Float64, Slice and Map type options.
• Options with Array arguments. The same option can be used multiple times with different arguments. The list of arguments will be saved into an Slice.
• 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.
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
• 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
• 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
.
• Supports passing --
to stop parsing arguments (everything after will be left in the remaining []string
).
• 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 the default error message.
• Errors and Help Strings exposed as public variables to allow overriding them for internationalization.
• Supports program commands and subcommands (when a command is passed a command function is triggered to handle the command logic).
• Built in opt.Dispatch
function calls commands and propagates context, options, arguments and cancellation signals.
• Multiple ways of managing unknown options:
- Fail on unknown (default).
- Warn on unknown.
- Pass through, allows for commands and can be combined with Require Order.
• Require order: Allows for commands. 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.
• Set options by reading Environment Variables. Precedence is CLI option over Env Var over Default.
== How to install it
. Get it from github:
+
go get github.com/DavidGamba/go-getoptions
. Then import it:
+
import "github.com/DavidGamba/go-getoptions" // As getoptions
. Enjoy!
== Dependencies
Go 1.16+
Only the last two versions of Go will be supported.
== Introduction
NOTE: For a <<quick_overview>>, jump to that section in the TOC or review the http://godoc.org/github.com/DavidGamba/go-getoptions[GoDoc Documentation].
Option parsing is the act of taking command line (CLI) arguments and converting them into meaningful structures within the program.
First declare a getoptions
instance:
[source, go]
opt := getoptions.New()
Then declare the options you want to parse:
[source, go]
opt.String("string", "default_value")
Optionally, define option modifiers:
[source, go]
opt.String("string", "default_value",
opt.Alias("s"), // Allow -s as an alias for --string
opt.Description("This is a string option"), // Add a description to the option
opt.Required(), // Mark the option as required
opt.GetEnv("STRING"), // Set the environment variable to read the option from
opt.ArgName("mystring"), // Set the argument name for the help output
// The help with show --string <mystring> instead of --string <string>
opt.ValidValues("value1", "value2"), // Set the valid values for the option, these are used for autocompletion too
opt.SetCalled(true), // Forcefully set the option as if called in the CLI
)
You can also define arguments:
[source, go]
opt.HelpSynopsisArg("", "arg1 description") opt.HelpSynopsisArg("", "arg2 description")
Define the function for the program:
opt.SetCommandFn(Run)
If no function is defined and opt.Dispatch
is called, the program will show a help message with any commands or subcommands.
Define any commands and their options, arguments and functions:
[source, go]
cmd := opt.NewCommand("command", "command description") cmd.String("int", 123) cmd.HelpSynopsisArg("", "arg1 description") cmd.SetCommandFn(CommandRun)
NOTE: Options defined at a parent level will be inherited by the command unless cmd.UnsetOptions()
is called.
After defining options and commands declare the help command, it must be the last one defined.
[source, go]
opt.HelpCommand("help", opt.Alias("?"))
Parse the CLI arguments (or any []string
):
[source, go]
remaining, err := opt.Parse(os.Args[1:])
Finally, call dispatch which will call the proper command function for the given arguments:
[source, go]
err = opt.Dispatch(ctx, remaining)
Dispatch requires a context.Context
to be passed which can be used to propagate cancellation signals or configuration values.
A built in helper to create a context with cancellation support (os.Interrupt
, syscall.SIGHUP
, syscall.SIGTERM
) is provided:
[source, go]
ctx, cancel, done := getoptions.InterruptContext() defer func() { cancel(); <-done }()
err = opt.Dispatch(ctx, remaining)
The actual functions running the business logic are the CommandFn
functions set with the SetCommandFn
.
The CommandFn
function signature is:
[source, go]
func Name(ctx context.Context, opt *getoptions.GetOpt, args []string) error { return nil }
This function will receive the context, the parsed options and the remaining arguments.
Read the received options from the opt
variable.
[source, go]
func Name(ctx context.Context, opt *getoptions.GetOpt, args []string) error { file := opt.Value("file").(string) count := opt.Value("count").(int) tags := opt.Value("tags").(map[string]string)
// logic
return nil
}
NOTE: The opt.Value
function returns an interface{}
so it needs to be type casted to the proper type.
The type cast will panic if trying to read an option that is not defined.
Read the received arguments from the args
slice.
Additionally, use the opt.GetRequiredArg
(with int and float64 variants) to simplify handling required arguments and providing error messages.
[source, go]
func Name(ctx context.Context, opt *getoptions.GetOpt, args []string) error { arg1, args, err := opt.GetRequiredArgInt(args) if err != nil { return err }
// logic
return nil
}
== Automatically generate help
For a proper extended man page for your program consider link:http://asciidoctor.org/[asciidoctor] that can generate manpages written in the Asciidoc markup.
For the built-in help, you can add a description to your program:
opt.Self("", "This is a program description")
NOTE: When the first argument is empty, it will use the program name from os.Args[0]
.
For options help ensure you add option descriptions and argument names.
opt.Description("This is a string option")
opt.ArgName("mystring")
The help command needs to be defined after all options, commands and subcommands.
opt.HelpCommand("help", opt.Alias("?"))
When calling the help command, you get the full help. Optionally you can print only given sections of the Help.
For example:
[source, go]
fmt.Fprintf(os.Stderr, "%s", opt.Help(getoptions.HelpSynopsis))
Or through a helper:
[source, go]
func ForceUnlock(ctx context.Context, opt *getoptions.GetOpt, args []string) error { lockID, args, err := opt.GetRequiredArg(args) if err != nil { return err }
In the code above, if there is no argument passed, the GetRequiredArg
will print an error plus the synopsis:
ERROR: Missing SYNOPSIS: program [--help]
The error return is getoptions.ErrorHelpCalled
which signals the help is already printed.
The dispatch error handling can handle this error and not print and additional error message.
[source, go]
err = opt.Dispatch(ctx, remaining)
if err != nil {
if errors.Is(err, getoptions.ErrorHelpCalled) {
return 1
}
fmt.Fprintf(os.Stderr, "ERROR: %s\n", err)
if errors.Is(err, getoptions.ErrorParsing) {
fmt.Fprintf(os.Stderr, "\n"+opt.Help())
}
return 1
}
return 0
Another helpful error to check for is getoptions.ErrorParsing
, as shown above, which indicates there was a problem parsing the CLI arguments.
This can be used, to print the help only in cases where the user didn't enter valid CLI options or arguments.
The built in help shows default values and environment variables when available.
It separates COMMANDS, ARGUMENTS, REQUIRED PARAMETERS and OPTIONS into separate sections.
For example, the following is a script using the built in help:
$ bt terraform force-unlock help NAME: bt terraform force-unlock
SYNOPSIS: bt terraform force-unlock [--help|-?] [--profile ] [--quiet] [--ws ]
ARGUMENTS: Lock ID
OPTIONS: --help|-? (default: false)
--profile <string> BT Terraform Profile to use (default: "default", env: AWS_PROFILE)
--quiet (default: false, env: QUIET)
--ws <string> Workspace to use (default: "")
And below is the output of the automated help of a program with multiple commands:
$ tz help SYNOPSIS: tz [--config|-c ] [--format-standard|--format-12-hour|--format-12h] [--group ] [--help|-?] [--short|-s] [--verbose] []
COMMANDS: cities filter cities list list list all timezones version show version
OPTIONS: --config|-c Config file (default: "")
--format-standard|--format-12-hour|--format-12h Use standard 12 hour AM/PM time format (default: false)
--group <string> Group to show (default: "")
--help|-? (default: false)
--short|-s Don't show timezone bars (default: false)
--verbose Enable logging (default: false, env: TZ_VERBOSE)
Use 'tz help ' for extra details.
Any built-in string in go-getoptions
, like titles, is exposed as a public variable so it can be overridden for internationalization.
== Autocompletion
To enable bash autocompletion, add the following line to your bash profile:
[source,bash]
complete -o default -C my-go-program my-go-program
For the above to work, the program must be in the PATH. Otherwise:
[source,bash]
complete -o default -C "$HOME/go/bin/my-go-program" my-go-program
To enable zsh autocompletion, add the following line to your zsh profile:
[source,zsh]
export ZSHELL="true" autoload -U +X compinit && compinit autoload -U +X bashcompinit && bashcompinit complete -o default -C my-go-program my-go-program
The ZSHELL="true"
export is required because bash and zsh have different ways of handling autocompletion and there is no reliable way to detect which shell is being used.
If testing completion in the CLI, you might require to first clean the completion entry that complete
auto generates when hitting Tab
twice:
complete -r ./my-go-program 2>/dev/null
When providing these as scripts that users source but not add into their profile you can use the following sourceme.bash
script:
.sourceme.bash [source,bash]
#!/bin/bash
Remove existing entries to ensure the right one is loaded
This is not required when the completion one liner is loaded in your bashrc.
complete -r ./my-go-program 2>/dev/null
complete -o default -C "$PWD/my-go-program" my-go-program
Then when the users go into the directory and run source sourceme.bash
the autocompletion will be enabled.
== Options
=== Boolean options
Opposite of default when passed on the command line.
ptr := opt.Bool(name, false)
opt.BoolVar(&ptr, name, false)
- Additionally, if all you want to know is if the option was passed you can use:
opt.Bool(name, false)
(without capturing its return value) and then checkopt.Called(name)
. - Also, you can get the value with
v, ok := opt.Value(name).(bool)
.
For example:
ls --all
=== Options with String arguments
The option will accept a string argument.
ptr := opt.String(name, "default")
.opt.StringVar(&ptr, name, "default")
.
For example:
grepp --ignore .txt
Additionally, arguments to options can be passed with the =
symbol.
grepp --ignore=.txt
or count --from=-123
=== 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.
ptr := opt.Int(name, 0)
.opt.IntVar(&ptr, name, 0)
.
For example:
grepp --contex-lines 3
and:
grepp --context-lines string
Error: 'string' is not a valid integer.
=== 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.
ptr := opt.Float64(name, 3.14)
.opt.Float64Var(&ptr, name, 3.14)
.
For example:
program --approximation 3.5
and:
$ program --approximation string
Error: 'string' is not a valid floating point value.
=== 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.
ptr := opt.StringSlice(name, 1, 99)
.opt.StringSliceVar(&ptr, name, 1, 99)
.ptr := opt.IntSlice(name, 1, 99)
.opt.IntSliceVar(&ptr, name, 1, 99)
.ptr := opt.Float64Slice(name, 1, 99)
.opt.Float64SliceVar(&ptr, name, 1, 99)
.
For example:
list-files --exclude .txt --exclude .html --exclude .pdf
or:
list-files --exclude .txt .html .pdf
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.
Good defaults are 1
and 99
.
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
.
=== Options with Key Value arguments
This allows the same option to be used multiple times with arguments of key value type.
strMap := opt.StringMap(name, 1, 99)
.opt.StringMapVar(&ptr, name, 1, 99)
.
For example:
rpmbuild --define name=myrpm --define version=123
or:
rpmbuild --define name=myrpm version=123
Also, 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
=== Incremental option
ptr := opt.Increment(name, default_value)
.opt.IncrementVar(&ptr, name, default_value)
.
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.
=== Options with optional arguments
ptr := opt.StringOptional(name, default_value)
.ptr := opt.IntOptional(name, default_value)
.ptr := opt.Float64Optional(name, default_value)
.- The above should be used in combination with
opt.Called(name)
.
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.
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 queryingopt.Called("level")
returnstrue
. - If the option
level
is called with--level=debug
, the value of*ptr
is"debug"
and queryingopt.Called("level")
returnstrue
. - Finally, If the option
level
is not called, the value of*ptr
is the default"info"
and queryingopt.Called("level")
returnsfalse
.
=== Stop parsing options when --
is passed
Useful when arguments start with dash -
and you don't want them interpreted as options.
=== 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!
The go-getoptions
parser knows when to expect arguments for an option so they can be intermixed with arguments without issues.
=== Allow pass through
opt.SetUnknownMode(getoptions.Pass)
.
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.
=== 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.
It can be explicitly set with:
opt.SetUnknownMode(getoptions.Fail)
.
=== 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(getoptions.Warn)
.
=== Option Modifiers (ModifyFn)
==== Aliases
opt.BoolVar(&flag, "flag", false, opt.Alias("alias", "alias-2"))
Use opt.CalledAs(<name>)
to determine the alias used to call the option.
==== Description
opt.BoolVar(&flag, "flag", false, opt.Description("This is a flag"))
Add a description to the option.
==== Required options
opt.BoolVar(&flag, "flag", false, opt.Required())
Mark an option as required. Return an error if the option is not called.
Optionally, override the default error message with opt.Required(msg)
.
For example:
opt.BoolVar(&flag, "flag", false, opt.Required("Missing --flag!"))
==== Read option value from environment variable
opt.BoolVar(&flag, "flag", false, opt.GetEnv("FLAG"))
Precedence is CLI option over Env Var over Default.
Supported for the following types:
opt.Bool
andopt.BoolVar
opt.String
,opt.StringVar
,opt.StringOptional
, andopt.StringVarOptional
opt.Int
,opt.IntVar
,opt.IntOptional
, andopt.IntVarOptional
opt.Float64
,opt.Float64Var
,opt.Float64Optional
, andopt.Float64VarOptional
NOTE: Non supported option types behave with a No-Op when opt.GetEnv
is defined.
When using opt.GetEnv
with opt.Bool
or opt.BoolVar
, only the words "true" or "false" are valid.
They can be provided in any casing, for example: "true", "True" or "TRUE".
NOTE: For numeric values, opt.Int
and opt.Float64
and their derivatives, environment variable string conversion errors are ignored and the default value is assigned.
==== Help argument name hint
opt.StringVar(&str, "str", false, opt.ArgName("my_arg_name"))
The default help string for an option is:
- string: ""
- int: ""
- float64: ""
Override it with opt.ArgName("my_arg_name")
.
It additionally shows in the autocompletion hints.
==== Suggested values
opt.StringVar(&str, "str", false, opt.SuggestedValues("value1", "value2"))
This list will be added to the autocompletion engine.
==== Valid values
opt.StringVar(&str, "str", false, opt.ValidValues("value1", "value2"))
Limit the list of valid values for the option. This list will be added to the autocompletion engine.
==== Set option as called
opt.StringVar(&str, "str", false, opt.SetCalled(true))
When calling CommandFn
directly, it is sometimes useful to set the option as called.
Use cases are for testing and wrappers.
[[operation_modes]] == Operation Modes: How to handle single dash '-' options
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 -
).
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
a|option: "opt"
, argument: nil
|--opt=arg
a|option: "opt"
, argument: "arg"
footnote:[Argument gets type casted depending on option definition.]
|-opt
a|option: "opt"
, argument: nil
|-opt=arg
a|option: "opt"
, argument: "arg"
footnote:[Argument gets type casted depending on option definition.]
|===
=== Bundling Mode
Set by defining opt.SetMode(getoptions.Bundling)
.
|=== |Given option |Interpretation
|--opt
a|option: "opt"
, argument: nil
|--opt=arg
a|option: "opt"
, argument: "arg"
footnote:[Argument gets type casted depending on option definition.]
|-opt
a|option: "o"
, argument: nil
+
option: "p"
, argument: nil
+
option: "t"
, argument: nil
|-opt=arg
a|option: "o"
, argument: nil
+
option: "p"
, argument: nil
+
option: "t"
, argument: "arg"
footnote:[Argument gets type casted depending on option definition.]
|===
=== Enforce Single Dash Mode
Set by defining opt.SetMode(getoptions.SingleDash)
.
|=== |Given option |Interpretation
|--opt
a|option: "opt"
, argument: nil
|--opt=arg
a|option: "opt"
, argument: "arg"
footnote:[Argument gets type casted depending on option definition.]
|-opt
a|option: "o"
, argument: "pt"
footnote:[Argument gets type casted depending on option definition.]
|-opt=arg
a|option: "o"
, argument: "pt=arg"
footnote:[Argument gets type casted depending on option definition.]
|===
== Command behaviour
This section describes how the parser resolves ambiguities between the program and the command.
Given a definition like:
[source, go]
func main() { var profile, password string opt := New() opt.SetUnknownMode(Pass) opt.StringVar(&profile, "profile", "") command := NewCommand() command.StringVar(&password, "password", "") opt.Command(command.Self("command", "").SetCommandFn(commandFn)) remaining, err := opt.Parse(os.Args[1:]) ... err = opt.Dispatch("help", remaining) ... }
func commandFn(opt *getoptions.GetOpt, args []string) error { args, err := opt.Parse(remaining) ... }
There is an option at the parent, profile
and one at the command, password
.
Passing --p <arg>
is ambiguous and results in an error.
At minimum, --pr <arg>
and --pa <arg>
are required.
Given a definition like:
[source, go]
func main() { var profile, password string opt := New() opt.SetUnknownMode(Pass) opt.StringVar(&profile, "profile", "") command := NewCommand() command.StringVar(&password, "password", "", opt.Alias("p")) opt.Command(command.Self("command", "").SetCommandFn(commandFn)) remaining, err := opt.Parse(os.Args[1:]) ... err = opt.Dispatch("help", remaining) ... }
func commandFn(opt *getoptions.GetOpt, args []string) error { args, err := opt.Parse(remaining) ... }
There is an option at the parent, profile
and one at the command, password
with alias p
.
Passing --p <arg>
at the parent results in the parent opt.Parse
call to leave the --p <arg>
option unhandled and leave it in the remaining slice.
The opt.Dispatch
call gets the -p <arg>
option and throws an error.
At minimum, --pr <arg>
is required to call profile
at the parent and command options must be passed after the command declaration.
For example, the calls below is correct:
$ ./program -pr <profile> command -p <password>
$ ./program command -pr <profile> -p <password>
But the following one is incorrect:
./program -pr <profile> -p <password> command
[[roadmap]] == ROADMAP
-
Generate compilation errors for commands without a defined
CommandFn
. -
Create new error description for errors when parsing integer ranges (
1..3
). -
Case insensitive matching.
-
prefix and prefix_pattern. The string that starts options. Defaults to "--" and "-" but could include "/" to support Win32 style argument handling.
-
Allow grouping commands so they can have a different order other than alphabetical in the help output.
-
Some Windows tests fail because the binary name includes .exe at the end. Update test suite to accommodate for Windows.
-
Introduce a
opt.NoArgs
so there are no[<args>]
listed in the help output. -
Add
CustomCompletionFn
before release figure out how to have ways to have custom completions with different engines for arg1 and arg2. -
Figure out how to have custom completions for option values.
-
Add OptionGroup to allow grouping options in the help output.
-
Document CustomCompletion and ValidValues in autocompletion section.
-
Mark optional as required in subcommand.
=== Possible Env Variable Roadmap
The Roadmap isn't clear given that there might not be enough value in implementing all of them.
- Handle
opt.Int
andopt.Float64
errors.
StringSlice and StringSliceVar:: Comma separated? <- Most likely + Comma space separated? Proper CSV parsing to allow comma escaping?
IntSlice and IntSliceVar:: Comma separated?
StringMap and StringMapVar:: Comma separated key=value?
== License
This file is part of go-getoptions.
Copyright (C) 2015-2024 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/.