cli

package module
v0.1.3 Latest Latest
Warning

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

Go to latest
Published: Feb 26, 2025 License: MIT Imports: 17 Imported by: 0

README

cli

Go Reference Go Report Card codecov gosec

A Go package for building CLIs.

Installation

go get github.com/broothie/cli@latest

Documentation

Detailed documentation can be found at pkg.go.dev.

Usage

Using cli is as simple as:

// Create and run a command called "fileserver".
// `Run` automatically passes down a `context.Background()` and parses `os.Args[1:]`.
// If an error is returned, and it is either a `cli.ExitError` or an `*exec.ExitError`, the error's exit code will be used.
// For any other errors returned, it exits with code 1.
cli.Run("fileserver", "An HTTP server.",

	// Add an optional positional argument called "root" which will default to ".".
	cli.AddArg("root", "Directory to serve from", cli.SetArgDefault(".")),

	// Add an optional flag called "port" which will default to 3000.
	cli.AddFlag("port", "Port to run server.", cli.SetFlagDefault(3000)),

	// Register a handler for this command.
	// If no handler is registered, it will simply print help and exit.
	cli.SetHandler(func(ctx context.Context) error {
		// Extract the value of the "root" argument.
		root, _ := cli.ArgValue[string](ctx, "root")

		// Extract the value of the "port" flag.
		port, _ := cli.FlagValue[int](ctx, "port")

		addr := fmt.Sprintf(":%d", port)
		return http.ListenAndServe(addr, http.FileServer(http.Dir(root)))
	}),
)

Here's an example using the complete set of options:

cmd, err := cli.NewCommand("git", "Modern version control.",
	// Set command version
	cli.SetVersion("2.37.0"),

	// Add a "--version" flag with a short flag "-V" for printing the command version
	cli.AddVersionFlag(cli.AddFlagShort('V')),

	// Add a "--help" flag
	cli.AddHelpFlag(

		// Add a short flag "-h" to help
		cli.AddFlagShort('h'),

		// Make this flag inherited by sub-commands
		cli.SetFlagIsInherited(true),
	),

	// Add a hidden "--debug" flag
	cli.AddFlag("debug", "Enable debugging",
		cli.SetFlagDefault(false), // Default parser for flags is cli.StringParser

		// Make it hidden
		cli.SetFlagIsHidden(true),
	),

	// Add a sub-command "clone"
	cli.AddSubCmd("clone", "Clone a repository.",

		// Add a required argument "<url>"
		cli.AddArg("url", "Repository to clone.",

			// Parse it into a *url.URL
			cli.SetArgParser(cli.URLParser),
		),

		// Add optional argument "<dir?>"
		cli.AddArg("dir", "Directory to clone repo into.",

			// Set its default value to "."
			cli.SetArgDefault("."),
		),

		// Add a flag "--verbose"
		cli.AddFlag("verbose", "Be more verbose.",

			// Add a short "-v"
			cli.AddFlagShort('v'),

			// Make it a boolean that defaults to false
			cli.SetFlagDefault(false),
		),
	),
)
if err != nil {
	cli.ExitWithError(err)
}

// Pass in your `context.Context` and args
if err := cmd.Run(context.TODO(), os.Args[1:]); err != nil {
	cli.ExitWithError(err)
}

Roadmap

  • Audit bare err returns
    • Two types of errors: config and parse
  • Tab completion
  • Allow variadic arguments
  • Allow slice and map based flags?

Documentation

Overview

Example (Basic_usage)
package main

import (
	"context"
	"fmt"
	"net/http"

	"github.com/broothie/cli"
)

func main() {
	// Create and run a command called "fileserver".
	// `Run` automatically passes down a `context.Background()` and parses `os.Args[1:]`.
	// If an error is returned, and it is either a `cli.ExitError` or an `*exec.ExitError`, the error's exit code will be used.
	// For any other errors returned, it exits with code 1.
	cli.Run("fileserver", "An HTTP server.",

		// Add an optional positional argument called "root" which will default to ".".
		cli.AddArg("root", "Directory to serve from", cli.SetArgDefault(".")),

		// Add an optional flag called "port" (usage: --port) which will default to 3000.
		cli.AddFlag("port", "Port to run server.", cli.SetFlagDefault(3000)),

		// Register a handler for this command.
		// If no handler is registered, it will simply print help and exit.
		cli.SetHandler(func(ctx context.Context) error {
			// Extract the value of the "root" argument.
			root, _ := cli.ArgValue[string](ctx, "root")

			// Extract the value of the "port" flag.
			port, _ := cli.FlagValue[int](ctx, "port")

			addr := fmt.Sprintf(":%d", port)
			return http.ListenAndServe(addr, http.FileServer(http.Dir(root)))
		}),
	)
}
Output:

Example (Kitchen_sink)
package main

import (
	"context"
	"os"

	"github.com/broothie/cli"
)

func main() {
	// Create a new command
	cmd, err := cli.NewCommand("git", "Modern version control.",
		// Set command version
		cli.SetVersion("2.37.0"),

		// Add a "--version" flag with a short flag "-V" for printing the command version
		cli.AddVersionFlag(cli.AddFlagShort('V')),

		// Add a "--help" flag
		cli.AddHelpFlag(

			// Add a short flag "-h" to help
			cli.AddFlagShort('h'),

			// Make this flag inherited by sub-commands
			cli.SetFlagIsInherited(true),
		),

		// Add a hidden "--debug" flag
		cli.AddFlag("debug", "Enable debugging",
			cli.SetFlagDefault(false), // Default parser for flags is cli.StringParser

			// Make it hidden
			cli.SetFlagIsHidden(true),
		),

		// Add a sub-command "clone"
		cli.AddSubCmd("clone", "Clone a repository.",

			// Add a required argument "<url>"
			cli.AddArg("url", "Repository to clone.",

				// Parse it into a *url.URL
				cli.SetArgParser(cli.URLParser),
			),

			// Add optional argument "<dir?>"
			cli.AddArg("dir", "Directory to clone repo into.",

				// Set its default value to "."
				cli.SetArgDefault("."),
			),

			// Add a flag "--verbose"
			cli.AddFlag("verbose", "Be more verbose.",

				// Add a short "-v"
				cli.AddFlagShort('v'),

				// Make it a boolean that defaults to false
				cli.SetFlagDefault(false),
			),
		),
	)
	if err != nil {
		cli.ExitWithError(err)
	}

	// Pass in your `context.Context` and args
	if err := cmd.Run(context.TODO(), os.Args[1:]); err != nil {
		cli.ExitWithError(err)
	}
}
Output:

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	NotACommandContextError = errors.New("not a command context")
	FlagNotFoundError       = errors.New("flag not found")
	ArgumentNotFoundError   = errors.New("argument not found")
)
View Source
var (
	InvalidFlagError        = errors.New("invalid flag")
	MissingFlagValueError   = errors.New("missing flag value")
	TooManyArgumentsError   = errors.New("too many arguments")
	FlagGroupWithEqualError = errors.New("short flags with equal signs cannot be grouped")
)
View Source
var ArgumentMissingValueError = errors.New("argument missing value")
View Source
var NotParseableError = errors.New("type not parseable")

Functions

func AddAlias

func AddAlias(alias string) option.Func[*Command]

AddAlias adds an alias to the command.

func AddArg

func AddArg(name, description string, options ...option.Option[*Argument]) option.Func[*Command]

AddArg adds an argument to the command.

Example
command, _ := NewCommand("server", "An http server.",
	AddArg("port", "Port to run server on", SetArgParser(IntParser)),
)

command.renderHelp(os.Stdout)
Output:

server: An http server.

Usage:
  server <port>

Arguments:
  <port>  Port to run server on  (type: int)

func AddFlag

func AddFlag(name, description string, options ...option.Option[*Flag]) option.Func[*Command]

AddFlag adds a flag to the command.

Example
command, _ := NewCommand("server", "An http server.",
	AddFlag("port", "Port to run server on",
		AddFlagShort('p'),
		SetFlagDefault(3000),
	),
)

command.renderHelp(os.Stdout)
Output:

server: An http server.

Usage:
  server [flags]

Flags:
  --port  -p  Port to run server on  (type: int, default: "3000")
Example (With_env)
os.Setenv("PORT", "8080")
defer os.Unsetenv("PORT")

command, _ := NewCommand("server", "An http server.",
	AddFlag("port", "Port to run server on",
		AddFlagShort('p'),
		SetFlagDefault(3000),
		SetFlagDefaultEnv("PORT"),
	),
)

command.renderHelp(os.Stdout)
Output:

server: An http server.

Usage:
  server [flags]

Flags:
  --port  -p  Port to run server on  (type: int, default: $PORT, "3000")

func AddFlagAlias

func AddFlagAlias(alias string) option.Func[*Flag]

AddFlagAlias adds an alias to the flag.

func AddFlagShort

func AddFlagShort(short rune) option.Func[*Flag]

AddFlagShort adds a short flag to the flag.

func AddHelpFlag

func AddHelpFlag(options ...option.Option[*Flag]) option.Func[*Command]

AddHelpFlag adds a help flag to the command.

Example
command, _ := NewCommand("server", "An http server.",
	AddHelpFlag(AddFlagShort('h')),
)

command.renderHelp(os.Stdout)
Output:

server: An http server.

Usage:
  server [flags]

Flags:
  --help  -h  Print help.  (type: bool, default: "false")

func AddSubCmd

func AddSubCmd(name, description string, options ...option.Option[*Command]) option.Func[*Command]

AddSubCmd adds a subcommand to the command.

Example
command, _ := NewCommand("server", "An http server.",
	AddSubCmd("start", "Start the server"),
)

command.renderHelp(os.Stdout)
Output:

server: An http server.

Usage:
  server [sub-command]

Sub-command:
  start: Start the server

func AddVersionFlag

func AddVersionFlag(options ...option.Option[*Flag]) option.Func[*Command]
Example
command, _ := NewCommand("server", "An http server.",
	SetVersion("v0.1.0"),
	AddVersionFlag(AddFlagShort('V')),
)

command.renderHelp(os.Stdout)
Output:

server v0.1.0: An http server.

Usage:
  server [flags]

Flags:
  --version  -V  Print version.  (type: bool, default: "false")

func ArgValue

func ArgValue[T any](ctx context.Context, name string) (T, error)

func BoolParser

func BoolParser(s string) (bool, error)

BoolParser parses a string into a bool.

func DurationParser

func DurationParser(s string) (time.Duration, error)

DurationParser parses a string into a time.Duration.

func ExitWithError added in v0.1.3

func ExitWithError(err error)

ExitWithError exits the program with an error.

func FlagValue

func FlagValue[T any](ctx context.Context, name string) (T, error)

func Float64Parser

func Float64Parser(s string) (float64, error)

Float64Parser parses a string into a float64.

func IntParser

func IntParser(s string) (int, error)

IntParser parses a string into an int.

func MountSubCmd added in v0.1.3

func MountSubCmd(subCommand *Command) option.Func[*Command]

MountSubCmd mounds an existing *Command as a sub-command.

func Run

func Run(name, description string, options ...option.Option[*Command])

Run creates and runs a command using os.Args as the arguments and context.Background as the context.

Example
oldArgs := os.Args
os.Args = []string{"echo", "hello"}
defer func() { os.Args = oldArgs }()

Run("echo", "Echo the arguments.",
	AddArg("arg", "The argument to echo."),
	SetHandler(func(ctx context.Context) error {
		arg, _ := ArgValue[string](ctx, "arg")

		fmt.Println(arg)
		return nil
	}),
)
Output:

hello

func SetArgDefault added in v0.1.2

func SetArgDefault[T Parseable](defaultValue T) option.Func[*Argument]

SetArgDefault sets the default value of the argument.

func SetArgParser

func SetArgParser[T any](parser ArgParser[T]) option.Func[*Argument]

SetArgParser sets the parser of the argument.

func SetFlagDefault

func SetFlagDefault[T Parseable](defaultValue T) option.Func[*Flag]

SetFlagDefault sets the default value of the flag.

func SetFlagDefaultAndParser

func SetFlagDefaultAndParser[T any](defaultValue T, argParser ArgParser[T]) option.Func[*Flag]

SetFlagDefaultAndParser sets the default value and parser of the flag.

func SetFlagDefaultEnv added in v0.1.3

func SetFlagDefaultEnv(name string) option.Func[*Flag]

SetFlagDefaultEnv sets the default value to that of the corresponding environment variable, and parser of the flag.

func SetFlagIsHidden

func SetFlagIsHidden(isHidden bool) option.Func[*Flag]

SetFlagIsHidden controls whether the flag is hidden from the help message.

func SetFlagIsInherited

func SetFlagIsInherited(isInherited bool) option.Func[*Flag]

SetFlagIsInherited controls whether the flag is inherited by child commands.

func SetHandler

func SetHandler(handler Handler) option.Func[*Command]

SetHandler sets the handler of the command.

Example
command, _ := NewCommand("server", "An http server.",
	SetHandler(func(ctx context.Context) error {
		fmt.Println("running server")
		return nil
	}),
)

command.Run(context.Background(), nil)
Output:

running server

func SetVersion

func SetVersion(version string) option.Func[*Command]

SetVersion sets the version of the command.

Example
command, _ := NewCommand("server", "An http server.",
	SetVersion("v0.1.0"),
)

command.renderHelp(os.Stdout)
Output:

server v0.1.0: An http server.

Usage:
  server

func StringParser

func StringParser(s string) (string, error)

StringParser parses a string into a string.

func TimeParser

func TimeParser(s string) (time.Time, error)

TimeParser parses a string into a time.Time.

func URLParser

func URLParser(s string) (*url.URL, error)

URLParser parses a string into a *url.URL.

Types

type ArgParser

type ArgParser[T any] func(string) (T, error)

ArgParser is a function that parses a string into a value of type T.

func NewArgParser

func NewArgParser[T any](f ArgParser[T]) ArgParser[T]

NewArgParser creates a new ArgParser.

func TimeLayoutParser

func TimeLayoutParser(timeLayout string) ArgParser[time.Time]

TimeLayoutParser parses a string into a time.Time using a specific time layout.

func (ArgParser[T]) Parse

func (p ArgParser[T]) Parse(s string) (any, error)

func (ArgParser[T]) Type

func (ArgParser[T]) Type() any

type Argument

type Argument struct {
	// contains filtered or unexported fields
}

type Command

type Command struct {
	// contains filtered or unexported fields
}

func NewCommand

func NewCommand(name, description string, options ...option.Option[*Command]) (*Command, error)

NewCommand creates a new command.

Example
command, _ := NewCommand("server", "An http server.",
	SetVersion("v0.1.0"),
	AddVersionFlag(AddFlagShort('V')),
	AddHelpFlag(AddFlagShort('h')),
	AddFlag("port", "Port to run server on.",
		AddFlagShort('p'),
		SetFlagDefault(3000),
		SetFlagDefaultEnv("PORT"),
	),
	AddFlag("auth-required", "Whether to require authentication.",
		SetFlagDefault(true),
	),
	AddSubCmd("proxy", "Proxy requests to another server.",
		AddArg("target", "Target server to proxy requests to.",
			SetArgParser(URLParser),
		),
	),
)

command.renderHelp(os.Stdout)
Output:

server v0.1.0: An http server.

Usage:
  server [flags] [sub-command]

Sub-command:
  proxy: Proxy requests to another server.

Flags:
  --version        -V  Print version.                      (type: bool, default: "false")
  --help           -h  Print help.                         (type: bool, default: "false")
  --port           -p  Port to run server on.              (type: int, default: $PORT, "3000")
  --auth-required      Whether to require authentication.  (type: bool, default: "true")

func (*Command) Run

func (c *Command) Run(ctx context.Context, rawArgs []string) error

Run runs the command.

type ExitError added in v0.1.3

type ExitError struct {
	Code int
}

ExitError is an error that causes the program to exit with a given status code.

func ExitCode added in v0.1.3

func ExitCode(code int) *ExitError

ExitCode returns an ExitError with the given code.

func (ExitError) Error added in v0.1.3

func (e ExitError) Error() string

Error implements the error interface.

type Flag

type Flag struct {
	// contains filtered or unexported fields
}

type Handler

type Handler func(ctx context.Context) error

type Parseable

type Parseable interface {
	string | bool | int | float64 | time.Time | time.Duration | *url.URL
}

Parseable is a type that can be parsed from a string.

Jump to

Keyboard shortcuts

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