Documentation
¶
Overview ¶
Package vclip implements a minimal, versatile command-line dispatcher.
The core idea is to keep the top-level parser small and neutral: it routes to subcommands but does not try to interpret or reorder their flags. This allows different subcommands to adopt different flag conventions while still sharing a consistent entry point and help behavior.
Design tradeoffs and behavior:
No global flag parsing: arguments before the subcommand are not reshuffled or guessed, because ownership is ambiguous when subcommands have different conventions.
Help is universal: the dispatcher treats "-h" and "--help" as aliases for the built-in help command, and appending "-h/--help" to a failing top-level invocation turns the error into contextual help.
Subcommands are responsible for their own flags: each command can use any parsing style (e.g., vflag), as long as it honors "--help".
The package evolved from real-world CLI use cases where subcommands emulate tools such as curl and dig, and it is designed to make migrating mixed-style parsers straightforward.
Example (DispatcherCommandHelpVersion) ¶
This example shows that `help version` dispatches to `version --help`, which just prints the version since the version subcommand ignores args.
package main
import (
"context"
"github.com/bassosimone/vclip"
"github.com/bassosimone/vflag"
)
func main() {
// create and init the dispatcher command
disp := vclip.NewDispatcherCommand("example", vflag.ContinueOnError)
disp.AddDescription("Dispatcher for network commands.")
disp.AddVersionHandlers("v0.1.0")
// add two commands faking curl and dig
disp.AddCommand(
"curl",
vclip.CommandFunc(func(ctx context.Context, args []string) error {
return nil
}),
"Utility to transfer URLs.",
)
disp.AddCommand(
"dig",
vclip.CommandFunc(func(ctx context.Context, args []string) error {
return nil
}),
"Utility to query DNS servers.",
)
// a background context is sufficient for this example
ctx := context.Background()
// Invoke with `help version`; dispatches to `version --help` which prints the version
disp.Main(ctx, []string{"help", "version"})
}
Output: v0.1.0
Example (DispatcherCommandUsageHelpHelp) ¶
This example shows that `help --help` is equivalent to `help` (if using ContinueOnError)
package main
import (
"context"
"github.com/bassosimone/vclip"
"github.com/bassosimone/vflag"
)
func main() {
// create and init the dispatcher command
disp := vclip.NewDispatcherCommand("example", vflag.ContinueOnError)
disp.AddDescription("Dispatcher for network commands.")
// add two commands faking curl and dig
disp.AddCommand(
"curl",
vclip.CommandFunc(func(ctx context.Context, args []string) error {
return nil
}),
"Utility to transfer URLs.",
)
disp.AddCommand(
"dig",
vclip.CommandFunc(func(ctx context.Context, args []string) error {
return nil
}),
"Utility to query DNS servers.",
)
// a background context is sufficient for this example
ctx := context.Background()
// add an extra `--help` but we should see the same output
disp.Main(ctx, []string{"help", "--help"})
}
Output: Usage example <command> [args...] Description Dispatcher for network commands. Commands curl Utility to transfer URLs. dig Utility to query DNS servers. -h, --help, help Show help about this command or about a subcommand. Hints Use `example <command> --help' to get command-specific help. Append `--help' or `-h' to any command line failing with usage errors to hide the error and obtain contextual help.
Example (DispatcherCommandUsageHelpWithInvalidFlag) ¶
This example shows what the help subcommand does when passed an invalid flag when using ContinueOnError (with ExitOnError we don't control what happens since it all depends on the vflag library)
package main
import (
"context"
"os"
"github.com/bassosimone/vclip"
"github.com/bassosimone/vflag"
)
func main() {
// create and init the dispatcher command
disp := vclip.NewDispatcherCommand("example", vflag.ContinueOnError)
disp.AddDescription("Dispatcher for network commands.")
// add two commands faking curl and dig
disp.AddCommand(
"curl",
vclip.CommandFunc(func(ctx context.Context, args []string) error {
return nil
}),
"Utility to transfer URLs.",
)
disp.AddCommand(
"dig",
vclip.CommandFunc(func(ctx context.Context, args []string) error {
return nil
}),
"Utility to query DNS servers.",
)
// Redirect the stderr to the stdout so that we can capture it
disp.Stderr = os.Stdout
// a background context is sufficient for this example
ctx := context.Background()
// pass an invalid flag to `help` to see an error
disp.Main(ctx, []string{"help", "--nope"})
}
Output: example help: unknown option: --nope example help: try `example help --help' for more help.
Example (DispatcherCommandUsageHelpWithInvalidSubcommand) ¶
This example shows what the help subcommand does when passed an invalid subcommand when using ContinueOnError (with ExitOnError we don't control what happens since it all depends on the vflag library)
package main
import (
"context"
"os"
"github.com/bassosimone/vclip"
"github.com/bassosimone/vflag"
)
func main() {
// create and init the dispatcher command
disp := vclip.NewDispatcherCommand("example", vflag.ContinueOnError)
disp.AddDescription("Dispatcher for network commands.")
// add two commands faking curl and dig
disp.AddCommand(
"curl",
vclip.CommandFunc(func(ctx context.Context, args []string) error {
return nil
}),
"Utility to transfer URLs.",
)
disp.AddCommand(
"dig",
vclip.CommandFunc(func(ctx context.Context, args []string) error {
return nil
}),
"Utility to query DNS servers.",
)
// Redirect the stderr to the stdout so that we can capture it
disp.Stderr = os.Stdout
// a background context is sufficient for this example
ctx := context.Background()
// pass an invalid subcommand to `help` to see an error
disp.Main(ctx, []string{"help", "nope"})
}
Output: example help: command not found: nope example help: try `example help --help' for more help.
Example (DispatcherCommandUsageWithHFlag) ¶
This example shows the usage printed when using the `-h` flag.
package main
import (
"context"
"github.com/bassosimone/vclip"
"github.com/bassosimone/vflag"
)
func main() {
// create and init the dispatcher command
disp := vclip.NewDispatcherCommand("example", vflag.ExitOnError)
disp.AddDescription("Dispatcher for network commands.")
// add two commands faking curl and dig
disp.AddCommand(
"curl",
vclip.CommandFunc(func(ctx context.Context, args []string) error {
return nil
}),
"Utility to transfer URLs.",
)
disp.AddCommand(
"dig",
vclip.CommandFunc(func(ctx context.Context, args []string) error {
return nil
}),
"Utility to query DNS servers.",
)
// a background context is sufficient for this example
ctx := context.Background()
// Invoke with `-h` so that we print the help
disp.Main(ctx, []string{"-h"})
}
Output: Usage example <command> [args...] Description Dispatcher for network commands. Commands curl Utility to transfer URLs. dig Utility to query DNS servers. -h, --help, help Show help about this command or about a subcommand. Hints Use `example <command> --help' to get command-specific help. Append `--help' or `-h' to any command line failing with usage errors to hide the error and obtain contextual help.
Example (DispatcherCommandUsageWithHelpFlag) ¶
This example shows the usage printed when using the `--help` flag.
package main
import (
"context"
"github.com/bassosimone/vclip"
"github.com/bassosimone/vflag"
)
func main() {
// create and init the dispatcher command
disp := vclip.NewDispatcherCommand("example", vflag.ExitOnError)
disp.AddDescription("Dispatcher for network commands.")
// add two commands faking curl and dig
disp.AddCommand(
"curl",
vclip.CommandFunc(func(ctx context.Context, args []string) error {
return nil
}),
"Utility to transfer URLs.",
)
disp.AddCommand(
"dig",
vclip.CommandFunc(func(ctx context.Context, args []string) error {
return nil
}),
"Utility to query DNS servers.",
)
// a background context is sufficient for this example
ctx := context.Background()
// Invoke with `--help` so that we print the help
disp.Main(ctx, []string{"--help"})
}
Output: Usage example <command> [args...] Description Dispatcher for network commands. Commands curl Utility to transfer URLs. dig Utility to query DNS servers. -h, --help, help Show help about this command or about a subcommand. Hints Use `example <command> --help' to get command-specific help. Append `--help' or `-h' to any command line failing with usage errors to hide the error and obtain contextual help.
Example (DispatcherCommandUsageWithInvalidCommand) ¶
This example shows the error emitted for an invalid command
package main
import (
"context"
"os"
"github.com/bassosimone/vclip"
"github.com/bassosimone/vflag"
)
func main() {
// create and init the dispatcher command
disp := vclip.NewDispatcherCommand("example", vflag.ExitOnError)
disp.AddDescription("Dispatcher for network commands.")
// add two commands faking curl and dig
disp.AddCommand(
"curl",
vclip.CommandFunc(func(ctx context.Context, args []string) error {
return nil
}),
"Utility to transfer URLs.",
)
disp.AddCommand(
"dig",
vclip.CommandFunc(func(ctx context.Context, args []string) error {
return nil
}),
"Utility to query DNS servers.",
)
// Override Exit to transform it into a panic: using ExitOnError for a subcommand
// eventually causes `disp.Exit(0)` to be invoked after printing help
disp.Exit = func(status int) {
panic("mocked exit invocation")
}
// Handle the panic by caused by Exit by simply ignoring it
defer func() { recover() }()
// Redirect the stderr to the stdout so that we can capture it
disp.Stderr = os.Stdout
// a background context is sufficient for this example
ctx := context.Background()
// Invoke with `nope` so that we fail
disp.Main(ctx, []string{"nope"})
}
Output: example: command not found: nope example: use `example --help' to see the available commands
Example (DispatcherCommandUsageWithInvalidCommandIfAppendH) ¶
This example shows that we show the help w/o failure w/ `-h` at the end of the command line.
package main
import (
"context"
"github.com/bassosimone/vclip"
"github.com/bassosimone/vflag"
)
func main() {
// create and init the dispatcher command
disp := vclip.NewDispatcherCommand("example", vflag.ExitOnError)
disp.AddDescription("Dispatcher for network commands.")
// add two commands faking curl and dig
disp.AddCommand(
"curl",
vclip.CommandFunc(func(ctx context.Context, args []string) error {
return nil
}),
"Utility to transfer URLs.",
)
disp.AddCommand(
"dig",
vclip.CommandFunc(func(ctx context.Context, args []string) error {
return nil
}),
"Utility to query DNS servers.",
)
// a background context is sufficient for this example
ctx := context.Background()
// append -h at the end to neutralize the error and show the help
disp.Main(ctx, []string{"nope", "-h"})
}
Output: Usage example <command> [args...] Description Dispatcher for network commands. Commands curl Utility to transfer URLs. dig Utility to query DNS servers. -h, --help, help Show help about this command or about a subcommand. Hints Use `example <command> --help' to get command-specific help. Append `--help' or `-h' to any command line failing with usage errors to hide the error and obtain contextual help.
Example (DispatcherCommandUsageWithInvalidCommandIfAppendHelp) ¶
This example shows that we show the help w/o failure w/ `--help` at the end of the command line.
package main
import (
"context"
"github.com/bassosimone/vclip"
"github.com/bassosimone/vflag"
)
func main() {
// create and init the dispatcher command
disp := vclip.NewDispatcherCommand("example", vflag.ExitOnError)
disp.AddDescription("Dispatcher for network commands.")
// add two commands faking curl and dig
disp.AddCommand(
"curl",
vclip.CommandFunc(func(ctx context.Context, args []string) error {
return nil
}),
"Utility to transfer URLs.",
)
disp.AddCommand(
"dig",
vclip.CommandFunc(func(ctx context.Context, args []string) error {
return nil
}),
"Utility to query DNS servers.",
)
// a background context is sufficient for this example
ctx := context.Background()
// append --help at the end to neutralize the error and show the help
disp.Main(ctx, []string{"nope", "--help"})
}
Output: Usage example <command> [args...] Description Dispatcher for network commands. Commands curl Utility to transfer URLs. dig Utility to query DNS servers. -h, --help, help Show help about this command or about a subcommand. Hints Use `example <command> --help' to get command-specific help. Append `--help' or `-h' to any command line failing with usage errors to hide the error and obtain contextual help.
Example (DispatcherCommandUsageWithoutArguments) ¶
This example shows the usage printed when invoked without arguments.
package main
import (
"context"
"github.com/bassosimone/vclip"
"github.com/bassosimone/vflag"
)
func main() {
// create and init the dispatcher command
disp := vclip.NewDispatcherCommand("example", vflag.ExitOnError)
disp.AddDescription("Dispatcher for network commands.")
// add two commands faking curl and dig
disp.AddCommand(
"curl",
vclip.CommandFunc(func(ctx context.Context, args []string) error {
return nil
}),
"Utility to transfer URLs.",
)
disp.AddCommand(
"dig",
vclip.CommandFunc(func(ctx context.Context, args []string) error {
return nil
}),
"Utility to query DNS servers.",
)
// a background context is sufficient for this example
ctx := context.Background()
// Invoke without argumuments so that we print the help
disp.Main(ctx, []string{})
}
Output: Usage example <command> [args...] Description Dispatcher for network commands. Commands curl Utility to transfer URLs. dig Utility to query DNS servers. -h, --help, help Show help about this command or about a subcommand. Hints Use `example <command> --help' to get command-specific help. Append `--help' or `-h' to any command line failing with usage errors to hide the error and obtain contextual help.
Example (DispatcherCommandVersionCommand) ¶
This example shows the version printed using the version command.
package main
import (
"context"
"github.com/bassosimone/vclip"
"github.com/bassosimone/vflag"
)
func main() {
// create and init the dispatcher command
disp := vclip.NewDispatcherCommand("example", vflag.ExitOnError)
disp.AddDescription("Dispatcher for network commands.")
disp.AddVersionHandlers("v0.1.0")
// add two commands faking curl and dig
disp.AddCommand(
"curl",
vclip.CommandFunc(func(ctx context.Context, args []string) error {
return nil
}),
"Utility to transfer URLs.",
)
disp.AddCommand(
"dig",
vclip.CommandFunc(func(ctx context.Context, args []string) error {
return nil
}),
"Utility to query DNS servers.",
)
// a background context is sufficient for this example
ctx := context.Background()
// Invoke with `version` so that we print the version number
disp.Main(ctx, []string{"version"})
}
Output: v0.1.0
Example (DispatcherCommandVersionFlag) ¶
This example shows the version printed using the `--version` flag.
package main
import (
"context"
"github.com/bassosimone/vclip"
"github.com/bassosimone/vflag"
)
func main() {
// create and init the dispatcher command
disp := vclip.NewDispatcherCommand("example", vflag.ExitOnError)
disp.AddDescription("Dispatcher for network commands.")
disp.AddVersionHandlers("v0.1.0")
// add two commands faking curl and dig
disp.AddCommand(
"curl",
vclip.CommandFunc(func(ctx context.Context, args []string) error {
return nil
}),
"Utility to transfer URLs.",
)
disp.AddCommand(
"dig",
vclip.CommandFunc(func(ctx context.Context, args []string) error {
return nil
}),
"Utility to query DNS servers.",
)
// a background context is sufficient for this example
ctx := context.Background()
// Invoke with `--version` so that we print the version number
disp.Main(ctx, []string{"--version"})
}
Output: v0.1.0
Example (DispatcherCommandVersionHelp) ¶
This example shows that `version --help` just prints the version because the version subcommand ignores all arguments.
package main
import (
"context"
"github.com/bassosimone/vclip"
"github.com/bassosimone/vflag"
)
func main() {
// create and init the dispatcher command
disp := vclip.NewDispatcherCommand("example", vflag.ContinueOnError)
disp.AddDescription("Dispatcher for network commands.")
disp.AddVersionHandlers("v0.1.0")
// add two commands faking curl and dig
disp.AddCommand(
"curl",
vclip.CommandFunc(func(ctx context.Context, args []string) error {
return nil
}),
"Utility to transfer URLs.",
)
disp.AddCommand(
"dig",
vclip.CommandFunc(func(ctx context.Context, args []string) error {
return nil
}),
"Utility to query DNS servers.",
)
// a background context is sufficient for this example
ctx := context.Background()
// Invoke with `version --help`; the version subcommand ignores extra args
disp.Main(ctx, []string{"version", "--help"})
}
Output: v0.1.0
Example (DispatcherCommandVersionInvalidFlag) ¶
This example shows that `version --nope` just prints the version because the version subcommand ignores all arguments.
package main
import (
"context"
"github.com/bassosimone/vclip"
"github.com/bassosimone/vflag"
)
func main() {
// create and init the dispatcher command
disp := vclip.NewDispatcherCommand("example", vflag.ContinueOnError)
disp.AddDescription("Dispatcher for network commands.")
disp.AddVersionHandlers("v0.1.0")
// add two commands faking curl and dig
disp.AddCommand(
"curl",
vclip.CommandFunc(func(ctx context.Context, args []string) error {
return nil
}),
"Utility to transfer URLs.",
)
disp.AddCommand(
"dig",
vclip.CommandFunc(func(ctx context.Context, args []string) error {
return nil
}),
"Utility to query DNS servers.",
)
// a background context is sufficient for this example
ctx := context.Background()
// Invoke with `version --nope`; the version subcommand ignores extra args
disp.Main(ctx, []string{"version", "--nope"})
}
Output: v0.1.0
Index ¶
- Variables
- func Main(ctx context.Context, cmd Command, args []string)
- type Command
- type CommandFunc
- type DefaultUsagePrinter
- type DescribedCommand
- type DispatcherCommand
- func (c *DispatcherCommand) AddCommand(name string, cmd Command, descr ...string)
- func (c *DispatcherCommand) AddDescription(text ...string)
- func (c *DispatcherCommand) AddVersionHandlers(version string)
- func (c *DispatcherCommand) Main(ctx context.Context, args []string) error
- func (c *DispatcherCommand) MustAddCommandAlias(curName, newAlias string)
- type UsagePrinter
Examples ¶
- Package (DispatcherCommandHelpVersion)
- Package (DispatcherCommandUsageHelpHelp)
- Package (DispatcherCommandUsageHelpWithInvalidFlag)
- Package (DispatcherCommandUsageHelpWithInvalidSubcommand)
- Package (DispatcherCommandUsageWithHFlag)
- Package (DispatcherCommandUsageWithHelpFlag)
- Package (DispatcherCommandUsageWithInvalidCommand)
- Package (DispatcherCommandUsageWithInvalidCommandIfAppendH)
- Package (DispatcherCommandUsageWithInvalidCommandIfAppendHelp)
- Package (DispatcherCommandUsageWithoutArguments)
- Package (DispatcherCommandVersionCommand)
- Package (DispatcherCommandVersionFlag)
- Package (DispatcherCommandVersionHelp)
- Package (DispatcherCommandVersionInvalidFlag)
Constants ¶
This section is empty.
Variables ¶
var ErrCommandNotFound = errors.New("command not found")
ErrCommandNotFound indicates that the given command was not found.
Functions ¶
func Main ¶
Main runs the given Command wrapping its execution as follows:
1. we wrap the context.Context using signal.NotifyContext so that interruptions such as `^C` interrupt the command exectution.
2. we use runtimex.LogFatalOnError0 to ensure that the error returned by the command is logged and leads to exiting.
The args MUST NOT contain the program name (e.g. `os.Args[1:]`).
Types ¶
type CommandFunc ¶
CommandFunc transforms a func into a Command.
type DefaultUsagePrinter ¶
type DefaultUsagePrinter struct{}
DefaultUsagePrinter is the default UsagePrinter implementation.
Construct using NewDefaultUsagePrinter.
func NewDefaultUsagePrinter ¶
func NewDefaultUsagePrinter() *DefaultUsagePrinter
NewDefaultUsagePrinter constructs a new *DefaultUsagePrinter.
func (*DefaultUsagePrinter) PrintHelp ¶
func (up *DefaultUsagePrinter) PrintHelp(c *DispatcherCommand, w io.Writer)
PrintHelp implements UsagePrinter.
This method panics on I/O error.
type DescribedCommand ¶
type DescribedCommand struct {
// Cmd is the [Command].
Cmd Command
// Descr contains the [Command] description.
Descr []string
}
DescribedCommand is a command living side by side with its docs.
func NewDescribedCommand ¶
func NewDescribedCommand(cmd Command, descr ...string) DescribedCommand
NewDescribedCommand creates a Command along with the related documentation.
type DispatcherCommand ¶
type DispatcherCommand struct {
// CommandAliasToName maps a command alias to its real name.
//
// [NewDispatcherCommand] initializes it as an empty map.
CommandAliasToName map[string]string
// CommandNameToAliases maps a command name to its aliases.
//
// [NewDispatcherCommand] initializes it as an empty map.
CommandNameToAliases map[string][]string
// Commands maps between names and [Command] instances.
//
// [NewDispatcherCommand] initializes the following built-in subcommands:
//
// 1. `-h`, `--help`, and `help` to print help.
//
// 2. `--version` and `version` to print the version number.
Commands map[string]DescribedCommand
// Description contains the description paragraphs.
//
// [NewDispatcherCommand] initializes this field to an empty slice.
Description []string
// ErrorHandling is the [vflag.ErrorHandling] policy to use.
//
// Set by the parameter passed to [NewDispatcherCommand].
ErrorHandling vflag.ErrorHandling
// Exit is the function to call with the [ExitOnError] policy.
//
// [NewDispatcherCommand] initializes it to [os.Exit].
Exit func(status int)
// Name is the command name.
//
// Set by the parameter passed to [NewDispatcherCommand].
Name string
// NewHelpSubcommandUsagePrinter returns the [vflag.UsagePrinter] that the
// auto-generated help subcommand should use.
//
// [NewDispatcherCommand] initializes this function to a sane default
// that includes a minimal command description.
NewHelpSubcommandUsagePrinter func() vflag.UsagePrinter
// Stderr is the [io.Writer] to use as the stderr.
//
// [NewDispatcherCommand] initializes this field to [os.Stderr].
//
// We use this field with [ExitOnError] policy.
Stderr io.Writer
// Stdout is the [io.Writer] to use as the stdout.
//
// [NewDispatcherCommand] initializes this field to [os.Stdout].
//
// We use this field with [ExitOnError] policy.
Stdout io.Writer
// UsagePrinter is the [UsagePrinter] to use.
//
// Initialized by [NewDispatcherCommand] using [NewDefaultUsagePrinter].
UsagePrinter UsagePrinter
}
DispatcherCommand is a command that dispatches execution to subcommands.
Construct using NewDispatcherCommand.
func NewDispatcherCommand ¶
func NewDispatcherCommand(name string, handling vflag.ErrorHandling) *DispatcherCommand
NewDispatcherCommand creates a new instance of *DispatcherCommand.
func (*DispatcherCommand) AddCommand ¶
func (c *DispatcherCommand) AddCommand(name string, cmd Command, descr ...string)
AddCommand adds a Command with the given name to the *DispatcherCommand.
Commands MUST handle the `--help` flag and provide help when they see it regardless of the otherwise different convention they use for flags.
func (*DispatcherCommand) AddDescription ¶
func (c *DispatcherCommand) AddDescription(text ...string)
AddDescription adds the given text to the description paragraphs.
func (*DispatcherCommand) AddVersionHandlers ¶
func (c *DispatcherCommand) AddVersionHandlers(version string)
AddVersionHandlers adds code to handle `version` and `--version` by printing the version number passed to this method.
func (*DispatcherCommand) Main ¶
func (c *DispatcherCommand) Main(ctx context.Context, args []string) error
Main implements [CommandHandler].
func (*DispatcherCommand) MustAddCommandAlias ¶
func (c *DispatcherCommand) MustAddCommandAlias(curName, newAlias string)
MustAddCommandAlias introduces an alias for an existing command.
This method panics if curName is not an existing command name.
type UsagePrinter ¶
type UsagePrinter interface {
PrintHelp(c *DispatcherCommand, w io.Writer)
}
UsagePrinter prints the help for *DispatcherCommand.