clikit

package module
v0.6.1 Latest Latest
Warning

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

Go to latest
Published: Nov 5, 2026 License: MIT Imports: 14 Imported by: 0

README

clikit

Help pages generated from one declaration, and one shared way to end a run, for Go command-line tools built on Kong.

A tool declares its commands as Kong structs and describes itself once as a clikit.App; its main and its tests run that same value. clikit renders every --help page from the declaration (synopsis, arguments, flags, defaults, constraints, exit codes), makes help work before any configuration loads, rejects declarations whose help would be untrue, and classifies every outcome into the tool's exit codes. clikittest runs the App with captured output and pins every help page against golden files.

go get github.com/qiushiyan/clikit
  • The contract and an example: doc.go, also on pkg.go.dev.
  • Why it is shaped this way: docs/design.md.

MIT licensed.

Documentation

Overview

Package clikit is the invocation and help contract shared by the ~/dev Go command-line tools. A tool declares its commands as native Kong structs (github.com/alecthomas/kong) and describes itself once, as an App; main and the tests run that same value.

One declaration drives both parsing and help: a command is a struct whose fields are flags and positional arguments, each with a help tag, and whose Run method does the work. Long prose belongs in a Help() string method on the same type, and titled blocks such as examples in a HelpSections method. Nothing about a command's syntax is written twice.

type CLI struct {
	Dir string `help:"Directory that holds the store." env:"TOOL_DIR" default:"~/.tool" type:"path"`
	Add AddCmd `cmd:"" help:"Register a repository."`
}

type AddCmd struct {
	Path string `arg:"" type:"existingdir" help:"Checkout to register."`
}

func (c *AddCmd) Run(ctx *kong.Context, s *Store) error { ... }

var app = clikit.App{
	Name:        "tool",
	Description: "tool keeps a registry of repositories.",
	New:         func() any { return &CLI{} },
	Options: []kong.Option{
		kong.BindToProvider(func(cli *CLI) (*Store, error) { return openStore(cli.Dir) }),
	},
	ExitCodes: map[int]string{3: "a repository is missing"},
}

func main() { os.Exit(app.Run(os.Args[1:])) }

Each invocation parses into a fresh grammar from New, so Options must not capture one. A Run method or provider that needs the parsed root or its flags takes the root type as a parameter (cli *CLI above); Kong binds it.

Run guarantees:

  • Help always works. --help (or "help [command…]", unless the tool declares a help command of its own) prints the page to stdout and exits 0 before any command runs, any hook after BeforeReset fires, or any environment variable is read. Load configuration and credentials in Run methods or through kong.BindToProvider (or kong.BindSingletonProvider, for a value several providers share), called only when a Run method asks for the value, never in grammar hooks or mappers. No option can take this away.
  • Help is rendered from the model. The synopsis, arguments, flags, typed placeholders, defaults, enums, environment variables, flag-group constraints, passthrough and exit codes come from the same declaration that parses argv, and Kong's error lines name a flag as help does. A default:"withargs" command's arguments and flags parse at its parent's level, so the parent's page lists them with their constraints; its Help() and sections stay on its own page: a tool whose default command is the whole tool returns the same text and sections from the root's methods. Generated rows wrap at 100 columns; Help() text and sections print verbatim, a command's own sections before the Shared ones it inherits.
  • A declaration whose help would be missing or untrue fails before parsing, and the first test (or run) of the tool names each problem: anything visible without help, and each form help cannot state, such as a Run method beside commands, which Kong runs after theirs too, or a flag group Kong checks differently from its tags.
  • Outcomes are classified by origin. Invalid argv exits with App.UsageCode after an error line, the synopsis of the command it concerns and a pointer to its --help, all on stderr. Around a default:"withargs" command, argv is held to its parent's page: a flag of the default typed before another command is refused, since the default would not run, and a word that names no command is reported against the parent, as if it had no default, unless the default takes the word as its argument. A bad value from an environment variable, and anything else that fails, exits with App.FailureCode. A command chooses its own status with Exit (it already reported) or Fail (Run prints the error), each code declared in App.ExitCodes, or reports a usage error it found itself with Usage. App.Outcome maps the tool's own errors onto those once, for every command; what a command returned through them skips it. UsageCode defaults to 2, which a migrating tool often already uses for something else: set it to the tool's existing code.

Commands write through the bound *kong.Context (ctx.Stdout, ctx.Stderr), which are App.Stdout and App.Stderr themselves, and read input from a bound io.Reader parameter (App.Stdin); a provider may take the *kong.Context too. The clikittest package runs an App with captured output and pins every help page. A test swaps a dependency (a clock, tmux, a terminal check) by appending an option that binds the same type to a copy of App.Options: Kong keeps one binding per type, and the later one wins.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Exit

func Exit(code int) error

Exit makes a command end with status code without printing anything more: the command has already reported what happened. Declare the code in App.ExitCodes so help lists it. Exit(0) is nil.

func Fail added in v0.2.0

func Fail(code int, err error) error

Fail makes a command end with status code after Run prints err the way it prints any failure, "<tool>: error: <err>": for a failure the tool gives a code of its own, such as an ambiguity its callers tell apart. Declare the code in App.ExitCodes so help lists it. Fail(code, nil) is nil, and Fail(0, err) is err.

func Usage

func Usage(err error) error

Usage marks err as a usage error the command found itself, such as a combination of flags its declaration cannot express. Run prints it with the command's synopsis and exits with App.UsageCode. It classifies the outcome only: return it before the command has changed anything.

Types

type App

type App struct {
	// Name is the program name in help and error lines. Empty means the base
	// name of os.Args[0].
	Name string

	// Description says what the tool is for; required.
	Description string

	// New returns a fresh grammar, a pointer to the root command struct. Every
	// invocation gets its own, since parsing writes into it.
	New func() any

	// Outcome maps an error a command returned onto Usage, Exit or Fail, so a
	// tool states that once rather than in every Run method. An error it
	// passes on is a failure as usual, and nil is a success. It can end in a
	// catch-all: what a command chose with Exit, Fail or Usage skips it, as
	// does a parse error.
	Outcome func(error) error

	// Options are Kong options: kong.Bind, kong.BindToProvider, kong.Vars,
	// mappers and so on. They are shared by every invocation, so they must not
	// capture a grammar instance; a provider or Run method that needs the
	// parsed root takes it as a parameter (func(cli *CLI) (*Store, error)).
	Options []kong.Option

	// ExitCodes adds the statuses the tool's commands return through Exit,
	// with their meaning. 0, UsageCode and FailureCode are always listed; an
	// entry here rewords one of them.
	ExitCodes map[int]string

	// UsageCode is the exit status for invalid argv. Zero means 2.
	UsageCode int

	// FailureCode is the exit status for every other failure. Zero means 1.
	FailureCode int

	// Stdin is bound for Run methods as an io.Reader. Nil means os.Stdin.
	Stdin io.Reader

	// Stdout and Stderr become ctx.Stdout and ctx.Stderr of the bound
	// *kong.Context, unwrapped. Nil means os.Stdout and os.Stderr.
	Stdout, Stderr io.Writer
}

App is one tool's whole command line: main runs it and its tests run the same value, so nothing about the tool is assembled twice.

func (App) Run

func (a App) Run(args []string) (code int)

Run parses args (without the program name), runs the selected command and returns the process exit status. It never calls os.Exit.

type HelpSections

type HelpSections interface {
	HelpSections() []Section
}

HelpSections is implemented by a command struct (or the root struct) to add sections such as Examples or Output to its help page.

type Section

type Section struct {
	Title string
	Body  string

	// Shared prints the section on this command's page and on the page of
	// every command below it, such as a tool-wide overview on the root.
	Shared bool
}

Section is a titled block of help a command declares, printed verbatim after the generated parts of its page.

Directories

Path Synopsis
Package clikittest runs a clikit App in tests exactly as main does, and pins every help page against golden files.
Package clikittest runs a clikit App in tests exactly as main does, and pins every help page against golden files.

Jump to

Keyboard shortcuts

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