flagparser

package module
v0.0.0-...-26de65c Latest Latest
Warning

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

Go to latest
Published: Jan 8, 2026 License: GPL-3.0 Imports: 6 Imported by: 0

README

Golang Low-Level CLI Flags Parser

GoDoc Build Status codecov

The flagparser Go package contains a low-level command-line arguments parser. It is a building block that enables building higher-level command-line-flags parsers.

For example:

import (
	"log"
	"os"

	"github.com/bassosimone/flagparser"
)

// Construct a parser recognizing GNU style options.
p := &flagparser.NewParser()
p.SetMinMaxPositionalArguments(1, 1)                    // exactly one positional
p.AddLongOptionWithArgumentOptional("compress", "gzip") // --compress[=gzip|bzip2|...]
p.AddEarlyOption('h', "help")                           // -h, --help
p.AddOptionWithArgumentRequired('o', "output")          // -o, --output FILE
p.AddOptionWithArgumentNone('v', "verbose")             // -v, --verbose

// Parse the command line
values, err := p.Parse(os.Args[1:])
if err != nil {
	log.Fatal(err)
}

The above example configures GNU style options but we support a wide variety of command-line-flags styles including Go, dig, Windows, and traditional Unix. See example_test.go.

Installation

To add this package as a dependency to your module:

go get github.com/bassosimone/flagparser

Development

To run the tests:

go test -v .

To measure test coverage:

go test -v -cover .

License

SPDX-License-Identifier: GPL-3.0-or-later

History

Adapted from bassosimone/clip.

Documentation

Overview

Package flagparser implements a flexible command line parser.

NewParser configures GNU-style defaults: short options use `-`, long options use `--`, the options-arguments separator is `--`, and option permutation is enabled. You can override any of these defaults to parse non-GNU command lines.

To parse arguments, you need to:

  1. Create a *Parser instance (typically using the NewParser factory).

  2. Initialize its options (e.g., with *Parser.AddOptionWithArgumentNone or by appending *Option values to the *Parser instance.).

  3. Optionally, adjust the separator, permutation, and prefixes to match the desired command-line convention.

  4. Invoke *Parser.Parse passing it `os.Args[1:]`.

The *Parser.Parse method returns a slice of Value.

Options-Arguments Separator

The *Parser can be configured to define a separator after which any command line token is treated as a positional argument, regardless of its prefix. The GNU getopt implementation and the Go standard library do this using the `--` separator. NewParser configures `--` as separator.

Permutation

By default, the parser permutes options ahead of positional arguments, matching the GNU getopt behavior. You can disable permutation (see the [*Parser.DisablePermute] knob) to preserve the original order, which can be useful when a subcommand expects its own flags.

Option Types

Each Option has its own OptionType, which is one of these values:

  1. OptionTypeEarlyArgumentNone: options processed before the actual command-line parsing to detect flags (e.g., `--help`) that should always cause specific actions (e.g., printing the help message on the stdout), regardless of the correctness of the rest of the command line. These options cannot receive arguments since they are processed ahead of the parsing.

  2. OptionTypeStandaloneArgumentNone: options that cannot be grouped and that require no arguments (e.g., `--verbose`).

  3. OptionTypeStandaloneArgumentRequired: options that cannot be grouped and that require an argument. The argument can be provided in a subsequent token (e.g., `--file FILE`) or after the `=` byte (`--file=FILE`).

  4. OptionTypeStandaloneArgumentOptional: options that cannot be grouped and take an optional argument. The argument must be provided after the `=` byte (e.g., `--deepscan=true`, `--deepscan=false`). Omitting the value (e.g., `--deepscan`) causes the default value to be used.

  5. OptionTypeGroupableArgumentNone: single-letter options that can be grouped together (e.g., `-xz` as a shortcut for `-x -z`).

  6. OptionTypeGroupableArgumentRequired: like the previous section but an argument must be specified, either as a subsequent token (e.g., `-xzf FILE`) or directly after the option (`-xzfFILE`) -- note that even though the latter may be confusing it is a GNU extension.

Option Prefixes

Each Option can define its own parsing prefix. Generally, it is advisable to use uniform prefixes for all options. For example, following the GNU convention, one should use the `-` prefix for groupable options and the `--` prefix for standalone options. This is the behavior that you get if you use *Parser.AddOptionWithArgumentNone and similar functions to initialize a parser. However, you can also use non-GNU conventions, such as standalone options prefixed by `-`, thus emulating the Go flag package option parsing style. To this end, manually create the Option.

This package also supports using distinct prefixes for distinct options of the same type. For example, both `+short` and `--verbose` could be standalone options. The only restriction, enforced by the *Parser, is that you cannot use the same prefix for groupable and standalone options. That is, if `-` is used for groupable options it cannot be used for standalone options as well.

The early options are an exception to this rule, since they are not really parsed, rather just pattern matched against the argv provided by the programmer. Therefore, it is possible to have `-a` and `-b` as groupable options and `-h` for help, provided that you declare `-h` as an early option. In other words, the prefixes assigned to early options do not have an impact on the single-prefix restriction.

Parsed Values

  1. ValueOption: contains a parsed *Option.

  2. ValuePositionalArgument: contains a positional argument.

  3. ValueOptionsArgumentsSeparator: contains the separator between the options and the arguments (usually `--`).

Example

Consider the following command line arguments:

-sv --output /dev/null -- https://example.com/

Assume you define these options:

Option{Name:s Prefix:- Type:OptionTypeGroupableArgumentNone}
Option{Name:v Prefix:- Type:OptionTypeGroupableArgumentNone}
Option{Name:output Prefix:-- Type:OptionTypeStandaloneArgumentRequired}

Assume you use `--` as the options-arguments separator.

Then, the parser will return:

ValueOption{Token:scanner.TokenOption{Prefix:-} Name:s}
ValueOption{Token:scanner.TokenOption{Prefix:-} Name:v}
ValueOption{Token:scanner.TokenOption{Prefix:--} Name:output Value:/dev/null}
ValueOptionsArgumentsSeparator{}
ValuePositionalArgument{Value:https://example.com/}

See the package examples for more examples.

Example (CurlParsingFailureTooFewPositionalArguments)

Failing parsing of curl-like invocation with too few positionals.

package main

import (
	"fmt"
	"math"

	"github.com/bassosimone/flagparser"
	"github.com/bassosimone/runtimex"
)

func main() {
	// Define a parser accepting curl-like command line options.
	parser := flagparser.NewParser()
	parser.SetMinMaxPositionalArguments(1, math.MaxInt)
	parser.AddOptionWithArgumentNone('f', "fail")
	parser.AddOptionWithArgumentNone('L', "location")
	parser.AddOptionWithArgumentRequired('o', "output")
	parser.AddOptionWithArgumentNone('S', "show-error")
	parser.AddOptionWithArgumentNone('s', "silent")

	// Define the argument vector to parse
	//
	// Note: we're not providing a URL.
	argv := []string{
		"curl",
		"--fail",
		"--silent",
		"--show-error",
		"--location",
		"--output=index.html",
	}

	// Parse the options; this is where min/max positionals are enforced.
	values, err := parser.Parse(argv[1:])
	runtimex.Assert(len(values) <= 0 && err != nil)

	// Print the error value
	fmt.Printf("%s\n", err.Error())

}
Output:

too few positional arguments: expected at least 1, got 0
Example (CurlParsingFailureTooManyPositionalArguments)

Failing parsing of curl-like invocation with too many positionals.

package main

import (
	"fmt"

	"github.com/bassosimone/flagparser"
	"github.com/bassosimone/runtimex"
)

func main() {
	// Define a parser accepting curl-like command line options.
	//
	// Note: the default is to expect zero positionals.
	parser := flagparser.NewParser()
	parser.AddOptionWithArgumentNone('f', "fail")
	parser.AddOptionWithArgumentNone('L', "location")
	parser.AddOptionWithArgumentRequired('o', "output")
	parser.AddOptionWithArgumentNone('S', "show-error")
	parser.AddOptionWithArgumentNone('s', "silent")

	// Define the argument vector to parse
	argv := []string{
		"curl",
		"https://www.example.com/",
		"--fail",
		"--silent",
		"--show-error",
		"--location",
		"--output=index.html",
	}

	// Parse the options; this is where min/max positionals are enforced.
	values, err := parser.Parse(argv[1:])
	runtimex.Assert(len(values) <= 0 && err != nil)

	// Print the error value
	fmt.Printf("%s\n", err.Error())

}
Output:

too many positional arguments: expected at most 0, got 1
Example (CurlParsingFailureWithInvalidOption)

Failing parsing of curl-like invocation with an unknown option.

package main

import (
	"fmt"
	"math"

	"github.com/bassosimone/flagparser"
	"github.com/bassosimone/runtimex"
)

func main() {
	// Define a parser accepting curl-like command line options.
	parser := flagparser.NewParser()
	parser.SetMinMaxPositionalArguments(1, math.MaxInt)
	parser.AddOptionWithArgumentNone('f', "fail")
	parser.AddOptionWithArgumentNone('L', "location")
	parser.AddOptionWithArgumentRequired('o', "output")
	parser.AddOptionWithArgumentNone('S', "show-error")
	parser.AddOptionWithArgumentNone('s', "silent")

	// Define the argument vector to parse
	argv := []string{
		"curl",
		"https://www.example.com/",
		"--nonexistent-option", // will cause failure
		"--fail",
		"--silent",
		"--show-error",
		"--location",
		"--output=index.html",
	}

	// Parse the options; this is where `--nonexistent-option` causes a failure
	values, err := parser.Parse(argv[1:])
	runtimex.Assert(len(values) <= 0 && err != nil)

	// Print the error value
	fmt.Printf("%s\n", err.Error())

}
Output:

unknown option: --nonexistent-option
Example (CurlParsingSuccessLongWithEqual)

Successful parsing of curl-like invocation with long options where a required argument is provided after an '=' sign.

package main

import (
	"fmt"
	"log"
	"math"

	"github.com/bassosimone/flagparser"
)

func main() {
	// Define a parser accepting curl-like command line options.
	parser := flagparser.NewParser()
	parser.SetMinMaxPositionalArguments(1, math.MaxInt)
	parser.AddOptionWithArgumentNone('f', "fail")
	parser.AddOptionWithArgumentNone('L', "location")
	parser.AddOptionWithArgumentRequired('o', "output")
	parser.AddOptionWithArgumentNone('S', "show-error")
	parser.AddOptionWithArgumentNone('s', "silent")

	// Define the argument vector to parse; the `--output` argument uses `=`.
	argv := []string{
		"curl",
		"https://www.example.com/",
		"--fail",
		"--silent",
		"--show-error",
		"--location",
		"--output=index.html",
	}

	// Parse the options; the early option wins over other errors.
	values, err := parser.Parse(argv[1:])
	if err != nil {
		log.Fatal(err)
	}

	// Print the parsed values to stdout
	//
	// Note: we have reordering by default so options are sorted before
	// positional arguments (respecting their relative order)
	for _, value := range values {
		fmt.Printf("%+v\n", value.Strings())
	}

}
Output:

[--fail]
[--silent]
[--show-error]
[--location]
[--output index.html]
[https://www.example.com/]
Example (CurlParsingSuccessLongWithOptionalValueNotPresent)

Successful parsing of curl-like invocation with long options where the optional option argument is not provided.

package main

import (
	"fmt"
	"log"
	"math"

	"github.com/bassosimone/flagparser"
)

func main() {
	// Define a parser accepting curl-like command line options.
	parser := flagparser.NewParser()
	parser.SetMinMaxPositionalArguments(1, math.MaxInt)
	parser.AddLongOptionWithArgumentOptional("fail", "true")
	parser.AddOptionWithArgumentRequired('o', "output")

	// Define the argument vector to parse; the `--fail` argument uses the default value.
	argv := []string{
		"curl",
		"https://www.example.com/",
		"--fail",
		"--output=index.html",
	}

	// Parse the options; the `--fail` gets assigned a default value.
	values, err := parser.Parse(argv[1:])
	if err != nil {
		log.Fatal(err)
	}

	// Print the parsed values to stdout
	//
	// Note: we have reordering by default so options are sorted before
	// positional arguments (respecting their relative order)
	for _, value := range values {
		fmt.Printf("%+v\n", value.Strings())
	}

}
Output:

[--fail=true]
[--output index.html]
[https://www.example.com/]
Example (CurlParsingSuccessLongWithOptionalValuePresent)

Successful parsing of curl-like invocation with long options where the optional option argument is provided after the `=` sign.

package main

import (
	"fmt"
	"log"
	"math"

	"github.com/bassosimone/flagparser"
)

func main() {
	// Define a parser accepting curl-like command line options.
	parser := flagparser.NewParser()
	parser.SetMinMaxPositionalArguments(1, math.MaxInt)
	parser.AddLongOptionWithArgumentOptional("fail", "true")
	parser.AddOptionWithArgumentRequired('o', "output")

	// Define the argument vector to parse; the `--fail` argument uses an explicit value.
	argv := []string{
		"curl",
		"https://www.example.com/",
		"--fail=false",
		"--output=index.html",
	}

	// Parse the options; the `--fail` gets assigned a default value.
	values, err := parser.Parse(argv[1:])
	if err != nil {
		log.Fatal(err)
	}

	// Print the parsed values to stdout
	//
	// Note: we have reordering by default so options are sorted before
	// positional arguments (respecting their relative order)
	for _, value := range values {
		fmt.Printf("%+v\n", value.Strings())
	}

}
Output:

[--fail=false]
[--output index.html]
[https://www.example.com/]
Example (CurlParsingSuccessLongWithSpace)

Successful parsing of curl-like invocation with long options where a required argument is provided as a separate token.

package main

import (
	"fmt"
	"log"
	"math"

	"github.com/bassosimone/flagparser"
)

func main() {
	// Define a parser accepting curl-like command line options.
	parser := flagparser.NewParser()
	parser.SetMinMaxPositionalArguments(1, math.MaxInt)
	parser.AddOptionWithArgumentNone('f', "fail")
	parser.AddOptionWithArgumentNone('L', "location")
	parser.AddOptionWithArgumentRequired('o', "output")
	parser.AddOptionWithArgumentNone('S', "show-error")
	parser.AddOptionWithArgumentNone('s', "silent")

	// Define the argument vector to parse; the `--output` argument is separate.
	argv := []string{
		"curl",
		"https://www.example.com/",
		"--fail",
		"--silent",
		"--show-error",
		"--location",
		"--output",
		"index.html",
	}

	// Parse the options
	values, err := parser.Parse(argv[1:])
	if err != nil {
		log.Fatal(err)
	}

	// Print the parsed values to stdout
	//
	// Note: we have reordering by default so options are sorted before
	// positional arguments (respecting their relative order)
	for _, value := range values {
		fmt.Printf("%+v\n", value.Strings())
	}

}
Output:

[--fail]
[--silent]
[--show-error]
[--location]
[--output index.html]
[https://www.example.com/]
Example (CurlParsingSuccessShortWithNoSpace)

Successful parsing of curl-like invocation with short options where a required argument is glued to the last short flag (GNU extension).

package main

import (
	"fmt"
	"log"
	"math"

	"github.com/bassosimone/flagparser"
)

func main() {
	// Define a parser accepting curl-like command line options.
	parser := flagparser.NewParser()
	parser.SetMinMaxPositionalArguments(1, math.MaxInt)
	parser.AddOptionWithArgumentNone('f', "fail")
	parser.AddOptionWithArgumentNone('L', "location")
	parser.AddOptionWithArgumentRequired('o', "output")
	parser.AddOptionWithArgumentNone('S', "show-error")
	parser.AddOptionWithArgumentNone('s', "silent")

	// Define the argument vector to parse; the `-o` argument is glued to the flag.
	argv := []string{"curl", "https://www.example.com/", "-fsSLoindex.html"}

	// Parse the options
	values, err := parser.Parse(argv[1:])
	if err != nil {
		log.Fatal(err)
	}

	// Print the parsed values to stdout
	//
	// Note: we have reordering by default so options are sorted before
	// positional arguments (respecting their relative order)
	for _, value := range values {
		fmt.Printf("%+v\n", value.Strings())
	}

}
Output:

[-f]
[-s]
[-S]
[-L]
[-o index.html]
[https://www.example.com/]
Example (CurlParsingSuccessShortWithSpace)

Successful parsing of curl-like invocation with short options where a required argument is provided as a separate token.

package main

import (
	"fmt"
	"log"
	"math"

	"github.com/bassosimone/flagparser"
)

func main() {
	// Define a parser accepting curl-like command line options.
	parser := flagparser.NewParser()
	parser.SetMinMaxPositionalArguments(1, math.MaxInt)
	parser.AddOptionWithArgumentNone('f', "fail")
	parser.AddOptionWithArgumentNone('L', "location")
	parser.AddOptionWithArgumentRequired('o', "output")
	parser.AddOptionWithArgumentNone('S', "show-error")
	parser.AddOptionWithArgumentNone('s', "silent")

	// Define the argument vector to parse; the `-o` argument is a separate token.
	argv := []string{"curl", "https://www.example.com/", "-fsSLo", "index.html"}

	// Parse the options
	values, err := parser.Parse(argv[1:])
	if err != nil {
		log.Fatal(err)
	}

	// Print the parsed values to stdout
	//
	// Note: we have reordering by default so options are sorted before
	// positional arguments (respecting their relative order)
	for _, value := range values {
		fmt.Printf("%+v\n", value.Strings())
	}

}
Output:

[-f]
[-s]
[-S]
[-L]
[-o index.html]
[https://www.example.com/]
Example (CurlParsingSuccessShortWithSpaceAndDashValue)

Successful parsing of curl-like invocation with short options where a required argument value is the `-` separate token (commonly used to indicate that we want to use the standard output).

package main

import (
	"fmt"
	"log"
	"math"

	"github.com/bassosimone/flagparser"
)

func main() {
	// Define a parser accepting curl-like command line options.
	parser := flagparser.NewParser()
	parser.SetMinMaxPositionalArguments(1, math.MaxInt)
	parser.AddOptionWithArgumentNone('f', "fail")
	parser.AddOptionWithArgumentNone('L', "location")
	parser.AddOptionWithArgumentRequired('o', "output")
	parser.AddOptionWithArgumentNone('S', "show-error")
	parser.AddOptionWithArgumentNone('s', "silent")

	// Define the argument vector to parse; the `-o` argument is a separate token.
	argv := []string{"curl", "https://www.example.com/", "-fsSLo", "-"}

	// Parse the options
	values, err := parser.Parse(argv[1:])
	if err != nil {
		log.Fatal(err)
	}

	// Print the parsed values to stdout
	//
	// Note: we have reordering by default so options are sorted before
	// positional arguments (respecting their relative order)
	for _, value := range values {
		fmt.Printf("%+v\n", value.Strings())
	}

}
Output:

[-f]
[-s]
[-S]
[-L]
[-o -]
[https://www.example.com/]
Example (CurlParsingSuccessWithEarlyHelpLong)

Successful parsing of curl-like invocation with `--help` acting as an "early" option that short-circuits parsing regardless of other errors.

package main

import (
	"fmt"
	"log"
	"math"

	"github.com/bassosimone/flagparser"
)

func main() {
	// Define a parser accepting curl-like command line options.
	parser := flagparser.NewParser()
	parser.SetMinMaxPositionalArguments(1, math.MaxInt)
	parser.AddOptionWithArgumentNone('f', "fail")
	parser.AddEarlyOption('h', "help")
	parser.AddOptionWithArgumentNone('L', "location")
	parser.AddOptionWithArgumentRequired('o', "output")
	parser.AddOptionWithArgumentNone('S', "show-error")
	parser.AddOptionWithArgumentNone('s', "silent")

	// Define the argument vector to parse
	argv := []string{
		"curl",
		"https://www.example.com/",
		"--nonexistent-option", // should cause failure
		"--fail",
		"--silent",
		"--show-error",
		"--location",
		"--output=index.html",
		"--help", // but we have `--help` here
	}

	// Parse the options; the early option wins over the nonexistent option
	values, err := parser.Parse(argv[1:])
	if err != nil {
		log.Fatal(err)
	}

	// Print the parsed values to stdout
	for _, value := range values {
		fmt.Printf("%+v\n", value.Strings())
	}

}
Output:

[--help]
Example (CurlParsingSuccessWithEarlyHelpShort)

Successful parsing of curl-like invocation with `-h` acting as an "early" option that short-circuits parsing regardless of other errors.

package main

import (
	"fmt"
	"log"
	"math"

	"github.com/bassosimone/flagparser"
)

func main() {
	// Define a parser accepting curl-like command line options.
	parser := flagparser.NewParser()
	parser.SetMinMaxPositionalArguments(1, math.MaxInt)
	parser.AddOptionWithArgumentNone('f', "fail")
	parser.AddEarlyOption('h', "help")
	parser.AddOptionWithArgumentNone('L', "location")
	parser.AddOptionWithArgumentRequired('o', "output")
	parser.AddOptionWithArgumentNone('S', "show-error")
	parser.AddOptionWithArgumentNone('s', "silent")

	// Define the argument vector to parse
	argv := []string{
		"curl",
		"https://www.example.com/",
		"--nonexistent-option", // should cause failure
		"--fail",
		"--silent",
		"--show-error",
		"--location",
		"--output=index.html",
		"-h", // but we have `-h` here
	}

	// Parse the options; the early option wins over the nonexistent option
	values, err := parser.Parse(argv[1:])
	if err != nil {
		log.Fatal(err)
	}

	// Print the parsed values to stdout
	for _, value := range values {
		fmt.Printf("%+v\n", value.Strings())
	}

}
Output:

[-h]
Example (DigParsingSuccessWithMixedPrefixes)

Successful parsing of dig-like invocation mixing GNU-style `-p` with `+short`.

package main

import (
	"fmt"
	"log"

	"github.com/bassosimone/flagparser"
)

func main() {
	// Define a parser with mixed prefixes and a groupable required argument.
	parser := &flagparser.Parser{
		MinPositionalArguments: 1,
		MaxPositionalArguments: 4,
		Options: []*flagparser.Option{
			{
				Name:   "p",
				Prefix: "-",
				Type:   flagparser.OptionTypeGroupableArgumentRequired,
			},
			{
				Name:   "short",
				Prefix: "+",
				Type:   flagparser.OptionTypeStandaloneArgumentNone,
			},
			{
				DefaultValue: "1024",
				Name:         "bufsize",
				Prefix:       "+",
				Type:         flagparser.OptionTypeStandaloneArgumentOptional,
			},
		},
	}

	// Define the argument vector to parse; `+bufsize` uses the default value.
	argv := []string{"dig", "@8.8.8.8", "-p53", "IN", "+short", "+bufsize", "A", "example.com"}

	// Parse the options
	values, err := parser.Parse(argv[1:])
	if err != nil {
		log.Fatal(err)
	}

	// Print the parsed values to stdout
	for _, value := range values {
		fmt.Printf("%+v\n", value.Strings())
	}

}
Output:

[-p 53]
[+short]
[+bufsize=1024]
[@8.8.8.8]
[IN]
[A]
[example.com]
Example (GitSubmoduleForeachWithSeparatorWithReordering)

Successful parsing of git-submodule-foreach-like invocation with the options-positionals separator and option permutation enabled.

package main

import (
	"fmt"
	"log"
	"math"

	"github.com/bassosimone/flagparser"
)

func main() {
	// Define a parser accepting git-like command line options.
	parser := flagparser.NewParser()
	parser.SetMinMaxPositionalArguments(0, math.MaxInt)
	parser.AddOptionWithArgumentNone('r', "recursive")

	// Define the argument vector to parse
	argv := []string{"git", "submodule", "foreach", "--recursive", "--", "git", "status", "-v"}

	// Parse the options
	values, err := parser.Parse(argv[1:])
	if err != nil {
		log.Fatal(err)
	}

	// Print the parsed values to stdout
	//
	// Note: reordering probably does not give us the desired output
	// unless the `--recursive` flag could be applied to `git`.
	//
	// You typically do not parse a command with subcommands directly
	// using this parser but this example is here to show what happens
	// and build a mental model of how it works.
	for _, value := range values {
		fmt.Printf("%+v\n", value.Strings())
	}

}
Output:

[--recursive]
[submodule]
[foreach]
[--]
[git]
[status]
[-v]
Example (GitSubmoduleForeachWithSeparatorWithoutReordering)

Successful parsing of git-submodule-foreach-like invocation with the options-positionals separator and option permutation disabled.

package main

import (
	"fmt"
	"log"
	"math"

	"github.com/bassosimone/flagparser"
)

func main() {
	// Define a parser accepting git-like command line options.
	parser := flagparser.NewParser()
	parser.DisablePermute = true
	parser.SetMinMaxPositionalArguments(0, math.MaxInt)
	parser.AddOptionWithArgumentNone('r', "recursive")

	// Define the argument vector to parse
	argv := []string{"git", "submodule", "foreach", "--recursive", "--", "git", "status", "-v"}

	// Parse the options
	values, err := parser.Parse(argv[1:])
	if err != nil {
		log.Fatal(err)
	}

	// Print the parsed values to stdout
	//
	// Note how this case gives us the correct positional result.
	for _, value := range values {
		fmt.Printf("%+v\n", value.Strings())
	}

}
Output:

[submodule]
[foreach]
[--recursive]
[--]
[git]
[status]
[-v]
Example (GitSubmoduleForeachWithoutSeparatorWithReordering)

Failing parsing of git-submodule-foreach-like invocation without an explicit separator in the args and with option permutation enabled.

package main

import (
	"fmt"
	"math"

	"github.com/bassosimone/flagparser"
	"github.com/bassosimone/runtimex"
)

func main() {
	// Define a parser accepting git-like command line options.
	parser := flagparser.NewParser()
	parser.SetMinMaxPositionalArguments(0, math.MaxInt)
	parser.AddOptionWithArgumentNone('r', "recursive")

	// Define the argument vector to parse
	argv := []string{"git", "submodule", "foreach", "--recursive", "git", "status", "-v"}

	// Parse the options
	//
	// Note: reordering probably does not give us the desired output
	// and specifically the `-v` is stolen from `git status` and causes
	// a parsing error, which is definitely not what we want.
	//
	// You typically do not parse a command with subcommands directly
	// using this parser but this example is here to show what happens
	// and build a mental model of how it works.
	values, err := parser.Parse(argv[1:])
	runtimex.Assert(len(values) <= 0 && err != nil)

	// Print the error value
	fmt.Printf("%s\n", err.Error())

}
Output:

unknown option: -v
Example (GitSubmoduleForeachWithoutSeparatorWithoutReordering)

Successful parsing of git-submodule-foreach-like invocation without an explicit separator in the args and with option permutation disabled.

package main

import (
	"fmt"
	"log"
	"math"

	"github.com/bassosimone/flagparser"
)

func main() {
	// Define a parser accepting git-like command line options.
	parser := flagparser.NewParser()
	parser.DisablePermute = true
	parser.SetMinMaxPositionalArguments(0, math.MaxInt)
	parser.AddOptionWithArgumentNone('r', "recursive")

	// Define the argument vector to parse
	argv := []string{"git", "submodule", "foreach", "--recursive", "git", "status", "-v"}

	// Parse the options
	values, err := parser.Parse(argv[1:])
	if err != nil {
		log.Fatal(err)
	}

	// Print the parsed values to stdout
	//
	// Note how this case gives us the correct positional result.
	for _, value := range values {
		fmt.Printf("%+v\n", value.Strings())
	}

}
Output:

[submodule]
[foreach]
[--recursive]
[git]
[status]
[-v]

Index

Examples

Constants

View Source
const (
	// OptionTypeEarlyArgumentNone indicates an early option requiring no arguments.
	//
	// Typically used for `-h` and `--help`.
	OptionTypeEarlyArgumentNone = optionKindEarly | optionArgumentNone

	// OptionTypeStandaloneArgumentNone indicates a standalone option requiring no arguments.
	//
	// Typically used for `--verbose` or `--quiet`.
	OptionTypeStandaloneArgumentNone = optionKindStandalone | optionArgumentNone

	// OptionTypeStandaloneArgumentRequired indicates a standalone option requiring an argument.
	//
	// Typically used for stuff like `--output FILE` (or `--output=FILE`).
	OptionTypeStandaloneArgumentRequired = optionKindStandalone | optionArgumentRequired

	// OptionTypeStandaloneArgumentOptional indicates a standalone option with an optional argument.
	//
	// Typically used for stuff like `--http=1.1` (or `--http` to get the default).
	OptionTypeStandaloneArgumentOptional = optionKindStandalone | optionArgumentOptional

	// OptionTypeGroupableArgumentNone indicates a groupable option requiring no arguments.
	//
	// Typically used for options like `-v` (for verbose).
	//
	// These options can be grouped together like in `-xvzd DIR`.
	OptionTypeGroupableArgumentNone = optionKindGroupable | optionArgumentNone

	// OptionTypeGroupableArgumentRequired indicates groupable option requiring an argument.
	//
	// Typically used for options like `-d DIR` or `-dDIR` (to select a directory).
	//
	// These options can be grouped together like in `-xvzd DIR`.
	OptionTypeGroupableArgumentRequired = optionKindGroupable | optionArgumentRequired
)

These constants define the allowed OptionType values.

Variables

This section is empty.

Functions

This section is empty.

Types

type ErrAmbiguousPrefix

type ErrAmbiguousPrefix struct {
	// Prefix is the prefix that is used for both standalone and groupable options.
	Prefix string
}

ErrAmbiguousPrefix indicates that the options contain ambiguous prefixes that are used for both standalone and groupable options.

func (ErrAmbiguousPrefix) Error

func (err ErrAmbiguousPrefix) Error() string

Error returns a string representation of this error.

type ErrEmptyOptionName

type ErrEmptyOptionName struct {
	// Option is the option with the empty name.
	Option *Option
}

ErrEmptyOptionName indicates that an option name is empty.

func (ErrEmptyOptionName) Error

func (err ErrEmptyOptionName) Error() string

Error returns a string representation of this error.

type ErrEmptyOptionPrefix

type ErrEmptyOptionPrefix struct {
	// Option is the option with the empty prefix.
	Option *Option
}

ErrEmptyOptionPrefix indicates that an option prefix is empty.

func (ErrEmptyOptionPrefix) Error

func (err ErrEmptyOptionPrefix) Error() string

Error returns a string representation of this error.

type ErrMultipleOptionsWithSameName

type ErrMultipleOptionsWithSameName struct {
	// Name is the name of the option that appears multiple times.
	Name string

	// Options is a slice of options with the same name.
	Options []*Option
}

ErrMultipleOptionWithSameName indicates that there are multiple options with the same name.

func (ErrMultipleOptionsWithSameName) Error

Error returns a string representation of this error.

type ErrOptionRequiresArgument

type ErrOptionRequiresArgument struct {
	// Option is the offending option
	Option *Option

	// Token is the related token
	Token flagscanner.Token
}

ErrOptionRequiresArgument indicates that no argument was passed to an option that requires an argument.

func (ErrOptionRequiresArgument) Error

func (err ErrOptionRequiresArgument) Error() string

Error returns a string representation of this error.

type ErrOptionRequiresNoArgument

type ErrOptionRequiresNoArgument struct {
	// Option is the offending option
	Option *Option

	// Token is the related token
	Token flagscanner.Token
}

ErrOptionRequiresNoArgument indicates that an argument was passed to an option that requires no arguments.

func (ErrOptionRequiresNoArgument) Error

func (err ErrOptionRequiresNoArgument) Error() string

Error returns a string representation of this error.

type ErrTooFewPositionalArguments

type ErrTooFewPositionalArguments struct {
	// Min is the minimum number of positional arguments required.
	Min int

	// Have is the number of positional arguments provided.
	Have int
}

ErrTooFewPositionalArguments is returned when the number of positional arguments is less than the configured minimum.

func (ErrTooFewPositionalArguments) Error

Error returns a string representation of this error.

type ErrTooLongGroupableOptionName

type ErrTooLongGroupableOptionName struct {
	Option *Option
}

ErrTooLongGroupableOptionName indicates that a groupable option name is longer than one byte.

func (ErrTooLongGroupableOptionName) Error

Error returns a string representation of this error.

type ErrTooManyPositionalArguments

type ErrTooManyPositionalArguments struct {
	// Max is the maximum number of positional arguments allowed.
	Max int

	// Have is the number of positional arguments provided.
	Have int
}

ErrTooManyPositionalArguments is returned when the number of positional arguments is greater than the configured maximum.

func (ErrTooManyPositionalArguments) Error

Error returns a string representation of this error.

type ErrUnknownOption

type ErrUnknownOption struct {
	// Name is the name of the unknown option.
	Name string

	// Prefix is the prefix of the unknown option.
	Prefix string

	// Token is the token of the unknown option.
	Token flagscanner.Token
}

ErrUnknownOption indicates that an option is unknown.

func (ErrUnknownOption) Error

func (err ErrUnknownOption) Error() string

Error returns a string representation of this error.

type Option

type Option struct {
	// DefaultValue is the default value assigned to the option
	// [Value] when the option argument is optional.
	DefaultValue string

	// Prefix is the prefix to use for parsing this option (e.g., `-`)
	Prefix string

	// Name is the option name without the prefix (e.g., `f`).
	Name string

	// Type is the option type.
	Type OptionType
}

Option specifies the kind of option to parse.

func NewEarlyOption

func NewEarlyOption(shortName byte, longName string) []*Option

NewEarlyOption creates early options with no arguments using GNU prefixes (- for short, -- for long).

You typically use "early" options to register `-h` and `--help` such that when the user uses those flags, regardless of whether the command line is correct, they see the help text in the output rather than parsing errors.

A zero short option value skips adding the short option. An empty long option value skips adding the long option. If both are zero/empty, this method returns a nil slice.

Setting invalid option names (e.g., a duplicate option name) will cause no errors until you attempt to parse the command line.

func NewLongOptionWithArgumentOptional

func NewLongOptionWithArgumentOptional(longName, defaultValue string) []*Option

NewLongOptionWithArgumentOptional creates a long option with an optional argument and a default value using the GNU `--` prefix.

If the long option name is empty, this method returns a nil slice.

Setting invalid option names (e.g., a duplicate option name) will cause no errors until you attempt to parse the command line.

func NewOptionWithArgumentNone

func NewOptionWithArgumentNone(shortName byte, longName string) []*Option

NewOptionWithArgumentNone creates options with no arguments using GNU prefixes (- for short, -- for long).

A zero short option value skips adding the short option. An empty long option value skips adding the long option. If both are zero/empty, this method returns a nil slice.

Setting invalid option names (e.g., a duplicate option name) will cause no errors until you attempt to parse the command line.

func NewOptionWithArgumentRequired

func NewOptionWithArgumentRequired(shortName byte, longName string) []*Option

NewOptionWithArgumentRequired creates options with a required argument using GNU prefixes (- for short, -- for long).

A zero short option value skips adding the short option. An empty long option value skips adding the long option. If both are zero/empty, this method returns a nil slice.

Setting invalid option names (e.g., a duplicate option name) will cause no errors until you attempt to parse the command line.

type OptionType

type OptionType int64

OptionType is the type of an Option.

type Parser

type Parser struct {
	// DisablePermute optionally disables permuting options and arguments.
	//
	// Consider the following command line arguments:
	//
	// 	https://www.google.com/ -H 'Host: google.com'
	//
	// The default behavior is to permute this to:
	//
	// 	-H 'Host: google.com' https://www.google.com/
	//
	// However, when DisablePermute is true, we keep the command
	// line unmodified. While permuting is a nice-to-have property
	// in general, consider instead the following case:
	//
	// 	foreach -kx git status -v
	//
	// With permutation, this command line would become:
	//
	// 	-kx -v foreach git status
	//
	// This is not the desired behavior if the foreach subcommand
	// takes another command and its options as arguments.
	//
	// To make the above command line work with permutation, a
	// user would instead need to write this:
	//
	// 	foreach -kx -- git status -v
	//
	// By setting DisablePermute to true, the `--` separator
	// becomes unnecessary and the UX is improved.
	DisablePermute bool

	// MaxPositionalArguments is the maximum number of positional
	// arguments allowed by the parser. The default is zero, meaning
	// that the parser won't accept more than zero positionals.
	MaxPositionalArguments int

	// MinPositionalArguments is the minimum number of positional
	// arguments allowed by the parser. The default is zero, meaning
	// that the parser won't accept less than zero positionals.
	MinPositionalArguments int

	// OptionsArgumentsSeparator is the optional separator that terminates
	// the parsing of options, treating all remaining tokens in the command
	// line as positional arguments. The default is empty, meaning that
	// the parser will always parse all the available options.
	OptionsArgumentsSeparator string

	// Options contains the optional options configured for this parser.
	//
	// When parsing, we will ensure there are no duplicate option names or
	// ambiguous separators across all options.
	//
	// If you don't set this field, the parser will automatically
	// configure itself to parse GNU-style options, meaning that it
	// will use `-` as the prefix for short options and `--` as
	// the prefix for long options. No options will be defined so
	// any option will be considered unknown and cause a parse error.
	Options []*Option
}

Parser is a command line parser.

Construct with NewParser to get GNU parsing semantics. Otherwise, if you need a distinct parser semantics, please construct manually.

func NewParser

func NewParser() *Parser

NewParser creates a new *Parser following the GNU convention.

Specifically, we use these settings:

  1. command line permutation is enabled

  2. zero positional arguments are allowed

  3. the separator is set to `--`

  4. no options have been defined yet

Create *Parser manually when you need different defaults.

func (*Parser) AddEarlyOption

func (px *Parser) AddEarlyOption(shortName byte, longName string)

AddEarlyOption adds an "early" short and long option taking no argument and using the `-` and `--` prefixes, which follow the GNU conventions.

You typically use "early" options to register `-h` and `--help` such that when the user uses those flags, regardless of whether the command line is correct, they see the help text in the output rather than parsing errors.

A zero short option value skips adding the short option. An empty long option value skips adding the long option. If both are zero/empty, this method is a no-operation that does not change the *Parser.

This method MUTATES *Parser and is NOT SAFE to call concurrently.

Setting invalid option names (e.g., a duplicate option name) will cause no errors until you attempt to parse the command line.

Use NewEarlyOption to construct options without mutating the parser.

func (*Parser) AddLongOptionWithArgumentOptional

func (px *Parser) AddLongOptionWithArgumentOptional(longName, defaultValue string)

AddLongOptionWithArgumentOptional adds a long option with an optional argument with the given default value and using `--` prefix, which follows the GNU conventions.

If the long option name is empty, this method is a no-operation that does not change the *Parser.

This method MUTATES *Parser and is NOT SAFE to call concurrently.

Setting invalid option names (e.g., a duplicate option name) will cause no errors until you attempt to parse the command line.

Use NewLongOptionWithArgumentOptional to construct options without mutating the parser.

func (*Parser) AddOption

func (px *Parser) AddOption(options ...*Option)

AddOption adds one or more options to the parser.

This method MUTATES *Parser and is NOT SAFE to call concurrently.

Setting invalid option names (e.g., a duplicate option name) will cause no errors until you attempt to parse the command line.

func (*Parser) AddOptionWithArgumentNone

func (px *Parser) AddOptionWithArgumentNone(shortName byte, longName string)

AddOptionWithArgumentNone adds a short and long option taking no argument and using the `-` and `--` prefixes, which follow the GNU conventions.

A zero short option value skips adding the short option. An empty long option value skips adding the long option. If both are zero/empty, this method is a no-operation that does not change the *Parser.

This method MUTATES *Parser and is NOT SAFE to call concurrently.

Setting invalid option names (e.g., a duplicate option name) will cause no errors until you attempt to parse the command line.

Use NewOptionWithArgumentNone to construct options without mutating the parser.

func (*Parser) AddOptionWithArgumentRequired

func (px *Parser) AddOptionWithArgumentRequired(shortName byte, longName string)

AddOptionWithArgumentRequired adds a short and long option with a required argument and using the `-` and `--` prefixes, which follow the GNU conventions.

A zero short option value skips adding the short option. An empty long option value skips adding the long option. If both are zero/empty, this method is a no-operation that does not change the *Parser.

This method MUTATES *Parser and is NOT SAFE to call concurrently.

Setting invalid option names (e.g., a duplicate option name) will cause no errors until you attempt to parse the command line.

Use NewOptionWithArgumentRequired to construct options without mutating the parser.

func (*Parser) Parse

func (px *Parser) Parse(args []string) ([]Value, error)

Parse parses the command line arguments.

This method does not mutate *Parser and is safe to call concurrently.

The args MUST NOT include the program name.

func (*Parser) SetMinMaxPositionalArguments

func (px *Parser) SetMinMaxPositionalArguments(minArgs, maxArgs int)

SetMinMaxPositionalArguments sets the minimum and maximum positional arguments.

This method MUTATES *Parser and is NOT SAFE to call concurrently.

Setting invalid minimum and maximum positional arguments values will cause no errors until you attempt to parse the command line.

type Value

type Value interface {
	// Strings returns the strings to append to a slice
	// to reconstruct the original command line.
	Strings() []string

	// Token returns the scanner token.
	Token() flagscanner.Token
}

Value is a value parsed by the *Parser.

type ValueOption

type ValueOption struct {
	// Option is the corresponding [*Option].
	Option *Option

	// Tok is the token from which we parsed this [*Option].
	Tok flagscanner.Token

	// Value is the possibly-empty value. Specifically:
	//
	//	1. For [OptionTypeEarlyArgumentNone] this field is empty.
	//
	//	2. For [OptionTypeStandaloneArgumentNone] this field is empty.
	//
	//	3. For [OptionTypeGroupedArgumentNone] this field is empty.
	//
	//	4. For [OptionTypeStandaloneArgumentRequired] this field
	// 	   contains the value of the parsed argument.
	//
	//	5. For [OptionTypeGroupedArgumentRequired] this field
	// 	   contains the value of the parsed argument.
	//
	//	6. For [OptionTypeStandaloneArgumentOptional] this field
	// 	   contains the value of the parsed argument, if any,
	// 	   or the default value specified in [*Option], otherwise.
	Value string
}

ValueOption is a Value containing a parsed *Option.

func (ValueOption) Strings

func (val ValueOption) Strings() []string

Strings implements Value.

func (ValueOption) Token

func (val ValueOption) Token() flagscanner.Token

Token implements Value.

type ValueOptionsArgumentsSeparator

type ValueOptionsArgumentsSeparator struct {
	// Separator is the separator value.
	Separator string

	// Tok is the token associated with the item.
	Tok flagscanner.Token
}

ValueOptionsArgumentsSeparator is a Value containing a parsed separator.

func (ValueOptionsArgumentsSeparator) Strings

func (s ValueOptionsArgumentsSeparator) Strings() []string

Strings implements Value.

func (ValueOptionsArgumentsSeparator) Token

Token implements Value.

type ValuePositionalArgument

type ValuePositionalArgument struct {
	// Tok is the token associated with the value.
	Tok flagscanner.Token

	// Value is the argument value.
	Value string
}

ValuePositionalArgument is a Value containing a parsed positional argument.

func (ValuePositionalArgument) Strings

func (a ValuePositionalArgument) Strings() []string

Strings implements Value.

func (ValuePositionalArgument) Token

Token implements Value.

Jump to

Keyboard shortcuts

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