shell

package module
v0.0.0-...-4cf0488 Latest Latest
Warning

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

Go to latest
Published: Apr 21, 2019 License: MIT Imports: 9 Imported by: 0

README

shell

shell provides a simple shell interface and an API to register commands and sub-commands for use within it. The goal of this shell package is to provide an API that makes the task of creating a functional custom shell as easy as possible.

For more information and API documentation see GoDoc.

Example

// The first argument is the app name and the second controls whether
// or not the default commands help and exit are added.
app := shell.NewApp("MyApp", true)

if err := app.AddCommand(Command{
	Name: "test",
	Synopsis: "run some tests",
	Usage: "${name} ${shortFlags}:

Do some things and run some tests and more detailed information.

${flags}",
	SetFlags: func(ctx *shell.Context) {
		ctx.Set("top", ctx.FlagSet().Int("top", 12, "example top-level flag"))
	},
	Main: func(ctx *shell.Context) shell.ExitStatus {
		ctx.App().Println("Hello world!", *ctx.MustGet("top").(*int))
		return ExitCmd
	},
	SubCommands: []Command{
		{
			Name: "secondary",
			SetFlags: func(ctx *Context) {
				ctx.Set("second", ctx.FlagSet().Int("second", 21, "example second-level flag"))
			},
			Main: func(ctx *Context) ExitStatus {
				ctx.App().Println("Hello world from a sub-command!")
				return ExitCmd
			},
		},
	}
})

app.Main() // Start the main loop.

Documentation

Overview

Package shell provides a simple shell interface and an API to register commands and sub-commands for use within it.

Basics

Create a new app and add a command:

// The first argument is the app name and the second controls whether
// or not the default commands help and exit are added.
app := shell.NewApp("MyApp", true)

if err := app.AddCommand(Command{
	Name: "test",
	Synopsis: "run some tests",
	Usage: "${name} ${shortFlags}:

Do some things and run some tests and more detailed information.

${flags}",
	SetFlags: func(ctx *shell.Context) {
		ctx.Set("top", ctx.FlagSet().Int("top", 12, "example top-level flag"))
	},
	Main: func(ctx *shell.Context) shell.ExitStatus {
		ctx.App().Println("Hello world!", *ctx.MustGet("top").(*int))
		return ExitCmd
	},
	SubCommands: []Command{
		{
			Name: "secondary",
			SetFlags: func(ctx *Context) {
				ctx.Set("second", ctx.FlagSet().Int("second", 21, "example second-level flag"))
			},
			Main: func(ctx *Context) ExitStatus {
				ctx.App().Println("Hello world from a sub-command!")
				return ExitCmd
			},
		},
	}
})

Start the main loop:

app.Main()

And you're all set!

Index

Constants

This section is empty.

Variables

View Source
var DefaultCommands = []*Command{
	{
		Name:     "exit",
		Synopsis: "exit shell",
		Usage:    "${name} ${shortFlags}:\n\n${flags}",
		SetFlags: func(ctx *Context) {
			ctx.Set("flagOnlyShell", ctx.FlagSet().Bool("shell-only", false, "exit only the shell, "+
				"returning to the main program"))
		},
		Main: func(ctx *Context) ExitStatus {

			if ctx.FlagSet().NArg() > 0 {
				return ExitUsage
			}

			if *ctx.ShouldGet("flagOnlyShell").(*bool) {
				return ExitShell
			}

			return ExitAll
		},
	},
	{
		Name:     "help",
		Synopsis: "list existing commands and their synopsis",
		Usage:    "${name} [<command name>]",
		Main: func(ctx *Context) ExitStatus {
			switch ctx.FlagSet().NArg() {
			case 0:
				list := make([]string, 0)
				for _, command := range ctx.App().Commands {
					if command.Name == "help" {
						continue
					}

					list = append(list, fmt.Sprintf("\t%s\t\t%s\n", command.Name, command.Synopsis))
				}
				sort.Strings(list)
				ctx.App().Printf("Available commands:\n%s\n\nFor more information, type `help <command name>`.",
					strings.Join(list, ""))
			case 1:
				requested, err := ctx.App().GetByName(ctx.FlagSet().Arg(0))
				if err != nil {
					ctx.App().Printf("%s: command not found\n", ctx.FlagSet().Arg(0))
					return ExitCmd
				}

				if requested.Usage == "" {
					ctx.App().Printf("%s\t\t%s\n", requested.Name, requested.Synopsis)
				} else {
					ctx.App().Printf("%s\n", requested.Usage)
				}
			default:
				return ExitUsage
			}

			return ExitCmd
		},
	},
}

DefaultCommands defines the following top-level commands: help and exit.

View Source
var DefaultSubCommands = []Command{
	{
		Name:     "commands",
		Synopsis: "list all sub-command names",
		Usage: `${name}:

Print a list of all sub-commands.`,
		Main: func(ctx *Context) ExitStatus {
			if ctx.FlagSet().NArg() > 0 {
				return ExitUsage
			}

			for _, subCmd := range ctx.Parent().SubCommands {
				ctx.App().Println(subCmd.Name)
			}

			return ExitCmd
		},
	},
	{
		Name:     "flags",
		Synopsis: "describe all known top-level flags",
		Usage: `${name} [<sub-command>]:

With an argument, print all flags of <sub-command>. Else, print a
description of all known top-level flags. (The basic help information only
discusses the most generally important top-level flags.)`,
		Main: func(ctx *Context) ExitStatus {
			flags := ctx.FlagSet()

			if flags.NArg() > 1 {
				return ExitUsage
			}

			var reqCmd *Command
			if flags.NArg() == 0 {
				reqCmd = ctx.Parent()
			} else {
				for _, cmd := range ctx.Parent().SubCommands {
					if flags.Arg(0) == cmd.Name {
						reqCmd = &cmd
						break
					}
				}

				if reqCmd == nil {
					ctx.App().Printf("%s %s: sub-command not found", ctx.Parent().Name, flags.Arg(0))
					return ExitCmd
				}
			}

			reqCtx := reqCmd.NewContext()

			if reqCmd.SetFlags != nil {
				reqCmd.SetFlags(reqCtx)
			}

			reqCtx.FlagSet().PrintDefaults()

			return ExitCmd
		},
	},
	{
		Name:     "help",
		Synopsis: "describe sub-commands and their syntax",
		Usage: `${name} [<sub-command>]:

With an argument, prints detailed information on the use of the specified
sub-command. With no argument, prints a list of all commands and a brief
description of each.`,
		Main: func(ctx *Context) ExitStatus {
			parent := ctx.Parent()

			switch ctx.FlagSet().NArg() {
			case 0:
				list := make([]string, 0)
				for _, subCmd := range parent.SubCommands {
					if subCmd.Name == "help" {
						continue
					}

					list = append(list, fmt.Sprintf("\t%s\t\t%s\n", subCmd.Name, subCmd.Synopsis))
				}
				sort.Strings(list)

				ctx.App().Printf("Usage: %s <sub-command> <sub-command args>\n\n"+
					"Sub-commands:\n%s", parent.Name, strings.Join(list, ""))
			case 1:
				var reqCmd *Command
				for _, cmd := range parent.SubCommands {
					if ctx.FlagSet().Arg(0) == cmd.Name {
						reqCmd = &cmd
						break
					}
				}

				if reqCmd == nil {
					ctx.App().Printf("%s %s: sub-command not found", parent.Name, ctx.FlagSet().Arg(0))
					return ExitCmd
				}

				if reqCmd.Usage == "" {
					ctx.App().Printf("%s\t\t%s\n", reqCmd.Name, reqCmd.Synopsis)
				} else {
					ctx.App().Printf("%s\n", reqCmd.Usage)
				}
			default:
				return ExitUsage
			}

			return ExitCmd
		},
	},
}

DefaultSubCommands defines the following sub-commands: commands, flags, and help.

Functions

This section is empty.

Types

type App

type App struct {
	// Name is the name of the program. No restrictions are applied. Defaults
	// to filepath.Base(os.Args[0]).
	Name string

	// Commands holds all commands attached to the App.
	Commands []*Command

	// Output controls the destination for general messages emitted by the App.
	Output io.Writer

	// ErrOutput controls the destination for usage and error messages.
	ErrOutput io.Writer

	// Input controls the reader used to fetch user input.
	Input io.ReadCloser
}

App is the main structure that makes up a single shell. Through it commands are created and managed. App is not intended to be directly created or manipulated, instead its methods and NewApp should be utilized.

func NewApp

func NewApp(name string, addDefaults bool) *App

NewApp creates an App and configures its logger. The first argument defines the name of the App and defaults to filepath.Base(os.Args[0]) if blank. A set of default commands are also automatically added unless false is passed as an argument.

func (*App) AddCommand

func (app *App) AddCommand(cmd Command) error

AddCommand takes a Command and adds it to the App. If the command or any of its sub-commands are invalid an error is returned.

func (*App) ExecuteString

func (app *App) ExecuteString(input string) (ExitStatus, error)

ExecuteString takes what is usually some user input and attempts to execute a command based on the input. If no matching command exists an ErrNoCmd is returned. If the input string is invalid an ErrParseInput is returned. If a command is successfully executed, it's ExitStatus is returned, otherwise ExecuteString defaults to ExitCmd. An ErrParseFlags may be returned in event of a failure when parsing the input flags.

func (*App) GetByName

func (app *App) GetByName(name string) (*Command, error)

GetByName takes a string and returns a pointer to a command or an error if no command by that name exists.

func (*App) Main

func (app *App) Main() ExitStatus

Main is the App's main loop. It accepts user input infinitely until some command returns an ExitStatus of ExitShell. Any errors that occur are not propagated back up but rather printed to the App's Output.

func (*App) Print

func (app *App) Print(a ...interface{})

Print prints to the App's Output. Arguments are handled in the manner of fmt.Print.

func (*App) Printf

func (app *App) Printf(format string, a ...interface{})

Printf prints to the App's Output. Arguments are handled in the manner of fmt.Printf.

func (*App) Println

func (app *App) Println(a ...interface{})

Println prints to the App's Output. Arguments are handled in the manner of fmt.Println.

type Command

type Command struct {
	// Name is required and should be as concise as possible. It may not
	// contain any spaces.
	Name string

	// Synopsis should contain a short description of the command. It usually
	// should not be more than a single sentence.
	Synopsis string

	// Usage should contain a detailed description of the command. There are no
	// limitations to its length. Several sequences are substituted with
	// information relating to the command when found within the usage string:
	// `${name}` is substituted with the name of the command, ${fullName} with
	// the full name of the command (including parent command name if the
	// command is a sub-command), ${flags} with the help information for the
	// command flags as described by/ flag.PrintDefaults, and ${shortFlags} for
	// a short list of all registered flags in the format of [-<flag name>] and
	// separated with spaces.
	Usage string

	// SetFlags should register any flags with the flag.FlagSet available
	// through the Context and store their result within via Context.[Get|Set].
	// SetFlags should not attempt to parse flags since it does not have access
	// to the complete input string.
	SetFlags func(*Context)

	// Main is required and contains the command logic itself. If SetFlags
	// exists, flags will be parsed immediately before Main is called and
	// the results should be accessible via the Context.
	Main func(*Context) ExitStatus

	// SubCommands should contain an arbitrary number of Commands. If the name
	// of a valid sub-command directly follows the name of this command in some
	// user input, the sub-command will be preferred over this Command.
	// Otherwise, this Command will be executed.
	SubCommands []Command

	// PreventDefaultSubCommands controls whether the sub-commands defined
	// within the exported table PreventDefaultSubCommands should be added by
	// default. If no sub-commands are defined in the SubCommands array, this
	// option is disregarded. If left blank, default sub-commands are added.
	PreventDefaultSubCommands bool
	// contains filtered or unexported fields
}

Command is a top-level command within a shell App. It may contain an arbitrary number of sub-command.

func (*Command) Execute

func (cmd *Command) Execute(input []string) (ExitStatus, error)

Execute takes an array of strings, usually representing some user input retrieved from the shell loop. It then executes this Command, first parsing the input for flags. If an error occurs while parsing flags, it is returned.

func (*Command) FullName

func (cmd *Command) FullName() string

FullName returns the full name of the command, checking if it has a parent and if so prepending it to its own name.

func (*Command) GetSubCommand

func (cmd *Command) GetSubCommand(name string) (*Command, error)

GetSubCommand attempts to fetch a sub-command by name, returning a pointer to the sub-command if successful and an error if it does not exist.

func (*Command) Match

func (cmd *Command) Match(input []string) (*Command, error)

Match takes an array of strings, usually representing some user input retrieved from the shell loop. If the input does not call for this command an error is returned, otherwise Match checks if the input calls for a sub- command, returning either it or this Command if no match is found.

func (*Command) NewContext

func (cmd *Command) NewContext() *Context

NewContext returns an empty context prepared for this command.

type Context

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

Context is a type that is passed to each handler when a Command is executed and allows arbitrary information to be shared between handlers besides providing access to specifics about the command itself. Context may be created manually due to its simplicity, but it is often helpful to utilize NewContext.

func NewContext

func NewContext(app *App, command *Command, flagSet *flag.FlagSet, parent *Command) *Context

NewContext creates a new context given an App, a Command, a flag.FlagSet, and optionally a parent Command.

func (*Context) App

func (context *Context) App() *App

App returns the connected shell App. Warning: if none exists a nil pointer will be returned.

func (*Context) Command

func (context *Context) Command() *Command

Command returns the Command for which the Context is acting. Warning: if none exists a nil pointer will be returned.

func (*Context) Delete

func (context *Context) Delete(name string)

Delete takes a string and delete the value stored at that index. No error is returned regardless of whether a deletion actually occurs.

func (*Context) FlagSet

func (context *Context) FlagSet() *flag.FlagSet

FlagSet returns the flag.FlagSet for use with command handler functions. Warning: if none exists a nil pointer will be returned.

func (*Context) Get

func (context *Context) Get(name string) (interface{}, error)

Get takes a string and returns its value or an error if the key does not exist.

func (*Context) MustGet

func (context *Context) MustGet(name string) interface{}

MustGet does the same as Get but panics if an error is returned.

func (*Context) Parent

func (context *Context) Parent() *Command

Parent returns the parent Command. Warning: if none exists a nil pointer will be returned.

func (*Context) Set

func (context *Context) Set(name string, value interface{})

Set takes a string and an interface and sets a value in the context.

func (*Context) ShouldGet

func (context *Context) ShouldGet(name string) interface{}

ShouldGet does the same as Get but returns nil if the key does not exist.

type ErrNoCmd

type ErrNoCmd struct {
	Name string
}

ErrNoCmd is returned from ExecuteString if the input does not call a valid command.

func (*ErrNoCmd) Error

func (err *ErrNoCmd) Error() string

Error implements the error interface for ErrNoCmd.

type ErrParseFlags

type ErrParseFlags struct {
	Name string
	Err  error
}

ErrParseFlags is returned from Command.Execute is the FlagSet fails to parse.

func (*ErrParseFlags) Error

func (err *ErrParseFlags) Error() string

Error implements the error interface for ErrParseFlags.

type ErrParseInput

type ErrParseInput struct {
	Input string
}

ErrParseInput is returned from ExecuteString is the input for some reason cannot be parsed. Currently occurs only when input is empty.

func (*ErrParseInput) Error

func (err *ErrParseInput) Error() string

Error implements the error interface for ErrParseInput

type ExitStatus

type ExitStatus int

ExitStatus is returned from Command handlers to instruct the program how to react to its completion.

const (
	// ExitCmd exits only the command.
	ExitCmd ExitStatus = iota

	// ExitUsage does the same as ExitCmd but also prints the Usage string for
	// the command.
	ExitUsage

	// ExitShell exits the shell's infinite loop, but does not itself trigger
	// the entire program to exit.
	ExitShell

	// ExitAll exits not only the shell loop, but also the entire program. It
	// is, however, left up to the enclosing program to respect this.
	ExitAll
)

Jump to

Keyboard shortcuts

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