Documentation
¶
Overview ¶
Package console is what an application writes its own commands against.
It is the command side of a project, and it is deliberately small: a Command is a value with a name, a sentence of help and a function, a Application is the slice of them a binary answers to, and an IO is the terminal that function was handed. Nothing here scans a directory, instantiates a class it found or parses a signature string at run time -- a command that is not in the application does not exist, and one that is in it with a broken signature does not build. What lives here is what the project itself runs; generating new files for a project is a separate concern.
The type used to be declared in the skeleton, in routes/console.go, which meant every project had a different nominal Command type and nothing could be written against two of them: neither the framework nor a library could ship a command. It is here now, and it is one type.
Isolation ¶
A command that must not run twice at once names a lock in Command.Isolated, and a Application given an issuer with WithLocks takes it before the command runs. The lock is not prefixed by tenant, and that is deliberate: a lock per tenant would let N replicas each run the task for a different tenant at the same time, which is the problem and not the solution. See cache.Locks and docs/15.
Application holds already-built commands rather than a map of names to construct on demand: a Command here is a value, so there is nothing left to defer.
Index ¶
- Constants
- Variables
- func ArtisanBinary() string
- func CommandMutexName(command Command) string
- func Exit(code int, format string, a ...any) error
- func ExitCode(err error) int
- func ForgetBootstrappers()
- func FormatCommandString(command string) string
- func GuardEnv(env config.Env, want config.Env, action string) error
- func IsProhibited(name string, o *IO, quiet ...bool) bool
- func MustParse(expression string) (string, []Argument, []Option)
- func Parse(expression string) (name string, arguments []Argument, options []Option, err error)
- func PhpBinary() string
- func Prohibit(name string, prohibit ...bool)
- func ResolveAvailabilityUsing(resolver func() bool)
- func ResolveTerminalWidthUsing(resolver func() int)
- func Starting(callback func(*Application))
- func WhenAvailable(callback func())
- type Application
- func (r *Application) Add(commands ...Command) *Application
- func (r *Application) AddCommand(command Command) *Application
- func (r *Application) All() []Command
- func (r *Application) Bootstrap() *Application
- func (r *Application) Call(ctx context.Context, name string, args ...string) error
- func (r *Application) CallSilent(ctx context.Context, name string, args ...string) error
- func (r *Application) CallSilently(ctx context.Context, name string, args ...string) error
- func (r *Application) Find(name string) (Command, bool)
- func (r *Application) Handle(ctx context.Context, args []string) error
- func (r *Application) Has(name string) bool
- func (r *Application) Help() string
- func (r *Application) Names() []string
- func (r *Application) Observe(o Observer) *Application
- func (r *Application) Output() string
- func (r *Application) Resolve(command Command) *Application
- func (r *Application) ResolveCommands(commands ...Command) *Application
- func (r *Application) WithLocks(locks *cache.Locks, ttl time.Duration) *Application
- type Argument
- type ArgumentMode
- type BufferedConsoleOutput
- type CacheCommandMutex
- func (m *CacheCommandMutex) Create(ctx context.Context, command Command) (bool, error)
- func (m *CacheCommandMutex) Exists(ctx context.Context, command Command) (bool, error)
- func (m *CacheCommandMutex) ExpiresAfter(ttl time.Duration) *CacheCommandMutex
- func (m *CacheCommandMutex) Forget(ctx context.Context, command Command) (bool, error)
- func (m *CacheCommandMutex) UseStore(locks *cache.Locks) *CacheCommandMutex
- type Command
- func (c Command) Definition() (name string, arguments []Argument, options []Option, err error)
- func (c Command) Execute(ctx context.Context, o *IO, mutex CommandMutex) error
- func (c Command) Fail(cause error) error
- func (c Command) Handle(ctx context.Context, o *IO) error
- func (c Command) IsHidden() bool
- func (c *Command) SetAliases(aliases ...string) *Command
- func (c *Command) SetDescription(description string) *Command
- func (c *Command) SetHelp(help string) *Command
- func (c *Command) SetHidden(hidden ...bool) *Command
- func (c *Command) SetName(name string) *Command
- type CommandMutex
- type ExitError
- type GeneratorCommand
- func (g GeneratorCommand) AlreadyExists(name string) bool
- func (g GeneratorCommand) BuildClass(name string) string
- func (g GeneratorCommand) GetNameInput(o *IO) string
- func (g GeneratorCommand) GetNamespace(name string) string
- func (g GeneratorCommand) GetPath(name string) string
- func (g GeneratorCommand) Handle(_ context.Context, o *IO) error
- func (g GeneratorCommand) HandleTestCreation(ctx context.Context, app *Application, o *IO, path string) error
- func (g GeneratorCommand) IsReservedName(name string) bool
- func (g GeneratorCommand) MakeDirectory(path string) error
- func (g GeneratorCommand) QualifyClass(name string) string
- func (g GeneratorCommand) ReplaceClass(stub, name string) string
- func (g GeneratorCommand) ReplaceNamespace(stub, name string) string
- func (g GeneratorCommand) SortImports(stub string) string
- type IO
- func (o *IO) Alert(format string, a ...any)
- func (o *IO) Anticipate(question string, choices []string, def string) (string, error)
- func (o *IO) Args() []string
- func (o *IO) Argument(name string) Value
- func (o *IO) Arguments() map[string]Value
- func (o *IO) Ask(question, def string) (string, error)
- func (o *IO) AskQuestion(question, def string) (string, error)
- func (o *IO) AskWithCompletion(question string, choices []string, def string) (string, error)
- func (o *IO) Choice(question string, options []string, def string) (string, error)
- func (o *IO) Comment(format string, a ...any)
- func (o *IO) ConfigurePrompts()
- func (o *IO) Confirm(question string, def bool) (bool, error)
- func (o *IO) ConfirmToProceed(warning string, shouldConfirm bool) (bool, error)
- func (o *IO) Error(format string, a ...any)
- func (o *IO) Flags() *flag.FlagSet
- func (o *IO) GetOutput() io.Writer
- func (o *IO) GetTerminalWidth() int
- func (o *IO) HasArgument(name string) bool
- func (o *IO) HasOption(name string) bool
- func (o *IO) Info(format string, a ...any)
- func (o *IO) Input() *Input
- func (o *IO) Interactive() bool
- func (o *IO) IsDebug() bool
- func (o *IO) IsQuiet() bool
- func (o *IO) IsVerbose() bool
- func (o *IO) IsVeryVerbose() bool
- func (o *IO) Line(format string, a ...any)
- func (o *IO) NewLine(count ...int)
- func (o *IO) NewLineWritten() bool
- func (o *IO) NewLinesWritten() int
- func (o *IO) Option(name string) Value
- func (o *IO) Options() map[string]Value
- func (o *IO) OutputComponents() *components.Factory
- func (o *IO) Progress(total int) *Progress
- func (o *IO) PromptForMissingArguments() error
- func (o *IO) Question(format string, a ...any)
- func (o *IO) Secret(question string) (string, error)
- func (o *IO) SetBase(base string)
- func (o *IO) SetInput(in *Input)
- func (o *IO) SetOutput(out io.Writer)
- func (o *IO) SetVerbosity(level Verbosity)
- func (o *IO) SetVerbosityNamed(level string)
- func (o *IO) Table(headers []string, rows [][]string)
- func (o *IO) Task(description string, fn func() error) error
- func (o *IO) Trap(callback func(os.Signal), signals ...os.Signal)
- func (o *IO) TwoColumnDetail(left, right string)
- func (o *IO) Untrap()
- func (o *IO) Verbosity() Verbosity
- func (o *IO) Warn(format string, a ...any)
- func (o *IO) WithProgressBar(totalSteps int, callback func(bar *Progress) error) error
- func (o *IO) Write(message string, verbosity ...Verbosity)
- func (o *IO) Writeln(message string, verbosity ...Verbosity)
- type Input
- func (in *Input) Argument(name string) Value
- func (in *Input) Arguments() map[string]Value
- func (in *Input) Definition() ([]Argument, []Option)
- func (in *Input) HasArgument(name string) bool
- func (in *Input) HasOption(name string) bool
- func (in *Input) Interactive() bool
- func (in *Input) Option(name string) Value
- func (in *Input) Options() map[string]Value
- func (in *Input) Parse(argv []string) error
- func (in *Input) SetInteractive(interactive bool)
- type MigrationGeneratorCommand
- type Observer
- type Option
- type OptionMode
- type Progress
- type QuestionHelper
- type Run
- type Signals
- type TaskResult
- type Value
- type Verbosity
Constants ¶
const ( // VerbosityQuiet writes nothing at all: -q. VerbosityQuiet = components.VerbosityQuiet // VerbosityNormal is the default. VerbosityNormal = components.VerbosityNormal // VerbosityVerbose is -v. VerbosityVerbose = components.VerbosityVerbose // VerbosityVeryVerbose is -vv. VerbosityVeryVerbose = components.VerbosityVeryVerbose // VerbosityDebug is -vvv. VerbosityDebug = components.VerbosityDebug )
The five levels, re-exported so a command that already imports console does not have to import the components package to name one.
const ( // TaskSuccess prints DONE. TaskSuccess = view.TaskSuccess // TaskFailure prints FAIL. TaskFailure = view.TaskFailure // TaskSkipped prints SKIPPED. TaskSkipped = view.TaskSkipped )
The three outcomes a task line reports.
const DefaultIsolationTTL = time.Hour
DefaultIsolationTTL is how long an isolation lock lives when the command does not say.
It is the deadlock protection and nothing else: a process that dies holding the lock releases it when this runs out, so it is sized above the longest isolated command rather than tight against the usual one.
Variables ¶
var ErrManuallyFailed = errors.New("command failed manually")
ErrManuallyFailed is what Fail throws when it is given no reason.
The command decided it was done and wrong, and there is nothing more to say than that.
Functions ¶
func ArtisanBinary ¶
func ArtisanBinary() string
ArtisanBinary is the console entry point.
A compiled binary is both the interpreter and the script, so there is no second file to name and this is empty -- FormatCommandString drops it.
func CommandMutexName ¶
CommandMutexName is the key one command's isolation lock is stored under.
It is keyed on Isolated rather than on the name when the command sets one, which is what lets two commands that must not overlap each other share a lock -- a nightly reconciliation and the manual command that does the same work name the same lock, and neither starts while the other holds it.
The key carries no tenant, and that is deliberate: a lock per tenant would let N replicas each run the command for a different tenant at the same time, which is the problem and not the solution. See docs/15.
func Exit ¶
Exit returns an error that ends the command with a status of its own.
The status is what a caller scripts against -- a pipeline that tells a failure it can retry from one it cannot -- so it is worth choosing. Use 1 when there is nothing to distinguish.
func ExitCode ¶
ExitCode is the status a binary should exit with, given what the command returned.
Nil is zero, an ExitError anywhere in the chain is its code, and anything else is 1. It is the whole of what an entry point needs:
if err := registry.Handle(ctx, os.Args[1:]); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(console.ExitCode(err))
}
func ForgetBootstrappers ¶
func ForgetBootstrappers()
ForgetBootstrappers drops every callback Starting registered.
It exists for the test that must not inherit what an earlier one registered.
func FormatCommandString ¶
FormatCommandString turns a command name into a line a shell can run.
The binary path, the script path and the command name are joined with spaces; the script path here is always empty, and an empty part is dropped, so what comes out is "/path/to/app schedule:finish" rather than a line with a hole in the middle.
func GuardEnv ¶
GuardEnv refuses an action outside the environment it belongs to.
It is the check that stands between `migrate:fresh` and a production database: it refuses outright, because a prompt is answered by whoever is at the keyboard and a deploy pipeline has nobody there.
action is what would have happened, in the imperative, so the message reads as a sentence: GuardEnv(cfg.App.Env, config.EnvDev, "dropping every table").
func IsProhibited ¶
IsProhibited reports whether the command was prohibited, and says so on the terminal unless it was asked to be quiet.
func MustParse ¶
MustParse is Parse for a signature written in source.
It panics, which is right for a constant: a command whose signature does not parse must not reach the registry, and there is nobody to hand the error to at the point a package-level Command is built.
func Parse ¶
Parse reads a command signature into a name, its arguments and its options.
The syntax is:
mail:send {user} a required argument
mail:send {user?} an optional one
mail:send {user=guest} optional, with a default
mail:send {user*} required, and takes every operand left
mail:send {user?*} the same, but may be empty
mail:send {user=*a,b} an array with two defaults
mail:send {--queue} a boolean flag
mail:send {--queue=} a flag that takes a value
mail:send {--queue=default} the same, with a default
mail:send {--queue=*} a flag that may be repeated
mail:send {--Q|queue=} the same, with a shortcut
mail:send {user : The user ID} a description, after a spaced colon
func PhpBinary ¶
func PhpBinary() string
PhpBinary returns the path to the binary a command runs under.
Go compiles, so there is no interpreter separate from the program: the binary is both, and this is the whole of what FormatCommandString needs to build a command line that runs this binary again.
func Prohibit ¶
Prohibit stops a command from running, or lets it run again.
Calling it with no second value prohibits. It is what a test suite calls so a destructive command cannot fire from inside a test run.
func ResolveAvailabilityUsing ¶
func ResolveAvailabilityUsing(resolver func() bool)
ResolveAvailabilityUsing sets how availability is decided.
It is what a test uses to run the trap path on a platform where taking a signal would kill the test binary.
func ResolveTerminalWidthUsing ¶
func ResolveTerminalWidthUsing(resolver func() int)
ResolveTerminalWidthUsing sets the width for the whole package, rather than once per listing command.
It tells the package how wide the terminal is. It exists for the test that has to pin the width: a two column line and a task line both size themselves to it, and a test whose assertions move with the window it was run in is a test nobody trusts. Passing nil puts the default back.
func Starting ¶
func Starting(callback func(*Application))
Starting registers a callback run against every application that is built after it.
It is where a package that ships commands adds them without the entry point having to name it.
func WhenAvailable ¶
func WhenAvailable(callback func())
WhenAvailable runs the callback if signal handling is available.
Types ¶
type Application ¶
type Application struct {
// contains filtered or unexported fields
}
Application is everything one binary answers to on the command line.
It replaces the switch of a dozen cases that every project copied into its own bootstrap: the built-in commands and the application's own are the same kind of value in the same map, so neither can shadow the other by accident and both are listed by the same help.
An Application is built at wiring time and read afterwards. It is not safe to Add to one while another goroutine is dispatching against it.
func NewApplication ¶
func NewApplication(out, errOut io.Writer, in io.Reader) *Application
NewApplication returns an empty application over three streams.
The streams are given rather than taken from the process, because a application that reaches for os.Stdout is a application no test can read the output of. The entry point passes os.Stdout, os.Stderr and os.Stdin; a test passes buffers.
func (*Application) Add ¶
func (r *Application) Add(commands ...Command) *Application
Add registers commands and returns the application, so wiring reads as one expression.
A command with no name, no Run, or a name that is already taken is a mistake made at wiring time and it panics, the way a duplicate route pattern does: the alternative is a binary that starts and silently answers the wrong thing.
func (*Application) AddCommand ¶
func (r *Application) AddCommand(command Command) *Application
AddCommand registers one command.
Add is the same thing for many.
func (*Application) All ¶
func (r *Application) All() []Command
All returns every registered command, hidden ones included, sorted by name.
Names is the same list with the hidden ones left out, which is what a person is offered.
func (*Application) Bootstrap ¶
func (r *Application) Bootstrap() *Application
Bootstrap runs the registered callbacks against this application.
It is a separate call from NewApplication: a constructor that runs arbitrary callbacks is a constructor no test can build quietly.
func (*Application) Call ¶
Call runs a registered command from inside another one.
It is how a composite command is written -- `migrate:fresh` calling `migrate` -- without either of them being a function that the listing does not know about.
The output goes where the caller's does and is buffered on the way past, so Output can hand it back.
func (*Application) CallSilent ¶
CallSilent is Call with the output thrown away.
The error is not: what it silences is the chatter of a command being used as a step, never the reason it failed.
func (*Application) CallSilently ¶
CallSilently is an alias of CallSilent.
func (*Application) Find ¶
func (r *Application) Find(name string) (Command, bool)
Find returns one registered command.
The second return is false when there is none.
func (*Application) Handle ¶
func (r *Application) Handle(ctx context.Context, args []string) error
Handle dispatches one command line: the name, then its arguments.
No arguments, or help, prints the listing. A name that is not registered is an ExitError that lists what was, because an error that only says the command is unknown costs a search and this one ends it.
func (*Application) Has ¶
func (r *Application) Has(name string) bool
Has reports whether a command with that name is registered.
func (*Application) Help ¶
func (r *Application) Help() string
Help renders the listing.
It is sorted by name and not by registration order, so the group prefix does the grouping -- every make: together, every migrate: together -- and adding a command cannot move an unrelated one.
func (*Application) Names ¶
func (r *Application) Names() []string
Names lists the commands a person can be told about, sorted.
Hidden commands are not in it: it is what an error message offers instead of the name that was not found.
func (*Application) Observe ¶
func (r *Application) Observe(o Observer) *Application
Observe adds an observer. They run in the order they were added.
func (*Application) Output ¶
func (r *Application) Output() string
Output is what the last command Call ran printed.
It empties the buffer, so a second read with nothing run between them is empty rather than the same text twice.
func (*Application) Resolve ¶
func (r *Application) Resolve(command Command) *Application
Resolve registers a command and returns the application.
The command given is already the value that will run: there is no class name to look up or construct.
func (*Application) ResolveCommands ¶
func (r *Application) ResolveCommands(commands ...Command) *Application
ResolveCommands registers many.
func (*Application) WithLocks ¶
func (r *Application) WithLocks(locks *cache.Locks, ttl time.Duration) *Application
WithLocks gives the application the issuer that isolated commands take a lock from, and the time to live of that lock.
One ttl covers every isolated command, and it is sized above the longest of them: it is the deadlock protection, and a process that dies holding a lock releases it only when the ttl runs out.
Without this, an isolated command is refused rather than run unprotected -- a command that says it must not overlap and then does is worse than one that does not start.
type Argument ¶
type Argument struct {
// Name is what argument() is called with.
Name string
// Mode is the bit field above.
Mode ArgumentMode
// Description is the sentence after " : " in the signature. It is what the
// help renders next to the name.
Description string
// Default is what stands in when the argument was left out.
//
// It is a slice and not a string because "{ids=1,2}" has two of them and
// "{name=guest}" has one: a scalar argument reads Default[0] and an array
// argument reads all of it.
Default []string
}
Argument is one positional operand of a command.
The signature "{user}" is one of these, and so is "{ids?*}".
func (Argument) IsRequired ¶
IsRequired reports whether the command refuses to run without the argument.
type ArgumentMode ¶
type ArgumentMode int
ArgumentMode is what a positional argument accepts.
It is a bit field: an array argument that is also required is ArgumentIsArray|ArgumentRequired.
const ( // ArgumentRequired means the command does not run without it. ArgumentRequired ArgumentMode = 1 // ArgumentOptional means it may be left out, and Default stands in. ArgumentOptional ArgumentMode = 2 // ArgumentIsArray means it swallows every remaining operand. ArgumentIsArray ArgumentMode = 4 )
type BufferedConsoleOutput ¶
type BufferedConsoleOutput struct {
// contains filtered or unexported fields
}
BufferedConsoleOutput keeps everything written to it and passes it on.
The point is both halves at once: a command run from inside another one has to reach the terminal -- otherwise the person watching sees a silent pause -- and the caller has to be able to read what it said, which is what Application.Output returns.
It is safe for concurrent use, because a command that runs work in parallel writes from more than one goroutine and a torn buffer is worse than a slow one.
func NewBufferedConsoleOutput ¶
func NewBufferedConsoleOutput(out io.Writer) *BufferedConsoleOutput
NewBufferedConsoleOutput returns the output.
out is where the bytes go on their way past, and nil means nowhere: that is the buffer-only form a test uses.
func (*BufferedConsoleOutput) Fetch ¶
func (b *BufferedConsoleOutput) Fetch() string
Fetch empties the buffer and returns what was in it.
A second Fetch with nothing written between them returns empty, which is what lets one buffer serve a sequence of commands without the second reading the first's output.
type CacheCommandMutex ¶
type CacheCommandMutex struct {
// contains filtered or unexported fields
}
CacheCommandMutex is the CommandMutex over the cache locks.
It is the one the framework wires: the lock is the same one cache.Locks issues, so an isolated command and a scheduled task contend through one mechanism rather than two.
func NewCacheCommandMutex ¶
func NewCacheCommandMutex(locks *cache.Locks) *CacheCommandMutex
NewCacheCommandMutex returns a mutex over the given lock issuer.
func (*CacheCommandMutex) Create ¶
Create takes the mutex for the command.
A lock another process holds is false and no error: that is the mutex working.
func (*CacheCommandMutex) Exists ¶
Exists reports true for the lock this process took, without going to the store to check.
For a lock this process does not hold, it acquires the lock to find out and releases it again immediately.
func (*CacheCommandMutex) ExpiresAfter ¶
func (m *CacheCommandMutex) ExpiresAfter(ttl time.Duration) *CacheCommandMutex
ExpiresAfter sets how long a lock this mutex takes lives.
func (*CacheCommandMutex) Forget ¶
Forget releases the mutex this process took.
A mutex this process does not hold is left alone and reported as not released: releasing it would be releasing somebody else's.
func (*CacheCommandMutex) UseStore ¶
func (m *CacheCommandMutex) UseStore(locks *cache.Locks) *CacheCommandMutex
UseStore points the mutex at another lock issuer.
type Command ¶
type Command struct {
// Name is what the person types. Lowercase, with a colon for the group:
// "invoice:close". It is the key of the registry, so it is unique.
//
// A command that sets Signature may leave this empty: the name is taken to
// be the first word of the signature.
Name string
// Signature is the name, the arguments and the options in one string:
//
// mail:send {user : The user ID} {--queue= : The queue to push onto}
//
// Parse is what reads it. A command that declares one gets an Input on its
// IO, so Argument and Option have something to read; a command that does
// not gets the raw arguments and nothing else.
//
// A signature that does not parse panics at wiring time rather than failing
// on the run: a binary must not start with a command nobody can call.
Signature string
// Help is the long explanation, printed when the command is asked about
// rather than run.
Help string
// Aliases are other names the same command answers to. They are listed
// beside the name in the help and are not separate entries.
Aliases []string
// Description is the one line the listing prints next to the name. One
// sentence, no full stop, in the imperative: "close the open invoices".
Description string
// Hidden keeps the command out of the listing and out of Names. It still
// runs when it is asked for by name. It is for a command that exists to be
// called by another program -- a health probe, an internal hook -- and that
// would only be noise to a person reading the list.
Hidden bool
// Isolated names the lock this command takes before it runs, and empty
// means it may run as many times at once as it is started.
//
// It is a name rather than a bool so two commands that must not overlap
// each other can share one -- a nightly reconciliation and the manual
// command that does the same work name the same lock, and neither starts
// while the other holds it.
//
// A registry without an issuer refuses to run an isolated command rather
// than running it unprotected: see Registry.WithLocks.
Isolated string
// Run does the work.
//
// It receives the context of the process and the terminal it was started
// from. The arguments that followed the command name are on the IO, so
// that a command that takes none has nothing to ignore.
Run func(ctx context.Context, o *IO) error
}
Command is one command a binary answers to.
It is a value, not an interface and not a class discovered by scanning a directory. What that buys is that the console listing and the compiler read the same slice: a command missing from it does not exist, and one in it with a broken Run does not build.
The collaborators a command needs -- a repository, a mailer, a queue -- are captured by the closure that Run is, built where the application is wired. A command that reaches for a global is a command no test can pin.
func (Command) Definition ¶
Definition parses the command's signature.
A command with no signature has no arguments and no options, and its name is Name.
func (Command) Execute ¶
Execute runs the command, holding its isolation mutex when it has one.
The order is the isolated check first, the work second, and the mutex released whatever happened. A command whose mutex is already held says so and reports success, because the work is being done -- which is what keeps a cron entry from paging somebody every minute.
mutex may be nil, and then an isolated command is refused rather than run unprotected: a command that says it must not overlap and then does is worse than one that does not start.
func (Command) Fail ¶
Fail ends the command with a reason of its own.
Passing nothing is ErrManuallyFailed; passing an error wraps it, so errors.Is finds both.
func (Command) Handle ¶
Handle does the command's own work.
What Execute adds around it is the isolation mutex.
func (*Command) SetAliases ¶
SetAliases sets the other names the command answers to.
func (*Command) SetDescription ¶
SetDescription sets the line the listing prints beside the name.
type CommandMutex ¶
type CommandMutex interface {
// Create takes the mutex, and reports whether it got it.
Create(ctx context.Context, command Command) (bool, error)
// Exists reports whether the mutex is held.
Exists(ctx context.Context, command Command) (bool, error)
// Forget releases the mutex.
Forget(ctx context.Context, command Command) (bool, error)
}
CommandMutex is what stops a command from running twice at once.
Every method takes a context, because a lock is a round trip to a store, and every method returns an error alongside the bool, because that round trip can fail and a mutex that reports "not held" when it could not ask is a mutex that lets both copies run.
type ExitError ¶
type ExitError struct {
// Code is the process status. It is never zero: an ExitError that exited
// successfully is a contradiction, and Exit refuses to build one.
Code int
// Message is what to print. It may be empty, for the command that already
// said everything it had to say and only wants the status changed.
Message string
}
ExitError is an error that also says what the process should exit with.
A command returns an error and the binary decides the status: os.Exit inside a command is a command no test can call, and a command that only returns an error can never say more than "1". This carries both, and ExitCode is how the entry point reads it back.
type GeneratorCommand ¶
type GeneratorCommand struct {
// Type is what is being generated, for the messages: "Model", "Policy".
Type string
// Stub is the template. The placeholders are {{ package }}, {{ class }} and
// {{ rootModule }}, in both the spaced and the unspaced spelling, which is
// the pair replaceNamespace looks for.
Stub string
// RootModule is the module path of the application, which a generated file
// imports itself relative to: "github.com/acme/app".
RootModule string
// BasePath is the directory the application's source lives in. A generated
// name is resolved against it.
BasePath string
// DefaultDirectory is where this generator's files go when the name carries
// no directory of its own: "models", "policies".
DefaultDirectory string
}
GeneratorCommand writes a new source file from a stub.
It is a value that each generator fills in, because the two things that vary between generators -- the stub and the type name -- are data, not behaviour.
func (GeneratorCommand) AlreadyExists ¶
func (g GeneratorCommand) AlreadyExists(name string) bool
AlreadyExists reports whether the file is already there.
func (GeneratorCommand) BuildClass ¶
func (g GeneratorCommand) BuildClass(name string) string
BuildClass renders the stub for a name.
The package is substituted first, then the type name.
func (GeneratorCommand) GetNameInput ¶
func (g GeneratorCommand) GetNameInput(o *IO) string
GetNameInput reads the name the command was given.
A trailing extension is dropped: somebody who typed the file name meant the type.
func (GeneratorCommand) GetNamespace ¶
func (g GeneratorCommand) GetNamespace(name string) string
GetNamespace is the package a generated file belongs to.
It is everything before the last separator. A name with no separator is the root package of the application.
func (GeneratorCommand) GetPath ¶
func (g GeneratorCommand) GetPath(name string) string
GetPath is where a generated file is written.
The file is named after the type, in snake case, which is what a Go file is called.
func (GeneratorCommand) Handle ¶
func (g GeneratorCommand) Handle(_ context.Context, o *IO) error
Handle generates the file.
The order is the reserved name check first, so nothing is written when the name would not compile; then the already-exists check, unless --force; then the directory, the file and the message.
func (GeneratorCommand) HandleTestCreation ¶
func (g GeneratorCommand) HandleTestCreation(ctx context.Context, app *Application, o *IO, path string) error
HandleTestCreation writes the matching test, when it was asked for.
The path is the generated file's, with _test appended, written by calling the same make:test a person would run by hand.
func (GeneratorCommand) IsReservedName ¶
func (g GeneratorCommand) IsReservedName(name string) bool
IsReservedName reports whether the name is a word the language owns.
The comparison is case-insensitive.
func (GeneratorCommand) MakeDirectory ¶
func (g GeneratorCommand) MakeDirectory(path string) error
MakeDirectory creates the directory a file is about to be written into.
func (GeneratorCommand) QualifyClass ¶
func (g GeneratorCommand) QualifyClass(name string) string
QualifyClass turns what was typed into the full name of what is generated.
A name that already carries a directory keeps it, and one that does not gets the generator's default.
func (GeneratorCommand) ReplaceClass ¶
func (g GeneratorCommand) ReplaceClass(stub, name string) string
ReplaceClass writes the type name into the stub.
func (GeneratorCommand) ReplaceNamespace ¶
func (g GeneratorCommand) ReplaceNamespace(stub, name string) string
ReplaceNamespace writes the package and the module path into the stub.
Both spellings of each placeholder are replaced.
func (GeneratorCommand) SortImports ¶
func (g GeneratorCommand) SortImports(stub string) string
SortImports puts a generated import block in order.
A stub that assembles its imports from replacements ends up with them in the order the replacements ran, and a file whose imports are not sorted is a file the formatter rewrites on the first save.
type IO ¶
type IO struct {
// contains filtered or unexported fields
}
IO is the terminal a command was started from.
It carries the three streams, the arguments that followed the command name and the flag set built from them. Everything a command prints or asks goes through it, which is what makes a command testable: give it a bytes.Buffer and a strings.Reader and the whole conversation is a value.
An IO is not safe for concurrent use. It is the handle of the one goroutine running the command.
func NewIO ¶
NewIO returns the terminal for one command.
The registry builds one per run, and a test builds one directly: that is the supported way to run a command's Run function without a process.
name is what the command is called, and it is what the flag set reports in a usage message. args is what followed the command name, unparsed.
func (*IO) Anticipate ¶
Anticipate puts a question and offers the choices as suggestions, without requiring one of them.
func (*IO) Args ¶
Args are the arguments that followed the command name.
Before the flag set is parsed they are all of them; after, they are what is left, which is what a command that takes both flags and operands wants.
func (*IO) Argument ¶
Argument returns the value of one argument.
A command with no signature has no arguments, and this returns the zero Value rather than failing.
func (*IO) Ask ¶
Ask puts a question and returns the answer, or def when the answer is empty.
The prompt is written and the answer read in one place, so a command cannot ask in a shape the rest of the output does not use.
func (*IO) AskQuestion ¶
AskQuestion puts a question and returns the answer, or def when the answer is empty.
It is the one place a question is written and an answer is read: Ask, Anticipate and AskWithCompletion all end up here.
A command that may not prompt -- --no-interaction, or a pipeline with nobody at the keyboard -- takes the default and does not wait. Waiting is how a deploy hangs at three in the morning.
func (*IO) AskWithCompletion ¶
AskWithCompletion puts a question and lists what the answer is likely to be.
The completion is offered rather than enforced: an answer that is not in the list is returned as typed, which is the difference between this and Choice.
choices is a slice rather than a callback: a caller that wants a computed list builds the slice before it asks.
func (*IO) Choice ¶
Choice offers a numbered list and returns the option that was picked.
A wrong answer is asked again, however many times it takes; only one option ever comes back.
It accepts the number or the option itself, because the number is what is quick to type and the option is what is in the person's head.
func (*IO) Comment ¶
Comment is an aside, the detail that helps but is not the answer. It is dimmed rather than coloured.
func (*IO) ConfigurePrompts ¶
func (o *IO) ConfigurePrompts()
ConfigurePrompts decides whether the command may ask anything.
A command run with --no-interaction, or with nothing on its input, does not ask -- it takes the default and carries on. A prompt in a deploy pipeline is a pipeline that hangs.
func (*IO) Confirm ¶
Confirm asks a yes or no question, and keeps asking until it gets one.
An answer that is neither is a typo, not a no: acting on it would be acting on something the person did not say.
func (*IO) ConfirmToProceed ¶
ConfirmToProceed asks before a destructive command runs.
The order is --force checked first, which skips the question; then the warning rendered as an alert; then the question put with a default of no. An answer that is not yes prints "Command cancelled." and returns false, and the command returns without doing anything.
shouldConfirm is passed rather than read from the environment, because a package that reaches for the environment is a package no test can put in either one.
It is the interactive guard. GuardEnv in exit.go is the other one: it refuses outright, for the pipeline where there is nobody at the keyboard to answer.
func (*IO) Error ¶
Error writes to the error stream, prefixed, for what went wrong.
Returning the error is still what ends the command: this only says it out loud, for the case where the command carries on and reports at the end.
func (*IO) Flags ¶
Flags returns the flag set of this command, created on first use.
The command declares its options on it and parses the arguments:
fs := o.Flags()
force := fs.Bool("force", false, "overwrite what is already there")
if err := fs.Parse(o.Args()); err != nil {
return err
}
It is ContinueOnError, so a bad flag is an error the command returns rather than an os.Exit inside a library, and its usage goes to the error stream.
func (*IO) GetTerminalWidth ¶
GetTerminalWidth is resolved once for the package rather than copied into each listing command.
It is how many columns there are to fill. The resolver wins when one is installed; otherwise it is COLUMNS, which is what a shell exports and what a CI runner sets, and the default when that is missing or nonsense.
func (*IO) HasArgument ¶
HasArgument reports whether the argument is declared in the signature.
func (*IO) Input ¶
Input returns the command line bound to the signature, or nil when the command declared none.
func (*IO) Interactive ¶
Interactive reports whether the command may prompt.
A command with no signature may: it was started from a terminal and nobody said otherwise.
func (*IO) IsVeryVerbose ¶
IsVeryVerbose reports whether the command was told to say much more, which is -vv.
func (*IO) Line ¶
Line writes one line to the output, verbatim.
It is the default, and it is what carries the answer the command was run for. Info, Comment, Warn and Error differ from it in emphasis and in stream, never in content: when the output is not a terminal, Line, Info and Comment are the same bytes.
func (*IO) NewLine ¶
NewLine writes count blank lines to the output, and one when count is left out.
func (*IO) NewLineWritten ¶
NewLineWritten reports whether the last write ended a line.
It is here because NewLineAware declares it.
func (*IO) NewLinesWritten ¶
NewLinesWritten is how many line endings the last write left behind.
func (*IO) OutputComponents ¶
func (o *IO) OutputComponents() *components.Factory
OutputComponents is the component set the command renders with.
It is built on first use, over this IO.
func (*IO) Progress ¶
Progress starts a bar over total steps.
On a terminal it redraws one line as the work advances. Everywhere else it writes nothing until Finish, because a progress bar written into a log file is a thousand lines of the same sentence.
func (*IO) PromptForMissingArguments ¶
PromptForMissingArguments asks for the required arguments that were left out.
It is what turns `make:model` with no name into a question rather than an error, and it asks only for what the signature says is required -- an optional argument has a default, and asking for it would be asking a question with a known answer.
It does nothing when the command may not prompt, so the pipeline gets the error and the person gets the question.
func (*IO) Question ¶
Question writes a line in the style a question is asked in.
It writes, it does not ask: Ask is what waits for an answer.
func (*IO) Secret ¶
Secret asks for a value the terminal must not show: a password, a token.
When the input is a terminal the echo is turned off for the duration and put back afterwards, whatever happens. When it is not -- a pipe, a test, a CI job -- there is no echo to turn off and the value is read as it comes.
A terminal that will not stop echoing is an error rather than a value typed in the clear, because a secret on the screen is a secret in the scrollback.
func (*IO) SetBase ¶
SetBase records the application root, which the components strip from a path before printing it.
func (*IO) SetOutput ¶
SetOutput points the command at another stream.
The component factory is dropped with it, so the next OutputComponents call builds one over the new stream rather than going on writing to the old one.
func (*IO) SetVerbosityNamed ¶
SetVerbosityNamed sets the level from one of the words: v, vv, vvv, quiet, normal.
An unknown word leaves the level alone: a typo in a log call must not silence the command.
func (*IO) Table ¶
Table writes rows under headers, in columns that line up, with no border drawn.
The columns are sized to the content, which is what makes the output usable in a terminal and greppable out of one. A row shorter than the headers is padded, and a longer one keeps its extra cells: dropping them would hide the data the command was run to see.
func (*IO) Task ¶
Task runs fn and reports whether it worked, on one line, with no run time printed.
The line is written when fn returns, so the outcome is on the same line as the description. The error is returned unchanged: this reports it, it does not swallow it.
func (*IO) Trap ¶
Trap runs the callback when any of the signals arrives.
The registrar is created on first use and lives on the IO, which is one command's handle: Untrap is what the run calls when the command returns.
func (*IO) TwoColumnDetail ¶
TwoColumnDetail writes a label on the left and its value on the right, joined by dots.
It is the shape a status listing takes -- a migration and whether it ran, a scheduled task and when it runs next -- and having it here is what keeps every command from inventing its own padding.
func (*IO) Warn ¶
Warn writes to the error stream, prefixed, for something that is off but did not stop the command.
The prefix is there and not only the colour, because a warning that is invisible once the output is piped is a warning nobody acts on.
func (*IO) WithProgressBar ¶
WithProgressBar runs the work while a bar advances over totalSteps.
The bar is started before the callback and finished after it, whether or not the callback failed, so a bar never outlives the work it was drawn for.
A Go method cannot be generic, so ranging over a collection is left to the caller: it advances the bar once per element by calling Advance from inside the callback.
type Input ¶
type Input struct {
// contains filtered or unexported fields
}
Input is the command line, bound to what a command declared it accepts.
It is here and not in a package of its own because a definition with nothing to bind it to is a definition nobody can read back.
func NewInput ¶
NewInput returns the input for a definition, before anything is parsed.
Every declared argument and option starts on its default, so a command that reads one it did not receive gets what the signature said rather than an error at the point of use.
func (*Input) Argument ¶
Argument returns the value of a declared argument by name, or a zero Value if none was declared with that name.
func (*Input) Definition ¶
Definition returns the arguments and options the input was built from, so the help can render them without a second copy of the signature.
func (*Input) HasArgument ¶
HasArgument reports whether the argument is declared.
It reports declared, not given.
func (*Input) HasOption ¶
HasOption reports whether the option is declared in the command signature.
func (*Input) Interactive ¶
Interactive reports whether the command may prompt.
func (*Input) Option ¶
Option returns the value of a declared option by name, or a zero Value if none was declared with that name.
func (*Input) Parse ¶
Parse is the token loop that binds a command line to the definition.
argv is what followed the command name. Everything after a bare "--" is an operand, however many dashes it starts with, which is how a value that looks like a flag reaches the command that wants it.
func (*Input) SetInteractive ¶
SetInteractive turns prompting on or off, which is what --no-interaction does and what a command calling another passes on.
type MigrationGeneratorCommand ¶
type MigrationGeneratorCommand struct {
// MigrationTableName is the table the migration creates.
MigrationTableName string
// MigrationStub is the template. Its one placeholder is {{table}}.
MigrationStub string
// MigrationPath is the directory migrations are written to.
MigrationPath string
// Now returns the timestamp a migration is named with. Nil means the
// process clock, and a test passes a fixed one.
Now func() string
}
MigrationGeneratorCommand writes a migration for one table.
The framework's own tables -- the cache table, the queue table, the session table -- each ship one of these, and the only thing that differs between them is the table name and the stub.
func (MigrationGeneratorCommand) CreateBaseMigration ¶
func (m MigrationGeneratorCommand) CreateBaseMigration() (string, error)
CreateBaseMigration writes the file and returns its path.
func (MigrationGeneratorCommand) Handle ¶
func (m MigrationGeneratorCommand) Handle(_ context.Context, o *IO) error
Handle generates the migration.
An existing migration is an error rather than an overwrite, because the one that is there may already have run somewhere.
func (MigrationGeneratorCommand) MigrationExists ¶
func (m MigrationGeneratorCommand) MigrationExists() (bool, error)
MigrationExists reports whether a migration for the table is already there.
The glob is on the suffix, so a migration written yesterday under a different timestamp still counts.
func (MigrationGeneratorCommand) ReplaceMigrationPlaceholders ¶
func (m MigrationGeneratorCommand) ReplaceMigrationPlaceholders(path string) error
ReplaceMigrationPlaceholders writes the stub into the file, table name substituted.
type Observer ¶
type Observer func(Run)
Observer is told about a command that has finished.
It is where a Collector, a metric or an audit line hangs. It runs after the command returns, on the same goroutine, so an observer that blocks holds the process: keep it to a record and a write.
type Option ¶
type Option struct {
// Name is what option() is called with, without the leading dashes.
Name string
// Shortcut is the single letter form, without the dash, or empty. It comes
// from the "Q|queue=" spelling in a signature.
Shortcut string
// Mode is the bit field above.
Mode OptionMode
// Description is the sentence after " : " in the signature.
Description string
// Default is what stands in when the flag was given without a value, for
// the same reason Argument.Default is a slice.
Default []string
}
Option is one --flag of a command.
func TestOptions ¶
TestOptions are the flags a generator adds when it can write a matching test.
Go has one test runner, so there is one flag: the three-way choice a language with several runners would need does not exist here.
func (Option) AcceptValue ¶
AcceptValue reports whether the option takes a value at all.
func (Option) IsValueRequired ¶
IsValueRequired reports whether --name must be followed by a value.
type OptionMode ¶
type OptionMode int
OptionMode is what a --flag accepts.
A signature has no syntax for a value that is required, but the constant is here because a definition a command builds by hand can use it.
const ( // OptionValueNone is a boolean flag: --force. OptionValueNone OptionMode = 1 // OptionValueRequired means --queue without a value is an error. OptionValueRequired OptionMode = 2 // OptionValueOptional means --queue may stand alone, and Default stands in. OptionValueOptional OptionMode = 4 // OptionValueIsArray means the flag may be repeated. OptionValueIsArray OptionMode = 8 )
type Progress ¶
type Progress struct {
// contains filtered or unexported fields
}
Progress is one progress bar.
type QuestionHelper ¶
type QuestionHelper struct{}
QuestionHelper writes the prompt a question is asked with.
It renders the question in the shape the components use, and that is the whole of it -- the reading of the answer is on IO, because there is no Question object here for a helper to be handed.
func (QuestionHelper) WritePrompt ¶
func (QuestionHelper) WritePrompt(question, def string) string
WritePrompt renders the question and its default.
A default is shown in brackets, and a question with none is shown bare: the brackets say "press enter for this", and showing empty ones says it about nothing.
type Run ¶
type Run struct {
// Name is the command that ran.
Name string
// Args is what followed it, unparsed.
Args []string
// Duration is how long Run took, including the wait for a lock.
Duration time.Duration
// Err is what the command returned. It is cache.ErrLocked when the command
// was isolated and another process held the lock -- that is the one error
// the application does not pass on to the caller, and the observer is where it
// stays visible.
Err error
}
Run is one finished command, as an observer sees it.
type Signals ¶
type Signals struct {
// contains filtered or unexported fields
}
Signals is the set of handlers one command installed.
It exists so the handlers a command registers are undone when it finishes: a command that traps SIGINT and leaves the handler behind changes what ctrl-c does for everything the process runs afterwards, which in a long-lived binary is the rest of its life.
func NewSignals ¶
func NewSignals() *Signals
NewSignals returns an empty set of handlers.
The previous handlers stay where they are: Go delivers a signal to every channel that asked for it, so nothing needs to be snapshotted or restored.
func (*Signals) Register ¶
Register installs a handler for one signal.
The callback runs on a goroutine of the registrar's, so a handler that blocks holds nothing but itself.
The handler that was there before is not replaced: Go delivers a signal to every channel that asked for it.
func (*Signals) Unregister ¶
func (s *Signals) Unregister()
Unregister takes every handler back off.
It waits for the goroutines to stop so a handler is never running after the command that installed it returned.
type TaskResult ¶
type TaskResult = view.TaskResult
TaskResult is how a task ended, re-exported so a command that renders one does not have to import the view package for the constant.
type Value ¶
type Value struct {
// contains filtered or unexported fields
}
Value is one argument or option as it was given.
It can be read as a string, a slice or a bool without the caller needing to know in advance which shape it holds: asking for the wrong shape returns the zero value of it rather than a panic.
func (Value) Bool ¶
Bool is whether a flag was given.
A flag that takes no value is true when it is present. One that takes a value is true when that value is not one of the spellings of no, so --force=false means what it reads as.
type Verbosity ¶
type Verbosity = components.Verbosity
Verbosity is how much a command is allowed to say.
It is an alias and not a declaration: the constants live in the components package, which may not import this one, and one type spelt twice is one type.
func ParseVerbosity ¶
ParseVerbosity reads a level written as a word.
An unknown word falls back to the given current level, because a typo in a log call must not silence the command.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package attributes holds metadata a command applies to itself.
|
Package attributes holds metadata a command applies to itself. |
|
Package contracts holds the small interfaces a console type can implement so another package can act on it without importing the concrete type.
|
Package contracts holds the small interfaces a console type can implement so another package can act on it without importing the concrete type. |
|
Package events holds the events the console and scheduler dispatch.
|
Package events holds the events the console and scheduler dispatch. |
|
Package scheduling is the events an application runs on a clock.
|
Package scheduling is the events an application runs on a clock. |
|
Package view holds only what a command and the components it renders with share: how a task ended.
|
Package view holds only what a command and the components it renders with share: how a task ended. |
|
components
Package components is the set of shapes a command prints in.
|
Package components is the set of shapes a command prints in. |