cmdr

package module
v1.9.0 Latest Latest
Warning

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

Go to latest
Published: Aug 26, 2023 License: Apache-2.0 Imports: 11 Imported by: 21

README

About the Go "commander" Cmdr module

This module has grown out of common parts of various projects. In all cases, it was desired to offer a consistent command-line experience. In some cases, it was also desired to support sub-commands or to make the main executable a multiplexer which acts based on argv[0].

The package is designed to be minimalistic but extensible. Anything which takes a context and a slice of string can be invoked as a command.

Overview of packages

The cmdr package provides the Cmd interface, the convenience type Func as well as a function to call from main, namely RunMain. See the documentation for a lot more details.

The cmdr/router package provides a command that routes execution to one of any number of named commands. The router cooperates with RunMain allowing command multiplexers, packing many commands efficiently into one executable.

The cmdr/cmdtest package provides utilities for testing command line tools, with a way to capture and observe exit code and printed output and error messages.

The cmdr/format package provides support for the OUTPUT_FORMAT= quasi-standard, which allows applications to produce output in a format desired by the user. This is excellent for producing JSON or shell-compatible output for scripting and automation.

Contributions

Contributions are welcome. Please try to respect Go requirements (1.18 at the moment) and the overall coding and testing style.

License and REUSE

This project is licensed under the Apache 2.0 license, see the LICENSE file for details. The project is compliant with https://reuse.software/, making it easy to ensure license and copyright compliance by automating software bill-of-materials. In other words, it's a good citizen in the modern free software stacks.

Documentation

Overview

Package cmdr provides an organized way to implement command line tools, with predictable behavior, arbitrary sub-commands and symlink-based multiplexing.

Index

Examples

Constants

View Source
const ExeSuffix = ""

ExeSuffix is the suffix for executable programs.

Variables

This section is empty.

Functions

func Name added in v1.5.0

func Name(ctx context.Context) (name string)

Name returns the name of the command.

Name always returns the accurate name of the executing command, which may be a nested name, for as long as RunMain and router.Cmd are used.

If the context is handled incorrectly, Name panics.

func RunMain

func RunMain(cmd Cmd, args []string, opts ...Option)

RunMain runs the given command with arguments.

A context.NotifyContext with os.Interrupt signal is provided as context, allowing commands to gracefully terminate on the interrupt signal. Other signals may be intercepted by passing WithSignals option.

If cmd has PassArgv0() bool method which returns true, then args[0] is preserved. This allows implementing symlink-based command multiplexers, such as Router.

If the command panics, the panic is recovered and an error message is printed. Other behaviors can be selected by passing WithPanicBehavior option. If the command returns an error, the error is printed, unless it is equal to flag.ErrHelp.

The exit code from the process is non-zero when the command returns an error or causes a panic. Errors implementing the ExitCoder interface can provide the exit code explicitly. The special flag.ErrHelp is translated to EX_USAGE (64). All other errors set exit code to one.

The standard output and error streams are always synchronized before terminating the process.

Example

You can structure your application as a number of command types that get invoked at the right time. All you need is a `Run(context.Context, []string) error` function on your types. Inside that function, you can do anything.

cmdr.RunMain(helloCmd{}, []string{"example"})
Output:

Hello World

func RunSubCommand added in v1.7.0

func RunSubCommand(ctx context.Context, cmd Cmd, name string, args []string) error

RunSubCommand runs a given command as a sub-command.

This function allows replicating all the router functionality in a custom type, perhaps one with logic beyond the capabilities of the default router.

Unlike just running the command directly, the invoked sub-command can access the correctly nested sub-command name through cmdr.Name.

func Stdio added in v1.1.0

func Stdio(ctx context.Context) (stdin io.Reader, stdout, stderr io.Writer)

Stdio returns the standard IO streams for the given context.

If the context was returned by WithStdio, the encoded streams are recovered. In all other cases os.{Stdin,Stdout,Stderr} is returned.

IO streams may be replaced for testing. Recovering the streams from the context avoids the need to use and manage per-command global variables.

Example

Apart from serving the usual role, the context may be used to obtain the three standard input and output streams. The returned streams are os.Stdin, os.Stdout and os.Stderr. In unit tests, those may be redirected to buffers to aid in testing the output produced by commands. This is especially useful and easy with the cmdtest package.

cmd := cmdr.Func(func(ctx context.Context, args []string) error {
	_, stdout, _ := cmdr.Stdio(ctx)

	_, _ = fmt.Fprintln(stdout, "Hello World")

	return nil
})
inv := cmdtest.Invoke(cmd)

if err := inv.ExpectStdout("Hello World\n"); err != nil {
	log.Fatal(err)
}
// This produces no output as the output is captured the invocation helper
// and is available for tests to inspect.
Output:

func WithStdio added in v1.1.0

func WithStdio(parent context.Context, stdin io.Reader, stdout, stderr io.Writer) context.Context

WithStdio returns a context remembering the standard IO streams.

Tests can create a context which provides a set of bytes.Buffer objects as IO streams, avoiding the need to maintain global variables which are modified during test execution.

Types

type Args0Observer

type Args0Observer interface {
	PreserveArgs0() bool
}

Args0Observer instructs RunMain to preserve args[0].

type Cmd

type Cmd interface {
	// Run runs the command with the given context and arguments.
	// The context can be used to pass global options to a command.
	Run(ctx context.Context, args []string) error
}

Cmd is a runnable command.

type ExitCoder

type ExitCoder interface {
	ExitCode() int
}

ExitCoder is an interface for errors to convey a process exit code.

type ExitMessenger added in v1.6.0

type ExitMessenger interface {
	ExitMessage() string
}

ExitMessenger is an interface for errors to convey a specific error message.

ExitMessenger may be used to suppress the system that automatically prints the error value if the error message returned is empty.

type Func

type Func func(ctx context.Context, args []string) error

Func is the type of functions runnable as commands.

Example

The most basic example uses `cmdr.RunMain` to run a function that takes a context, arguments and returns an error. The context has multiple uses as we will see later. The `cmdr.Func` type adapts a function to `cmdr.Cmd` interface. In essence, anything that can be called with string arguments is runnable as a command.

cmd := cmdr.Func(func(ctx context.Context, args []string) error {
	fmt.Println("Hello World")

	return nil
})

cmdr.RunMain(cmd, []string{"example"})
Output:

Hello World

func (Func) Run

func (fn Func) Run(ctx context.Context, args []string) error

Run runs the underlying function.

type Option added in v1.2.0

type Option func(*mainOpts)

Option influences how RunMain operates.

func WithArgv0 added in v1.2.0

func WithArgv0(argv0 string) Option

WithArgv0 sets the name of the main command.

RunMain occasionally prints the name of the main command. The name of the main command is either "?" or the base name of os.Args[0]. This option allows using a custom name.

func WithBaseContext added in v1.2.0

func WithBaseContext(ctx context.Context) Option

WithBaseContext sets the base of the context provided to the main command.

RunMain provides a context to executed command. This context defaults to a context derived from context.Background. This option allows using a custom base context, for example, a context with a deadline.

func WithExitFunc added in v1.2.1

func WithExitFunc(fn func(code int)) Option

WithExitFunc overrides the default os.Exit with a custom function.

Note that the exit function when a non-zero needs to be returned. On successful exit or when the error maps to an exit code of zero, the exit function is not called.

func WithPanicBehavior added in v1.2.0

func WithPanicBehavior(b PanicBehavior) Option

WithPanicBehavior configures panic behavior.

RunMain recovers any panic that happens during execution of the main command. By default, only the panic message is printed. This option allows selecting other behavior.

func WithSignals added in v1.2.0

func WithSignals(sigs ...os.Signal) Option

WithSignals sets signals canceling the context passed to the main command.

RunMain establishes signal handlers through signal.NotifyContext. By default, os.Interrupt is intercepted. This option allows using a different set of signals.

type PanicBehavior added in v1.2.0

type PanicBehavior int

PanicBehavior controls reaction to panic encountered in RunMain.

Use WithPanicBehavior to

const (
	// RecoverExit is the default panic behavior of RunMain.
	//
	// Panic caused by command execution is recovered. The recovered value
	// is printed to standard error. Program execution is terminated with
	// os.Exit, or the configured exit function, with code 70.
	RecoverExit PanicBehavior = iota

	// RecoverTraceExit prints a stack trace on panic.
	//
	// RecoverStackTrace is like RecoverExit in the sense that a panic is
	// both recovered and that the process terminates with an exit code. In
	// addition, the stack trace of the go routine which has caused the
	// panic is printed to standard error.
	RecoverTraceExit
)

type SilentError added in v1.6.0

type SilentError uint8

SilentError is an error type that produces a given error code but no error message.

func (SilentError) Error added in v1.6.0

func (e SilentError) Error() string

Error returns the empty string.

func (SilentError) ExitCode added in v1.6.0

func (e SilentError) ExitCode() int

ExitCode returns the integer encoded in the error.

func (SilentError) ExitMessage added in v1.6.0

func (e SilentError) ExitMessage() string

ExitMessage returns the empty string.

cmdr.RunMain suppresses error output when ExitMessage is implemented by the error value and the value is the empty string.

Directories

Path Synopsis
Package cmdtest contains utilities for testing commands.
Package cmdtest contains utilities for testing commands.
internal/install
Package install exists to provide regression tests against https://gitlab.com/zygoon/go-cmdr/-/issues/4
Package install exists to provide regression tests against https://gitlab.com/zygoon/go-cmdr/-/issues/4
Package flagcmd implements extensible commands based on standard library flag.FlagSet argument parser.
Package flagcmd implements extensible commands based on standard library flag.FlagSet argument parser.
Package format implements the output format hint system.
Package format implements the output format hint system.
jsonformat
Package jsonformat implements support for outputting JSON.
Package jsonformat implements support for outputting JSON.
diff
Package diff computes differences between text files or strings.
Package diff computes differences between text files or strings.
diff/difftest
Package difftest supplies a set of tests that will operate on any implementation of a diff algorithm as exposed by "golang.org/x/tools/internal/diff"
Package difftest supplies a set of tests that will operate on any implementation of a diff algorithm as exposed by "golang.org/x/tools/internal/diff"
diff/lcs
Package lcs contains code to find longest-common-subsequences (and diffs)
Package lcs contains code to find longest-common-subsequences (and diffs)
Package router provides command router useful for creating arbitrary sub-command trees.
Package router provides command router useful for creating arbitrary sub-command trees.
samples module

Jump to

Keyboard shortcuts

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