cmder

package module
v0.0.3 Latest Latest
Warning

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

Go to latest
Published: Dec 27, 2025 License: MIT Imports: 11 Imported by: 0

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. If you're coming from Cobra and have used it for any length of time, you have surely had your fair share of difficulties with the library. cmder will feel quite a bit more comfortable and easy to use, and the wide range of examples throughout the project should help you get started.

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.

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.

cmder also offers a flag package which is a drop-in replacement for the standard library package of the same name for parsing POSIX/GNU style flags.

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",
		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, etc.

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.BaseCommand

	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 main() {
	cmd := &BaseCommandExample{
		BaseCommand: cmder.BaseCommand{
			CommandName: "base-command",
			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.
  • RunnableLifecycle: If your command needs some initialization or teardown, implement this interface.
  • FlagInitializer: If your command has flags, implement this interface.
package main

import (
	"context"
	"fmt"
	"flag"

	"github.com/brandon1024/cmder"
)

const LifecycleCommandUsageLine = `lifecycle [-m <msg>] [<args>...]`

const LifecycleCommandShortHelpText = `Example command with lifecycle routines`

const LifecycleCommandHelpText = `
'lifecycle' demonstrates a command that implements the RunnableLifecycle interface, defining initialization and
destroy routines.
`

const LifecycleCommandExamples = `
# demonstrate initialization and teardown
lifecycle
`

type LifecycleCommand struct{
	msg string
}

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

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 *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.Printf("lifecycle: %s\n", c.msg)
	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
}

func (c *LifecycleCommand) Hidden() bool {
	return false
}

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

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

cmder is a simple and flexible library for building command-line interfaces in Go. If you're coming from Cobra and have used it for any length of time, you have surely had your fair share of difficulties with the library. cmder will feel quite a bit more comfortable and easy to use, and the wide range of examples throughout the project should help you get started.

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.

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 flag.

Index

Examples

Constants

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

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 . }}`

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

Variables

View Source
var (
	// Returned when a [Command] provided to [Execute] is illegal.
	ErrIllegalCommandConfiguration = errors.New("cmder: illegal command configuration")

	// Returned when an [ExecuteOption] provided to [Execute] is illegal.
	ErrIllegalExecuteOptions = errors.New("cmder: illegal command execution option")
)
View Source
var UsageOutputWriter io.Writer = os.Stderr

The default writer for command usage information. Standard error is recommended, but you can override this if needed (particularly useful in tests).

View Source
var UsageTemplate = CobraUsageTemplate

The text template for rendering command usage information.

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 RunnableLifecycle, the RunnableLifecycle Initialize() and Destroy() routines will be invoked before and after calling Run().

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

  1. Root RunnableLifecycle Initialize()
  2. Child RunnableLifecycle Initialize()
  3. Child Runnable Run()
  4. Child RunnableLifecycle Destroy()
  5. Root RunnableLifecycle 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 or ErrIllegalExecuteOptions if a command is misconfigured or options are invalid.

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 and must not be set by the application.

Usage and Help Texts

Whenever the user provides the '-h' or '--help' flag at the command line, Execute will display command usage and exit. The format of the help text can be adjusted by configuring UsageTemplate. By default, usage information will be written to stderr, but this can be adjusted by setting UsageOutputWriter.

Types

type BaseCommand

type BaseCommand struct {
	// 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

	// 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 [Documented].
	IsHidden bool
}

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

Example
package main

import (
	"context"
	"fmt"

	"github.com/brandon1024/cmder"
)

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

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

type BaseCommandExample struct {
	cmder.BaseCommand
}

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

func main() {
	cmd := &BaseCommandExample{
		BaseCommand: cmder.BaseCommand{
			CommandName: "base-command",
			Usage:       "base-command [<args>...]",
			ShortHelp:   "Simple demonstration of BaseCommand",
			Help:        BaseCommandExampleHelpText,
			Examples:    BaseCommandExampleExamples,
			Children: []cmder.Command{
				&cmder.BaseCommand{
					CommandName: "child",
					Usage:       "child [<args>...]",
					ShortHelp:   "A child command with simple behaviour",
					Help:        "I'm a simple subcommand with simple behaviour!",
					RunFunc: func(ctx context.Context, args []string) error {
						fmt.Printf("child: %v\n", args)
						return nil
					},
				},
			},
		},
	}

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

}
Output:

child: [cmder]

func (BaseCommand) Destroy

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

Destroy runs BaseCommand DestroyFunc, if not nil.

See RunnableLifecycle.

func (BaseCommand) ExampleText

func (c BaseCommand) ExampleText() string

ExampleText returns BaseCommand Examples.

See Documented.

func (BaseCommand) HelpText

func (c BaseCommand) HelpText() string

HelpText returns BaseCommand Help.

See Documented.

func (BaseCommand) Hidden

func (c BaseCommand) Hidden() bool

Hidden returns BaseCommand Hidden.

See Documented.

func (BaseCommand) Initialize

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

Initialize runs BaseCommand InitFunc, if not nil.

See RunnableLifecycle.

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) ShortHelpText

func (c BaseCommand) ShortHelpText() string

ShortHelpText returns BaseCommand ShortHelp.

See Documented.

func (BaseCommand) Subcommands

func (c BaseCommand) Subcommands() []Command

Subcommands returns BaseCommand Children.

See RootCommand.

func (BaseCommand) UsageLine

func (c BaseCommand) UsageLine() string

UsageLine returns BaseCommand Usage.

See Documented.

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.
	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 RunnableLifecycle.
  • 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"
)

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
}

func (c *HelloWorldCommand) Hidden() bool {
	return false
}

func main() {
	args := []string{"from", "cmder"}
	cmd := &HelloWorldCommand{}

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

}
Output:

hello-world: [from cmder]

type Documented

type Documented interface {
	// UsageLine returns the usage line for your command. 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 mainly used to summarize
	// available subcommands.
	ShortHelpText() string

	// HelpText returns long 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

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

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

type ExecuteOption

type ExecuteOption func(*ExecuteOptions)

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.

type ExecuteOptions

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

Options used to configure behaviour of Execute.

type FlagInitializer

type FlagInitializer interface {
	// InitializeFlags initializes flags. Invoked by [Execute] before any lifecycle routines.
	//
	// Help flags '-h' and '--help' are registered automatically and will instruct [Execute] to render usage information
	// to the [UsageOutputWriter].
	//
	// See [Execute] for more information.
	InitializeFlags(*flag.FlagSet)
}

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

Example
package main

import (
	"context"
	"fmt"

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

// === PARENT COMMAND ===

const ParentFlagsCommandUsageLine = `parent-flags [<subcommand>] [<args>...]`

const ParentFlagsCommandShortHelpText = `Example of parent command with flags`

const ParentFlagsCommandHelpText = `
'parent-flags' demonstrates an example of a command with subcommands and flags. Commands can define flags by
implementing the FlagInitializer interface. During execution, flags are parsed at each level in the command tree and
can be accessed by the command's Run routine.
`

const ParentFlagsCommandExamples = `
# run the parent 'Run' routine with flags
parent-flags --option string

# run the child 'Run' routine with flags
parent-flags child-flags --count 1

# run with some additional args
parent-flags --option string hello-world
parent-flags --option string child-flags --count 1 hello-world
`

type ParentFlagsCommand struct {
	cmder.BaseCommand

	option string
}

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

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

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

func (c *ParentFlagsCommand) InitializeFlags(fs *flag.FlagSet) {
	fs.StringVar(&c.option, "option", "", "parent command option string argument")
}

func NewParentFlagsCommand() *ParentFlagsCommand {
	return &ParentFlagsCommand{
		BaseCommand: cmder.BaseCommand{
			CommandName: "parent-flags",
			Usage:       ParentFlagsCommandUsageLine,
			ShortHelp:   ParentFlagsCommandShortHelpText,
			Help:        ParentFlagsCommandHelpText,
			Examples:    ParentFlagsCommandExamples,
			Children: []cmder.Command{
				NewChildFlagsCommand(),
			},
		},
	}
}

// === CHILD COMMAND ===

const ChildFlagsCommandUsageLine = `child-flags [<args>...]`

const ChildFlagsCommandShortHelpText = `Example of child command`

const ChildFlagsCommandHelpText = `
'child-flags' is the subcommand of 'parent-flags'.
`

const ChildFlagsCommandExamples = `
# run the child 'Run' routine with flags
parent-flags child-flags --count 1

# run with some additional args
parent-flags --option string child-flags --count 1 hello-world
`

type ChildFlagsCommand struct {
	cmder.BaseCommand

	count int
}

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

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

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

func (c *ChildFlagsCommand) InitializeFlags(fs *flag.FlagSet) {
	fs.IntVar(&c.count, "count", 0, "child command count integer argument")
}

func NewChildFlagsCommand() *ChildFlagsCommand {
	return &ChildFlagsCommand{
		BaseCommand: cmder.BaseCommand{
			CommandName: "child-flags",
			Usage:       ChildFlagsCommandUsageLine,
			ShortHelp:   ChildFlagsCommandShortHelpText,
			Help:        ChildFlagsCommandHelpText,
			Examples:    ChildFlagsCommandExamples,
		},
	}
}

// === EXAMPLE ===

func main() {
	cmd := NewParentFlagsCommand()

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

}
Output:

parent-flags: init [test=1] [child-flags --count 1 hello-world]
child-flags: init [1] [hello-world]
child-flags: run [1] [hello-world]
child-flags: destroy [1] [hello-world]
parent-flags: destroy [test=1] [child-flags --count 1 hello-world]

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"
)

// === 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) Hidden() bool {
	return false
}

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
}

func (c *ChildCommand) Hidden() bool {
	return false
}

// === EXAMPLE ===

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)
	}

}
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 RunnableLifecycle to carry out any initialization and teardown necessary for your Run() routine.

type RunnableLifecycle

type RunnableLifecycle 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

	// 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
}

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

When executing subcommands, the Initialize() and Destroy() routines of parent commands are also invoked. For instance, if executing subcommand 'child' of command 'parent', lifecycle routines are invoked in this order:

  1. parent: Initialize()
  2. child: Initialize()
  3. child: Run()
  4. child: Destroy()
  5. parent: Destroy()

When executing subcommands, the arguments provided to the Initialize() and Destroy() routines of parent commands will include the unprocessed args and flags of child commands. For example:

$ parent --option test child --count 1 arg-1 arg-2

will execute lifecycle routines with the arguments:

  1. parent: Initialize [child --count 1 arg-1 arg-2]
  2. child: Initialize [arg-1 arg-2]
  3. child: Run [arg-1 arg-2]
  4. child: Destroy [arg-1 arg-2]
  5. parent: Destroy [child --count 1 arg-1 arg-2]
Example
package main

import (
	"context"
	"fmt"

	"github.com/brandon1024/cmder"
)

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

const LifecycleCommandShortHelpText = `Example command with lifecycle routines`

const LifecycleCommandHelpText = `
'lifecycle' demonstrates a command that implements the RunnableLifecycle interface, 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) main() string {
	return LifecycleCommandExamples
}

func (c *LifecycleCommand) Hidden() bool {
	return false
}

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)
	}

}
Output:

lifecycle: initializing
lifecycle: running
lifecycle: shutting down

Directories

Path Synopsis
examples
hello-world command
http command
Package flag is an alternative to the standard library package of the same name.
Package flag is an alternative to the standard library package of the same name.

Jump to

Keyboard shortcuts

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