Documentation
¶
Overview ¶
Package exoskeleton allows you to create modern multi-CLI applications whose subcommands are external to them.
You use it by defining an Entrypoint and a list of paths where external subcommands may be found.
Here's an example:
Assuming we have two executables that respond to `--summary` like this:
$ ~/libexec/rm --summary
Remove directory entries
$ ~/libexec/ls --summary
List directory contents
And a binary, `example`, that is implemented like this:
package main
import (
"os"
"github.com/square/exoskeleton"
)
func main() {
exoskeleton.Exec([]string{os.Getenv("HOME") + "/libexec"})
}
Then our example program will behave like this:
$ ./example
USAGE
example <command> [<args>]
COMMANDS
ls List directory contents
rm Remove directory entries
Run example help <command> to print information on a specific command.
And running `example ls` will forward execution to `~/libexec/ls`.
Discovery Contracts ¶
Exoskeleton uses a contract system to determine how commands and modules are discovered and built. Four default contracts are provided:
- DirectoryModuleContract: Directories containing a .exoskeleton file
- ExecutableModuleContract: Executables with .exoskeleton extension
- ScriptCommandContract: Shell scripts with magic comments (# SUMMARY:, # HELP:)
- ExecutableCommandContract: Executables that respond to --summary and --help
Users can customize discovery by providing their own contracts via WithContracts():
e, _ := exoskeleton.New(
paths,
exoskeleton.WithContracts(
&MyCustomContract{},
&exoskeleton.DirectoryModuleContract{},
),
)
Index ¶
- Constants
- Variables
- func CompleteCommands(e *Entrypoint, args, env []string) ([]string, shellcomp.Directive, error)
- func CompleteExec(e *Entrypoint, args, env []string) error
- func CompleteFiles(e *Entrypoint, args, env []string) ([]string, shellcomp.Directive, error)
- func Exec(paths []string, options ...Option)
- func GenerateCompletionScript(name, shell string, w io.Writer) (err error)
- func HelpExec(e *Entrypoint, args, _ []string) error
- func IsEmbedded(command Command) bool
- func IsModule(command Command) bool
- func IsNull(command Command) bool
- func MenuFor(m Module, opts *MenuOptions) (string, []error)
- func Usage(cmd Command) string
- func UsageRelativeTo(cmd Command, m Module) string
- func WhichExec(e *Entrypoint, args, _ []string) error
- type AfterIdentifyFunc
- type Cache
- type Command
- type CommandDescribeError
- type CommandError
- type CommandHelpError
- type CommandNotFoundFunc
- type CommandSummaryError
- type Commands
- type CompleteFunc
- type Contract
- type DirectoryContract
- type DiscoveryContext
- type DiscoveryError
- type EmbeddedCommand
- type EmbeddedModule
- type Entrypoint
- func (e *Entrypoint) Complete(_ *Entrypoint, args, _ []string) (completions []string, directive shellcomp.Directive, err error)
- func (e *Entrypoint) Exec(_ *Entrypoint, rawArgs, env []string) error
- func (e *Entrypoint) GenerateCompletionScript(shell string, w io.Writer) error
- func (e *Entrypoint) Help() (string, error)
- func (e *Entrypoint) Identify(args []string) (Command, []string, error)
- func (e *Entrypoint) Name() string
- func (e *Entrypoint) Parent() Module
- func (e *Entrypoint) Path() string
- func (e *Entrypoint) Subcommands() (Commands, error)
- func (e *Entrypoint) Summary() (string, error)
- type ErrorFunc
- type ExecFunc
- type ExecutableContract
- type ExecutorFunc
- type ExpandOption
- type FileCache
- type Menu
- type MenuHeadingForFunc
- type MenuItem
- type MenuItems
- type MenuOptions
- type MenuSection
- type MenuSections
- type Module
- type Option
- func AfterIdentify(fn AfterIdentifyFunc) Option
- func AppendCommands(cmds ...interface{}) Option
- func OnCommandNotFound(fn CommandNotFoundFunc) Option
- func OnError(fn ErrorFunc) Option
- func PrependCommands(cmds ...interface{}) Option
- func WithCache(c Cache) Option
- func WithContracts(contracts ...Contract) Option
- func WithExecutor(value ExecutorFunc) Option
- func WithMaxDepth(value int) Option
- func WithMenuHeadingFor(fn MenuHeadingForFunc) Option
- func WithMenuTemplate(value *template.Template) Option
- func WithModuleMetadataFilename(value string) Option
- func WithName(value string) Option
- type ShellScriptContract
- type StandaloneExecutableContract
- type SummaryFunc
- type SymlinkError
Constants ¶
const CompleteHelp = `` /* 969-byte string literal not displayed */
CompleteHelp is the help text for the built-in 'complete' command.
const HelpHelp = `` /* 269-byte string literal not displayed */
HelpHelp is the help text for the built-in 'help' command.
const WhichHelp = `` /* 423-byte string literal not displayed */
WhichHelp is the help text for the built-in 'which' command.
Variables ¶
var ErrNotApplicable = errors.New("contract does not apply")
ErrNotApplicable indicates that a contract does not apply to a given file/directory. Discovery will try the next contract in the list.
Functions ¶
func CompleteCommands ¶
CompleteCommands is a CompleteFunc that provides completions for command names. It is used by commands like 'help' and 'which' which expect their arguments to be command names.
func CompleteExec ¶ added in v1.5.0
func CompleteExec(e *Entrypoint, args, env []string) error
CompleteExec implements the 'complete' command.
func CompleteFiles ¶ added in v1.1.1
CompleteFiles is a CompleteFunc that provides completions for files and paths.
func GenerateCompletionScript ¶
GenerateCompletionScript generates a completion script for the given shell ("bash" or "zsh") and writes it to the given writer.
func HelpExec ¶ added in v1.5.0
func HelpExec(e *Entrypoint, args, _ []string) error
HelpExec implements the 'help' command.
func IsEmbedded ¶ added in v1.1.1
IsEmbedded returns true if the given Command is built into the exoskeleton.
func IsModule ¶ added in v1.1.1
IsModule returns true if the given Command is a Module and false if it is not.
func MenuFor ¶ added in v1.6.0
func MenuFor(m Module, opts *MenuOptions) (string, []error)
MenuFor renders a menu of commands for a Module.
func Usage ¶
Usage returns the usage string for the given command. For example, given the Tidy command in the Go CLI, Usage(Tidy) would be 'go mod tidy'.
func UsageRelativeTo ¶
UsageRelativeTo returns the usage string for the given command relative to the given module. For example, given the Tidy command in the Go CLI ('go mod tidy'), UsageRelativeTo(Tidy, Mod) would be 'tidy' and UsageRelativeTo(Tidy, Go) would be 'mod tidy'.
func WhichExec ¶ added in v1.5.0
func WhichExec(e *Entrypoint, args, _ []string) error
WhichExec implements the 'which' command.
Types ¶
type AfterIdentifyFunc ¶ added in v1.6.0
type AfterIdentifyFunc func(*Entrypoint, Command, []string)
AfterIdentifyFunc is a function that is called after a command is identified. It accepts the Command and the remaining arguments that were not used to identify the command.
type Cache ¶ added in v1.9.0
type Cache interface {
// Fetch returns a cached value for the given command and key, or calls compute()
// to generate it. If compute() returns an error, the error is returned and the
// value is not cached.
//
// The key identifies the operation (e.g., "summary", "describe-commands").
// Implementations typically use cmd.Path() combined with key to form a cache key,
// and may use the file's modification time for cache invalidation.
Fetch(cmd Command, key string, compute func() (string, error)) (string, error)
}
Cache provides caching for expensive string-producing operations. Implementations control their own expiry/invalidation logic.
Cache implementations must be safe for concurrent use.
type Command ¶
type Command interface {
// Path returns the location of the executable that defines the command.
// For built-in commands, it returns the path to the entrypoint executable itself.
// It is used by the built-in command 'which'.
Path() string
// Name returns the name of the command.
Name() string
// Parent returns the module that contains the command.
//
// For unnamespaced commands, Parent returns the Entrypoint. For the Entrypoint,
// Parent returns nil.
//
// In the Go CLI, 'go test' and 'go mod tidy' are both commands. If modeled with
// Exoskeleton, 'tidy”s Parent would be the 'mod' Module and 'test”s Parent
// would be the entrypoint itself, 'go'.
Parent() Module
// Exec executes the command.
Exec(e *Entrypoint, args, env []string) error
// Complete asks the command to return completions.
// It is used by the built-in command 'complete' which provides shell completions.
Complete(e *Entrypoint, args, env []string) ([]string, shellcomp.Directive, error)
// Summary returns the (short!) description of the command to be displayed
// in menus.
//
// Returns a CommandError if the command does not fulfill the contract
// for providing its summary.
Summary() (string, error)
// Help returns the help text for the command.
//
// Returns a CommandError if the command does not fulfill the contract
// for providing its help.
Help() (string, error)
}
Command is a command in your CLI application.
In the Go CLI, 'go test' and 'go mod tidy' are both commands. Exoskeleton would model 'test' and 'tidy' as Commands. 'tidy' would be nested one level deeper than 'test', beneath a Module named 'mod'.
type CommandDescribeError ¶ added in v1.4.0
type CommandDescribeError struct{ CommandError }
CommandDescribeError indicates that an executable module did not properly respond to `--describe-commands`
type CommandError ¶
CommandError records an error that occurred with a command's implementation of its interface
func (CommandError) Error ¶
func (e CommandError) Error() string
func (CommandError) Unwrap ¶
func (e CommandError) Unwrap() error
type CommandHelpError ¶ added in v1.4.0
type CommandHelpError struct{ CommandError }
CommandHelpError indicates that a command did not properly implement the interface for providing help
type CommandNotFoundFunc ¶
type CommandNotFoundFunc func(*Entrypoint, Command)
CommandNotFoundFunc is a function that accepts a Null Command object. It is called when a command is not found.
type CommandSummaryError ¶ added in v1.4.0
type CommandSummaryError struct{ CommandError }
CommandSummaryError indicates that a command did not properly implement the interface for providing a summary
type Commands ¶
type Commands []Command
func (Commands) Expand ¶ added in v1.7.0
func (c Commands) Expand(fops ...ExpandOption) (Commands, []error)
Expand returns a list of commands, recursively replacing modules with their subcommands up to a given depth, along with any errors returned by modules' Subcommands().
type CompleteFunc ¶
CompleteFunc is called when an built-in command is asked to supply shell completions.
type Contract ¶ added in v1.8.0
type Contract interface {
// BuildCommand constructs a Command using this contract's rules.
// Returns ErrNotApplicable if this contract doesn't handle the file/directory.
// Returns nil, nil if the file/directory should be ignored (e.g., non-executable file).
BuildCommand(path string, info fs.DirEntry, parent Module, d DiscoveryContext) (Command, error)
}
Contract represents an agreement between Exoskeleton and a command about how the command is discovered.
Contracts are tried in order during discovery. The first contract that doesn't return ErrNotApplicable builds the command.
type DirectoryContract ¶ added in v1.8.0
type DirectoryContract struct {
MetadataFilename string
}
DirectoryContract handles directories containing a module metadata file.
The module metadata filename is configured on the Entrypoint and passed through the discoverer, not stored on the contract itself.
func (*DirectoryContract) BuildCommand ¶ added in v1.8.0
func (c *DirectoryContract) BuildCommand(path string, info fs.DirEntry, parent Module, d DiscoveryContext) (Command, error)
type DiscoveryContext ¶ added in v1.8.0
type DiscoveryContext interface {
DiscoverIn(path string, parent Module) (Commands, []error)
Executor() ExecutorFunc
MaxDepth() int
Next() DiscoveryContext
Cache() Cache
}
type DiscoveryError ¶
DiscoveryError records an error that occurred while discovering commands in a directory.
func (DiscoveryError) Error ¶
func (e DiscoveryError) Error() string
func (DiscoveryError) Unwrap ¶
func (e DiscoveryError) Unwrap() error
type EmbeddedCommand ¶ added in v1.1.0
type EmbeddedCommand struct {
Name string
Summary string
Help string
Exec ExecFunc
Complete CompleteFunc
}
EmbeddedCommand defines a built-in command that can be added to an Entrypoint (as opposed to an executable external to the Entrypoint).
type EmbeddedModule ¶ added in v1.1.0
EmbeddedCommand defines a built-in module that can be added to an Entrypoint (as opposed to a directory external to the Entrypoint).
type Entrypoint ¶
type Entrypoint struct {
// contains filtered or unexported fields
}
Entrypoint is the root of an exoskeleton CLI application.
func New ¶
func New(paths []string, options ...Option) (*Entrypoint, error)
New searches the given paths and constructs an Entrypoint with a list of commands discovered in those paths. It also accepts options that can be used to customize the behavior of the Entrypoint.
func (*Entrypoint) Complete ¶
func (e *Entrypoint) Complete(_ *Entrypoint, args, _ []string) (completions []string, directive shellcomp.Directive, err error)
func (*Entrypoint) Exec ¶
func (e *Entrypoint) Exec(_ *Entrypoint, rawArgs, env []string) error
func (*Entrypoint) GenerateCompletionScript ¶
func (e *Entrypoint) GenerateCompletionScript(shell string, w io.Writer) error
GenerateCompletionScript generates a completion script for the given shell ("bash" or "zsh") and writes it to the given writer.
func (*Entrypoint) Help ¶
func (e *Entrypoint) Help() (string, error)
func (*Entrypoint) Identify ¶
func (e *Entrypoint) Identify(args []string) (Command, []string, error)
Identify identifies the command being invoked.
The function also returns any arguments that were not used to identify the command. For example, if the Go CLI were implemented with Exoskeleton, and we ran it with the rawArgs 'get -u github.com/square/exoskeleton', Identify would return the Get command and the arguments {'-u', 'github.com/square/exoskeleton'}.
If no command is identified, Identify invokes CommandNotFound callbacks and returns NullCommand.
Returns a CommandError if the command does not fulfill the contract for providing its subcommands.
func (*Entrypoint) Name ¶
func (e *Entrypoint) Name() string
func (*Entrypoint) Parent ¶
func (e *Entrypoint) Parent() Module
func (*Entrypoint) Path ¶
func (e *Entrypoint) Path() string
func (*Entrypoint) Subcommands ¶
func (e *Entrypoint) Subcommands() (Commands, error)
func (*Entrypoint) Summary ¶
func (e *Entrypoint) Summary() (string, error)
type ExecFunc ¶
type ExecFunc func(e *Entrypoint, args, env []string) error
ExecFunc is called when an built-in command is run.
type ExecutableContract ¶ added in v1.8.0
type ExecutableContract struct {
}
ExecutableContract handles executables that respond to --describe-commands.
The extension is hardcoded to ".exoskeleton", and the --describe-commands flag is also hardcoded.
func (*ExecutableContract) BuildCommand ¶ added in v1.8.0
func (c *ExecutableContract) BuildCommand(path string, info fs.DirEntry, parent Module, d DiscoveryContext) (Command, error)
type ExecutorFunc ¶ added in v1.6.6
type ExpandOption ¶ added in v1.7.0
type ExpandOption func(*expandOptions)
func WithDepth ¶ added in v1.7.0
func WithDepth(d int) ExpandOption
func WithoutExpandedModules ¶ added in v1.7.0
func WithoutExpandedModules() ExpandOption
type FileCache ¶ added in v1.9.0
type FileCache struct {
// Path is the location of the cache file (e.g., "~/.myapp/cache.json").
Path string
// ExpiresAfter is the maximum age of a cache entry before it's considered stale.
// If zero, entries never expire based on age (only mtime changes invalidate).
ExpiresAfter time.Duration
// contains filtered or unexported fields
}
FileCache is a file-backed cache with mtime-based invalidation and optional TTL expiration. It persists cache entries to a JSON file and invalidates entries when the source file's mtime changes or the TTL expires.
FileCache is safe for concurrent use. It uses singleflight to deduplicate concurrent calls with the same key.
type Menu ¶ added in v1.6.0
type Menu struct {
Usage string
HelpUsage string
Sections MenuSections
}
Menu is the data passed to MenuOptions.Template when it is executed.
type MenuHeadingForFunc ¶
MenuHeadingForFunc is a function that is expected to return the heading under which a command should be listed when it is printed in a menu. (The default value is "COMMANDS".)
It accepts the Module whose subcommands are being listed and the Command whose heading should be returned.
type MenuOptions ¶ added in v1.6.0
type MenuOptions struct {
// Depth describes how recursively a menu should be constructed. Its default
// value is 0, which indicates that the menu should list only the commands
// that are descendants of the module. A value of 1 would list descendants one
// level deep, a value of 2 would list descendants two levels deep, etc. A value
// -1 lists all descendants.
Depth int
// HeadingFor accepts a Command the Module the menu is being prepared for
// and returns a string to use as a section heading for the Command.
// The default function returns "COMMANDS".
HeadingFor MenuHeadingForFunc
// SummaryFor accepts a Command and returns its summary and, optionally, an error.
// The default function invokes Summary() on the provided Command.
SummaryFor SummaryFunc
// Template is executed with the constructed exoskeleton.Menu to render
// help content for a Module.
Template *template.Template
}
MenuOptions are the options that control how menus are constructed for modules.
type MenuSection ¶ added in v1.6.0
type MenuSections ¶ added in v1.6.0
type MenuSections []MenuSection
type Module ¶
type Module interface {
Command
// Subcommands returns the list of Commands that are contained by this module.
//
// For example, in the Go CLI, 'go mod' is a Module and its Subcommands would
// be 'download', 'edit', 'graph', 'init', 'tidy', 'vendor', 'verify', and 'why'.
//
// Returns a CommandError if the command does not fulfill the contract
// for providing its subcommands.
Subcommands() (Commands, error)
}
Module is a namespace of commands in your CLI application.
In the Go CLI, 'go mod tidy' is a command. Exoskeleton would module 'mod' as a Module and 'tidy' as a Command that's nested beneath it.
Executing a Module produces a menu of its subcommands.
type Option ¶
type Option interface {
Apply(*Entrypoint)
}
An Option applies optional changes to an Exoskeleton Entrypoint.
func AfterIdentify ¶ added in v1.6.0
func AfterIdentify(fn AfterIdentifyFunc) Option
AfterIdentify registers a callback (AfterIdentifyFunc) to be invoked with any command that is successfully identified.
func AppendCommands ¶ added in v1.1.0
func AppendCommands(cmds ...interface{}) Option
AppendCommands adds new embedded commands to the Entrypoint. The commands are added to the end of the list and will have the lowest precedence: an executable with the same name as one of these commands would override it.
func OnCommandNotFound ¶
func OnCommandNotFound(fn CommandNotFoundFunc) Option
OnCommandNotFound registers a callback (CommandNotFoundFunc) to be invoked when the command a user attempted to execute is not found. The callback is also invoked when the user asks for help on a command that can not be found.
func OnError ¶
OnError registers a callback (ErrorFunc) to be invoked when a nonfatal error occurs.
These are recoverable errors such as
- a broken symlink is encountered in one of the paths being searched
- a command exits unnsuccessfully when invoked with --summary or --help
func PrependCommands ¶ added in v1.1.0
func PrependCommands(cmds ...interface{}) Option
PrependCommands adds new embedded commands to the Entrypoint. The command are added to the front of the list and will have the highest precedence: an executable with the same name as one of these commands would be overridden by it.
func WithCache ¶ added in v1.9.0
WithCache sets a cache for expensive operations like command execution. If not set, no caching is performed.
func WithContracts ¶ added in v1.8.0
WithContracts sets the contracts used during discovery. Contracts are tried in order; the first that doesn't return ErrNotApplicable builds the command.
The default contracts are:
- DirectoryContract (directories that contain the module metadata file)
- ExecutableContract (executables with .exoskeleton extension which must implement --describe-commands)
- ShellScriptContract (shell scripts with magic comments)
- StandaloneExecutableContract (all other executables which must implement --summary)
func WithExecutor ¶ added in v1.6.6
func WithExecutor(value ExecutorFunc) Option
WithExecutor supplies a function that executes a subcommand. The default executor calls `Run()` on the command and returns the error.
func WithMaxDepth ¶
WithMaxDepth sets the maximum depth of the command tree.
A value of 0 prohibits any submodules. All subcommands are leaves of the Entrypoint. (i.e. If the Go CLI were an exoskeleton, 'go doc' would be allowed, 'go mod tidy' would not.)
A value of -1 (the default value) means there is no maximum depth.
func WithMenuHeadingFor ¶
func WithMenuHeadingFor(fn MenuHeadingForFunc) Option
WithMenuHeadingFor allows you to supply a function that determines the heading a Command should be listed under in the main menu.
func WithMenuTemplate ¶ added in v1.6.0
WithMenuTemplate sets the template that will be used to render help for modules. The template will be executed with an instance of exoskeleton.Menu as its data.
func WithModuleMetadataFilename ¶
WithModuleMetadataFilename sets the filename to use for module metadata. (Default: ".exoskeleton")
type ShellScriptContract ¶ added in v1.8.0
type ShellScriptContract struct{}
ShellScriptContract handles shell scripts with magic comments.
Scripts are detected by checking for a shebang (#!) at the start of the file. They provide metadata via magic comments like "# SUMMARY:" and "# HELP:".
func (*ShellScriptContract) BuildCommand ¶ added in v1.8.0
func (c *ShellScriptContract) BuildCommand(path string, info fs.DirEntry, parent Module, d DiscoveryContext) (Command, error)
type StandaloneExecutableContract ¶ added in v1.8.0
type StandaloneExecutableContract struct{}
StandaloneExecutableContract handles executable files that respond to --summary and --help.
Note: This contract should be ordered AFTER ScriptCommandContract and ExecutableModuleContract in the contract list, as it matches all executable files.
func (*StandaloneExecutableContract) BuildCommand ¶ added in v1.8.0
func (c *StandaloneExecutableContract) BuildCommand(path string, info fs.DirEntry, parent Module, d DiscoveryContext) (Command, error)
type SummaryFunc ¶ added in v1.6.0
SummaryFunc is a function that is expected to return the heading
type SymlinkError ¶
SymlinkError records an error that occurred while following a symlink.
func (SymlinkError) Error ¶
func (e SymlinkError) Error() string
func (SymlinkError) Unwrap ¶
func (e SymlinkError) Unwrap() error
Source Files
¶
- builtin_command.go
- builtin_module.go
- cache.go
- command.go
- commands.go
- complete_cmd.go
- completion_scripts.go
- completions.go
- contract.go
- contract_directory.go
- contract_executable.go
- contract_shell_script.go
- contract_standalone_executable.go
- directory_module.go
- discovery.go
- discovery_error.go
- doc.go
- entrypoint.go
- exec.go
- executable_command.go
- executable_module.go
- help_cmd.go
- identification.go
- menu.go
- module.go
- null_command.go
- options.go
- predicates.go
- suggestions.go
- symlink_error.go
- usage.go
- which_cmd.go