tool

package
v1.2.9 Latest Latest
Warning

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

Go to latest
Published: Oct 11, 2026 License: MIT Imports: 19 Imported by: 0

Documentation

Overview

Package tool is the one shape of a nova command. A Tool is its verbs; a Verb declares its flags and returns one value, Out, which is rendered either as typed lines or as the JSON of the same value (out.go). Everything a command writes for itself lives here once: the verb dispatch, the banner (what the tool is, how it works, its usage lines, its exit codes, a runnable example block), `help` and `<verb> -h`, the `version` verb, the standard flags (--json on every verb; --max and --dry-run where a verb opts in), refusing to guess (every problem of one invocation named at once), and the refusal line with its remedy: an unknown verb or flag is answered with the nearest name and the ones there are, and a verb group's -h lists its verbs. A row carries a prose tail (Out.ItemText): a reason or a command renders plain after the row's typed fields and as the `text` field of the row's JSON, so the line and the object stay one value. A tool whose exit 0 already means CLEAR sets HelpRefused, and `<verb> -h` is then a refusal at exit 2 naming `help`, never an answer at exit 0. A tool may name a default verb (`<tool> <file>`) and its own status words (STALE beside FAIL). A verb may be hidden (Verb.Hidden): it runs and answers `-h`, and the banner, the usage block and the unknown-verb list do not show it, a probe step verb a user never types. A long-running verb prints each item as it goes; the Out it returns is the closing line. Call.Ctx is cancelled when the run's context ends and on interrupt (RunContext). A command holds only what its verbs do.

Index

Constants

View Source
const (
	HowLines = 5
	HowWidth = 100
)

HowLines and HowWidth bound the banner's how-it-works text: a reader takes in five lines at a glance, and a line past 100 characters wraps.

View Source
const HowLabel = "how it works: "

HowLabel opens the how-it-works text in the banner (docs/ONBOARDING.md point 6), so a tool's How is the paragraph without it; the width bound counts it on the first line, where it is printed.

View Source
const MaxRemedy = "--max <n> raises the ceiling, --max 0 lists all"

MaxRemedy is the remedy every MORE line names.

View Source
const MaxWords = 6

MaxWords bounds a tool's own status words: a reader learns them all at once.

Variables

This section is empty.

Functions

This section is empty.

Types

type Call

type Call struct {
	// Ctx is the run's context, cancelled when that context ends and on
	// interrupt (skeleton contract 2.4). A verb that runs for a while selects
	// on it; a context that has ended is the closing line, not the verb's Out.
	Ctx            context.Context
	Stdin          io.Reader
	Stdout, Stderr io.Writer // written by a verb that Prints
	// contains filtered or unexported fields
}

Call is one invocation of a verb: its streams, its context and its parsed flags.

func (*Call) Bool

func (c *Call) Bool(name string) bool

func (*Call) DryRun

func (c *Call) DryRun() bool

DryRun reports whether --dry-run was given (only a Verb with DryRun takes it). A verb reads it before it writes and, when it is set, returns the plan the real run would carry out, from the same code path, and writes nothing; the skeleton adds dry_run=true to the OK line unless the verb set it.

func (*Call) Dur

func (c *Call) Dur(name string) time.Duration

func (*Call) Get

func (c *Call) Get(name string) any

Get is a declared flag's value, for a flag.Value of the verb's own (a flag.Getter).

func (*Call) Given

func (c *Call) Given(name string) bool

Given reports whether the flag was on the command line.

func (*Call) Int

func (c *Call) Int(name string) int

func (*Call) Problem

func (c *Call) Problem(what string)

Problem records one reason the invocation cannot run.

func (*Call) ProblemAs

func (c *Call) ProblemAs(reason, what string)

ProblemAs records one reason with a stable code (skeleton contract 2.10, STANDARD §2). The refusal line carries reason=<code> before the colon, and the JSON result adds "reasons" beside "why": a program reads the code, a person reads the sentence.

func (*Call) Refused

func (c *Call) Refused() *Out

Refused is the refusal naming every problem recorded, or nil when there is none.

func (*Call) Str

func (c *Call) Str(name string) string

Str, Int, Bool and Dur read a declared flag's value.

func (*Call) Want

func (c *Call) Want(name, wants string) string

Want reads a required string flag, recording a problem that says what it wants when it is empty. Every Want is read before Refused, so one run names every missing flag (docs/ONBOARDING.md point 2).

type Effect

type Effect string

Effect is what running a verb does beyond printing: one of the three below, optionally with a clause after it; a verb whose flags change it states the strongest and says which flag (Problems holds every verb to one of the three).

const (
	Inspection Effect = "inspection: reads, writes nothing"
	LocalWrite Effect = "local write: writes files on this machine"
	Delivery   Effect = "delivery: sends beyond this machine"
)

type Field

type Field struct {
	K string
	V any
}

Field is one key=value.

type Fields

type Fields []Field

Fields keeps its keys in the order they were added, in both renderings.

func (Fields) MarshalJSON

func (fs Fields) MarshalJSON() ([]byte, error)

MarshalJSON writes the fields as one object in the order they were added.

type Flags

type Flags struct {
	*flag.FlagSet
	// contains filtered or unexported fields
}

Flags is one verb's flag set: package flag's own, with its two mouths closed (verbflag.New), and the standard flags a verb opts into.

func (*Flags) Check

func (f *Flags) Check(rule func(c *Call))

Check adds a rule over the parsed flags (c.Problem, c.Want), run with the required flags, --max and the arguments before the verb runs.

func (*Flags) Max

func (f *Flags) Max()

Max adds --max: the items listed before one MORE line stands for the rest.

func (*Flags) Prints

func (f *Flags) Prints()

Prints marks a verb that writes its own output (a payload a program reads, a child's stream, or a body shared with a tool not yet on this package): it gets no --json, and returns Exit(code) after writing to c.Stdout and c.Stderr.

func (*Flags) Required

func (f *Flags) Required(name, wants string)

Required declares a string flag the verb cannot run without: empty, it is a problem that says what it wants, named with every other problem at once.

type Item

type Item struct {
	Kind   string `json:"kind"`
	Fields Fields `json:"fields"`
	Text   string `json:"text,omitempty"`
}

Item is one typed row: `<TOKEN> <KIND> k=v ...[: <text>]`, where the text is the row's prose tail (ItemText): a reason or a command, printed plain after the typed fields and carried as the `text` field of the JSON, so the line and the object stay one value.

type More

type More struct {
	Kind   string `json:"kind"`
	Shown  int    `json:"shown"`
	Total  int    `json:"total"`
	Remedy string `json:"remedy"`
}

More stands for the items of one kind that --max did not list.

type Out

type Out struct {
	Verb    string
	Status  Status
	Exit    int
	Word    string   // the tool's own status word in place of OK or FAILED (Out.As); "" is the plain one
	Remedy  string   // what to run next: the tool's help on a refusal unless the verb names better
	Why     []string // every reason it failed or was refused
	Reasons []string // a stable code per Why, parallel to it; a program reads the code (skeleton contract 2.10)
	Facts   Fields
	Items   []Item
	More    []More
	Notes   []string
	Payload string
	// contains filtered or unexported fields
}

Out is the one value every verb returns. Render writes it as typed lines:

<TOKEN> OK|FAILED|REFUSED [reason=<r>] k=v ...[: <why>][; run: <remedy>]   one line per why
<TOKEN> <KIND> k=v ...[: <text>]                            one line per item, the text its prose tail
<TOKEN> MORE kind=<kind> shown=<n> total=<n> <remedy>       one per capped kind
<TOKEN> NOTE <text>                                         one per note

or as the JSON of the same value:

{"result":{"verb","status","exit","remedy","why","reasons"},"facts":{},"items":[{"kind","fields","text"}],
 "more":[{"kind","shown","total","remedy"}],"notes":[],"payload":""}

A payload (the version line, a document a program reads) is printed as it is, last, in the text form, and alone when the result carries nothing else; it is the "payload" key of the JSON. Values are strings, integers, booleans or Text; an empty value is "-" in the text form. The JSON is not HTML-escaped: `<dir>` is `<dir>` in both renderings.

func Done

func Done() *Out

Done is an OK result, Refuse one that could not run, Fail one that ran and said no.

func Exit

func Exit(code int) *Out

Exit is the result of a verb that printed its own output (Flags.Prints).

func Fail

func Fail(why ...string) *Out

func Payload

func Payload(text string) *Out

func Refuse

func Refuse(why ...string) *Out

func (*Out) As

func (o *Out) As(word string) *Out

As puts one of the tool's own status words (Tool.Words) in place of OK or FAILED on the first line; the status and the exit stay: `Done().As("UNCHANGED")` exits 0, and a gate that says no is `Fail(why).As("STALE")`, exit 1, apart from a refusal's 2. A word the tool does not declare, or one on a refusal, is turned into a FAILED naming the bug.

func (*Out) Cap

func (o *Out) Cap(max int) *Out

Cap keeps the first max items of each kind (0 keeps all) and records a More for each kind with items left over, counted by pkg/bounded: the `MORE kind= shown= total=` cut. A verb with Flags.Max is capped by the skeleton; a verb that bounds a listing of its own calls Cap.

func (*Out) Fact

func (o *Out) Fact(k string, v any) *Out

Fact adds one key=value to the first line.

func (*Out) Findings

func (o *Out) Findings(kinds ...string) *Out

Findings names the item kinds that are a verb's findings: in the text form their lines go to stderr, and the rest of a verb that ran (its first line, its other items, MORE and NOTE) to stdout, whether it said OK or FAILED. A refusal stays whole on stderr; JSON stays one object on stdout.

func (*Out) Item

func (o *Out) Item(kind string, kv ...any) *Out

Item adds one row of a kind, its fields given as key, value, key, value.

func (*Out) ItemText

func (o *Out) ItemText(kind, text string, kv ...any) *Out

ItemText adds one row of a kind with a prose tail: the typed fields render as key=value tokens and the text renders plain after them, `: <text>` (oneline.Escape), while the JSON carries it as the `text` field beside the typed fields (STANDARD §2, one output structure, two renderings). A reason or a command lives in the tail, never escaped into one key=value field.

func (*Out) MarshalJSON

func (o *Out) MarshalJSON() ([]byte, error)

MarshalJSON is the JSON rendering: the result, then the value's parts.

func (*Out) Note

func (o *Out) Note(text string) *Out

func (*Out) Render

func (o *Out) Render(w io.Writer, asJSON bool) int

Render writes o as typed lines, or as one JSON object when json is set, and returns the exit that stands: o.Exit, or 1 when o is no JSON (a NaN or an infinite float, a value of the verb's own that JSON cannot carry), which is then a FAILED line naming the verb, never an empty line and a success. Every value goes through pkg/oneline.

type Status

type Status string

Status is how a verb ended: ok (exit 0), failed (it ran and said no, exit 1) or refused (it could not run, exit 2).

const (
	OK      Status = "ok"
	Failed  Status = "failed"
	Refused Status = "refused"
)

type Text

type Text string

Text is a value of free text, a reason, a sentence or a command to run, as a fact or an item's field: the text form prints it after the line's typed fields, its prose tail, quoted (oneline.Quote) so it keeps its spaces and is not hex-escaped, where a typed value (a name, a path, a count) is one token (oneline.Field): `REPORT UNKNOWN name=x path=p reason="no version line" run="nova-version report -h"`. JSON carries it as a string under its key.

type Tool

type Tool struct {
	Name string // the binary: nova-<name>
	What string // line 1 of the banner: what the tool is for
	// Stage, when set, is one sentence on how ready the tool is ("nova-x is
	// pre-alpha: not ready for production use."): the banner's line 2, the
	// second line of every verb's -h, and an indented NOTE line under a bare
	// command's refusal, so no reader meets the tool without it.
	Stage     string
	How       string // how it works: the paragraph under line 1
	Verbs     []Verb // in banner order; version and help are added here
	ExitTable string // "0 ..., 1 ..., 2 ...": the banner's exit-codes line
	Stamp     string // the build stamp (-ldflags -X main.version), for version
	// Default is the verb run when the first word is no verb: a flag, a path
	// (a word with a separator), or a word naming a file that is there
	// (`<tool> <file>...`); any other word is refused as no verb and no file.
	// The default verb accepts positional arguments, also when named explicitly.
	// "" makes every first word a verb and leaves every verb flags-only.
	Default string
	// Exists reports whether path is a file or directory that is there: the
	// one seam the skeleton reads the filesystem through (skeleton contract
	// 2.1). Nil defaults to checking with os.Stat. Tests pass a map.
	Exists func(path string) bool
	// Words are the tool's own status words (STALE, MISSING, UNCHANGED), the
	// only ones Out.As may put in place of OK or FAILED: at most MaxWords,
	// upper case, none of OK, FAILED, REFUSED, MORE or NOTE (Problems).
	Words []string
	// HelpRefused refuses `<verb> -h` (and --help) at exit 2 instead of
	// answering it at exit 0: a tool sets it when its exit 0 already means
	// CLEAR, so a `-h` answer could read as CLEAR (STANDARD §3 names the one
	// exception). The refusal names `help` as the door. No tool sets it yet.
	HelpRefused bool
	// NoJSON, when set, replaces the banner's standard --json sentence's
	// clause after the colon: a tool whose verbs print their own prose (the
	// note body a bus carries) says there why they take no --json and pastes
	// a line they print, so the help and the output cannot drift (ONBOARDING
	// point 6). Empty keeps the standard sentence.
	NoJSON string
	// UsageNote, when set, is printed under the usage block, before the
	// standard flags sentence: a tool states once, where a reader has just
	// read the usage lines, the shape of a value several of them name (the
	// manifest of --file), so no usage line carries it. Empty prints none.
	UsageNote string
	// Topics are the tool's help topics: `help <topic>` prints the topic's
	// text at exit 0, and the banner lists the topic names on one line
	// (skeleton contract 2.7). A tool's reference text lives here, never in
	// the banner, which a reader takes in at a glance (STANDARD §3 point 6).
	// A topic's name is none of the tool's verbs, since `help <name>` is one
	// door (Problems).
	Topics []Topic
}

Tool is one command.

func (*Tool) Banner

func (t *Tool) Banner() string

Banner is what `help` prints: what, how, usage, the standard flags, the exit codes, and the example block last (docs/ONBOARDING.md point 1).

func (*Tool) Main

func (t *Tool) Main() int

Main runs the tool over the process's arguments and streams and returns the exit code.

func (*Tool) Problems

func (t *Tool) Problems() []string

Problems is where t falls short of the standard its banner and help carry by construction only when the definition is complete: a what line, an exit table, a how text of at most HowLines lines of at most HowWidth characters, and every verb's effect one of inspection, local write or delivery. A tool on this package runs it in its own tests (internal/ci holds every such package to that).

func (*Tool) Run

func (t *Tool) Run(args []string, stdin io.Reader, stdout, stderr io.Writer) int

Run dispatches one invocation: help, version, or a verb. `<verb> -h` and `help <verb>` print that verb's help on stdout at exit 0 before anything is read or written (the CLI style's rule (b)); a tool that refuses help (HelpRefused) still answers `help <verb>` by name, while `<verb> -h` is a refusal at exit 2, since its exit 0 would read as CLEAR.

func (*Tool) RunContext

func (t *Tool) RunContext(ctx context.Context, args []string, stdin io.Reader, stdout, stderr io.Writer) int

RunContext is Run on ctx. Call.Ctx is ctx, cancelled also on interrupt, so a long-running verb can stop (skeleton contract 2.4, STANDARD §2). A context that has already ended does not run the verb: the closing line names the context's error. One cancelled while the verb runs replaces that closing line the same way.

type Topic

type Topic struct {
	Name string
	Text string
}

Topic is one help topic: `<tool> help <name>` prints Text on stdout at exit 0. It is where a tool's reference text lives, so the banner stays short (skeleton contract 2.7, STANDARD §3 point 6).

type Verb

type Verb struct {
	Name      string
	Usage     string         // the usage line(s) after the tool's name, one form per line
	Example   string         // runnable line(s) after the tool's name, for the banner's example block
	Effect    Effect         // what running it does to the world, stated in `help <verb>`
	Detail    string         // lines `help <verb>` prints above its flags: a format, a worked example
	ExitTable string         // this verb's exit codes, quoted by its -h; "" quotes the tool's
	DryRun    bool           // the verb takes --dry-run and honours it (Call.DryRun): it plans and writes nothing
	Hidden    bool           // the verb runs and answers -h and `help <it>`, but the banner, the usage block and the verb lists a refusal names do not show it: a probe step verb a user never types (STANDARD §3, help is never a refusal; §2, a list names the verbs there are for the reader)
	Flags     func(f *Flags) // declares the verb's flags; nil declares none
	Run       func(c *Call) *Out
}

Verb is one verb of a tool. A name of two words ("fn load") puts the verb in a group ("fn"): `<tool> fn -h` lists the group's verbs at exit 0.

Jump to

Keyboard shortcuts

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