wand

package module
v0.0.0-...-3b0fa9f Latest Latest
Warning

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

Go to latest
Published: Jan 15, 2026 License: MIT Imports: 23 Imported by: 0

README

Wand

Wand is a library to wrap Go functions as executable binaries. It supports:

  • hierarchy of subcommands
  • binding of command line options
  • binding of environment variables
  • binding of configuration entries
  • positional arguments
  • automatically generated help and documentation from go doc

Example:

package main

import (
	"strings"
	"code.squareroundforest.org/arpio/wand"
)

func main() {
	wand.Exec(strings.Split)
}

Lib docs:

wand tool

The wand tool helps with the following tasks:

  • generating code from the go doc of the library that is converted into an executable binary by wand
  • generating man (roff) or markdown documentation
  • executing arbitrary Go functions in the command line addressed by their go path

Generating code with the docs of the wrapped library is particularly useful, because this way the automatically generated help can be based on the go docs. This is an important part of the attempt to achieve that ideal situation, where a solution to a problem is presented both as a library and a command line tool, and there is only a single place ("source of truth") of documentation.

Executing arbitrary Go functions in the command line is only a lucky side effect of the way wand was built. While it's not the primary function of the tool, it can be useful when just quickly trying out some Go functions or expressions, without having to create a throwaway main.go file.

wand tool installation
sudo make install

or in the current user's home:

prefix=~/.local make install

(See the recommended way below on how to use the locked version in the build tooling of a project.)

wand tool usage

Let's consider a project hierarchy where in the root we have a library (mylib), and in the ./cmd directory we have a main package, wrapping one or more functions of that library with wand as an executable binary:

./cmd/main.go
./lib.go

Generating the compiled docs from the go doc of a library:

wand docreflect mylib .

Generating a man page for the command:

wand manpages --date-string 2026-01-07 --version v1.0.0 ./cmd
wand tool bonus usage
wand --import strings strings.Split 1:2:3 :
wand tool docs

When installed:

man wand

When not installed with man pages:

wand help
wand tool usage with go.mod version

When generating the docs for a shared project, we may want to ensure that the same version of wand is used for both docreflect and manpages in every environment. It is best to use the version that is defined in go.mod. This can be achieved using some trivial scripts. The example below shows docreflect:

package main

import (
	"code.squareroundforest.org/arpio/wand/tools"
	"log"
	"os"
)

func main() {
	if len(os.Args) < 2 {
		log.Fatalln("expected package name")
	}

	if err := tools.Docreflect(os.Stdout, os.Args[1], os.Args[2:]...); err != nil {
		log.Fatalln(err)
	}
}

and calling it, e.g. from a Makefile, with go run like:

go run script/gendocs.go mylib . gopath.to/me/mylib > docs.gen.go

Made in Berlin, DE

Documentation

Overview

Wand provides utilities for constructing command line applications from functions, with automatic parameter binding from command line arguments, environment variables and configuration files, and automatically generated help and documentation.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Exec

func Exec(impl any, conf ...Config)

Exec executes a command.

The implementation parameter can be either a command, or a function, in which case it gets automatically wrapped by a command.

Types

type Cmd

type Cmd struct {
	// contains filtered or unexported fields
}

Cmd represents a command, a subcommand or a subcommand group.

func Args

func Args(cmd Cmd, min, max int) Cmd

Args can be used to specify the minimum and the maximum number of positional arguments when the implementation function has variadic parameters.

func Command

func Command(name string, impl any, subcmds ...Cmd) Cmd

Command defines a command or a subcommand.

The name argument is expected to be a valid symbol, with non-leading dashes allowed (^[a-zA-Z_][a-zA-Z_0-9-]*$). It is optional to set the name of the top level command, and it is inferred from the executing binary's name. The name of the executable should also be a symbol, otherwise the automatic binding of the environment variables may not work properly.

The implementation argument needs to be a function. The input parameters of the function need to be bindable, or either an io.Reader or io.Writer. Bindable means that the type can accept scalar values, like numbers, strings, time and duration, or it has fields like a struct. Pointers to bindable types are also bindable.

Scalar arguments are considered as positional arguments of the command. Variadic arguments are supported.

The fields of struct arguments define which command line options are supported by the command. Input for the options is accepted from both command line flags or environment variables, and, if defined, from configuration. Values defined in the environment override the configuration, and values passed in as command line flags override the environment (not propagated automatically to the environment of any child processes). Zero or more struct parameters are accepted, e.g. func(g Globals, o Options).

The names of the command line flags are inferred from the struct path and field names, and are expected in kebab case with double leading dashes, unless a short form is defined. Option values are accepted both with = or just space separated following the flag. In case of bool options, omitting the value is considered as true value. Since the struct path and field names are collapsed into their flat kebab case representation, this results in ambiguity, e.g. Foo.BarBaz and Foo.Bar.Baz. In such cases, the types of the ambiguous fields must be compatible, and each such field will be bound to the same input value. Slice fields are supported, they accept zero or more options of the same name, every option value is bound as an item of the slice, e.g. --foo one --foo two --foo three.

The names of the environment variables are inferred from the struct path and field names, are prefixed with the name of the executing binary, and are expected in lower or upper snake case. E.g. for a field InputValue: FOO_BAR_INPUT_VALUE=42 /usr/bin/foo-bar. The same ambiguity rules apply as in case of the command line flags. Slice fields are supported, the values slice fields can be separated by the : character, like in case of the PATH environment variable. When necessary, the : character can be escaped as \:.

For the implementation function, zero or one io.Reader and zero or one io.Writer parameter is accepted. When present, io.Reader is populated by os.Stdin and io.Writer is populated by os.Stdout.

The implementation function can have zero or more output parameters. If the last output parameter is of type error, and the returned value is not nil, the rest of the output parameters will be ignored and the error will be printed onto os.Stderr, and the command will exit with a non zero code. In case of no error, every return value will be printed on its own line, and if a return value is a slice, then every item will also be printed on its own line. If a return value is an io.Reader, that reader will be copied onto os.Stdout. If a value is a primitive value, it will be printed onto os.Stdout using the stdlib fmt package. Complex values will be printed to os.Stdout using code.squareroundforest.org/arpio/notation.

A command can have zero or more subcommands. When executing a subcommand, the subcommands path must be at the start of the command line expression. E.g. foo bar baz --qux 42 corge, where bar is a subcommand of foo and baz is a subcommand of bar. All rules that apply to the top command, also apply to the subcommands.

If a struct field doesn't override it, a --help command line flag will be automatically injected, and calling it will display an automatically generated help. Similarly, a help subcommand is also automatically injected under every command in the command tree, if a subcommand has not already taken the name help. The help subcommand has the same effect as the --help flag. When the user provides invalid input, the command exits with a non zero code, and displays a short suggestion to the user on how to display this help.

The automatically generated help can display the command synopsis and the possible args and options, but it will not contain any descriptions. Wand is meant to be used together with docreflect, which can extract the godoc documentation of the implementation function during development time, and compile it into the final binary. When done so, the automatically generated help will include the godocs of the implementation function and the description of the fields that serve as the command line options. It is also possible to generate man pages or markdown from the godoc documentation. For more details, see the documentation of the wand tool.

When executing a command, there are two distinct stages of validation. The first one validates that the command definition itself is valid, while the second stage validates the user input against the command definition. The validation of the command definition happens without considering the user input, and errors are prefixed with "program error:". This way we can know during development time if the command definition is valid or not.

func Default

func Default(cmd Cmd) Cmd

Default sets a subcommand as the default, to be used in a group.

func Group

func Group(name string, subcmds ...Cmd) Cmd

Group is like command but without implementation. It is used to group subcommands. It must have at least one subcommand. Optionally, one of the subcommands can be set as the default. When a default is set, and calling the group without specifying a subcommand, the default subcommand will be executed.

func ShortForm

func ShortForm(cmd Cmd, f ...string) Cmd

ShortForm can be used to define short-form flags for command line options. E.g: ShortForm(cmd, "f", "foo", "b", "bar", "z", "baz"). In which case the resulting command can be called as: my-command -f one -b two -z three. If say the Foo and Bar fields are of type boolean, then the flags can be grouped as: my-command -fbz three. The defined short forms apply to the entire command tree represented by the cmd parameter.

func Version

func Version(cmd Cmd, version string) Cmd

Version inserts a subcommand that, when called, displays the provided version.

type Config

type Config struct {
	// contains filtered or unexported fields
}

Config represents one or more configuration sources.

func ConfigFile

func ConfigFile(name string) Config

ConfigFile defines a configuration file source. Configuration files, when defined, are used in the entire execution scope, regardless of which subcommand is called, and the subcommands interpret only those config file fields that apply to them.

The configuration files are expected to use the ini file format, e.g values like foo_bar = 42 on separate lines. Repeated values are interpreted as distinct values for slice fields. Multiple config files are merged, such that value definitions in the config files with the higher index override the value definitions in the config files with lower indices. Discarding a value in a lower definition can be done with setting the key without a value, e.g: foo_bar. The formal definition of the used ini file format can be found here: ./ini.treerack.

When a configuration file is not marked as optional with the OptionalConfig() function, it is expected to be provided by the user.

Instead of using the static ConfigFile(name) definition, consider one or more of the dynamic definitions: Etc(), UserConfig(), ConfigFromOption() or SystemConfig().

func ConfigFromOption

func ConfigFromOption() Config

ConfigFromOption defines zero or more optional config files provided by the command line option --config. When used, multiple such config files can be specified by the user.

func Etc

func Etc() Config

Etc defines an optional system wide configuration file found at /etc/<name of the binary>/config.

func MergeConfig

func MergeConfig(conf ...Config) Config

MergeConfig merges multiple configuration definitions.

func OptionalConfig

func OptionalConfig(conf Config) Config

OptionalConfig marks a configuration file definition as optional. Without it, the configuration is expected to be present during the execution of the command.

func SystemConfig

func SystemConfig() Config

SystemConfig defines a typical set of optional configuration files, merging the Etc(), UserConfig() and ConfigFromOption() definitions.

func UserConfig

func UserConfig() Config

UserConfig defines an optional user specific config file found at ~/.<name of the binary>/config or ~/.config/<name of the binary>/config.

Directories

Path Synopsis
cmd
wand command
internal
script/man command
script/markdown command
Package tools provides tools to work with the wand library.
Package tools provides tools to work with the wand library.

Jump to

Keyboard shortcuts

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