flags

package module
v0.6.0 Latest Latest
Warning

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

Go to latest
Published: Mar 29, 2022 License: BSD-3-Clause Imports: 15 Imported by: 0

README

Package flags

Go Reference builds.sr.ht status Coverage badge Version badge

Package flags implements command-line flags parsing, configured with struct tags.

Install

The package can be installed from the command line using the go tool. There are no dependencies beyond the standard library. Any version of Go should work.

go get git.sr.ht/~rj/flags

Getting Started

Package documentation and examples are on Go Reference.

Contribute

To submit bug reports and suggestions, please use the issue tracker.

Discussions occur using the mailing list. The mailing list can also be used to submit patches.

Scope
  • flags: Package flags implements command-line flags parsing (standard library).
  • github.com/google/subcommands: Subcommands is a Go package that implements a simple way for a single command to have many subcommands, each of which takes arguments and so forth.
  • github.com/spf13/cobra: Package cobra is a commander providing a simple interface to create powerful modern CLI interfaces. In addition to providing an interface, Cobra simultaneously provides a controller to organize your application code.
  • github.com/muesli/coral: A friendly Cobra fork with nearly of its features, but only 4 dependencies.

License

BSD (c) Robert Johnstone

Documentation

Overview

Package flags implements command-line flags parsing, configured with struct tags.

Instead of imperatively configuring the command-line flags for an application, this package instead uses reflection to build the command-line parser from a struct. This package also supports the configuration of sub-commands.

All of the functions in this package must first build a model of the CLI based on type of the command, and the command's struct tags. If any errors are found in the description of the CLI, then this compilation will panic.

Dependency Injection

To facilitate testing of Commands, both Run and RunSingle support dependency injection of the standard streams (stdin, stdout, and stderr). The command must also implement the IOInjector interface.

For testing, users can create tests where they call Parse to configure a command, inject memory buffers for input and output, and then call the method Run directly.

Example (Run)
package main

import (
	"fmt"

	"git.sr.ht/~rj/flags"
)

type ExampleCLI struct {
	Greeting string `flags:"-g,--greet" description:"Salutation to use"`
	Name     string `flags:"" description:"Name of person to greet."`
	Count    int    `flags:"-c,--count" description:"Number of repititions"`
}

func (cli *ExampleCLI) Run() error {
	fmt.Printf("%s, %s!\n", cli.Greeting, cli.Name)
	return nil
}

func (cli *ExampleCLI) Description() string {
	return "Describe the command."
}

func main() {
	// Initialize the commands for CLI.  Initial values for the fields will be
	// used as defaults.
	command1 := ExampleCLI{
		Greeting: "Hello",
		Name:     "world",
		Count:    1,
	}
	command2 := ExampleCLI{
		Greeting: "Hello",
		Name:     "world",
		Count:    1,
	}

	// Execute the program.  The call to run will handle all command-line
	// parsing, will report errors, and call the appropriate command.
	flags.Run(
		map[string]flags.Command{
			"cmd1": &command1,
			"cmd2": &command2,
		},
	)
}
Output:

Example (Run_single)
package main

import (
	"fmt"

	"git.sr.ht/~rj/flags"
)

type ExampleCLI struct {
	Greeting string `flags:"-g,--greet" description:"Salutation to use"`
	Name     string `flags:"" description:"Name of person to greet."`
	Count    int    `flags:"-c,--count" description:"Number of repititions"`
}

func (cli *ExampleCLI) Run() error {
	fmt.Printf("%s, %s!\n", cli.Greeting, cli.Name)
	return nil
}

func (cli *ExampleCLI) Description() string {
	return "Describe the command."
}

func main() {
	// Initialize the CLI and provide defaults for the parameters.
	cli := ExampleCLI{
		Greeting: "Hello",
		Name:     "world",
	}

	// Execute the program.
	flags.RunSingle(&cli)
}
Output:

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Parse

func Parse(command interface{}, args []string) error

Parse parses command-line flags, and stores the result in the struct. The first command should be a pointer to a struct whose fields have tags to specify the argument names.

Parse expects to see only the command-line arguments. For example, one could use os.Args[1:].

Example
package main

import (
	"fmt"

	"git.sr.ht/~rj/flags"
)

func main() {
	// Initialize the CLI and provide defaults for the parameters.
	cli := struct {
		Greeting string `flags:"-g,--greet" description:"Salutation to use."`
		Name     string `flags:"" description:"Name of person to greet."`
	}{
		Greeting: "Hello",
		Name:     "world",
	}

	// Parse the command line.
	err := flags.Parse(&cli,
		// Typically provide os.Args[1:] here
		[]string{"-g", "Bonjour", "le monde"})
	must(err) // To be replaced with proper error handling.

	// Take action.
	fmt.Printf("%s, %s!\n", cli.Greeting, cli.Name)

}

func must(err error) {
	if err != nil {
		fmt.Printf("error: %s\n", err)
	}
}
Output:

Bonjour, le monde!
Example (Bool)
package main

import (
	"fmt"
	"strings"

	"git.sr.ht/~rj/flags"
)

func main() {
	// Initialize the CLI and provide defaults for the parameters.
	cli := struct {
		A     bool `flags:"-a"`
		B     bool `flags:"-b"`
		C     bool `flags:"-c"`
		D     bool `flags:"-d"`
		Quiet bool `flags:"--quiet"`
	}{}

	// Parse the command line.
	err := flags.Parse(&cli,
		// Typically provide os.Args[1:] here
		strings.Fields("-abc --quiet"))
	must(err) // To be replaced with proper error handling.

	// Take action.
	fmt.Printf("%v %v %v %v %v\n", cli.A, cli.B, cli.C, cli.D, cli.Quiet)

}

func must(err error) {
	if err != nil {
		fmt.Printf("error: %s\n", err)
	}
}
Output:

true true true false true
Example (Repeated)
package main

import (
	"fmt"
	"strings"

	"git.sr.ht/~rj/flags"
)

func main() {
	// Initialize the CLI and provide defaults for the parameters.
	cli := struct {
		Count int      `flags:""`
		Files []string `flags:""`
	}{}

	// Parse the command line.
	err := flags.Parse(&cli,
		// Typically provide os.Args[1:] here
		strings.Fields("123 file1.txt file2.txt file3.txt"))
	must(err) // To be replaced with proper error handling.

	// Take action.
	fmt.Printf("%d %v\n", cli.Count, cli.Files)

}

func must(err error) {
	if err != nil {
		fmt.Printf("error: %s\n", err)
	}
}
Output:

123 [file1.txt file2.txt file3.txt]
Example (Slice)
package main

import (
	"fmt"
	"strings"

	"git.sr.ht/~rj/flags"
)

func main() {
	// Initialize the CLI and provide defaults for the parameters.
	cli := struct {
		Files []string `flags:"-f,--file"`
	}{}

	// Parse the command line.
	err := flags.Parse(&cli,
		// Typically provide os.Args[1:] here
		strings.Fields("-f file1.txt -f=file2.txt -ffile3.txt --file file4.txt --file=file5.txt"))
	must(err) // To be replaced with proper error handling.

	// Take action.
	fmt.Printf("%v\n", cli.Files)

}

func must(err error) {
	if err != nil {
		fmt.Printf("error: %s\n", err)
	}
}
Output:

[file1.txt file2.txt file3.txt file4.txt file5.txt]

func PrintSingleUsage

func PrintSingleUsage(w io.Writer, command interface{}, options ...Option)

PrintSingleUsage generates and prints the usage for a command to the writer. It prints the same message as would happen in a call to RunSingle, if the arguments requested help.

Example
// Initialize the CLI and provide defaults for the parameters.
cli := ExampleCLI{
	Greeting: "Hello",
	Name:     "world",
	Count:    1,
}

flags.PrintSingleUsage(os.Stdout, &cli, flags.Name("example"))
Output:

Usage:  example [options]... <name>
        example (--help | -h)

Describe the command.

Required Arguments:
    <name>
        Name of person to greet.

Optional Arguments:
    -g=<string>, --greet=<string>
        Salutation to use (default "Hello")
    -c=<int>, --count=<int>
        Number of repititions (default 1)
Example (Options)
// Initialize the CLI and provide defaults for the parameters.
cli := ExampleCLI{
	Greeting: "Hello",
	Name:     "world",
	Count:    1,
}

flags.PrintSingleUsage(
	os.Stdout,
	&cli,
	flags.Name("example"),
	flags.Version("v0.1.2"),
	flags.Description("Example help from a godoc example."),
)
Output:

Usage:  example [options]... <name>
        example (--help | -h)
        example (--version | -v)

Example help from a godoc example.

Required Arguments:
    <name>
        Name of person to greet.

Optional Arguments:
    -g=<string>, --greet=<string>
        Salutation to use (default "Hello")
    -c=<int>, --count=<int>
        Number of repititions (default 1)
Example (Repeated)
package main

import (
	"os"

	"git.sr.ht/~rj/flags"
)

func main() {
	// Initialize the CLI and provide defaults for the parameters.
	cli := struct {
		Count int      `flags:"" description:"Number of times to repeat the operation."`
		Files []string `flags:"" description:"Some files needed for the operation."`
	}{}

	// Parse the command line.
	flags.PrintSingleUsage(
		os.Stdout,
		&cli,
		flags.Name("example"),
		flags.Version("v0.1.2"),
		flags.Description("Example help from a godoc example."),
	)

}
Output:

Usage:  example <count> <files>...
        example (--help | -h)
        example (--version | -v)

Example help from a godoc example.

Required Arguments:
    <count>
        Number of times to repeat the operation.
    <files>...
        Some files needed for the operation.

func Run

func Run(commands map[string]Command, options ...Option)

Run parses the command-line to select and configure a Command. It then runs that command.

Run will check for standard options to see if the user has requested help, or possibly to print version information. When a standard option is present, Run will handle the option and then exit the program.

If there are any errors parsing the command line, or when executing the command, Run will abort the program.

Example
package main

import (
	"fmt"

	"git.sr.ht/~rj/flags"
)

type ExampleCLI struct {
	Greeting string `flags:"-g,--greet" description:"Salutation to use"`
	Name     string `flags:"" description:"Name of person to greet."`
	Count    int    `flags:"-c,--count" description:"Number of repititions"`
}

func (cli *ExampleCLI) Run() error {
	fmt.Printf("%s, %s!\n", cli.Greeting, cli.Name)
	return nil
}

func (cli *ExampleCLI) Description() string {
	return "Describe the command."
}

func main() {
	// Initialize the CLI and provide defaults for the parameters.
	command1 := ExampleCLI{
		Greeting: "Hello",
		Name:     "world",
		Count:    1,
	}
	command2 := ExampleCLI{
		Greeting: "Hello",
		Name:     "world",
		Count:    1,
	}

	flags.Run(
		map[string]flags.Command{
			"cmd1": &command1,
			"cmd2": &command2,
		},
		flags.Name("example"),
		flags.Version("v0.1.2"),
		// For testing only, specify the command-line argument.
		flags.Args("./example", "cmd1", "-g", "Hallo", "welt"),
	)

}
Output:

Hallo, welt!
Example (Command_help)
package main

import (
	"fmt"

	"git.sr.ht/~rj/flags"
)

type ExampleCLI struct {
	Greeting string `flags:"-g,--greet" description:"Salutation to use"`
	Name     string `flags:"" description:"Name of person to greet."`
	Count    int    `flags:"-c,--count" description:"Number of repititions"`
}

func (cli *ExampleCLI) Run() error {
	fmt.Printf("%s, %s!\n", cli.Greeting, cli.Name)
	return nil
}

func (cli *ExampleCLI) Description() string {
	return "Describe the command."
}

func main() {
	// Initialize the CLI and provide defaults for the parameters.
	command1 := ExampleCLI{
		Greeting: "Hello",
		Name:     "world",
		Count:    1,
	}
	command2 := ExampleCLI{
		Greeting: "Bonjour",
		Name:     "le monde",
		Count:    1,
	}

	// Print the automatically generated usage to standard out.
	flags.Run(
		map[string]flags.Command{
			"cmd1": &command1,
			"cmd2": &command2,
		},
		flags.Name("example"),
		flags.Version("v0.1.2"),
		// For testing only, specify the command-line argument.
		flags.Args("./example", "cmd1", "--help"),
		// For testing only, prevent call to os.Exit that normally occurs after
		// printing help.
		flags.Testing(true),
	)

}
Output:

Usage:  example cmd1 [options]... <name>
        example cmd1 (--help | -h)

Describe the command.

Required Arguments:
    <name>
        Name of person to greet.

Optional Arguments:
    -g=<string>, --greet=<string>
        Salutation to use (default "Hello")
    -c=<int>, --count=<int>
        Number of repititions (default 1)
Example (Help)
package main

import (
	"fmt"

	"git.sr.ht/~rj/flags"
)

type ExampleCLI struct {
	Greeting string `flags:"-g,--greet" description:"Salutation to use"`
	Name     string `flags:"" description:"Name of person to greet."`
	Count    int    `flags:"-c,--count" description:"Number of repititions"`
}

func (cli *ExampleCLI) Run() error {
	fmt.Printf("%s, %s!\n", cli.Greeting, cli.Name)
	return nil
}

func (cli *ExampleCLI) Description() string {
	return "Describe the command."
}

func main() {
	// Initialize the CLI and provide defaults for the parameters.
	command1 := ExampleCLI{
		Greeting: "Hello",
		Name:     "world",
		Count:    1,
	}
	command2 := ExampleCLI{
		Greeting: "Bonjour",
		Name:     "le monde",
		Count:    1,
	}

	// Print the automatically generated usage to standard out.
	flags.Run(
		map[string]flags.Command{
			"cmd1": &command1,
			"cmd2": &command2,
		},
		flags.Name("example"),
		flags.Version("v0.1.2"),
		// For testing only, specify the command-line argument.
		flags.Args("./example", "--help"),
		// For testing only, prevent call to os.Exit that normally occurs after
		// printing help.
		flags.Testing(true),
	)

}
Output:

Usage:  example <command> [options]... <arguments>...
        example (--help | -h)
        example (--version | -v)

Available commands:
    cmd1    Describe the command.
    cmd2    Describe the command.
Example (Version)
package main

import (
	"fmt"

	"git.sr.ht/~rj/flags"
)

type ExampleCLI struct {
	Greeting string `flags:"-g,--greet" description:"Salutation to use"`
	Name     string `flags:"" description:"Name of person to greet."`
	Count    int    `flags:"-c,--count" description:"Number of repititions"`
}

func (cli *ExampleCLI) Run() error {
	fmt.Printf("%s, %s!\n", cli.Greeting, cli.Name)
	return nil
}

func (cli *ExampleCLI) Description() string {
	return "Describe the command."
}

func main() {
	// Initialize the CLI and provide defaults for the parameters.
	command1 := ExampleCLI{
		Greeting: "Hello",
		Name:     "world",
	}
	command2 := ExampleCLI{
		Greeting: "Bonjour",
		Name:     "le monde",
	}

	// Print the automatically generated usage to standard out.
	flags.Run(
		map[string]flags.Command{
			"cmd1": &command1,
			"cmd2": &command2,
		},
		flags.Name("example"),
		flags.Version("v0.1.2"),
		// For testing only, specify the command-line argument.
		flags.Args("./example", "--version"),
		// For testing only, prevent call to os.Exit that normally occurs after
		// printing version.
		flags.Testing(true),
	)

}
Output:

example (v0.1.2)

func RunSingle

func RunSingle(command Command, options ...Option)

RunSingle parses the command-line to configure a Command. It then runs that command.

RunSingle will check for standard options to see if the user has requested help, or possibly to print version information. When a standard option is present, RunSingle will handle the option and then exit the program.

If there are any errors parsing the command line, or when executing the command, Run will abort the program.

Example
package main

import (
	"fmt"

	"git.sr.ht/~rj/flags"
)

type ExampleCLI struct {
	Greeting string `flags:"-g,--greet" description:"Salutation to use"`
	Name     string `flags:"" description:"Name of person to greet."`
	Count    int    `flags:"-c,--count" description:"Number of repititions"`
}

func (cli *ExampleCLI) Run() error {
	fmt.Printf("%s, %s!\n", cli.Greeting, cli.Name)
	return nil
}

func (cli *ExampleCLI) Description() string {
	return "Describe the command."
}

func main() {
	// Initialize the CLI and provide defaults for the parameters.
	cli := ExampleCLI{
		Greeting: "Hello",
		Name:     "world",
	}

	flags.RunSingle(&cli,
		flags.Name("example"),
		flags.Version("v0.1.2"),
		// For testing only, specify the command-line argument.
		flags.Args("./example", "-g", "Hallo", "welt"),
	)

}
Output:

Hallo, welt!
Example (Help)
package main

import (
	"fmt"

	"git.sr.ht/~rj/flags"
)

type ExampleCLI struct {
	Greeting string `flags:"-g,--greet" description:"Salutation to use"`
	Name     string `flags:"" description:"Name of person to greet."`
	Count    int    `flags:"-c,--count" description:"Number of repititions"`
}

func (cli *ExampleCLI) Run() error {
	fmt.Printf("%s, %s!\n", cli.Greeting, cli.Name)
	return nil
}

func (cli *ExampleCLI) Description() string {
	return "Describe the command."
}

func main() {
	// Initialize the CLI and provide defaults for the parameters.
	cli := ExampleCLI{
		Greeting: "Hello",
		Name:     "world",
		Count:    1,
	}

	flags.RunSingle(&cli,
		flags.Name("example"),
		flags.Version("v0.1.2"),
		// For testing only, specify the command-line argument.
		flags.Args("./example", "--help"),
		// For testing only, prevent call to os.Exit that normally occurs after
		// printing help.
		flags.Testing(true),
	)

}
Output:

Usage:  example [options]... <name>
        example (--help | -h)
        example (--version | -v)

Required Arguments:
    <name>
        Name of person to greet.

Optional Arguments:
    -g=<string>, --greet=<string>
        Salutation to use (default "Hello")
    -c=<int>, --count=<int>
        Number of repititions (default 1)
Example (Reflow)
package main

import (
	"fmt"

	"git.sr.ht/~rj/flags"
)

type ExampleCLI struct {
	Greeting string `flags:"-g,--greet" description:"Salutation to use"`
	Name     string `flags:"" description:"Name of person to greet."`
	Count    int    `flags:"-c,--count" description:"Number of repititions"`
}

func (cli *ExampleCLI) Run() error {
	fmt.Printf("%s, %s!\n", cli.Greeting, cli.Name)
	return nil
}

func (cli *ExampleCLI) Description() string {
	return "Describe the command."
}

func main() {
	const lorem = `Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent molestie facilisis mauris, quis tristique justo viverra id. Integer efficitur consectetur rhoncus. Ut et eros et augue sagittis rutrum in ac diam. Nunc eget erat vel nisi ullamcorper euismod. Phasellus pretium pharetra maximus. Mauris eget lectus ac massa luctus semper. Donec faucibus nisi et elit tempor congue at non ligula. Fusce.` //nolint:lll

	// Initialize the CLI and provide defaults for the parameters.
	cli := ExampleCLI{
		Greeting: "Hello",
		Name:     "world",
		Count:    1,
	}

	flags.RunSingle(&cli,
		flags.Name("example"),
		flags.Description(lorem),
		flags.Version("v0.1.2"),
		// For testing only, specify the command-line argument.
		flags.Args("./example", "--help"),
		// For testing only, prevent call to os.Exit that normally occurs after
		// printing help.
		flags.Testing(true),
	)

}
Output:

Usage:  example [options]... <name>
        example (--help | -h)
        example (--version | -v)

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent molestie
facilisis mauris, quis tristique justo viverra id. Integer efficitur consectetur
rhoncus. Ut et eros et augue sagittis rutrum in ac diam. Nunc eget erat vel nisi
ullamcorper euismod. Phasellus pretium pharetra maximus. Mauris eget lectus ac
massa luctus semper. Donec faucibus nisi et elit tempor congue at non ligula.
Fusce.

Required Arguments:
    <name>
        Name of person to greet.

Optional Arguments:
    -g=<string>, --greet=<string>
        Salutation to use (default "Hello")
    -c=<int>, --count=<int>
        Number of repititions (default 1)

Types

type Command

type Command interface {
	Run() error
}

Command is a subcommand.

type Describer

type Describer interface {
	Command
	Description() string
}

Describer allows a command to set a description for itself, useful when print usage.

type IOInject

type IOInject struct {
	Stdin  io.Reader
	Stdout io.Writer
	Stderr io.Writer
}

IOInject is meant to be embedded in Commands that which to have dependency injection for standard input, standard output, and standard error.

func (*IOInject) Inject

func (i *IOInject) Inject(in io.Reader, out io.Writer, err io.Writer)

Inject fulfills the IOInjector interface.

type IOInjector

type IOInjector interface {
	Inject(in io.Reader, out io.Writer, err io.Writer)
}

IOInjector is an interface for dependency injection of input and output.

Example
package main

import (
	"bytes"
	"fmt"

	"git.sr.ht/~rj/flags"
)

type InjectorCommand struct {
	flags.IOInject
	ExampleCLI
}

func (cmd *InjectorCommand) Run() error {
	fmt.Fprintf(cmd.Stdout, "%s, %s!\n", cmd.Greeting, cmd.Name)
	return nil
}

func main() {
	cmd := InjectorCommand{
		IOInject: flags.IOInject{},

		ExampleCLI: ExampleCLI{
			Greeting: "Hello",
			Name:     "world",
		},
	}

	stdout := bytes.NewBuffer(nil)
	cmd.Inject(nil, stdout, nil)
	_ = cmd.Run()

	fmt.Printf(">>> %s", stdout.String())

}
Output:

>>> Hello, world!

type Option

type Option func(*config)

Option specifies additionial metadata to help with printing usage information, or with parsing command-line arguments.

func Args

func Args(args ...string) Option

Args overrides the command-line arguments used for parsing. The arguments should start with the program name (similar to os.Args).

func Description

func Description(desc string) Option

Description sets a description of the program, to be printed after basic usage information. The description can span multiple paragraphs (separated by a single newline).

func Name

func Name(name string) Option

Name sets the name of the program, for use with printing usage.

func Testing

func Testing(testing bool) Option

Testing sets a flag to indicate that calls to os.Exit should be skipped.

func Version

func Version(version string) Option

Version sets a version string for the program. Setting a version will enable additional standard options so that the user can print version information.

type Value

type Value interface {
	String() string
	Set(string) error
}

Value is the interface that allow custom types to be set by command-line flags.

Set is called once for each flag present.

Example
package main

import (
	"fmt"
	"time"

	"git.sr.ht/~rj/flags"
)

type TimeOption struct {
	time.Time
}

func (t *TimeOption) String() string {
	return t.Time.String()
}

func (t *TimeOption) Set(value string) (err error) {
	t.Time, err = time.Parse("2006-01-02 15:04:05.999999999 -0700 MST", value)
	return err
}

func main() {
	cli := struct {
		Now TimeOption `flags:"--time"`
	}{
		TimeOption{Time: time.Now()},
	}

	err := flags.Parse(&cli, []string{"--time", "2006-01-02 15:04:05 -0700 MST"})
	must(err) // To be replaced with proper error handling.

	fmt.Println(cli.Now.String())

}
Output:

2006-01-02 15:04:05 -0700 MST

Directories

Path Synopsis
examples
command
Command is an example CLI where that uses sub-commands.
Command is an example CLI where that uses sub-commands.
single
Single is an example CLI where there are no sub-commands.
Single is an example CLI where there are no sub-commands.
internal

Jump to

Keyboard shortcuts

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