library documentation
This commit is contained in:
parent
96f3e02de8
commit
114ef27b6c
@ -2,34 +2,35 @@
|
||||
Generated with https://code.squareroundforest.org/arpio/docreflect
|
||||
*/
|
||||
|
||||
|
||||
package wand
|
||||
|
||||
import "code.squareroundforest.org/arpio/docreflect"
|
||||
|
||||
func init() {
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools", "")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.Docreflect", "\nfunc(out, packageName, gopaths)")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.Exec", "\nfunc(o, stdin, stdout, args)")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.ExecOptions", "")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.ExecOptions.CacheDir", "")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.ExecOptions.ClearCache", "")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.ExecOptions.Import", "")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.ExecOptions.NoCache", "")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.ExecOptions.ReplaceModule", "")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.Man", "\nfunc(out, o, commandDir)")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.ManOptions", "")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.ManOptions.DateString", "")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.ManOptions.Version", "")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.Markdown", "\nfunc(out, o, commandDir)")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.MarkdownOptions", "")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.MarkdownOptions.Level", "")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.commandReader", "\nfunc(in)")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.execCommandDir", "\nfunc(out, commandDir, env)")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.execInput", "\nfunc(o, stdin, stdout, stderr, args)")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.execInternal", "\nfunc(command, args)")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.execWand", "\nfunc(o, stdin, stdout, stderr, args)")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.execc", "\nfunc(stdin, stdout, stderr, command, args, env)")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.hash", "\nfunc(expression, imports)")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.printGoFile", "\nfunc(fn, expression, imports)")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.readExec", "\nfunc(o, stdin, stdout, stderr)")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.selfPkg", "")
|
||||
}
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools", "")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.Docreflect", "\nfunc(out, packageName, gopaths)")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.Exec", "\nfunc(o, stdin, stdout, args)")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.ExecOptions", "")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.ExecOptions.CacheDir", "")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.ExecOptions.ClearCache", "")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.ExecOptions.Import", "")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.ExecOptions.NoCache", "")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.ExecOptions.ReplaceModule", "")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.Man", "\nfunc(out, o, commandDir)")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.ManOptions", "")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.ManOptions.DateString", "")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.ManOptions.Version", "")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.Markdown", "\nfunc(out, o, commandDir)")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.MarkdownOptions", "")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.MarkdownOptions.Level", "")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.commandReader", "\nfunc(in)")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.execCommandDir", "\nfunc(out, commandDir, env)")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.execInput", "\nfunc(o, stdin, stdout, stderr, args)")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.execInternal", "\nfunc(command, args)")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.execWand", "\nfunc(o, stdin, stdout, stderr, args)")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.execc", "\nfunc(stdin, stdout, stderr, command, args, env)")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.hash", "\nfunc(expression, imports)")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.printGoFile", "\nfunc(fn, expression, imports)")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.readExec", "\nfunc(o, stdin, stdout, stderr)")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/tools.selfPkg", "")
|
||||
}
|
||||
|
||||
@ -2,21 +2,22 @@
|
||||
Generated with https://code.squareroundforest.org/arpio/docreflect
|
||||
*/
|
||||
|
||||
|
||||
package wand
|
||||
|
||||
import "code.squareroundforest.org/arpio/docreflect"
|
||||
|
||||
func init() {
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/internal/tests/testlib", "")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/internal/tests/testlib.Bar", "\nfunc(out, a, b, c)")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/internal/tests/testlib.Baz", "\nfunc(o)")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/internal/tests/testlib.CustomHelp", "\nfunc(o)")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/internal/tests/testlib.Foo", "Foo sums three numbers.\nIt prints the sum to stdout.\n\nThe input numbers can be any integer.\n\nfunc(a, b, c)")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/internal/tests/testlib.OptionWithHelp", "")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/internal/tests/testlib.OptionWithHelp.Help", "Custom help.\n")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/internal/tests/testlib.Options", "")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/internal/tests/testlib.Options.Bar", "Bars, any number.\n")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/internal/tests/testlib.Options.Duration", "Duration is another option.\n")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/internal/tests/testlib.Options.Foo", "Foo is an option.\n")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/internal/tests/testlib.Options.Some", "Some is an option of any type.\n")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/internal/tests/testlib.Options.Time", "Time is the third option here.\n")
|
||||
}
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/internal/tests/testlib", "")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/internal/tests/testlib.Bar", "\nfunc(out, a, b, c)")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/internal/tests/testlib.Baz", "\nfunc(o)")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/internal/tests/testlib.CustomHelp", "\nfunc(o)")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/internal/tests/testlib.Foo", "Foo sums three numbers.\nIt prints the sum to stdout.\n\nThe input numbers can be any integer.\n\nfunc(a, b, c)")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/internal/tests/testlib.OptionWithHelp", "")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/internal/tests/testlib.OptionWithHelp.Help", "Custom help.\n")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/internal/tests/testlib.Options", "")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/internal/tests/testlib.Options.Bar", "Bars, any number.\n")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/internal/tests/testlib.Options.Duration", "Duration is another option.\n")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/internal/tests/testlib.Options.Foo", "Foo is an option.\n")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/internal/tests/testlib.Options.Some", "Some is an option of any type.\n")
|
||||
docreflect.Register("code.squareroundforest.org/arpio/wand/internal/tests/testlib.Options.Time", "Time is the third option here.\n")
|
||||
}
|
||||
|
||||
151
lib.go
151
lib.go
@ -1,5 +1,6 @@
|
||||
// Wand provides utilities for constructing command line applications from functions, with automatic parameter
|
||||
// binding from command line arguments, environment variables and configuration files.
|
||||
// binding from command line arguments, environment variables and configuration files, and automatically
|
||||
// generated help and documentation.
|
||||
package wand
|
||||
|
||||
import (
|
||||
@ -8,7 +9,7 @@ import (
|
||||
"path"
|
||||
)
|
||||
|
||||
// the parsing errors might be tricky
|
||||
// Config represents one or more configuration sources.
|
||||
type Config struct {
|
||||
file func(Cmd) *file
|
||||
merge []Config
|
||||
@ -17,6 +18,7 @@ type Config struct {
|
||||
test string
|
||||
}
|
||||
|
||||
// Cmd represents a command, a subcommand or a subcommand group.
|
||||
type Cmd struct {
|
||||
name string
|
||||
impl any
|
||||
@ -30,27 +32,104 @@ type Cmd struct {
|
||||
version string
|
||||
}
|
||||
|
||||
// name needs to be a valid symbol. The application name should also be a valid symbol,
|
||||
// though not mandatory. If it is not, the environment variables may not work properly.
|
||||
// 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 both from 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 ambigous 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 execting 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 poplated 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
|
||||
// values 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 does not have 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 is 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 Command(name string, impl any, subcmds ...Cmd) Cmd {
|
||||
return Cmd{name: name, impl: impl, subcommands: subcmds}
|
||||
}
|
||||
|
||||
// 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 Group(name string, subcmds ...Cmd) Cmd {
|
||||
return Cmd{name: name, group: true, subcommands: subcmds}
|
||||
}
|
||||
|
||||
// Default sets a subcommand as the default, to be used in a group.
|
||||
func Default(cmd Cmd) Cmd {
|
||||
cmd.isDefault = true
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Args can be used to specify the minimum and the maximum number of positional arguments when the
|
||||
// implementation function has variadic parameters.
|
||||
func Args(cmd Cmd, min, max int) Cmd {
|
||||
cmd.minPositional = min
|
||||
cmd.maxPositional = max
|
||||
return 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 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 ShortForm(cmd Cmd, f ...string) Cmd {
|
||||
if len(f)%2 != 0 {
|
||||
f = f[:len(f)-1]
|
||||
@ -67,6 +146,7 @@ func ShortForm(cmd Cmd, f ...string) Cmd {
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Version inserts a subcommand that, when called, displays the provided version.
|
||||
func Version(cmd Cmd, version string) Cmd {
|
||||
cmd.subcommands = append(
|
||||
cmd.subcommands,
|
||||
@ -76,28 +156,29 @@ func Version(cmd Cmd, version string) Cmd {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func MergeConfig(conf ...Config) Config {
|
||||
return Config{
|
||||
merge: conf,
|
||||
}
|
||||
}
|
||||
|
||||
func OptionalConfig(conf Config) Config {
|
||||
conf.optional = true
|
||||
for i := range conf.merge {
|
||||
conf.merge[i] = OptionalConfig(conf.merge[i])
|
||||
}
|
||||
|
||||
return conf
|
||||
}
|
||||
|
||||
// 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 ConfigFile(name string) Config {
|
||||
return Config{
|
||||
file: func(Cmd) *file { return fileReader(name) },
|
||||
}
|
||||
}
|
||||
|
||||
// the config will consider the command name as in the arguments
|
||||
// Etc defines an optional system wide configuration file found at /etc/<name of the binary>/config.
|
||||
func Etc() Config {
|
||||
return OptionalConfig(Config{
|
||||
file: func(cmd Cmd) *file {
|
||||
@ -106,7 +187,8 @@ func Etc() Config {
|
||||
})
|
||||
}
|
||||
|
||||
// the config will consider the command name as in the arguments
|
||||
// UserConfig defines an optional user specific config file found at ~/.<name of the binary>/config or
|
||||
// ~/.config/<name of the binary>/config.
|
||||
func UserConfig() Config {
|
||||
return OptionalConfig(
|
||||
MergeConfig(
|
||||
@ -124,15 +206,40 @@ func UserConfig() 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 ConfigFromOption() Config {
|
||||
return Config{fromOption: true}
|
||||
}
|
||||
|
||||
// SystemConfig defines a typical set of optional configuration files, merging the Etc(), UserConfig() and
|
||||
// ConfigFromOption() definitions.
|
||||
func SystemConfig() Config {
|
||||
return MergeConfig(Etc(), UserConfig(), ConfigFromOption())
|
||||
}
|
||||
|
||||
// the env will consider the command name as in the arguments
|
||||
// MergeConfig merges multiple configuration definitions.
|
||||
func MergeConfig(conf ...Config) Config {
|
||||
return Config{
|
||||
merge: conf,
|
||||
}
|
||||
}
|
||||
|
||||
// OptionalConfig marks a configuration file definition as optional. Without it, the configuration is expected
|
||||
// to be present during executing the command.
|
||||
func OptionalConfig(conf Config) Config {
|
||||
conf.optional = true
|
||||
for i := range conf.merge {
|
||||
conf.merge[i] = OptionalConfig(conf.merge[i])
|
||||
}
|
||||
|
||||
return conf
|
||||
}
|
||||
|
||||
// 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.
|
||||
func Exec(impl any, conf ...Config) {
|
||||
exec(os.Stdin, os.Stdout, os.Stderr, os.Exit, wrap(impl), MergeConfig(conf...), os.Environ(), os.Args)
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user