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 ¶
- Constants
- Variables
- func Execute(ctx context.Context, cmd Command, op ...ExecuteOption) error
- type BaseCommand
- func (c BaseCommand) Destroy(ctx context.Context, args []string) error
- func (c BaseCommand) Initialize(ctx context.Context, args []string) error
- func (c BaseCommand) InitializeFlags(fs *flag.FlagSet)
- func (c BaseCommand) Name() string
- func (c BaseCommand) Run(ctx context.Context, args []string) error
- func (c BaseCommand) Subcommands() []Command
- type Command
- type CommandDocumentation
- type Destroyer
- type Documented
- type ExecuteOption
- func WithArgs(args []string) ExecuteOption
- func WithEnvironmentBinding() ExecuteOption
- func WithInterspersedArgs() ExecuteOption
- func WithNativeFlags() ExecuteOption
- func WithPrefixedEnvironmentBinding(prefix string) ExecuteOption
- func WithUsageOutput(output io.Writer) ExecuteOption
- func WithUsageTemplate(tmpl string) ExecuteOption
- type ExecuteOptions
- type FlagInitializer
- type HiddenCommand
- type Initializer
- type RootCommand
- type Runnable
Examples ¶
Constants ¶
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.
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 ¶
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).
var ErrIllegalCommandConfiguration = errors.New("cmder: illegal command configuration")
ErrIllegalCommandConfiguration is an error returned when a Command provided to Execute is illegal.
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:
- Root Initializer Initialize()
- Child Initializer Initialize()
- Child Runnable Run()
- Child Destroyer Destroy()
- 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:
- If you want to configure setup and teardown routines for a command, see Initializer and Destroyer.
- If your command has subcommands, see RootCommand.
- If your command has command-life flags and switches, see FlagInitializer.
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 ¶
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.