clif

package module
v0.0.4 Latest Latest
Warning

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

Go to latest
Published: Sep 1, 2024 License: MIT Imports: 7 Imported by: 0

Documentation

Overview

Package clif provides a framework for writing command line applications.

The framework starts with an Application, which defines some global flags that apply to all commands and the different Commands that the application accepts.

Each flag is defined by a FlagDef, which describes the flag name and how to parse it.

Each Command describes the command name, any subcommands and flags it accepts, and other information about parsing the command and how to execute it.

Once input is matched to the Command, it calls the HandlerBuilder associated with that Command. The HandlerBuilder is responsible for turning flags, arguments, and a Command into a Handler. It's separated out from the Handler so the business logic of the Handler can be separated out from the logic to parse the flags and arguments.

Finally, once we have a Handler, it gets executed, with a Response to write output to and record the desired exit code of the command.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func FlagsHelp

func FlagsHelp(command parseable) string

FlagsHelp returns a default usage string for the flags defined for the passed Command or Application.

func SubcommandsHelp

func SubcommandsHelp(command parseable) string

SubcommandsHelp returns a default usage string for the subcommands provided by the passed Command or Application.

Types

type Application

type Application struct {
	// Commands are the commands that the application supports.
	Commands []Command

	// Flags are the definitions for any global flags the application
	// supports.
	Flags []FlagDef
}

Application is the root definition of a CLI.

Example
package main

import (
	"context"
	"fmt"

	"impractical.co/clif"
	"impractical.co/clif/flagtypes"
)

type funcCommandHandler func(ctx context.Context, resp *clif.Response)

func (f funcCommandHandler) Build(_ context.Context, _ map[string]clif.Flag, _ []string, _ *clif.Response) clif.Handler { //nolint:ireturn // filling an interface
	return f
}

func (f funcCommandHandler) Handle(ctx context.Context, resp *clif.Response) {
	f(ctx, resp)
}

type flagCommandHandler struct {
	flags map[string]clif.Flag
	args  []string
	f     func(ctx context.Context, flags map[string]clif.Flag, args []string, resp *clif.Response)
}

func (f flagCommandHandler) Build(_ context.Context, flags map[string]clif.Flag, args []string, _ *clif.Response) clif.Handler { //nolint:ireturn // filling an interface
	f.flags = flags
	f.args = args
	return f
}

func (f flagCommandHandler) Handle(ctx context.Context, resp *clif.Response) {
	f.f(ctx, f.flags, f.args, resp)
}

//nolint:errcheck // several places we're not checking errors because they can't fail
func main() {
	app := clif.Application{
		Commands: []clif.Command{
			{
				Name:        "help",
				Description: "Displays help information about this program.",
				Handler: funcCommandHandler(func(_ context.Context, resp *clif.Response) {
					resp.Output.Write([]byte("this is help information\n"))
				}),
			},
			{
				Name: "foo",
				Subcommands: []clif.Command{
					{
						Name: "bar",
						Flags: []clif.FlagDef{
							{
								Name:                 "quux",
								ValueAccepted:        true,
								OnlyAfterCommandName: false,
								Parser:               flagtypes.StringParser{},
							},
						},
						Handler: flagCommandHandler{
							f: func(_ context.Context, flags map[string]clif.Flag, args []string, resp *clif.Response) {
								fmt.Fprintln(resp.Output, flags, args)
							},
						},
					},
				},
			},
		},
		Flags: []clif.FlagDef{
			{
				Name:   "baaz",
				Parser: flagtypes.BoolParser{},
			},
		},
	}
	res := app.Run(context.Background(), clif.WithArgs([]string{"help"}))
	fmt.Println(res)
	res = app.Run(context.Background(), clif.WithArgs([]string{"foo", "bar", "--quux", "hello"}))
	fmt.Println(res)
	res = app.Run(context.Background(), clif.WithArgs([]string{"foo", "--quux", "hello", "bar"}))
	fmt.Println(res)
	res = app.Run(context.Background(), clif.WithArgs([]string{"--quux", "hello", "foo", "bar"}))
	fmt.Println(res)
	res = app.Run(context.Background(), clif.WithArgs([]string{"foo", "bar", "--quux=hello"}))
	fmt.Println(res)
	res = app.Run(context.Background(), clif.WithArgs([]string{"foo", "--quux=hello", "bar"}))
	fmt.Println(res)
	res = app.Run(context.Background(), clif.WithArgs([]string{"--quux=hello", "foo", "bar"}))
	fmt.Println(res)
	res = app.Run(context.Background(), clif.WithArgs([]string{"--baaz", "foo", "bar", "--quux", "hello"}))
	fmt.Println(res)
}
Output:

this is help information
0
map[quux:{quux hello hello}] []
0
map[quux:{quux hello hello}] []
0
map[quux:{quux hello hello}] []
0
map[quux:{quux hello hello}] []
0
map[quux:{quux hello hello}] []
0
map[quux:{quux hello hello}] []
0
map[baaz:{baaz  true} quux:{quux hello hello}] []
0

func (Application) Run

func (app Application) Run(ctx context.Context, opts ...RunOption) int

Run executes the invoked command. It routes the input to the appropriate Command, parses it with the HandlerBuilder, and executes the Handler. The return is the status code the command has indicated it exited with.

type Command

type Command struct {
	// Name is the name of the command, what the user will type to prompt
	// its functionality.
	Name string

	// Aliases are acceptable variations on Name; they will be treated as
	// equivalent to Name, but will not be listed in the SubcommandsHelp
	// output.
	Aliases []string

	// Description is a short, one-line description of the command, used
	// when generating the SubcommandsHelp output.
	Description string

	// Hidden indicates whether a command should be included in
	// SubcommandsHelp output or not. If set to true, the command will be
	// omitted from SubcommandsHelp output.
	Hidden bool

	// Flags holds definitions for the flags, if any, that this command
	// accepts.
	Flags []FlagDef

	// Subcommands are the various subcommands, if any, that this command
	// accepts.
	Subcommands []Command

	// Handler is the HandlerBuilder executed when this Command is used.
	// The Handler will not be executed if a subcommand of this Command is
	// used.
	Handler HandlerBuilder

	// ArgsAccepted indicates whether free input is expected as part of
	// this command, separate from flag values and subcommands.
	ArgsAccepted bool

	// AllowNonFlagFlags controls whether things that aren't flags (like
	// flag values, subcommands, and arguments) can start with --. If
	// false, we'll throw an error when we encounter an -- that doesn't
	// have a FlagDef for it on this command or any of its subcommands. If
	// true, we'll allow it, using it either as a flag value, subcommand,
	// or argument, whichever is allowed. If none are allowed, it will
	// still throw an invalid flag error.
	AllowNonFlagFlags bool
}

Command defines a command the user can run. Commands can have handlers, that get invoked when the command is run, and subcommands, which are other commands namespaced under their command. Commands with subcommands can still be invoked, and should still have a handler defined, even if it just prints out usage information on the subcommands.

type DuplicateFlagNameError

type DuplicateFlagNameError string

DuplicateFlagNameError is returned when multiple Commands use the same flag name, and it would be ambiguous which Command the flag applies to. The underlying string will be the flag name, without leading --.

func (DuplicateFlagNameError) Error

func (err DuplicateFlagNameError) Error() string

type ExtraInputError

type ExtraInputError struct {
	// Application is the Application that produced the error.
	Application Application
	// CommandPath is the Commands, in order, that were matched before the
	// error was produced. Each Command in the slice is the child of the
	// Command before it in the slice.
	CommandPath []Command
	// Flags holds the Flags that were matched before the error was
	// produced.
	Flags map[string]Flag
	// Args holds the positional arguments that were parsed before the
	// error was produced.
	Args []string
	// ExtraInput holds the extra, unexpected input.
	ExtraInput []string
}

ExtraInputError is returned when a command gets more input than it knows what to do with.

func (ExtraInputError) Error

func (err ExtraInputError) Error() string

type Flag

type Flag interface {
	GetName() string
	GetRawValue() string
}

Flag is an interface that holds information about a flag at runtime. Applications will almost always want to type assert this to the flag type returned by the FlagParser in the FlagDef for that specific flag, to get a parsed version of the value.

type FlagDef

type FlagDef struct {
	// Name is the name of the flag. It's what will be surfaced in
	// documentation and what the user will use when applying the flag to a
	// command. Names must be unique across all commands, or the parser
	// won't know which command to apply the flag to.
	Name string

	// Aliases holds any alternative names the flag should accept from the
	// user. Aliases are not surfaced in documentation, by default. Aliases
	// must be unique across all other aliases and names for all commands,
	// or the parser won't know which command to apply the flag to.
	Aliases []string

	// Description is a user-friendly description of what the flag does and
	// what it's for, to be presented as part of help output.
	Description string

	// ValueAccepted indicates whether or not the flag should allow a
	// value. If set to false, attempting to pass a value will surface an
	// error.
	ValueAccepted bool

	// OnlyAfterCommandName indicates whether the flag should only be
	// acceptable after the command name in the invocation, or if can
	// appear anywhere in the invocation. If set to true, passing the flag
	// before the subcommand it belongs to will return an error.
	OnlyAfterCommandName bool

	// Parser determines how the flag value should be parsed.
	Parser FlagParser
}

FlagDef holds the definition of a flag.

type FlagParser

type FlagParser interface {
	// Parse is called to turn a name and value string into a Flag. The
	// prior value is passed to support flags that can be passed multiple
	// times to specify multiple values.
	Parse(ctx context.Context, name, value string, prior Flag) (Flag, error)

	// FlagType should return a user-friendly indication of the type of
	// input this flag type expects, like "string" or "int" or "timestamp".
	FlagType() string
}

FlagParser is an interface for parsing flag values. Implementing it allows the definition of new types of flags.

type Handler

type Handler interface {
	// Handle is a method that will be called when the command is executed.
	// It should contain the business logic of the command.
	Handle(ctx context.Context, resp *Response)
}

Handler is an interface that commands should implement. The implementing type should probably be a struct, with arguments and dependencies defined as fields on the struct.

type HandlerBuilder

type HandlerBuilder interface {
	// Build creates a Handler by parsing the Flags and args into the
	// appropriate handler type.
	Build(ctx context.Context, flags map[string]Flag, args []string, resp *Response) Handler
}

HandlerBuilder is an interface that should wrap a Handler. It parses the passed Flags and args into a Handler, to separate out the parsing logic from the business logic.

type MissingFlagValueError

type MissingFlagValueError string

MissingFlagValueError is returned when a flag was used without a value, but the flag requires a value. The underlying string is the name of the flag, without leading --.

func (MissingFlagValueError) Error

func (err MissingFlagValueError) Error() string

type Response

type Response struct {
	// Code is the status code the command will exit with.
	Code int

	// Output is the writer that should be used for command output. It will
	// usually be set to the shell's standard output.
	Output io.Writer

	// Error is the writer that should be used to communicate error
	// conditions. It will usually be set to the shell's standard error.
	Error io.Writer
}

Response holds the ways a command can present information to the user.

type RouteResult

type RouteResult struct {
	// Command is the Command that Route believes should be run.
	Command Command
	// Flags are the Flags that should be applied to that command.
	Flags map[string]Flag
	// Args are the positional arguments that should be passed to that
	// command.
	Args []string
}

RouteResult holds information about the Command that should be run and the Flags and arguments to pass to it, based on the parsing done by Route.

func Route

func Route(ctx context.Context, root Application, input []string) (RouteResult, error)

Route parses the passed input in the context of the passed Application, turning it into a Command with Flags and arguments.

type RunOption

type RunOption func(*RunOptions)

RunOption is a function type that modifies a passed RunOptions when called. It's used to configure the behavior of Application.Run.

func WithArgs

func WithArgs(args []string) RunOption

WithArgs is a RunOption that sets the arguments that will be parsed as the command's input to the passed strings.

func WithError

func WithError(w io.Writer) RunOption

WithError is a RunOption that sets the io.Writer the application will write information about errors to to the passed io.Writer.

func WithOutput

func WithOutput(w io.Writer) RunOption

WithOutput is a RunOption that sets the command output to the passed io.Writer.

type RunOptions

type RunOptions struct {
	// Output is where command output should be written. Defaults to
	// os.Stdout.
	Output io.Writer

	// Error is where the command should write information about errors.
	// Defaults to os.Stderr.
	Error io.Writer

	// Args are the arguments that were passed to the command. Defaults
	// to os.Args[1:].
	Args []string
}

RunOptions holds all the options to pass to Application.Run. It should be built by using RunOption functions to modify a passed in RunOptions.

type UnexpectedCommandArgError

type UnexpectedCommandArgError string

UnexpectedCommandArgError is returned when a command that wasn't expecting an argument gets one.

func (UnexpectedCommandArgError) Error

func (err UnexpectedCommandArgError) Error() string

type UnexpectedFlagValueError

type UnexpectedFlagValueError struct {
	// Flag is the flag name, without leading --.
	Flag string

	// Value is the value that was passed to the flag.
	Value string
}

UnexpectedFlagValueError is returned when a flag was used with a value, but the flag doesn't accept values.

func (UnexpectedFlagValueError) Error

func (err UnexpectedFlagValueError) Error() string

type UnknownFlagNameError

type UnknownFlagNameError string

UnknownFlagNameError is returned when an argument uses flag syntax, starting with a --, but doesn't match a flag configured for that Command. The underlying string will be the flag name, without leading --.

func (UnknownFlagNameError) Error

func (err UnknownFlagNameError) Error() string

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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