console

package
v0.31.0 Latest Latest
Warning

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

Go to latest
Published: Sep 8, 2026 License: MIT Imports: 24 Imported by: 0

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

View Source
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.

View Source
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.

View Source
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

View Source
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

func CommandMutexName(command Command) string

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

func Exit(code int, format string, a ...any) error

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

func ExitCode(err error) int

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

func FormatCommandString(command string) string

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

func GuardEnv(env config.Env, want config.Env, action string) error

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

func IsProhibited(name string, o *IO, quiet ...bool) bool

IsProhibited reports whether the command was prohibited, and says so on the terminal unless it was asked to be quiet.

func MustParse

func MustParse(expression string) (string, []Argument, []Option)

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

func Parse(expression string) (name string, arguments []Argument, options []Option, err error)

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

func Prohibit(name string, prohibit ...bool)

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

func (r *Application) Call(ctx context.Context, name string, args ...string) error

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

func (r *Application) CallSilent(ctx context.Context, name string, args ...string) error

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

func (r *Application) CallSilently(ctx context.Context, name string, args ...string) error

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

func (a Argument) IsArray() bool

IsArray reports whether the argument swallows every remaining operand.

func (Argument) IsRequired

func (a Argument) IsRequired() bool

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.

func (*BufferedConsoleOutput) Write

func (b *BufferedConsoleOutput) Write(p []byte) (int, error)

Write buffers the bytes and passes them on.

It is an io.Writer so an IO can be pointed straight at it.

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

func (m *CacheCommandMutex) Create(ctx context.Context, command Command) (bool, error)

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

func (m *CacheCommandMutex) Exists(ctx context.Context, command Command) (bool, error)

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

func (m *CacheCommandMutex) Forget(ctx context.Context, command Command) (bool, error)

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

func (c Command) Definition() (name string, arguments []Argument, options []Option, err error)

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

func (c Command) Execute(ctx context.Context, o *IO, mutex CommandMutex) error

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

func (c Command) Fail(cause error) error

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

func (c Command) Handle(ctx context.Context, o *IO) error

Handle does the command's own work.

What Execute adds around it is the isolation mutex.

func (Command) IsHidden

func (c Command) IsHidden() bool

IsHidden reports whether the command stays out of the listing.

func (*Command) SetAliases

func (c *Command) SetAliases(aliases ...string) *Command

SetAliases sets the other names the command answers to.

func (*Command) SetDescription

func (c *Command) SetDescription(description string) *Command

SetDescription sets the line the listing prints beside the name.

func (*Command) SetHelp

func (c *Command) SetHelp(help string) *Command

SetHelp sets the long explanation.

func (*Command) SetHidden

func (c *Command) SetHidden(hidden ...bool) *Command

SetHidden keeps the command out of the listing, or puts it back.

Setting it with no value hides.

func (*Command) SetName

func (c *Command) SetName(name string) *Command

SetName names the command.

It returns the command so the setters chain.

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.

func (*ExitError) Error

func (e *ExitError) Error() string

Error satisfies the error interface: it returns Message, or a generic exit status line when Message is empty.

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

func NewIO(name string, args []string, out, errOut io.Writer, in io.Reader) *IO

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

func (o *IO) Alert(format string, a ...any)

Alert writes a message in a box, in capitals.

func (*IO) Anticipate

func (o *IO) Anticipate(question string, choices []string, def string) (string, error)

Anticipate puts a question and offers the choices as suggestions, without requiring one of them.

func (*IO) Args

func (o *IO) Args() []string

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

func (o *IO) Argument(name string) Value

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

func (o *IO) Arguments() map[string]Value

Arguments returns every argument, keyed by name.

func (*IO) Ask

func (o *IO) Ask(question, def string) (string, error)

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

func (o *IO) AskQuestion(question, def string) (string, error)

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

func (o *IO) AskWithCompletion(question string, choices []string, def string) (string, error)

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

func (o *IO) Choice(question string, options []string, def string) (string, error)

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

func (o *IO) Comment(format string, a ...any)

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

func (o *IO) Confirm(question string, def bool) (bool, error)

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

func (o *IO) ConfirmToProceed(warning string, shouldConfirm bool) (bool, error)

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

func (o *IO) Error(format string, a ...any)

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

func (o *IO) Flags() *flag.FlagSet

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

func (o *IO) GetOutput() io.Writer

GetOutput returns the stream the command writes to.

func (*IO) GetTerminalWidth

func (o *IO) GetTerminalWidth() int

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

func (o *IO) HasArgument(name string) bool

HasArgument reports whether the argument is declared in the signature.

func (*IO) HasOption

func (o *IO) HasOption(name string) bool

HasOption reports whether the option is declared in the signature.

func (*IO) Info

func (o *IO) Info(format string, a ...any)

Info is a line that reports progress or a fact worth reading.

func (*IO) Input

func (o *IO) Input() *Input

Input returns the command line bound to the signature, or nil when the command declared none.

func (*IO) Interactive

func (o *IO) Interactive() bool

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

func (o *IO) IsDebug() bool

IsDebug reports whether the command was told to say everything, which is -vvv.

func (*IO) IsQuiet

func (o *IO) IsQuiet() bool

IsQuiet reports whether the command was told to say nothing, which is -q.

func (*IO) IsVerbose

func (o *IO) IsVerbose() bool

IsVerbose reports whether the command was told to say more, which is -v.

func (*IO) IsVeryVerbose

func (o *IO) IsVeryVerbose() bool

IsVeryVerbose reports whether the command was told to say much more, which is -vv.

func (*IO) Line

func (o *IO) Line(format string, a ...any)

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

func (o *IO) NewLine(count ...int)

NewLine writes count blank lines to the output, and one when count is left out.

func (*IO) NewLineWritten

func (o *IO) NewLineWritten() bool

NewLineWritten reports whether the last write ended a line.

It is here because NewLineAware declares it.

func (*IO) NewLinesWritten

func (o *IO) NewLinesWritten() int

NewLinesWritten is how many line endings the last write left behind.

func (*IO) Option

func (o *IO) Option(name string) Value

Option returns the value of one option.

func (*IO) Options

func (o *IO) Options() map[string]Value

Options returns every option, keyed by name.

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

func (o *IO) Progress(total int) *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

func (o *IO) PromptForMissingArguments() error

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

func (o *IO) Question(format string, a ...any)

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

func (o *IO) Secret(question string) (string, error)

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

func (o *IO) SetBase(base string)

SetBase records the application root, which the components strip from a path before printing it.

func (*IO) SetInput

func (o *IO) SetInput(in *Input)

SetInput binds a parsed command line to this IO.

func (*IO) SetOutput

func (o *IO) SetOutput(out io.Writer)

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

func (o *IO) SetVerbosity(level Verbosity)

SetVerbosity sets the level.

func (*IO) SetVerbosityNamed

func (o *IO) SetVerbosityNamed(level string)

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

func (o *IO) Table(headers []string, rows [][]string)

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

func (o *IO) Task(description string, fn func() error) error

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

func (o *IO) Trap(callback func(os.Signal), signals ...os.Signal)

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

func (o *IO) TwoColumnDetail(left, right string)

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

func (o *IO) Untrap()

Untrap takes back every handler this command installed.

func (*IO) Verbosity

func (o *IO) Verbosity() Verbosity

Verbosity is the level the command is running at.

func (*IO) Warn

func (o *IO) Warn(format string, a ...any)

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

func (o *IO) WithProgressBar(totalSteps int, callback func(bar *Progress) error) error

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.

func (*IO) Write

func (o *IO) Write(message string, verbosity ...Verbosity)

Write puts text out without a line ending.

It is what the components render through. A verbosity above the one the command is running at writes nothing, which is what -q and -v decide.

func (*IO) Writeln

func (o *IO) Writeln(message string, verbosity ...Verbosity)

Writeln puts one line out.

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

func NewInput(arguments []Argument, options []Option) *Input

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

func (in *Input) Argument(name string) Value

Argument returns the value of a declared argument by name, or a zero Value if none was declared with that name.

func (*Input) Arguments

func (in *Input) Arguments() map[string]Value

Arguments returns every argument, keyed by name.

func (*Input) Definition

func (in *Input) Definition() ([]Argument, []Option)

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

func (in *Input) HasArgument(name string) bool

HasArgument reports whether the argument is declared.

It reports declared, not given.

func (*Input) HasOption

func (in *Input) HasOption(name string) bool

HasOption reports whether the option is declared in the command signature.

func (*Input) Interactive

func (in *Input) Interactive() bool

Interactive reports whether the command may prompt.

func (*Input) Option

func (in *Input) Option(name string) Value

Option returns the value of a declared option by name, or a zero Value if none was declared with that name.

func (*Input) Options

func (in *Input) Options() map[string]Value

Options returns every option, keyed by name.

func (*Input) Parse

func (in *Input) Parse(argv []string) error

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

func (in *Input) SetInteractive(interactive bool)

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

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

func TestOptions(typeName string) []Option

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

func (o Option) AcceptValue() bool

AcceptValue reports whether the option takes a value at all.

func (Option) IsArray

func (o Option) IsArray() bool

IsArray reports whether the flag may be repeated and collects every value.

func (Option) IsValueRequired

func (o Option) IsValueRequired() bool

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.

func (*Progress) Advance

func (p *Progress) Advance(n int)

Advance moves the bar forward by n, and never passes the total.

func (*Progress) Finish

func (p *Progress) Finish()

Finish ends the bar's line.

Calling it twice does nothing the second time, so a deferred Finish beside an explicit one is not two bars.

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

func (s *Signals) Register(sig os.Signal, callback func(os.Signal))

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

func (v Value) Bool() 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.

func (Value) Int

func (v Value) Int() (int, error)

Int is the value as a number.

func (Value) Present

func (v Value) Present() bool

Present reports whether the value was given on the command line, as opposed to standing in from a default.

func (Value) Slice

func (v Value) Slice() []string

Slice is every value that was given, in order.

func (Value) String

func (v Value) String() string

String is the value as one string. An array value is its first element, and a value that was never given is empty.

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

func ParseVerbosity(level string, current Verbosity) Verbosity

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.

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.

Jump to

Keyboard shortcuts

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