Documentation
¶
Overview ¶
Package oden is a small framework for extensible subcommand-driven CLI tools: a bin's inline commands merged with the executables named <bin>-<sub> on PATH (git- and hiiro-style third-party commands), resolved by exact name, alias or unique prefix. It knows no application.
Index ¶
- Constants
- Variables
- func Executable(fsys FS, path string) bool
- func ExitCode(err error) int
- func PathDirs(path string) []string
- func Report(w io.Writer, prefix string, err error) int
- type App
- func (a *App) Add(cmds ...Command) *App
- func (a *App) Catalog() *Catalog
- func (a *App) CatalogDoc() CatalogDoc
- func (a *App) Cmd(name, summary string, run Handler, aliases ...string) *App
- func (a *App) CommandRows() []Row
- func (a *App) Help(w io.Writer)
- func (a *App) List(w io.Writer, entries []*Entry)
- func (a *App) NotFound(word string) error
- func (a *App) RootName() string
- func (a *App) Run(ctx context.Context) error
- func (a *App) RunEntry(ctx context.Context, e *Entry, args []string) error
- func (a *App) Suggest(word string) []string
- func (a *App) WriteCatalog(w io.Writer) error
- func (a *App) WriteState(w io.Writer) error
- type Audience
- type Catalog
- type CatalogDoc
- type CatalogNoun
- type CatalogParam
- type CatalogVerb
- type Claim
- type Command
- type CommandData
- type Ctx
- func (c *Ctx) Arg(i int, def string) string
- func (c *Ctx) Input() io.ReadCloser
- func (c *Ctx) Lines() iter.Seq2[string, error]
- func (c *Ctx) Open(paths ...string) io.ReadCloser
- func (c *Ctx) Printf(f string, a ...any) error
- func (c *Ctx) Println(a ...any) error
- func (c *Ctx) Sub(opts ...Option) *App
- func (c *Ctx) Text() (string, error)
- func (c *Ctx) Usage(synopsis string) error
- type Entry
- type ExitError
- type External
- type FS
- type Handler
- type Kind
- type Option
- func WithBuiltins() Option
- func WithCategories(categories map[string]string) Option
- func WithDefault(run func(ctx context.Context, args []string) error) Option
- func WithEnv(env []string) Option
- func WithFS(fsys FS) Option
- func WithPath(path string) Option
- func WithPrefixes(names ...string) Option
- func WithStdio(in io.Reader, out, errOut io.Writer) Option
- type Parameter
- type Resolution
- type Row
Constants ¶
const ( EnvParent = "ODEN_PARENT" // the bin that ran it EnvRoot = "ODEN_ROOT" // the outermost bin of the chain )
Environment variables an external command is started with.
const CatalogSchema = 1
CatalogSchema is the catalog format version CatalogDoc writes: the schema ochazuke/cli.Catalog writes, so a host (nabe, o-inventory) reads a plain oden bin and an ochazuke-backed one alike.
const CommandNoun = "command"
CommandNoun is the one noun a plain oden bin's catalog has: its commands, the catalog entries.
Variables ¶
var ErrNotFound = errors.New("command not found")
ErrNotFound is what a Default returns to defer an unknown command word to Run: Run then reports it as NotFound does. A Default that returns it with no words gets it back unchanged.
Functions ¶
func Executable ¶
Executable reports whether path is a regular file (or a link to one) with an executable bit.
Types ¶
type App ¶
type App struct {
Name string
Root string // the outermost bin; "" is Name
Args []string
// Default runs when no command word is given or the word names no
// command; it receives every argument. Without it, help is printed to
// Stderr and Run exits 1. Default returns ErrNotFound to hand an
// unknown word back to Run, which then reports it as NotFound does.
Default func(ctx context.Context, args []string) error
Path func() string // the PATH list; nil reads $PATH
FS FS // nil is OS
Env []string // an external's environment; nil is os.Environ()
// Prefixes (WithPrefixes) are extra bin names whose <name>-<sub>
// executables are discovered after the bin's own.
Prefixes []string
Categories map[string]string // optional category overrides by canonical command name
Stdin io.Reader
Stdout, Stderr io.Writer
HelpText string // optional application notes appended to help
// contains filtered or unexported fields
}
App is one bin: its name, its arguments (without the bin name), its inline commands and where it looks for external ones.
func Child ¶
Child is the App for parent's command sub: named <parent>-<sub>, so its own externals are <parent>-<sub>-<word>, sharing the parent's root, PATH, file system, environment and streams.
func New ¶
New builds an App named name over args. Root is taken from ODEN_ROOT when name is nested under it (an external run by an oden parent).
func (*App) Add ¶
Add registers inline commands, recording the caller as their Source when none is set.
func (*App) Catalog ¶
Catalog is the merged inline and external commands, discovered on first use and cached until the next Add. A command group whose name an external also has adds that external's subcommands (merge.go).
func (*App) CatalogDoc ¶
func (a *App) CatalogDoc() CatalogDoc
CatalogDoc is the bin's catalog answer: the command noun with list (rows command), show (positional name) and state (the catalog's state). Version is the main module's build version.
func (*App) CommandRows ¶
CommandRows are the command rows, one per catalog entry, by name: id is the name, label "NAME KIND PATH-OR-SOURCE".
func (*App) Help ¶
Help writes commands separated by audience, then by optional category, including their kind, summary, source and shadowed claims.
func (*App) List ¶
List writes entries grouped by optional category, with uncategorized entries under Other. Without categories it keeps a flat listing.
func (*App) NotFound ¶
NotFound reports word as naming no command of this level on Stderr: a "BIN: command "WORD" not found" line ("BIN GROUP: subcommand ..." below the root), then a "did you mean:" line and a pointer to --help when Suggest finds any, else the level's help. It returns an ExitError with code 1 and no cause.
func (*App) Run ¶
Run resolves the first argument and runs what it names with the rest: an inline command in process, an external with inherited streams. No word or an unknown one runs Default (or prints help and exits 1); an ambiguous prefix prints the candidates and exits 1.
func (*App) Suggest ¶
Suggest lists the commands word may have meant, at most three, for a word that names none: first those whose name or alias is a prefix of word (tasks → task), then those within a small edit distance of word (one edit for a word of up to five letters, else two). It only suggests; Resolve never takes these.
func (*App) WriteCatalog ¶
WriteCatalog writes CatalogDoc as indented JSON.
type Audience ¶
type Audience string
Audience identifies a command's intended consumer, independently of its domain category. Unspecified commands are presented as human-facing.
type Catalog ¶
type Catalog struct {
Bin string
Entries []*Entry // sorted by name
// contains filtered or unexported fields
}
Catalog is the commands one bin can run: its inline commands merged with the externals discovered on PATH. Ownership of a token, highest first:
- an inline command's name (the first registration wins)
- an external's name (the first on PATH wins)
- an inline alias (the first registration wins)
Every losing claim is kept in the winner's Shadowed list.
func NewCatalog ¶
NewCatalog merges inline commands (in registration order), externals (in PATH order) and the externals that lost to an earlier PATH entry.
func (*Catalog) Resolve ¶
func (c *Catalog) Resolve(word string) Resolution
Resolve finds what word names: an exact name or alias, else the one entry with a token starting with word. Descendants never make a prefix ambiguous: a match named <other>-... where <other> also matches is dropped, so bio resolves to biographies over biographies-freddie-kruger, while biographies and books stay ambiguous. These are hiiro's semantics (~/proj/hiiro/lib/hiiro/catalog.rb:56-57).
type CatalogDoc ¶
type CatalogDoc struct {
Schema int `json:"schema"`
Name string `json:"name"`
Version string `json:"version"`
State string `json:"state,omitempty"`
Nouns []CatalogNoun `json:"nouns"`
Bindings []any `json:"bindings"`
Pages []any `json:"pages"`
}
CatalogDoc is a bin's catalog answer in the ochazuke/cli schema. A plain oden bin has one noun, command, whose rows are its catalog entries; it has no bindings and no pages. State is "command.state".
type CatalogNoun ¶
type CatalogNoun struct {
Name string `json:"name"`
Pin string `json:"pin,omitempty"`
Verbs []CatalogVerb `json:"verbs"`
}
CatalogNoun is a noun and its verbs, sorted by name.
type CatalogParam ¶
type CatalogParam struct {
Type string `json:"type"`
Required bool `json:"required,omitempty"`
Description string `json:"description,omitempty"`
}
CatalogParam is a verb parameter, in ochazuke.Parameter's field names.
type CatalogVerb ¶
type CatalogVerb struct {
Name string `json:"name"`
Description string `json:"description,omitempty"`
Kind string `json:"kind,omitempty"`
Params map[string]CatalogParam `json:"params,omitempty"`
Rows string `json:"rows,omitempty"`
Quiet bool `json:"quiet,omitempty"`
Positional []string `json:"positional,omitempty"`
}
CatalogVerb is one verb, in ochazuke/cli.CatalogVerb's field names.
type Claim ¶
type Claim struct {
Name string `json:"name"`
Kind Kind `json:"kind"`
Source string `json:"source"`
AliasOf string `json:"alias_of,omitempty"`
// Merged marks the external a command group merges: its subcommands
// join the group's children (see App.RunEntry).
Merged bool `json:"merged,omitempty"`
}
Claim is a losing claim on a token: a later inline registration, an external that lost to an inline command or to an earlier PATH entry, or an alias that names a command already owned.
type Command ¶
type Command struct {
Name string
Aliases []string
Summary string
Category string // optional listing group
Audience Audience // empty defaults to human; overrides inherit a builtin's audience
// Children makes this a command group. A non-nil empty slice is an
// empty group; nil leaves Run/Handle in control of the arguments.
Children []Command
Usage string // optional leaf argument synopsis, for contextual help
Details string // additional descriptive help text
Parameters []Parameter
Run func(ctx context.Context, args []string) error
Handle Handler // takes precedence over Run when set
Source string
// contains filtered or unexported fields
}
Command is an inline command. Run receives the words after the command's own. Source is where it was registered (file:line), filled by Add.
type CommandData ¶
type CommandData struct {
Kind Kind `json:"kind"`
Category string `json:"category,omitempty"`
Summary string `json:"summary,omitempty"`
Audience Audience `json:"audience,omitempty"`
Path string `json:"path,omitempty"` // an external's executable
Source string `json:"source"`
Aliases []string `json:"aliases"`
Shadows []Claim `json:"shadows"`
Prefix string `json:"prefix,omitempty"` // an external's <prefix>-
Sub string `json:"sub,omitempty"` // a merged external's subcommand
}
CommandData is a command row's record: how the entry runs, where from, its aliases and every claim it shadows.
type Ctx ¶
type Ctx struct {
context.Context
App *App
Name string
Args []string
Stdin io.Reader
Stdout, Stderr io.Writer
}
Ctx is one invocation. Name is the canonical command name, not its alias. Input helpers open OS files; WithFS configures discovery only.
func (*Ctx) Input ¶
func (c *Ctx) Input() io.ReadCloser
Input treats Args as file paths, like cat. Parse flags before calling Open explicitly if the command accepts anything other than paths.
func (*Ctx) Lines ¶
Lines yields lines without newlines, then any read error once. It closes files even when iteration stops early. Scanner's default 64 KiB limit applies.
func (*Ctx) Open ¶
func (c *Ctx) Open(paths ...string) io.ReadCloser
Open reads paths in order, opening one file at a time. "-" means stdin; no paths means stdin. Open and read errors are returned by Read. Close releases the current file but never closes stdin. Callers must close it.
func (*Ctx) Sub ¶
Sub creates a child app over the remaining arguments and inherited streams. Options override inherited settings; extra prefixes are not inherited.
type Entry ¶
type Entry struct {
Name string `json:"name"`
Kind Kind `json:"kind"`
Aliases []string `json:"aliases"`
Summary string `json:"summary,omitempty"`
Category string `json:"category,omitempty"`
Audience Audience `json:"audience,omitempty"`
Source string `json:"source"`
Prefix string `json:"prefix,omitempty"` // an external's <prefix>-
Shadowed []Claim `json:"shadowed"`
// Sub is set for a subcommand of a merged external: it runs as
// Path Sub ARGS... (BIN-NAME SUB ARGS...).
Sub string `json:"sub,omitempty"`
Command *Command `json:"-"` // set for KindInline
Path string `json:"-"` // set for KindExternal
// contains filtered or unexported fields
}
Entry is one command of a catalog: the winning claim on its name, its aliases and every claim it shadows.
type ExitError ¶
ExitError carries a process exit code; Err (may be nil) is the cause. A nil Err means the message, if any, was already printed.
type External ¶
type External struct {
Name string `json:"name"`
Path string `json:"path"`
Prefix string `json:"prefix,omitempty"`
}
External is an executable named <Prefix>-<Name> found on PATH. Prefix is the bin itself or one of its extra prefixes (WithPrefixes).
func Discover ¶
Discover scans the PATH directories for executables named <bin>-<sub>. The first one on PATH for each sub is found; later ones are shadowed. Unreadable directories, directories and non-executables are skipped. Both lists are in PATH order, then by name within a directory.
func DiscoverAll ¶
DiscoverAll discovers each prefix in turn: every <bin>-<sub> on PATH first, then every <extra>-<sub> per extra prefix, in the given order. A sub already found under an earlier prefix shadows the later ones.
type FS ¶
type FS interface {
ReadDir(dir string) ([]fs.DirEntry, error)
Stat(path string) (fs.FileInfo, error)
}
FS is the file system discovery reads: directory listings and Stat (which follows symlinks). OS is the real one; tests pass a fake.
var OS FS = osFS{}
OS is the operating system's file system.
type Kind ¶
type Kind string
Kind says how an entry runs: an inline Command in this process, or an external executable found on PATH.
type Option ¶
type Option func(*App)
Option configures an App in New.
func WithBuiltins ¶
func WithBuiltins() Option
WithBuiltins adds human-facing help, info, version and which, and machine-facing commands, catalog, state and command list|show|state. Machine commands retain their JSON formats and argument handling. An app's own command always owns its name over a builtin.
func WithCategories ¶
WithCategories assigns listing groups by canonical command name, including discovered externals. Unassigned commands remain visible under Other.
func WithDefault ¶
WithDefault sets the handler for no command word or an unknown one.
func WithPrefixes ¶
WithPrefixes adds bin names whose <name>-<sub> executables on PATH are also this bin's commands, discovered after its own <bin>-<sub> and in the given order: with WithPrefixes("o"), mtm proj runs o-proj unless an mtm-proj is on PATH, which then shadows it. A Child does not inherit them.
type Parameter ¶
type Parameter struct {
Name string
Type string
Description string
Required bool
Choices []string
Source string
Free bool
}
Parameter describes a command argument for help. Oden does not parse it.
type Resolution ¶
Resolution is what a word names: the entry when it names exactly one (Exact for a name or alias, else a unique prefix), or the candidates when it is a prefix of several.
func (Resolution) Resolved ¶
func (r Resolution) Resolved() bool
Resolved reports whether the word names one entry.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
o
command
Command o is the family root bin (oden, ochazuke): every o-<tool> on PATH is o's command <tool> by oden's normal rule (o proj new x runs o-proj new x with ODEN_PARENT=o), plus oden's builtins.
|
Command o is the family root bin (oden, ochazuke): every o-<tool> on PATH is o's command <tool> by oden's normal rule (o proj new x runs o-proj new x with ODEN_PARENT=o), plus oden's builtins. |
|
Build this source under the names listed in README's delegation examples.
|
Build this source under the names listed in README's delegation examples. |
|
hello
command
Command hello demonstrates context handlers (greet, echo, cat, lines) and any hello-<sub> executable on PATH.
|
Command hello demonstrates context handlers (greet, echo, cat, lines) and any hello-<sub> executable on PATH. |