ecdysis

package module
v0.4.2 Latest Latest
Warning

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

Go to latest
Published: Apr 23, 2025 License: Apache-2.0 Imports: 14 Imported by: 14

README

Ecdysis

License Test Go Report Card Go Reference

Ecdysis is a library for building CLI tools in Go. It is using spf13/cobra under the hood and provides a novel approach to building commands by declaring types with methods that define the command's behavior.

Quick Start

Install it using:

go get github.com/conduitio/ecdysis

To create a new command, define a struct that implements ecdysis.Command and any other ecdysis.CommandWith* interfaces you need. The recommended pattern is to list the interfaces that the command implements in a var block.

type VersionCommand struct{}

var (
	_ ecdysis.CommandWithExecute = (*VersionCommand)(nil)
	_ ecdysis.CommandWithDocs    = (*VersionCommand)(nil)
)

func (*VersionCommand) Usage() string { return "version" }
func (*VersionCommand) Docs() ecdysis.Docs {
	return ecdysis.Docs{
		Short: "Print the version number of example-cli",
	}
}
func (*VersionCommand) Execute(context.Context) error {
	fmt.Println("example-cli v0.1.0")
	return nil
}

In the main function, call ecdysis.New and build a Cobra command that can be executed like any other Cobra command.

func main() {
	e := ecdysis.New()
	cmd := e.MustBuildCobraCommand(&VersionCommand{})
	if err := cmd.Execute(); err != nil {
		log.Fatal(err)
	}
}

Decorators

Decorators enable you to add functionality to commands and configure the resulting Cobra command as you need. Ecdysis comes with a set of default decorators that you can use to add flags, arguments, confirmation prompts, deprecation notices, and other features to your commands. Check out the Go Reference for a full list of decorators.

You can implement your own decorators and use them to extend the functionality of your commands.

For example, this is how you would add support for commands that log using Zerolog:

type CommandWithZerolog interface {
	Command
	Zerolog(zerolog.Logger)
}

type CommandWithZerologDecorator struct{
	Logger zerolog.Logger
}

func (d CommandWithZerologDecorator) Decorate(_ *Ecdysis, _ *cobra.Command, c Command) error {
	v, ok := c.(CommandWithZerolog)
	if !ok {
		return nil
	}

	v.Logger(d.Logger)
	return nil
}

You need to supply the decorator to ecdysis when creating it.

func main() {
	e := ecdysis.New(
		ecdysis.WithDecorators(
			&CommandWithZerologDecorator{Logger: zerolog.New(os.Stdout)},
		),
	)
	// build and execute command ...
}
CommandWithConfig

Ecdysis provides an automatic way to parse a configuration file, environment variables, and flags using the viper library. To use it, you need to implement the CommandWithConfig interface.

The order of precedence for configuration values is:

  1. Default values (slices and maps are not currently supported)
  2. Configuration file
  3. Environment variables
  4. Flags

[!IMPORTANT]
For flags, it's important to set default values to ensure that the configuration will be correctly parsed. Otherwise, they will be empty, and it will be considered as if the user set that intentionally. example: flags.SetDefault("config.path", c.cfg.ConduitCfgPath)

var (
    _ ecdysis.CommandWithFlags   = (*RootCommand)(nil)
    _ ecdysis.CommandWithExecute = (*RootCommand)(nil)
    _ ecdysis.CommandWithConfig  = (*RootCommand)(nil)
)

type ConduitConfig struct {
    ConduitCfgPath string `long:"config.path" usage:"global conduit configuration file" default:"./conduit.yaml"`
    
    Connectors struct {
        Path string `long:"connectors.path" usage:"path to standalone connectors' directory"`
    }
    
    // ...
}

type RootFlags struct {
    ConduitConfig // you can embed any configuration, and it'll use the proper tags
}

type RootCommand struct {
    flags RootFlags
    cfg   ConduitConfig
}

func (c *RootCommand) Config() ecdysis.Config {
    return ecdysis.Config{
        EnvPrefix:     "CONDUIT",
        Parsed:        &c.Cfg,
        Path:          c.flags.ConduitCfgPath,
        DefaultValues: conduit.DefaultConfigWithBasePath(path),
    }
}

func (c *RootCommand) Execute(_ context.Context) error {
    // c.cfg is now populated with the right parsed configuration
    return nil
}

func (c *RootCommand) Flags() []ecdysis.Flag {
    flags := ecdysis.BuildFlags(&c.flags)
    
    // set a default value for each flag
    flags.SetDefault("config.path", c.cfg.ConduitCfgPath) 
    // ...
	
    return flags
}
Fetching cobra.Command from CommandWithExecute

If you need to access the cobra.Command instance from a CommandWithExecute implementation, you can utilize the ecdysis.CobraCmdFromContext function to fetch it from the context:

func (c *RootCommand) Execute(ctx context.Context) error {
    if cmd := ecdysis.CobraCmdFromContext(ctx); cmd != nil {
        return cmd.Help()
    }
    return nil
}

Flags

Ecdysis provides a way to define flags using field tags. Flags will be automatically parsed and populated.

type MyCommand struct {
	flags struct {
		Verbose bool   `long:"verbose" short:"v", usage:"enable verbose output" persistent:"true"`
		Config  string `long:"config" usage:"config file (default is $HOME/.example-cli.yaml)" persistent:"true"`
	}
}


func (c *MyCommand) Flags() []ecdysis.Flag {
	return ecdysis.BuildFlags(&c.flags)
}

A full list of supported tags:

  • long: The long flag name
  • short: The short flag name
  • required: Whether the flag is required
  • persistent: Whether the flag is persistent (i.e. available to subcommands)
  • usage: The flag usage
  • hidden: Whether the flag is hidden (i.e. not shown in help)

For a more example on how to use persistent flags in subcommands, see the example.

Documentation

Index

Constants

This section is empty.

Variables

Functions

func CobraCmdFromContext added in v0.1.0

func CobraCmdFromContext(ctx context.Context) *cobra.Command

CobraCmdFromContext fetches the cobra command from the context. If the context does not contain a cobra command, it returns nil.

func ContextWithCobraCommand added in v0.2.0

func ContextWithCobraCommand(ctx context.Context, cmd *cobra.Command) context.Context

ContextWithCobraCommand provides the cobra command to the context. This is useful for situations such as wanting to execute cmd.Help() directly from Execute().

func ParseConfig added in v0.2.0

func ParseConfig(cfg Config, cmd *cobra.Command) error

ParseConfig parses the configuration into cfg.Parsed using viper. This is useful for any decorator that needs to parse configuration based on the available flags.

Types

type Command

type Command interface {
	// Usage is the one-line usage message.
	// Recommended syntax is as follows:
	//   [ ] identifies an optional argument. Arguments that are not enclosed in
	//       brackets are required.
	//   ... indicates that you can specify multiple values for the previous
	//       argument.
	//   |   indicates mutually exclusive information. You can use the argument
	//       to the left of the separator or the argument to the right of the
	//       separator. You cannot use both arguments in a single use of the
	//       command.
	//   { } delimits a set of mutually exclusive arguments when one of the
	//       arguments is required. If the arguments are optional, they are
	//       enclosed in brackets ([ ]).
	// Example: add [-F file | -D dir]... [-f format] profile
	Usage() string
}

Command is an interface that represents a command that can be decorated and converted to a cobra.Command instance.

type CommandWithAliases

type CommandWithAliases interface {
	Command
	// Aliases is a slice of aliases that can be used instead of the first word
	// in Usage.
	Aliases() []string
}

CommandWithAliases can be implemented by a command to provide aliases.

type CommandWithAliasesDecorator

type CommandWithAliasesDecorator struct{}

CommandWithAliasesDecorator is a decorator that sets the command aliases.

func (CommandWithAliasesDecorator) Decorate

Decorate sets the command aliases.

type CommandWithArgs

type CommandWithArgs interface {
	Command
	// Args is meant to parse arguments after the command name.
	Args([]string) error
}

CommandWithArgs can be implemented by a command to parse arguments.

type CommandWithArgsDecorator

type CommandWithArgsDecorator struct{}

CommandWithArgsDecorator is a decorator that provides the command arguments.

func (CommandWithArgsDecorator) Decorate

Decorate provides the command arguments.

type CommandWithConfig

type CommandWithConfig interface {
	Command

	Config() Config
}

CommandWithConfig can be implemented by a command to parsing configuration.

type CommandWithConfigDecorator

type CommandWithConfigDecorator struct{}

CommandWithConfigDecorator is a decorator that sets the command flags.

func (CommandWithConfigDecorator) Decorate

Decorate parses the configuration based on flags.

type CommandWithConfirm

type CommandWithConfirm interface {
	Command
	// ValueToConfirm adds a prompt before the command is executed where the
	// user is asked to write the exact value as wantInput. If the user input
	// matches the command will be executed, otherwise processing will be
	// aborted.
	ValueToConfirm(context.Context) (wantInput string)
}

CommandWithConfirm can be implemented by a command to require confirmation before execution. The user will be prompted to enter a specific value. If the value matches, the command will be executed, otherwise it will be aborted.

type CommandWithConfirmDecorator

type CommandWithConfirmDecorator struct{}

CommandWithConfirmDecorator is a decorator that sets up a confirmation prompt before executing the command.

func (CommandWithConfirmDecorator) Decorate

Decorate sets up a confirmation prompt before executing the command.

type CommandWithDeprecated

type CommandWithDeprecated interface {
	Command
	// Deprecated returns a message that will be printed when the command is used.
	Deprecated() string
}

CommandWithDeprecated can be implemented by a command to mark it as deprecated and print a message when it is used.

type CommandWithDeprecatedDecorator

type CommandWithDeprecatedDecorator struct{}

CommandWithDeprecatedDecorator is a decorator that deprecates the command.

func (CommandWithDeprecatedDecorator) Decorate

Decorate deprecates the command.

type CommandWithDocs

type CommandWithDocs interface {
	Command
	// Docs returns the documentation for the command.
	Docs() Docs
}

CommandWithDocs can be implemented by a command to provide documentation.

type CommandWithDocsDecorator

type CommandWithDocsDecorator struct{}

CommandWithDocsDecorator is a decorator that sets the command documentation.

func (CommandWithDocsDecorator) Decorate

Decorate sets the command documentation.

type CommandWithExecute

type CommandWithExecute interface {
	Command
	// Execute is the actual work function. Most commands will implement this.
	Execute(ctx context.Context) error
}

CommandWithExecute can be implemented by a command to provide an execution function.

type CommandWithExecuteDecorator

type CommandWithExecuteDecorator struct{}

CommandWithExecuteDecorator is a decorator that sets the command execution.

func (CommandWithExecuteDecorator) Decorate

Decorate sets the command execution.

type CommandWithFlags

type CommandWithFlags interface {
	Command
	// Flags returns the set of flags on this command.
	Flags() []Flag
}

CommandWithFlags can be implemented by a command to provide flags.

type CommandWithFlagsDecorator

type CommandWithFlagsDecorator struct{}

CommandWithFlagsDecorator is a decorator that sets the command flags.

func (CommandWithFlagsDecorator) Decorate

Decorate sets the command flags.

type CommandWithHidden

type CommandWithHidden interface {
	Command
	// Hidden returns the desired hidden value for the command.
	Hidden() bool
}

CommandWithHidden can be implemented by a command to hide it from the help.

type CommandWithHiddenDecorator

type CommandWithHiddenDecorator struct{}

CommandWithHiddenDecorator is a decorator that sets the command hidden value.

func (CommandWithHiddenDecorator) Decorate

Decorate sets the command hidden value.

type CommandWithLogger

type CommandWithLogger interface {
	Command
	// Logger provides the logger to the command.
	Logger(*slog.Logger)
}

CommandWithLogger can be implemented by a command to get a logger.

type CommandWithLoggerDecorator

type CommandWithLoggerDecorator struct {
	Logger *slog.Logger
}

CommandWithLoggerDecorator is a decorator that provides a logger to the command. If the Logger field is not set, the default slog logger will be provided.

func (CommandWithLoggerDecorator) Decorate

Decorate provides the logger to the command.

type CommandWithOutput added in v0.3.0

type CommandWithOutput interface {
	Command
	Output(output Output)
}

CommandWithOutput can be implemented by a command to provide its own stdout and stderr.

type CommandWithOutputDecorator added in v0.3.0

type CommandWithOutputDecorator struct {
	Output Output
}

CommandWithOutputDecorator is a decorator that provides a Stdout to the command. If the Stdout field is not set, the default stdout will be provided.

func (CommandWithOutputDecorator) Decorate added in v0.3.0

func (d CommandWithOutputDecorator) Decorate(_ *Ecdysis, cmd *cobra.Command, c Command) error

Decorate provides the logger to the command.

type CommandWithPrompt

type CommandWithPrompt interface {
	Command

	// Prompt adds a prompt before the command is executed where the user is
	// asked to answer Y/N to proceed. It returns the message to be printed and
	// a boolean indicating if the prompt was successfully processed.
	Prompt() (message string, ok bool)
	// SkipPrompt will return logic around when to skip prompt (e.g.: when all
	// flags and arguments are specified).
	SkipPrompt() bool
}

CommandWithPrompt can be implemented by a command to require confirmation before execution. The user will be prompted to answer Y/N to proceed.

type CommandWithPromptDecorator

type CommandWithPromptDecorator struct{}

CommandWithPromptDecorator is a decorator that sets up a confirmation prompt before executing the command.

func (CommandWithPromptDecorator) Decorate

Decorate sets up a confirmation prompt before executing the command.

type CommandWithSubCommands

type CommandWithSubCommands interface {
	Command
	// SubCommands defines subcommands of a command.
	SubCommands() []Command
}

CommandWithSubCommands can be implemented by a command to provide subcommands.

type CommandWithSubCommandsDecorator

type CommandWithSubCommandsDecorator struct{}

CommandWithSubCommandsDecorator is a decorator that sets the command subcommands.

func (CommandWithSubCommandsDecorator) Decorate

Decorate sets the command subcommands.

type Config

type Config struct {
	EnvPrefix     string
	Parsed        any
	DefaultValues any
	Path          string
}

type Decorator

type Decorator interface {
	Decorate(e *Ecdysis, cmd *cobra.Command, c Command) error
}

Decorator is an interface that can be used to decorate a cobra.Command instance.

type DefaultOutput added in v0.3.0

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

func NewDefaultOutput added in v0.3.0

func NewDefaultOutput(cmd *cobra.Command) *DefaultOutput

func (*DefaultOutput) Output added in v0.3.0

func (d *DefaultOutput) Output(stdout, stderr io.Writer)

Output allows overwriting the stdout and/or stderr for specific use cases (like testing).

func (*DefaultOutput) Stderr added in v0.3.0

func (d *DefaultOutput) Stderr(msg any)

Stderr writes a message to the configured standard error.

func (*DefaultOutput) Stdout added in v0.3.0

func (d *DefaultOutput) Stdout(msg any)

Stdout writes a message to the configured standard output.

type Docs

type Docs struct {
	// Short is the short description shown in the 'help' output.
	Short string
	// Long is the long message shown in the 'help <this-command>' output.
	Long string
	// Example is examples of how to use the command.
	Example string
}

Docs will be shown to the user when typing 'help' as well as in generated docs.

type Ecdysis

type Ecdysis struct {
	// Decorators is a list of decorators that are applied to all commands.
	Decorators []Decorator
}

Ecdysis is the main struct that holds all decorators and is used to build cobra.Command instances from Command instances.

func New

func New(opts ...Option) *Ecdysis

New creates a new Ecdysis instance with the provided options. By default, it uses the DefaultDecorators. Options can be used to add or replace decorators.

func (*Ecdysis) BuildCobraCommand

func (e *Ecdysis) BuildCobraCommand(c Command) (*cobra.Command, error)

BuildCobraCommand creates a new cobra.Command instance from the provided Command instance. It decorates the command with all registered decorators.

func (*Ecdysis) MustBuildCobraCommand

func (e *Ecdysis) MustBuildCobraCommand(c Command) *cobra.Command

MustBuildCobraCommand creates a new cobra.Command instance from the provided Command instance. It decorates the command with all registered decorators. If an error occurs, it panics.

type Flag

type Flag struct {
	// Long name of the flag.
	Long string
	// Short name of the flag (one character).
	Short string
	// Usage is the description shown in the 'help' output.
	Usage string
	// Required is used to mark the flag as required.
	Required bool
	// Persistent is used to propagate the flag to subcommands.
	Persistent bool
	// Default is the default value when the flag is not explicitly supplied.
	// It should have the same type as the value behind the pointer in field Ptr.
	Default any
	// Ptr is a pointer to the value into which the flag will be parsed.
	Ptr any
	// Hidden is used to mark the flag as hidden.
	Hidden bool
}

Flag describes a single command line flag.

type Flags

type Flags []Flag

func BuildFlags

func BuildFlags(obj any) Flags

BuildFlags creates a slice of Flags from a struct. It supports nested structs and will only generate flags if it finds a 'short' or 'long' tag.

func (Flags) GetFlag

func (f Flags) GetFlag(long string) (Flag, bool)

GetFlag returns the flag with the given long name.

func (Flags) SetDefault

func (f Flags) SetDefault(long string, val any) bool

SetDefault sets the default value for the flag with the given long name.

type Option

type Option func(*Ecdysis)

Option is a function type that modifies an Ecdysis instance.

func WithDecorators

func WithDecorators(decorators ...Decorator) Option

WithDecorators adds or replaces a decorator of the same type.

func WithoutDefaultDecorators

func WithoutDefaultDecorators() Option

WithoutDefaultDecorators removes all default decorators.

type Output added in v0.3.0

type Output interface {
	Stdout(any)
	Stderr(any)
}

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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