Documentation
¶
Overview ¶
Package clic provides streamlined POSIX-friendly CLI command parsing.
CLI Argument Types ¶
Three types of arguments are handled: commands (and subcommands), flags (and their values), and operands. Commands and subcommands can each define their own flags and operands. Arguments that are not subcommands or flag-related (no hyphen prefix, not a flag value) will be treated as operands.
Example argument layout:
command --flag=flag-value subcommand -f flag-value operand_a operand_b
Custom Templating ¶
The Tmpl type eases custom templating. Custom data can be attached to instances of Clic, FlagSet, Flag, OperandSet, and Operand via their Meta fields for use in templates. The default template construction function (NewUsageTmpl) can be used as a reference for custom templates.
Designed To Be... ¶
POSIX-friendly:
- Long and short flags should be supported in a reasonably normal manner
- Use the term "Operand" rather than the ambiguous "Arg"
- Flags should not be processed after operands
- Usage output should be familiar and effective
Simple and powerful:
- The API surface should be small and flexible
- Flags and operands should not be auto-set (e.g. help flag set explicitly)
- Advanced usage should look similar to basic usage
- Users should be trusted to understand/use the type system (e.g. interfaces)
- Users should be trusted to understand/use advanced control flow mechanisms
Example ¶
package main import ( "context" "fmt" "github.com/daved/clic" ) func main() { // error handling omitted to keep example focused var ( info = "default" value = "unset" ) printFn := func(ctx context.Context) error { fmt.Printf("info flag = %s\nvalue operand = %v\n", info, value) return nil } // Associate HandlerFunc with command name root := clic.NewFromFunc(printFn, "myapp") // Associate flag and operand variables with relevant names root.Flag(&info, "i|info", "Set additional info.") root.Operand(&value, true, "first_operand", "Value to be printed.") // Parse the cli command as `myapp --info=flagval arrrg` cmd, _ := root.Parse([]string{"--info=flagval", "arrrg"}) // Run the handler that Parse resolved to _ = cmd.Handle(context.Background()) fmt.Println() fmt.Println(cmd.Usage()) }
Output: info flag = flagval value operand = arrrg Usage: myapp [FLAGS] <first_operand> Flags for myapp: -i, --info =STRING default: default Set additional info.
Example (Aliases) ¶
// error handling omitted to keep example focused // Associate HandlerFunc with command name and alias root := clic.NewFromFunc(hello, "hello|aliased") // Parse the cli command as `myapp aliased`, and run the handler cmd, _ := root.Parse([]string{"aliased"}) _ = cmd.Handle(context.Background())
Output: Hello, World
Example (AppArchitectureHiStructure) ¶
package main import ( "context" "fmt" "io" "log" "os" "github.com/daved/clic" ) type PrintCfg struct { Info string Value string } func NewPrintCfg() *PrintCfg { return &PrintCfg{ Info: "default", Value: "unset", } } // Print focuses solely on an action without any knowledge about how it is/was // called from the command line. This is not meant to be received as a rigid // prescription, rather, as a reference for how various complex needs might be // addressed (obviously it's excessive for this trivial example). type Print struct { out io.Writer cnf *PrintCfg } func NewPrint(out io.Writer, cnf *PrintCfg) *Print { return &Print{ out: out, cnf: cnf, } } func (p *Print) Run(ctx context.Context) error { fmt.Fprintf(p.out, "info flag = %s\nvalue operand = %v\n", p.cnf.Info, p.cnf.Value) return nil } // HandlePriոt focuses solely on composing an action (and related configuration) // into its place within the Clic tree. Apart from structuring and constructing // types, there's no special handling or knowledge. This helps keep code // uncluttered, and diffs focused (i.e. in relevant files). type HandlePriոt struct { action *Print actCnf *PrintCfg } func NewHandlePriոt(out io.Writer) *HandlePriոt { tCnf := NewPrintCfg() return &HandlePriոt{ action: NewPrint(out, tCnf), actCnf: tCnf, } } // AsClic constrains Clic construction to user-defined types. Setting up Clic // instances in this way helps to clean up calling code (e.g. main funcs). func (h *HandlePriոt) AsClic(name string, subs ...*clic.Clic) *clic.Clic { c := clic.New(h, name, subs...) c.Flag(&h.actCnf.Info, "i|info", "Set additional info.") c.Operand(&h.actCnf.Value, true, "first_operand", "Value to be printed.") return c } func (h *HandlePriոt) HandleCommand(ctx context.Context) error { return h.action.Run(ctx) } // The rest of HandleRoot is found in the LoStructure example file. func (h *HandleRoot) AsClic(name string, subs ...*clic.Clic) *clic.Clic { return clic.New(h, name, subs...) } func main() { out := os.Stdout // emulate an interesting dependency // Set up command root := NewHandleRoot(out).AsClic( // construct root handler with deps "myapp", // set root cmd name NewHandlePriոt(out).AsClic("print"), // set subcmd as newly constructed print handler ) // Parse the cli command as `myapp print --info=flagval arrrg` cmd, err := root.Parse(args[1:]) if err != nil { log.Fatalln(err) } // Run the handler that Parse resolved to if err := cmd.Handle(context.Background()); err != nil { log.Fatalln(err) } }
Output: info flag = flagval value operand = arrrg
Example (AppArchitectureLoStructure) ¶
package main import ( "context" "fmt" "io" "log" "os" "github.com/daved/clic" ) type HandleRoot struct { out io.Writer } func NewHandleRoot(out io.Writer) *HandleRoot { return &HandleRoot{ out: out, } } func (s *HandleRoot) HandleCommand(ctx context.Context) error { fmt.Fprintln(s.out, "from root") return nil } type HandlePrint struct { out io.Writer info string value string } func NewHandlePrint(out io.Writer) *HandlePrint { return &HandlePrint{ out: out, info: "default", value: "unset", } } func (s *HandlePrint) HandleCommand(ctx context.Context) error { fmt.Fprintf(s.out, "info flag = %s\nvalue operand = %v\n", s.info, s.value) return nil } func main() { out := os.Stdout // emulate an interesting dependency // Associate Handler with command name handlePrint := NewHandlePrint(out) print := clic.New(handlePrint, "print") // Associate "print" flag and operand variables with relevant names print.Flag(&handlePrint.info, "i|info", "Set additional info.") print.Operand(&handlePrint.value, true, "first_operand", "Value to be printed.") // Associate Handler with application name, adding "print" as a subcommand root := clic.New(NewHandleRoot(out), "myapp", print) // Parse the cli command as `myapp print --info=flagval arrrg` cmd, err := root.Parse(args[1:]) if err != nil { log.Fatalln(err) } // Run the handler that Parse resolved to if err := cmd.Handle(context.Background()); err != nil { log.Fatalln(err) } }
Output: info flag = flagval value operand = arrrg
Example (AppArchitectureNoStructure) ¶
package main import ( "context" "fmt" "log" "os" "github.com/daved/clic" ) var args = []string{"myapp", "print", "--info=flagval", "arrrg"} func main() { var ( info = "default" value = "unset" out = os.Stdout // emulate an interesting dependency ) // Associate HandlerFunc with command name print := clic.NewFromFunc( func(ctx context.Context) error { fmt.Fprintf(out, "info flag = %s\nvalue operand = %v\n", info, value) return nil }, "print", ) // Associate "print" flag and operand variables with relevant names print.Flag(&info, "i|info", "Set additional info.") print.Operand(&value, true, "first_operand", "Value to be printed.") // Associate HandlerFunc with application name, adding "print" as a subcommand root := clic.NewFromFunc( func(ctx context.Context) error { fmt.Fprintln(out, "from root") return nil }, "myapp", print, ) // Parse the cli command as `myapp print --info=flagval arrrg` cmd, err := root.Parse(args[1:]) if err != nil { log.Fatalln(err) } // Run the handler that Parse resolved to if err := cmd.Handle(context.Background()); err != nil { log.Fatalln(err) } }
Output: info flag = flagval value operand = arrrg
Example (Categories) ¶
// Associate HandlerFuncs with command names, setting cat and desc fields hello := clic.NewFromFunc(hello, "hello") hello.Category = "Salutations" hello.Description = "Show hello world message" goodbye := clic.NewFromFunc(goodbye, "goodbye") goodbye.Category = "Salutations" goodbye.Description = "Show goodbye message" details := clic.NewFromFunc(details, "details") details.Category = "Informational" details.Description = "List details (os.Args)" // Associate HandlerFunc with command name root := clic.NewFromFunc(printRoot, "myapp", hello, goodbye, details) root.SubRequired = true // Set up subcommand category order // Category names seperated from optional descriptions by "|" root.SubCmdCatsSort = []string{"Salutations|Salutations-related", "Informational|All things info"} // Parse the cli command as `myapp`; will return error from lack of subcommand cmd, err := root.Parse([]string{}) if err != nil { fmt.Println(cmd.Usage()) fmt.Println(err) }
Output: Usage: myapp {hello|goodbye|details} Subcommands for myapp: Salutations Salutations-related hello Show hello world message goodbye Show goodbye message Informational All things info details List details (os.Args) cli command: parse: subcommand required
Example (HelpAndVersionFlags) ¶
// some error handling omitted to keep example focused errHelpRequested := errors.New("help requested") errVersionRequested := errors.New("version requested") // Associate HandlerFunc with command name, and set flags root := clic.NewFromFunc(printRoot, "myapp") root.Flag(errHelpRequested, "help", "Print usage and quit") root.Flag(errVersionRequested, "version", "Print version and quit") // Parse the cli command as `myapp --version` cmd, err := root.Parse([]string{"--version"}) if err != nil { switch { case errors.Is(err, errHelpRequested): fmt.Println(cmd.Usage()) return case errors.Is(err, errVersionRequested): fmt.Println("version: v1.2.3") return default: fmt.Println(cmd.Usage()) fmt.Println(err) return // likely as non-zero using os.Exit(n) } }
Output: version: v1.2.3
Example (RecursivelyWrappingHandlers) ¶
// error handling omitted to keep example focused // Associate HandlerFuncs with command names hello := clic.NewFromFunc(hello, "hello") goodbye := clic.NewFromFunc(goodbye, "goodbye") details := clic.NewFromFunc(details, "details") root := clic.NewFromFunc(printRoot, "myapp", hello, goodbye, details) root.SubRequired = true root.Recursively(func(c *clic.Clic) { next := c.Handler.HandleCommand c.Handler = clic.HandlerFunc(func(ctx context.Context) error { fmt.Println("before") if err := next(ctx); err != nil { return err } fmt.Println("after") return nil }) }) // Parse the cli command as `myapp hello`, and run the handler cmd, _ := root.Parse([]string{"hello"}) _ = cmd.Handle(context.Background()) // Parse the cli command as `myapp goodbye`, and run the handler cmd, _ = root.Parse([]string{"goodbye"}) _ = cmd.Handle(context.Background())
Output: before Hello, World after before Goodbye after
Example (UserFriendlyErrorMessages) ¶
var info string // Associate HandlerFunc with command name, and set info flag root := clic.NewFromFunc(printRoot, "myapp") root.Flag(&info, "info", "Set info") // Parse the cli command as `myapp --force-err` cmd, err := root.Parse([]string{"--force-err"}) if err != nil { fmt.Println(cmd.Usage()) fmt.Println(clic.UserFriendlyError(err)) return // likely as non-zero using os.Exit(n) }
Output: Usage: myapp [FLAGS] Flags for myapp: --info =STRING Set info Unrecognized flag "force-err"
Example (Verbosity) ¶
// error handling omitted to keep example focused var verbosity []bool // Associate HandlerFunc with command name, and set verbosity flag root := clic.NewFromFunc(hello, "myapp") root.Flag(&verbosity, "v", "Set verbosity. Can be set multiple times.") // Parse the cli command as `myapp -vvv`, and run the handler cmd, _ := root.Parse([]string{"-vvv"}) _ = cmd.Handle(context.Background()) fmt.Printf("verbosity: length=%d value=%v\n", len(verbosity), verbosity) fmt.Println() fmt.Println(cmd.Usage())
Output: Hello, World verbosity: length=3 value=[true true true] Usage: myapp [FLAGS] Flags for myapp: -v =BOOL Set verbosity. Can be set multiple times.
Index ¶
- Variables
- func UserFriendlyError(err error) error
- type Clic
- func (c *Clic) Flag(val any, names, usage string) *flagset.Flag
- func (c *Clic) Handle(ctx context.Context) error
- func (c *Clic) Operand(val any, req bool, name, desc string) *operandset.Operand
- func (c *Clic) Parse(args []string) (*Clic, error)
- func (c *Clic) Recursively(fn func(*Clic))
- func (c *Clic) Usage() string
- type Handler
- type HandlerFunc
- type Links
- type Tmpl
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( CauseParseSubCmdRequired = ErrSubCmdRequired CauseParseFlagResolve = &flagset.ResolveError{} CauseParseFlagUnrecognized = flagset.ErrFlagUnrecognized CauseParseOperandResolve = &operandset.ResolveError{} CauseParseOperandRequired = operandset.ErrOperandRequired CauseParseHydrateError = &vtype.HydrateError{} // from Flag and Operand Resolve CauseParseTypeUnsupported = vtype.ErrTypeUnsupported // from Flag and Operand Resolve )
Cause values are provided for documentation, and to allow callers to easily detect error conditions using a switch/case and errors.Is. If error inspection is required, use errors.As.
var ErrSubCmdRequired = errors.New("subcommand required")
ErrSubCmdRequired signals that a subcommand is required and not set.
Functions ¶
func UserFriendlyError ¶
UserFriendlyError returns a new error containing a plain language message.
Types ¶
type Clic ¶
type Clic struct { Links // Templating Tmpl *Tmpl // set to NewUsageTmpl by default Description string HideUsage bool Category string SubCmdCatsSort []string Meta map[string]any // Additional Configuration SubRequired bool // Reconfiguration (modify as needed) Handler Handler Aliases []string // Accessing (avoid modification) FlagSet *flagset.FlagSet OperandSet *operandset.OperandSet }
Clic manages a CLI command handler and related information.
func NewFromFunc ¶
func NewFromFunc(f HandlerFunc, name string, subs ...*Clic) *Clic
NewFromFunc returns an instance of Clic. Any function that is compatible with HandlerFunc will be converted automatically.
func (*Clic) Flag ¶
Flag adds a flag option. See flagset.FlagSet.Flag for more details like compatible value types.
func (*Clic) Operand ¶
Operand adds an operand option. See operandset.OperandSet.Operand for more details like compatible value types.
func (*Clic) Recursively ¶
Recursively applies the provided function to the current Clic instance and all its subcommands recursively.
func (*Clic) Usage ¶
Usage returns usage text. The default template construction function (NewUsageTmpl) can be used as a reference for custom templates which should be used to set the "Tmpl" field on Clic (likely using *Clic.Recursively).
type HandlerFunc ¶
HandlerFunc converts compatible functions to a Handler implementation.
func (HandlerFunc) HandleCommand ¶
func (f HandlerFunc) HandleCommand(ctx context.Context) error
HandleCommand implements Handler.
type Links ¶
type Links struct {
// contains filtered or unexported fields
}
Links contains Clic instance relationships. The relationships are defined here to clean up the Clic type's docs.
type Tmpl ¶
Tmpl holds template configuration details.
func NewUsageTmpl ¶
NewUsageTmpl returns the default template configuration. This can be used as an example of how to setup custom usage output templating.