cmder

package module
v0.0.11 Latest Latest
Warning

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

Go to latest
Published: Feb 22, 2026 License: MIT Imports: 15 Imported by: 1

README

cmder

cmder is an opinionated library for building powerful command-line applications in Go.

Go Reference Go Report Card

Overview

cmder is a simple and flexible library for building command-line interfaces in Go. cmder takes a very opinionated approach to building command-line interfaces. The library will help you define, structure and execute your commands, but that's about it. cmder embraces simplicity because sometimes, less is better. The wide range of examples throughout the project should help you get started.

To define a new command, simply define a type that implements the Command interface. If you want your command to have additional behaviour like flags or subcommands, simply implement the appropriate interfaces.

Here are some highlights:

  • Bring your own types. cmder doens't force you to use special command structs. As long as you implement our narrow interfaces, you're good to go!
  • cmder is unobtrustive. Define your command and execute it. Simplicity above all else!
  • cmder is totally stateless making it super easy to unit test your commands. This isn't the case in other libraries.
  • We take great pride in our documentation. If you find anything unclear, please let us know so we can fix it.

Usage

First, include cmder in your project:

$ go get github.com/brandon1024/cmder

The easiest way to build commands is cmder.BaseCommand. For simple commands, this is the cleanest way to go. This might feel a little familiar if you're coming from Cobra.

package main

import (
	"context"
	"fmt"

	"github.com/brandon1024/cmder"
)

const HelloWorldHelpText = `hello-world - broadcast hello to the world

'hello-world' demonstrates how to build commands with the BaseCommand type.
`

const HelloWorldExamples = `
# broadcast hello to the world
hello-world from cmder
`

func run(ctx context.Context, args []string) error {
	fmt.Println("Hello World!")
	return nil
}

func main() {
	cmd := &cmder.BaseCommand{
		CommandName: "hello-world",
		CommandDocumentation: cmder.CommandDocumentation{
			Usage:       "hello-world [<args>...]",
			ShortHelp:   "Simple demonstration of cmder",
			Help:        HelloWorldHelpText,
			Examples:    HelloWorldExamples,
		},
		RunFunc:     run,
	}

	if err := cmder.Execute(context.Background(), cmd); err != nil {
		fmt.Printf("unexpected error occurred: %v", err)
	}
}

For more complex commands, you can define your own command type. By embedding cmder.BaseCommand, your command automatically implements all of the important interfaces needed to document your command, define flags, register subcommands, and so on. You can then override the default behaviour with your own.

package main

import (
	"context"
	"fmt"
	"flag"

	"github.com/brandon1024/cmder"
)

const BaseCommandExampleHelpText = `base-command - a simple example with struct embedding

'base-command' demonstrates how to build commands and subcommands with BaseCommand.
`

const BaseCommandExampleExamples = `
# broadcast hello to the world
base-command from cmder

# broadcast another message
base-command --msg 'hi bob!'
`

type BaseCommandExample struct {
	cmder.CommandDocumentation

	msg string
}

func (c *BaseCommandExample) InitializeFlags(fs *flag.FlagSet) {
	fs.StringVar(&c.msg, "m", "hello world", "message to broadcast")
	fs.StringVar(&c.msg, "msg", "hello world", "message to broadcast")
}

func (c *BaseCommandExample) Run(ctx context.Context, args []string) error {
	fmt.Printf("%s: %s\n", c.Name(), c.msg)
	return nil
}

func (c *BaseCommandExample) Name() string {
	return "base-command"
}

func main() {
	cmd := &BaseCommandExample{
		CommandDocumentation: cmder.CommandDocumentation{
			Usage:       "base-command [-m | --msg <message>] [<args>...]",
			ShortHelp:   "A simple example with struct embedding",
			Help:        BaseCommandExampleHelpText,
			Examples:    BaseCommandExampleExamples,
		},
	}

	if err := cmder.Execute(context.Background(), cmd); err != nil {
		fmt.Printf("unexpected error occurred: %v", err)
		os.Exit(1)
	}
}

If you need even more flexibility, you can instead implement the interfaces that are relevant for your command:

  • Command: All commands and subcommands must implement this interface.
  • FlagInitializer: If your command has flags, implement this interface.
  • Initializer: If your command needs some initialization, implement this interface.
  • Destroyer: If your command needs some teardown, implement this interface.

For more information, read through our package documentation on pkg.go.dev.

Development

To build the project and run tests:

$ make

License

All software components herein are subject to the terms and conditions specified in the MIT License.

Documentation

Overview

Package cmder is a simple and flexible library for building command-line interfaces in Go. cmder takes a very opinionated approach to building command-line interfaces. The library will help you define, structure and execute your commands, but that's about it. cmder embraces simplicity because sometimes, less is better. The wide range of examples throughout the project should help you get started.

To define a new command, simply define a type that implements the Command interface. If you want your command to have additional behaviour like flags or subcommands, simply implement the appropriate interfaces.

  • Bring your own types. cmder doens't force you to use special command structs. As long as you implement our narrow interfaces, you're good to go!
  • cmder is unobtrustive. Define your command and execute it. Simplicity above all else!
  • cmder is totally stateless making it super easy to unit test your commands. This isn't the case in other libraries.
  • We take great pride in our documentation. If you find anything unclear, please let us know so we can fix it.

To get started, see Command and Execute.

For POSIX/GNU flag parsing, see package getopt.

Index

Examples

Constants

View Source
const CobraUsageTemplate = `` /* 1371-byte string literal not displayed */

CobraUsageTemplate is a text template for rendering command usage information in a format similar to that of the popular github.com/spf13/cobra library.

View Source
const StdFlagUsageTemplate = `usage: {{ .Command.UsageLine }}
{{ flagusage . }}`

StdFlagUsageTemplate is a text template for rendering command usage information in a minimal format similar to that of the flag library.

Variables

View Source
var ErrEnvironmentBindFailure = errors.New("cmder: failed to update flag from environment variable")

ErrEnvironmentBindFailure is an error returned when Execute failed to update a flag value from environment variables (see WithEnvironmentBinding).

View Source
var ErrIllegalCommandConfiguration = errors.New("cmder: illegal command configuration")

ErrIllegalCommandConfiguration is an error returned when a Command provided to Execute is illegal.

View Source
var ErrShowUsage = flag.ErrHelp

ErrShowUsage instructs cmder to render usage and exit (status 2).

Functions

func Execute

func Execute(ctx context.Context, cmd Command, op ...ExecuteOption) error

Execute runs a Command.

Execution Lifecycle

When executing a command, Execute will call the Runnable Run() routine of your command. If the command also implements Initializer or Destroyer, the Initialize() or Destroy() routines will be invoked before and after calling Run().

If the command implements RootCommand and a subcommand is invoked, Execute will invoke the Initializer and Destroyer routines of parent and child commands:

  1. Root Initializer Initialize()
  2. Child Initializer Initialize()
  3. Child Runnable Run()
  4. Child Destroyer Destroy()
  5. Root Destroyer Destroy()

If a command implements RootCommand but the first argument passed to the command doesn't match a recognized child command Name(), the Run() routine will be executed.

Error Handling

Whenever a lifecycle routine (Initialize(), Run(), Destroy()) returns a non-nil error, execution is aborted immediately and the error is returned at once. For example, returning an error from Run() will prevent execution of Destroy() of the current command and any parents.

Execute may return ErrIllegalCommandConfiguration if a command is misconfigured.

Command Contexts

A context.Context derived from ctx is passed to all lifecycle routines. The context is cancelled when Execute returns. Commands should use this context to manage their resources correctly.

Execution Options

Execute accepts one or more ExecuteOption options. You can provide these options to tweak the behaviour of Execute.

Flag Initialization

If the command also implements FlagInitializer, InitializeFlags() will be invoked to register additional command-line flags. Each command/subcommand is given a unique flag.FlagSet. Help flags ('-h', '--help') are configured automatically if not defined and will instruct Execute to render command usage.

Execute parses getopt-style (GNU/POSIX) command-line arguments with the help of package getopt. To use the standard flag syntax instead, see WithNativeFlags. Flags and arguments cannot be interspersed by default. You can change this behaviour with WithInterspersedArgs.

To bind environment variables to flags, see WithEnvironmentBinding.

Usage and Help Texts

Whenever the user provides the '-h' or '--help' flag at the command line and the command doesn't register custom help flags, Execute will display command usage and return ErrShowUsage. The format of the help text can be adjusted with WithUsageTemplate. By default, usage information will be written to stderr, but this can be adjusted by setting WithUsageOutput.

If a command's Run routine returns ErrShowUsage (or an error wrapping ErrShowUsage), Execute will render help text and return the error.

Types

type BaseCommand

type BaseCommand struct {
	CommandDocumentation

	// The command name. See Name() in [Command].
	CommandName string

	// Optional function invoked by the default InitializeFlags() function.
	InitFlagsFunc func(*flag.FlagSet)

	// Optional function invoked by the default Initialize() function.
	InitFunc func(context.Context, []string) error

	// Optional function invoked by the default Run() function.
	RunFunc func(context.Context, []string) error

	// Optional function invoked by the default Destroy() function.
	DestroyFunc func(context.Context, []string) error

	// Subcommands for this command, if applicable. See [RootCommand].
	Children []Command
}

BaseCommand is an implementation of the Command, Initializer, Destroyer, RootCommand and FlagInitializer interfaces and may be embedded in your command types to reduce boilerplate.

Example

This example demonstrates the simplest usage of cmder.BaseCommand. By using BaseCommand directly, you don't need to define your own command types. This can be a nice convenience for simple commands.

package main

import (
	"archive/tar"
	"bytes"
	"context"
	"encoding/hex"
	"flag"
	"fmt"
	"io"
	"os"

	"github.com/brandon1024/cmder"
)

// This example demonstrates the simplest usage of [cmder.BaseCommand]. By using BaseCommand directly, you don't need to
// define your own command types. This can be a nice convenience for simple commands.
func main() {
	exampleSetup()

	args := []string{"-Co-", "-"}

	if err := cmder.Execute(context.Background(), untar, cmder.WithArgs(args)); err != nil {
		fmt.Printf("unexpected error occurred: %v", err)
	}
}

const UntarDesc = `
'untar' demonstrates the simplest usage of [cmder.BaseCommand], using the type to implement a simple command that reads
a tar archive and dumps inflated content.
`

const UntarExamples = `
untar example.tar
untar -Co=example.out example.tar
untar -Co- - <example.tar
`

var (
	untar = &cmder.BaseCommand{
		CommandName: "untar",
		CommandDocumentation: cmder.CommandDocumentation{
			Usage:     "untar [-o <file>] [-C] <file>",
			ShortHelp: "A simple demonstration of direct usage of BaseCommand.",
			Help:      UntarDesc,
			Examples:  UntarExamples,
		},
		RunFunc:       run,
		InitFlagsFunc: flags,
	}
)

var (
	output  string = "-"
	hexdump bool
)

var (
	in io.ReadWriter = os.Stdin
)

// Configure flags. We register two flags, '-o' and '-C'.
func flags(fs *flag.FlagSet) {
	fs.StringVar(&output, "o", output, "dump archive content to `file`")
	fs.BoolVar(&hexdump, "C", hexdump, "dump archive content in a canonical hex+ascii format")
}

// The command's run function.
func run(ctx context.Context, args []string) error {
	if len(args) != 1 {
		return cmder.ErrShowUsage
	}

	var out io.Writer = os.Stdout

	if args[0] != "-" {
		inputf, err := os.Open(args[0])
		if err != nil {
			return err
		}

		defer inputf.Close()
		in = inputf
	}

	if output != "-" {
		outputf, err := os.Create(output)
		if err != nil {
			return err
		}

		defer outputf.Close()
		out = outputf
	}

	if hexdump {
		dumper := hex.Dumper(out)
		defer dumper.Close()
		out = dumper
	}

	reader := tar.NewReader(in)
	for {
		_, err := reader.Next()
		if err == io.EOF {
			break
		}
		if err != nil {
			return err
		}

		if _, err := io.Copy(out, reader); err != nil {
			return err
		}
	}

	return nil
}

// Setup for the example.
func exampleSetup() {
	in = &bytes.Buffer{}

	tw := tar.NewWriter(in)
	defer tw.Close()

	tw.WriteHeader(&tar.Header{
		Name: "usage-example",
		Mode: 0644,
		Size: int64(len(UntarDesc)),
	})
	tw.Write([]byte(UntarDesc))
}
Output:

00000000  0a 27 75 6e 74 61 72 27  20 64 65 6d 6f 6e 73 74  |.'untar' demonst|
00000010  72 61 74 65 73 20 74 68  65 20 73 69 6d 70 6c 65  |rates the simple|
00000020  73 74 20 75 73 61 67 65  20 6f 66 20 5b 63 6d 64  |st usage of [cmd|
00000030  65 72 2e 42 61 73 65 43  6f 6d 6d 61 6e 64 5d 2c  |er.BaseCommand],|
00000040  20 75 73 69 6e 67 20 74  68 65 20 74 79 70 65 20  | using the type |
00000050  74 6f 20 69 6d 70 6c 65  6d 65 6e 74 20 61 20 73  |to implement a s|
00000060  69 6d 70 6c 65 20 63 6f  6d 6d 61 6e 64 20 74 68  |imple command th|
00000070  61 74 20 72 65 61 64 73  0a 61 20 74 61 72 20 61  |at reads.a tar a|
00000080  72 63 68 69 76 65 20 61  6e 64 20 64 75 6d 70 73  |rchive and dumps|
00000090  20 69 6e 66 6c 61 74 65  64 20 63 6f 6e 74 65 6e  | inflated conten|
000000a0  74 2e 0a                                          |t..|
Example (Embedding)

This example demonstrates an alternative usage of cmder.BaseCommand. By embedding BaseCommand into your own types, your type implements all required interfaces needed to fulfill cmder.Command. You can override the standard methods with your own implementation.

package main

import (
	"context"
	"flag"
	"fmt"
	"strconv"
	"strings"

	"github.com/brandon1024/cmder"
	"github.com/brandon1024/cmder/getopt"
)

// This example demonstrates an alternative usage of [cmder.BaseCommand]. By embedding BaseCommand into your own types,
// your type implements all required interfaces needed to fulfill [cmder.Command]. You can override the standard methods
// with your own implementation.
func main() {
	args := []string{"-m", "1.6.17-beta.0+20130313144700"}

	if err := cmder.Execute(context.Background(), semver, cmder.WithArgs(args)); err != nil {
		fmt.Printf("unexpected error occurred: %v", err)
	}
}

const SemverDesc = `
'semver' demonstrates an alternative usage of [cmder.BaseCommand]. By embedding BaseCommand into your own types, your
type implements all required interfaces needed to fulfill [cmder.Command]. You can override the standard methods with
your own implementation.

Example 'semver' manipulates a version string, bumping it up a major/minor/patch version or attaches build/pre-release
information. By default, input versoins are bumped up a patch version.
`

const SemverExamples = `
semver 0.5.3
semver --minor 1.6.17-alpha
semver --pre-release alpha --build ${BUILD_ID} 1.3.4
`

var (
	semver = &Semver{
		BaseCommand: cmder.BaseCommand{
			CommandName: "semver",
			CommandDocumentation: cmder.CommandDocumentation{
				Usage:     "semver [--major | --minor | --patch] [--pre-release <pre>] [--build <build>] <version>",
				ShortHelp: "A simple demonstration of embedding BaseCommand in custom types.",
				Help:      SemverDesc,
				Examples:  SemverExamples,
			},
		},
	}
)

type Semver struct {
	cmder.BaseCommand

	major, minor, patch bool
	pre, build          string
}

// Configure command flags. Register short aliases for all long flag options.
//
// InitializeFlags overrides the default [cmder.BaseCommand.InitializeFlags] implementation.
func (s *Semver) InitializeFlags(fs *flag.FlagSet) {
	fs.BoolVar(&s.major, "major", s.major, "bump version to the next major version")
	fs.BoolVar(&s.minor, "minor", s.minor, "bump version to the next minor version")
	fs.BoolVar(&s.patch, "patch", s.patch, "bump version to the next patch version")
	fs.StringVar(&s.pre, "pre-release", s.pre, "include pre-release information in output (e.g. alpha, x.7.z.92)")
	fs.StringVar(&s.build, "build", s.build, "include build information in output (e.g. 20130313144700, exp.sha.5114f85)")

	getopt.Alias(fs, "major", "M")
	getopt.Alias(fs, "minor", "m")
	getopt.Alias(fs, "patch", "p")
	getopt.Alias(fs, "pre-release", "x")
	getopt.Alias(fs, "build", "b")
}

// The command's run function.
//
// Run overrides the default [cmder.BaseCommand.Run] implementation.
func (s *Semver) Run(ctx context.Context, args []string) error {
	if len(args) != 1 {
		return cmder.ErrShowUsage
	}

	version, _, _ := strings.Cut(args[0], "+")
	version, pre, _ := strings.Cut(version, "-")

	var (
		parts []string
		level = 2
	)

	if s.minor {
		level = 1
	}
	if s.major {
		level = 0
	}

	for i, v := range strings.Split(version, ".") {
		num, err := strconv.Atoi(v)
		if err != nil {
			return fmt.Errorf("semver: invalid input: %s", args[0])
		}
		if i == level {
			num++
		}
		if i > level {
			num = 0
		}

		parts = append(parts, strconv.Itoa(num))
	}

	version = strings.Join(parts, ".")

	if s.pre != "" {
		version += "-" + s.pre
	} else if pre != "" {
		version += "-" + pre
	}

	if s.build != "" {
		version += "+" + s.build
	}

	fmt.Println(version)

	return nil
}
Output:

1.7.0-beta.0

func (BaseCommand) Destroy

func (c BaseCommand) Destroy(ctx context.Context, args []string) error

Destroy runs BaseCommand DestroyFunc, if not nil.

See Destroyer.

func (BaseCommand) Initialize

func (c BaseCommand) Initialize(ctx context.Context, args []string) error

Initialize runs BaseCommand InitFunc, if not nil.

See Initializer.

func (BaseCommand) InitializeFlags

func (c BaseCommand) InitializeFlags(fs *flag.FlagSet)

InitializeFlags runs BaseCommand InitFlagsFunc, if not nil.

See FlagInitializer.

func (BaseCommand) Name

func (c BaseCommand) Name() string

Name returns BaseCommand CommandName.

See Command.

func (BaseCommand) Run

func (c BaseCommand) Run(ctx context.Context, args []string) error

Run runs BaseCommand RunFunc, if not nil.

See Runnable.

func (BaseCommand) Subcommands

func (c BaseCommand) Subcommands() []Command

Subcommands returns BaseCommand Children.

See RootCommand.

type Command

type Command interface {
	// All commands are [Runnable] and implement a Run routine.
	Runnable

	// Great tools come with great documentation. All commands need to provide documentation, which is used when
	// rendering usage and help texts.
	Documented

	// Name returns the name of this command or subcommand.
	Name() string
}

Command is the fundamental interface implemented by types that are runnable commands or subcommands. Commands can be executed with Execute.

Concrete types can implement additional interfaces to configure additional behaviour, like setup/teardown routines, subcommands, command-line flags, and other behaviour:

Example
package main

import (
	"context"
	"fmt"

	"github.com/brandon1024/cmder"
)

func main() {
	cmd := &HelloWorldCommand{}

	args := []string{"from", "cmder"}

	if err := cmder.Execute(context.Background(), cmd, cmder.WithArgs(args)); err != nil {
		fmt.Printf("unexpected error occurred: %v", err)
	}
}

const HelloWorldCommandUsageLine = `hello-world [<args>...]`

const HelloWorldCommandShortHelpText = `Simple demonstration of cmder`

const HelloWorldCommandHelpText = `
'hello-world' demonstrates the simplest usage of cmder. This example defines a single command 'hello-world' that
implements the Runnable and Documented interfaces.
`

const HelloWorldCommandExamples = `
# broadcast hello to the world
hello-world from cmder
`

type HelloWorldCommand struct{}

func (c *HelloWorldCommand) Name() string {
	return "hello-world"
}

func (c *HelloWorldCommand) Run(ctx context.Context, args []string) error {
	fmt.Printf("%s: %v\n", c.Name(), args)
	return nil
}

func (c *HelloWorldCommand) UsageLine() string {
	return HelloWorldCommandUsageLine
}

func (c *HelloWorldCommand) ShortHelpText() string {
	return HelloWorldCommandShortHelpText
}

func (c *HelloWorldCommand) HelpText() string {
	return HelloWorldCommandHelpText
}

func (c *HelloWorldCommand) main() string {
	return HelloWorldCommandExamples
}
Output:

hello-world: [from cmder]

type CommandDocumentation added in v0.0.9

type CommandDocumentation struct {
	// The usage line. See UsageLine() in [Documented].
	Usage string

	// The short help line. See ShortHelpText() in [Documented].
	ShortHelp string

	// Documentation for your command. See HelpText() in [Documented].
	Help string

	// Usage examples for your command. See ExampleText() in [Documented].
	Examples string

	// Whether this command is hidden in help and usage texts. See Hidden() in [HiddenCommand].
	IsHidden bool
}

CommandDocumentation implements Documented and can be embdded in command types to reduce boilerplate.

func (CommandDocumentation) ExampleText added in v0.0.9

func (d CommandDocumentation) ExampleText() string

ExampleText returns CommandDocumentation Examples.

See Documented.

func (CommandDocumentation) HelpText added in v0.0.9

func (d CommandDocumentation) HelpText() string

HelpText returns CommandDocumentation Help.

See Documented.

func (CommandDocumentation) Hidden added in v0.0.9

func (d CommandDocumentation) Hidden() bool

Hidden returns CommandDocumentation Hidden.

See HiddenCommand.

func (CommandDocumentation) ShortHelpText added in v0.0.9

func (d CommandDocumentation) ShortHelpText() string

ShortHelpText returns CommandDocumentation ShortHelp.

See Documented.

func (CommandDocumentation) UsageLine added in v0.0.9

func (d CommandDocumentation) UsageLine() string

UsageLine returns CommandDocumentation Usage.

See Documented.

type Destroyer added in v0.0.10

type Destroyer interface {
	// Destroy carries out any teardown needed for this [Command]. Errors returned by Destroy will abort execution of
	// the command lifecycle (Destroy of this command and parent command(s)).
	Destroy(context.Context, []string) error
}

Destroyer may be implemented by commands that need to do some work after the Runnable Run() routine is invoked.

See Execute for more details on the lifecycle of command execution.

Example
package main

import (
	"context"
	"fmt"

	"github.com/brandon1024/cmder"
)

func main() {
	args := []string{}

	cmd := &LifecycleCommand{}

	if err := cmder.Execute(context.Background(), cmd, cmder.WithArgs(args)); err != nil {
		fmt.Printf("unexpected error occurred: %v", err)
	}
}

const LifecycleCommandUsageLine = `lifecycle [<args>...]`

const LifecycleCommandShortHelpText = `Example command with lifecycle routines`

const LifecycleCommandHelpText = `
'lifecycle' demonstrates a command that implements the Initializer and Destroyer interfaces, defining initialization and
destroy routines.
`

const LifecycleCommandExamples = `
# demonstrate initialization and teardown
lifecycle
`

type LifecycleCommand struct{}

func (c *LifecycleCommand) Name() string {
	return "lifecycle"
}

func (c *LifecycleCommand) Initialize(ctx context.Context, args []string) error {
	fmt.Println("lifecycle: initializing")
	return nil
}

func (c *LifecycleCommand) Run(ctx context.Context, args []string) error {
	fmt.Println("lifecycle: running")
	return nil
}

func (c *LifecycleCommand) Destroy(ctx context.Context, args []string) error {
	fmt.Println("lifecycle: shutting down")
	return nil
}

func (c *LifecycleCommand) UsageLine() string {
	return LifecycleCommandUsageLine
}

func (c *LifecycleCommand) ShortHelpText() string {
	return LifecycleCommandShortHelpText
}

func (c *LifecycleCommand) HelpText() string {
	return LifecycleCommandHelpText
}

func (c *LifecycleCommand) ExampleText() string {
	return LifecycleCommandExamples
}
Output:

lifecycle: initializing
lifecycle: running
lifecycle: shutting down

type Documented

type Documented interface {
	// UsageLine returns the usage line for your command. This is akin to the "SYNOPSIS" section you would typically
	// find in a man page. Generally, usage lines have a well accepted format:
	//
	//	- [ ] identifies an optional argument or flag. Arguments that are not enclosed in brackets are required.
	//	- ... identifies arguments or flags that can be provided more than once.
	//	-  |  identifies mutually exclusive arguments or flags.
	//	- ( ) identifies groups of flags or arguments that are required together.
	//	- < > identifies argument(s) or flag(s).
	//
	// Here are a few examples:
	//
	//	git add [<options>] [--] <pathspec>...
	//	kubectl get [(-o|--output=)json|yaml|wide] (TYPE[.VERSION][.GROUP] [NAME | -l label] | TYPE[.VERSION][.GROUP]/NAME ...) [flags] [options]
	//	crane index filter [flags]
	UsageLine() string

	// ShortHelpText returns a short-and-sweet one-line description of your command. This is akin to the "NAME" section
	// you would typically find in a man page. This is mainly used to summarize available subcommands.
	//
	// Here are a few examples:
	//
	//	get image registry and namespace information
	//	download installation files for an application version
	//	display the contents of a file in a terminal
	ShortHelpText() string

	// HelpText returns longer usage and help information for your users about this subcommand. Here you can describe
	// the behaviour of your command, summarize usage of certain flags and arguments and provide hints on where to find
	// additional information. This is akin to the "DESCRIPTION" section you would typically find in a man page.
	//
	// For a better viewing experience in terminals, consider maintaining consistent line length limits (120 is a good
	// target).
	HelpText() string

	// ExampleText returns motivating usage examples for your command.
	ExampleText() string
}

Documented is implemented by all commands and provides help and usage information for your users.

type ExecuteOption

type ExecuteOption func(*ExecuteOptions)

ExecuteOption is a single option passed to Execute.

func WithArgs

func WithArgs(args []string) ExecuteOption

WithArgs configures Execute to run with the arguments given. By default, Execute will execute with arguments from os.Args.

func WithEnvironmentBinding added in v0.0.5

func WithEnvironmentBinding() ExecuteOption

WithEnvironmentBinding configures Execute to set flag values from the enclosing environment. Environment variables are mapped to flags as follows:

COMMAND_FLAGNAME
COMMAND_SUBCOMMAND_FLAGNAME
COMMAND_SUBCOMMAND_SUBCOMMAND_FLAGNAME

Command and flag names are all uppercased. Special characters are removed. Flags explicitly set at the command line take precedence over environment variables.

git log --format=oneline   ->   GIT_LOG_FORMAT=oneline
git log --no-abbrev-commit ->   GIT_LOG_NOABBREVCOMMIT=true
Example
package main

import (
	"context"
	"flag"
	"fmt"
	"os"

	"github.com/brandon1024/cmder"
)

func main() {
	_ = os.Setenv("BINDENV_SHOW_FORMAT", "overidden-by-flag")
	_ = os.Setenv("BINDENV_SHOW_PAGECOUNT", "20")

	args := []string{"show", "--format=pretty"}

	ops := []cmder.ExecuteOption{
		cmder.WithArgs(args),
		cmder.WithEnvironmentBinding(),
	}

	if err := cmder.Execute(context.Background(), GetCommand(), ops...); err != nil {
		fmt.Printf("unexpected error occurred: %v", err)
	}
}

const BindEnvHelpText = `
'bind-env' desmonstrates how cmder can be configured to bind environment variables to flags. Explicit command-line
arguments always take precedence over environment variables.
`

const BindEnvExamples = `
# print all default flag values
bind-env show
> default 10

# print flag values from environment
BINDENV_SHOW_FORMAT=pretty BINDENV_SHOW_PAGECOUNT=20 bind-env show --page-count=15
> pretty 15
`

func GetCommand() *cmder.BaseCommand {
	return &cmder.BaseCommand{
		CommandName: "bind-env",
		CommandDocumentation: cmder.CommandDocumentation{
			Usage:     "bind-env [subcommand] [flags]",
			ShortHelp: "Simple demonstration of binding environment variables to command flags.",
			Help:      BindEnvHelpText,
			Examples:  BindEnvExamples,
		},
		Children: []cmder.Command{GetShowCommand()},
	}
}

func GetShowCommand() *cmder.BaseCommand {
	return &cmder.BaseCommand{
		CommandName: "show",
		CommandDocumentation: cmder.CommandDocumentation{
			Usage:     `show [flags]`,
			ShortHelp: `Show flag values`,
			Help:      `'show' dumps flag values to stdout.`,
			Examples:  BindEnvExamples,
		},
		InitFlagsFunc: showFlags,
		RunFunc:       show,
	}
}

var (
	format string = "default"
	count  uint   = 10
)

func showFlags(fs *flag.FlagSet) {
	fs.StringVar(&format, "format", format, "output format (default, pretty)")
	fs.UintVar(&count, "page-count", count, "number of pages")
}

func show(ctx context.Context, args []string) error {
	switch format {
	case "default":
		fmt.Printf("%v %v\n", format, count)
	case "pretty":
		fmt.Printf("format: %v\npage-count: %v\n", format, count)
	default:
		return fmt.Errorf("illegal format: %s", format)
	}

	return nil
}
Output:

format: pretty
page-count: 20

func WithInterspersedArgs added in v0.0.7

func WithInterspersedArgs() ExecuteOption

WithInterspersedArgs enables interspersed args parsing, allowing command-line arguments and flags to be mixed. When interspersed arg parsing is enabled, the following is permitted:

git log origin/main -p

When interspersed arg parsing is disabled, flags must always come before args:

git log -p origin/main
Example
package main

import (
	"context"
	"crypto/md5"
	"crypto/sha1"
	"crypto/sha256"
	"flag"
	"fmt"
	"hash"

	"github.com/brandon1024/cmder"
)

func main() {
	args := []string{"string-1", "-a", "md5", "string-2", "-c10", "string-3"}

	ops := []cmder.ExecuteOption{
		cmder.WithArgs(args),
		cmder.WithInterspersedArgs(),
	}

	if err := cmder.Execute(context.Background(), hasher, ops...); err != nil {
		fmt.Printf("unexpected error occurred: %v", err)
	}
}

const HashDesc = `
'hash' desmonstrates how cmder can be configured to parse args with interspersed args and flags. The command generates
and prints a hash of the concatenated command args.
`

const HashExamples = `
# with interspersed args
hash string-1 -a md5 string-2 -c 10 string-3

# without interspersed args
hash -a md5 -c 10 string-1 string-2 string-3
`

var (
	hasher = &Hasher{
		BaseCommand: cmder.BaseCommand{
			CommandName: "hash",
			CommandDocumentation: cmder.CommandDocumentation{
				Usage:     "hash [<str>...] [<flags>...]",
				ShortHelp: "Simple demonstration of interspersed arg parsing.",
				Help:      HashDesc,
				Examples:  HashExamples,
			},
		},
		algo:   "sha256",
		rounds: 1,
	}
)

type Hasher struct {
	cmder.BaseCommand

	algo   string
	rounds uint
}

func (h *Hasher) InitializeFlags(fs *flag.FlagSet) {
	fs.StringVar(&h.algo, "algo", h.algo, "select hashing algorithm (md5, sha1, sha256)")
	fs.StringVar(&h.algo, "a", h.algo, "select hashing algorithm (md5, sha1, sha256)")
	fs.UintVar(&h.rounds, "rounds", h.rounds, "number of hashing rounds")
	fs.UintVar(&h.rounds, "c", h.rounds, "number of hashing rounds")
}

func (h *Hasher) Run(ctx context.Context, args []string) error {
	algos := map[string]hash.Hash{
		"md5":    md5.New(),
		"sha1":   sha1.New(),
		"sha256": sha256.New(),
	}

	alg, ok := algos[h.algo]
	if !ok {
		return fmt.Errorf("no such algorithm: %s", h.algo)
	}

	for range h.rounds {
		for _, s := range args {
			alg.Write([]byte(s))
		}
	}

	fmt.Printf("%x\n", alg.Sum(nil))

	return nil
}
Output:

0559406fc9a7b5704464c303ebbba64c

func WithNativeFlags added in v0.0.5

func WithNativeFlags() ExecuteOption

WithNativeFlags configures Execute to parse flags using the standard flag package instead of the default getopt package.

func WithPrefixedEnvironmentBinding added in v0.0.5

func WithPrefixedEnvironmentBinding(prefix string) ExecuteOption

WithPrefixedEnvironmentBinding is like WithEnvironmentBinding but with a variable name prefix.

<PREFIX>COMMAND_FLAGNAME
<PREFIX>COMMAND_SUBCOMMAND_FLAGNAME
<PREFIX>COMMAND_SUBCOMMAND_SUBCOMMAND_FLAGNAME

func WithUsageOutput added in v0.0.10

func WithUsageOutput(output io.Writer) ExecuteOption

WithUsageOutput is used to provide an alternate io.Writer to write rendered command usage help text. By default, os.Stderr is used.

func WithUsageTemplate added in v0.0.10

func WithUsageTemplate(tmpl string) ExecuteOption

WithUsageTemplate is used to provide an alternate template for rendering command usage help text. The template is rendered by the standard text/template package. This is particularly useful for applications which prefer to format command usage information differently than the cmder defaults.

By default, the CobraUsageTemplate template is used.

type ExecuteOptions

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

ExecuteOptions configure the behaviour of Execute.

type FlagInitializer

type FlagInitializer interface {
	InitializeFlags(*flag.FlagSet)
}

FlagInitializer is an interface implemented by commands that need to register flags.

InitializeFlags will be invoked during Execute, prior to any lifecycle routines. You can use this to register flags for your command.

Help flags '-h' and '--help' are registered automatically and will instruct Execute to render usage information to the [UsageOutputWriter].

Example

This example demonstrates how to register command flags. The flags and documentation were borrowed from 'kubectl apply' to showcase what a real-world example woud look like.

package main

import (
	"context"
	"flag"
	"fmt"
	"time"

	"github.com/brandon1024/cmder"
	"github.com/brandon1024/cmder/getopt"
)

// This example demonstrates how to register command flags. The flags and documentation were borrowed from 'kubectl
// apply' to showcase what a real-world example woud look like.
func main() {
	var cmd KubectlApply

	args := []string{"-h"}

	if err := cmder.Execute(context.Background(), &cmd, cmder.WithArgs(args)); err != nil {
		fmt.Printf("unexpected error occurred: %v", err)
	}
}

const KubectlApplyDesc = `
Apply a configuration to a resource by file name or stdin. The resource name must be specified. This resource will be
created if it doesn't exist yet. To use 'apply', always create the resource initially with either 'apply' or
'create --save-config'.

JSON and YAML formats are accepted.
`

const KubectlApplyExamples = `
# Apply the configuration in pod.json to a pod
kubectl apply -f ./pod.json

# Apply resources from a directory containing kustomization.yaml - e.g. dir/kustomization.yaml
kubectl apply -k dir/

# Apply the JSON passed into stdin to a pod
cat pod.json | kubectl apply -f -

# Apply the configuration from all files that end with '.json'
kubectl apply -f '*.json'

# Note: --prune is still in Alpha
# Apply the configuration in manifest.yaml that matches label app=nginx and delete all other resources that are not in the file and match label app=nginx
kubectl apply --prune -f manifest.yaml -l app=nginx

# Apply the configuration in manifest.yaml and delete all the other config maps that are not in the file
kubectl apply --prune -f manifest.yaml --all --prune-allowlist=core/v1/ConfigMap
`

type KubectlApply struct {
	all                      bool
	allowMissingTemplateKeys bool
	cascade                  string
	dryRun                   string
	fieldManager             string
	filename                 getopt.StringsVar
	force                    bool
	forceConflicts           bool
	gracePeriod              int
	kustomize                string
	openapiPatch             bool
	output                   string
	overwrite                bool
	prune                    bool
	pruneAllowlist           getopt.StringsVar
	recursive                bool
	selector                 string
	serverSide               bool
	showManagedFields        bool
	subresource              string
	template                 string
	timeout                  time.Duration
	validate                 string
	wait                     bool
}

func (a *KubectlApply) InitializeFlags(fs *flag.FlagSet) {
	fs.BoolVar(&a.all, "all", false,
		"Select all resources in the namespace of the specified resource types.")
	fs.BoolVar(&a.allowMissingTemplateKeys, "allow-missing-template-keys", true,
		"If true, ignore any errors in templates when a field or map key is missing in the template. Only applies to golang and jsonpath output formats.")
	fs.StringVar(&a.cascade, "cascade", "background",
		"Must be \"background\", \"orphan\", or \"foreground\". Selects the deletion cascading strategy for the dependents (e.g. Pods created by a ReplicationController). Defaults to background.")
	fs.StringVar(&a.dryRun, "dry-run", "none",
		"Must be \"none\", \"server\", or \"client\". If client strategy, only print the object that would be sent, without sending it. If server strategy, submit server-side request without persisting the resource.")
	fs.StringVar(&a.fieldManager, "field-manager", "kubectl-client-side-apply",
		"Name of the manager used to track field ownership.")
	fs.Var(&a.filename, "filename",
		"The files that contain the configurations to apply.")
	fs.Var(&a.filename, "f",
		"The files that contain the configurations to apply.")
	fs.BoolVar(&a.force, "force", false,
		"If true, immediately remove resources from API and bypass graceful deletion. Note that immediate deletion of some resources may result in inconsistency or data loss and requires confirmation.")
	fs.BoolVar(&a.forceConflicts, "force-conflicts", false,
		"If true, server-side apply will force the changes against conflicts.")
	fs.IntVar(&a.gracePeriod, "grace-period", -1,
		"Period of time in seconds given to the resource to terminate gracefully. Ignored if negative. Set to 1 for immediate shutdown. Can only be set to 0 when --force is true (force deletion).")
	fs.StringVar(&a.kustomize, "kustomize", "",
		"Process a kustomization directory. This flag can't be used together with -f or -R.")
	fs.BoolVar(&a.openapiPatch, "openapi-patch", true,
		"If true, use openapi to calculate diff when the openapi presents and the resource can be found in the openapi spec. Otherwise, fall back to use baked-in types.")
	fs.StringVar(&a.output, "output", "",
		"Output format. One of: (json, yaml, kyaml, name, go-template, go-template-file, template, templatefile, jsonpath, jsonpath-as-json, jsonpath-file).")
	fs.StringVar(&a.output, "o", "",
		"Output format. One of: (json, yaml, kyaml, name, go-template, go-template-file, template, templatefile, jsonpath, jsonpath-as-json, jsonpath-file).")
	fs.BoolVar(&a.overwrite, "overwrite", true,
		"Automatically resolve conflicts between the modified and live configuration by using values from the modified configuration.")
	fs.BoolVar(&a.prune, "prune", false,
		"Automatically delete resource objects, that do not appear in the configs and are created by either apply or create --save-config. Should be used with either -l or --all.")
	fs.Var(&a.pruneAllowlist, "prune-allowlist",
		"Overwrite the default allowlist with <group/version/kind> for --prune.")
	fs.BoolVar(&a.recursive, "recursive", false,
		"Process the directory used in -f, --filename recursively. Useful when you want to manage related manifests organized within the same directory.")
	fs.BoolVar(&a.recursive, "R", false,
		"Process the directory used in -f, --filename recursively. Useful when you want to manage related manifests organized within the same directory.")
	fs.StringVar(&a.selector, "selector", "",
		"Selector (label query) to filter on, supports '=', '==', '!=', 'in', 'notin'.(e.g. -l key1=value1,key2=value2,key3 in (value3)). Matching objects must satisfy all of the specified label constraints.")
	fs.StringVar(&a.selector, "l", "",
		"Selector (label query) to filter on, supports '=', '==', '!=', 'in', 'notin'.(e.g. -l key1=value1,key2=value2,key3 in (value3)). Matching objects must satisfy all of the specified label constraints.")
	fs.BoolVar(&a.serverSide, "server-side", false,
		"If true, apply runs in the server instead of the client.")
	fs.BoolVar(&a.showManagedFields, "show-managed-fields", false,
		"If true, keep the managedFields when printing objects in JSON or YAML format.")
	fs.StringVar(&a.subresource, "subresource", "",
		"If specified, apply will operate on the subresource of the requested object. Only allowed when using --server-side.")
	fs.StringVar(&a.template, "template", "",
		"Template string or path to template file to use when -o=go-template, -o=go-template-file. The template format is golang templates [http://golang.org/pkg/text/template/#pkg-overview].")
	fs.DurationVar(&a.timeout, "timeout", time.Duration(0),
		"The length of time to wait before giving up on a delete, zero means determine a timeout from the size of the object.")
	fs.StringVar(&a.validate, "validate", "strict",
		"Must be one of: strict (or true), warn, ignore (or false). \"true\" or \"strict\" will use a schema to validate the input and fail the request if invalid. It will perform server side validation if ServerSideFieldValidation is enabled on the api-server, but will fall back to less reliable client-side validation if not. \"warn\" will warn about unknown or duplicate fields without blocking the request if server-side field validation is enabled on the API server, and behave as \"ignore\" otherwise. \"false\" or \"ignore\" will not perform any schema validation, silently dropping any unknown or duplicate fields.")
	fs.BoolVar(&a.wait, "wait", false, "If true, wait for resources to be gone before returning. This waits for finalizers.")
}

func (a *KubectlApply) Run(ctx context.Context, args []string) error {
	// left as an exercise for the reader...
	return nil
}

func (a *KubectlApply) Name() string {
	return "apply"
}

func (a *KubectlApply) UsageLine() string {
	return "kubectl apply (-f FILENAME | -k DIRECTORY)"
}

func (a *KubectlApply) ShortHelpText() string {
	return "Apply a configuration change to a resource from a file or stdin."
}

func (a *KubectlApply) HelpText() string {
	return KubectlApplyDesc
}

func (a *KubectlApply) main() string {
	return KubectlApplyExamples
}

type HiddenCommand added in v0.0.8

type HiddenCommand interface {
	// Hidden returns a flag indicating whether to mark this command as hidden, preventing it from being rendered in
	// help output.
	Hidden() bool
}

HiddenCommand is implemented by commands which are not user facing. Hidden commands are not displayed in help texts.

type Initializer added in v0.0.10

type Initializer interface {
	// Initialize carries out any initialization needed for this [Command]. Errors returned by Initialize will abort
	// execution of the command lifecycle (Run()/Destroy() of this command and parent command(s)).
	Initialize(context.Context, []string) error
}

Initializer may be implemented by commands that need to do some work before the Runnable Run() routine is invoked.

See Execute for more details on the lifecycle of command execution.

Example
package main

import (
	"context"
	"fmt"

	"github.com/brandon1024/cmder"
)

func main() {
	args := []string{}

	cmd := &LifecycleCommand{}

	if err := cmder.Execute(context.Background(), cmd, cmder.WithArgs(args)); err != nil {
		fmt.Printf("unexpected error occurred: %v", err)
	}
}

const LifecycleCommandUsageLine = `lifecycle [<args>...]`

const LifecycleCommandShortHelpText = `Example command with lifecycle routines`

const LifecycleCommandHelpText = `
'lifecycle' demonstrates a command that implements the Initializer and Destroyer interfaces, defining initialization and
destroy routines.
`

const LifecycleCommandExamples = `
# demonstrate initialization and teardown
lifecycle
`

type LifecycleCommand struct{}

func (c *LifecycleCommand) Name() string {
	return "lifecycle"
}

func (c *LifecycleCommand) Initialize(ctx context.Context, args []string) error {
	fmt.Println("lifecycle: initializing")
	return nil
}

func (c *LifecycleCommand) Run(ctx context.Context, args []string) error {
	fmt.Println("lifecycle: running")
	return nil
}

func (c *LifecycleCommand) Destroy(ctx context.Context, args []string) error {
	fmt.Println("lifecycle: shutting down")
	return nil
}

func (c *LifecycleCommand) UsageLine() string {
	return LifecycleCommandUsageLine
}

func (c *LifecycleCommand) ShortHelpText() string {
	return LifecycleCommandShortHelpText
}

func (c *LifecycleCommand) HelpText() string {
	return LifecycleCommandHelpText
}

func (c *LifecycleCommand) ExampleText() string {
	return LifecycleCommandExamples
}
Output:

lifecycle: initializing
lifecycle: running
lifecycle: shutting down

type RootCommand

type RootCommand interface {
	// Subcommands returns a slice of subcommands of this RootCommand. May return nil or an empty slice to treat this
	// command as a leaf command.
	Subcommands() []Command
}

RootCommand may be implemented by commands that have subcommands.

Example
package main

import (
	"context"
	"fmt"

	"github.com/brandon1024/cmder"
)

func main() {
	cmd := &ParentCommand{
		subcommands: []cmder.Command{&ChildCommand{}},
	}

	args := []string{"child", "hello-world"}
	if err := cmder.Execute(context.Background(), cmd, cmder.WithArgs(args)); err != nil {
		fmt.Printf("unexpected error occurred: %v", err)
	}
}

// === PARENT COMMAND ===

const ParentCommandUsageLine = `parent [<subcommand>] [<args>...]`

const ParentCommandShortHelpText = `Example of parent command`

const ParentCommandHelpText = `
'parent' demonstrates an example of a command with subcommands. When executed without any arguments, the parent's Run
routine is executed, but if the child subcommand is provided the child subcommand Run routine will be exeuted instead.

The parent implements RootCommand indicating that it is a root command with runnable subcommands. The child does not
implement RootCommand, indicating it is a leaf command.

In this example, the parent and child commands implement RunnableLifecycle, and their respective init/destroy routines
are invoked in this order:

  1. parent Initialize
  2. child Initialize
  3. child Run
  4. child Destroy
  5. parent Destroy
`

const ParentCommandExamples = `
# run the parent 'Run' routine
parent

# run the child 'Run' routine
parent child

# run with some additional args
parent hello-world
parent child hello-world
`

type ParentCommand struct {
	subcommands []cmder.Command
}

func (c *ParentCommand) Name() string {
	return "parent"
}

func (c *ParentCommand) Initialize(ctx context.Context, args []string) error {
	fmt.Printf("%s: init %v\n", c.Name(), args)
	return nil
}

func (c *ParentCommand) Run(ctx context.Context, args []string) error {
	fmt.Printf("%s: run %v\n", c.Name(), args)
	return nil
}

func (c *ParentCommand) Destroy(ctx context.Context, args []string) error {
	fmt.Printf("%s: destroy %v\n", c.Name(), args)
	return nil
}

func (c *ParentCommand) UsageLine() string {
	return ParentCommandUsageLine
}

func (c *ParentCommand) ShortHelpText() string {
	return ParentCommandShortHelpText
}

func (c *ParentCommand) HelpText() string {
	return ParentCommandHelpText
}

func (c *ParentCommand) main() string {
	return ParentCommandExamples
}

func (c *ParentCommand) Subcommands() []cmder.Command {
	return c.subcommands
}

// === CHILD COMMAND ===

const ChildCommandUsageLine = `child [<args>...]`

const ChildCommandShortHelpText = `Example of child command`

const ChildCommandHelpText = `
'child' is the subcommand of 'parent'.
`

const ChildCommandExamples = `
# run the child 'Run' routine
parent child

# run with some additional args
parent child hello-world
`

type ChildCommand struct{}

func (c *ChildCommand) Name() string {
	return "child"
}

func (c *ChildCommand) Initialize(ctx context.Context, args []string) error {
	fmt.Printf("%s: init %v\n", c.Name(), args)
	return nil
}

func (c *ChildCommand) Run(ctx context.Context, args []string) error {
	fmt.Printf("%s: run %v\n", c.Name(), args)
	return nil
}

func (c *ChildCommand) Destroy(ctx context.Context, args []string) error {
	fmt.Printf("%s: destroy %v\n", c.Name(), args)
	return nil
}

func (c *ChildCommand) UsageLine() string {
	return ChildCommandUsageLine
}

func (c *ChildCommand) ShortHelpText() string {
	return ChildCommandShortHelpText
}

func (c *ChildCommand) HelpText() string {
	return ChildCommandHelpText
}

func (c *ChildCommand) main() string {
	return ChildCommandExamples
}
Output:

parent: init [child hello-world]
child: init [hello-world]
child: run [hello-world]
child: destroy [hello-world]
parent: destroy [child hello-world]

type Runnable

type Runnable interface {
	// Run is the main body of your command executed by [Execute].
	//
	// The given [context.Context] is derived from the context provided to Execute() and is cancelled when Execute()
	// returns. Use this context to cleanup resources.
	//
	// The second argument is the list of command-line arguments and switches that remain after parsing flags.
	Run(context.Context, []string) error
}

Runnable is a fundamental interface implemented by commands. The Run routine is what carries out the work of your command.

Concrete types can also implement Initializer or Destroyer to carry out any initialization and teardown necessary for your Run() routine.

Directories

Path Synopsis
examples
http command
Package getopt offers utilities for parsing getopt-style command-line options (flags).
Package getopt offers utilities for parsing getopt-style command-line options (flags).

Jump to

Keyboard shortcuts

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