cmdr

package module
v1.17.0 Latest Latest
Warning

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

Go to latest
Published: Oct 4, 2026 License: Apache-2.0 Imports: 13 Imported by: 22

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.

The cmdr/stockcmds package contains stock commands that most programs will likely use. At the moment the only inhabitant is the stockcmds.VersionCmd command, which implements a version command.

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.

Internationalization (i18n) is supported through the Translator interface, configured with the WithTranslator option: usage output and error messages then render translated. Errors that implement the LocalizedError interface render themselves for the context of the failed run. SignalError, the cancellation cause RunMain records when a signal arrives, is one of them.

Flag defaults are a special case: flag.FlagSet.PrintDefaults hardcodes its "(default ...)" hint in English, with no i18n hook of its own. Commands that render usage through flagcmd.HelperContext should call flagcmd.PrintDefaults(ctx, fl) instead of fl.PrintDefaults() to keep that hint translated; calling fl.PrintDefaults() directly always renders it in English regardless of the installed Translator.

Index

Examples

Constants

View Source
const ExeSuffix = ""

ExeSuffix is the suffix for executable programs.

Variables

This section is empty.

Functions

func CauseSignal added in v1.17.0

func CauseSignal(ctx context.Context) os.Signal

CauseSignal returns the signal that cancelled ctx, or nil when ctx was not cancelled by a signal. It recognizes the cause RunMain records, SignalError, also when wrapped.

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.

The context provided to the command is cancelled when os.Interrupt arrives, allowing commands to gracefully terminate on the interrupt signal. Other signals may be intercepted by passing WithSignals option. The cancellation cause is a SignalError naming the signal received; see CauseSignal.

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.

package main

import (
	"context"
	"fmt"

	"gitlab.com/zygoon/go-cmdr"
)

type helloCmd struct{}

func (helloCmd) Run(context.Context, []string) error {
	fmt.Println("Hello World")
	return nil
}

func main() {
	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.

package main

import (
	"context"
	"fmt"
	"log"

	"gitlab.com/zygoon/go-cmdr"
	"gitlab.com/zygoon/go-cmdr/cmdtest"
)

func main() {
	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.

}

func Tr added in v1.14.0

func Tr(ctx context.Context, msgid string) string

Tr translates msgid through the Translator carried by ctx — the one configured with WithTranslator. Without a translator the msgid is returned as-is. Commands use it for their own user-facing strings, with the same context they were run with.

func TrCtx added in v1.16.0

func TrCtx(ctx context.Context, msgctxt, msgid string) string

TrCtx translates msgid through the Translator carried by ctx, the same as Tr, but disambiguated by msgctxt: use it for short or generic msgids (e.g. "Version") that read differently in different places and would otherwise collide in the catalog. Without a translator the msgid is returned as-is.

func TrCtxN added in v1.16.0

func TrCtxN(ctx context.Context, msgctxt, msgid, msgidPlural string, n int) string

TrCtxN combines TrCtx and TrN: translates a singular/plural message pair selected by n, disambiguated by msgctxt. Without a translator, English selection is used: msgid when n == 1, msgidPlural otherwise.

func TrN added in v1.14.0

func TrN(ctx context.Context, msgid, msgidPlural string, n int) string

TrN translates a singular/plural message pair selected by n through the Translator carried by ctx. Without a translator, English selection is used: msgid when n == 1, msgidPlural otherwise.

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.

package main

import (
	"context"
	"fmt"

	"gitlab.com/zygoon/go-cmdr"
)

func main() {
	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 LocalizedError added in v1.14.0

type LocalizedError interface {
	error
	LocalizedError(ctx context.Context) string
}

LocalizedError is the interface of errors that can render themselves for the context of the failed run — typically translated by an i18n library. It is detected structurally with errors.As, so error types from any library satisfy it without go-cmdr depending on one.

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 cancels the context when one of the signals arrives, recording a SignalError as the cancellation cause. By default, os.Interrupt is intercepted. This option allows using a different set of signals.

func WithTranslator added in v1.14.0

func WithTranslator(tr Translator) Option

WithTranslator sets the translator for internationalization.

RunMain uses the translator to render translated usage output and error messages, and commands can reach it through cmdr.Tr and cmdr.TrN with the context they were run with. If no translator is set, message IDs are used as-is.

The translator must resolve msgids from go-cmdr's own catalog (router headers, stock command one-liners, built-in error messages) as well as the application's own — go-cmdr's rendering code and the application's commands read back the same context-carried translator through cmdr.Tr/cmdr.TrN. A gettext-style i18n library typically builds this by binding the application's domain together with go-cmdr's own.

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 SignalError added in v1.17.0

type SignalError struct {
	Signal os.Signal
}

SignalError is the cancellation cause RunMain records when one of the signals selected with WithSignals arrives. Unlike the cause recorded by signal.NotifyContext, it keeps the signal itself, so commands can tell which signal ended the run without matching on error text.

SignalError matches context.Canceled with errors.Is, and its Error text is the same as the one signal.NotifyContext uses.

func (SignalError) Error added in v1.17.0

func (e SignalError) Error() string

Error returns an error message naming the received signal.

func (SignalError) Is added in v1.17.0

func (e SignalError) Is(target error) bool

Is reports whether target is context.Canceled.

func (SignalError) LocalizedError added in v1.17.0

func (e SignalError) LocalizedError(ctx context.Context) string

LocalizedError renders the message through the Translator carried by ctx. The signal name is translated as well, for the signals go-cmdr knows by name; others keep the name the operating system gives them.

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.

type Translator added in v1.14.0

type Translator = internal.Translator

Translator is the interface for internationalization similar to gettext. Implement this interface to provide translation for error messages and other user-facing strings.

type UnexpectedArgumentsError added in v1.10.0

type UnexpectedArgumentsError []string

UnexpectedArgumentsError records arguments that were not anticipated by a command.

func (UnexpectedArgumentsError) Error added in v1.10.0

func (e UnexpectedArgumentsError) Error() string

Error returns an error message describing unexpected arguments.

func (UnexpectedArgumentsError) LocalizedError added in v1.14.0

func (e UnexpectedArgumentsError) LocalizedError(ctx context.Context) string

LocalizedError renders the message through the Translator carried by ctx. The message is composed with Sprintf, so unlike plain sentinel errors it can never translate by exact text; implementing the LocalizedError interface lets RunMain's error printer render it.

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.
Package l10n ships go-cmdr's translation catalogs as data — .po files behind an embed.FS — with no dependency on any translation library.
Package l10n ships go-cmdr's translation catalogs as data — .po files behind an embed.FS — with no dependency on any translation library.
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
Package stockcmds contains stock commands that are applicable to many programs.
Package stockcmds contains stock commands that are applicable to many programs.

Jump to

Keyboard shortcuts

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