subcmd

package
v0.0.0-...-e2c53ed Latest Latest
Warning

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

Go to latest
Published: Mar 26, 2024 License: Apache-2.0 Imports: 14 Imported by: 21

README

Package cloudeng.io/cmdutil/subcmd

import cloudeng.io/cmdutil/subcmd

Package subcmd provides a multi-level command facility of the following form:

Usage of <tool>
  <sub-command-1> <flags for sub-command-1> <args for sub-comand-1>
    <sub-command-2-1> <flags for sub-command-2-1> <args for sub-comand-2-1>
    ...
    <sub-command-2-2> <flags for sub-command-2-2> <args for sub-comand-2-2>
  ...
  <sub-command-n> <flags for sub-command-n> <args for sub-comand-n>

The primary motivation for this package was to avoid the need to use global variables to store flag values packages. Such global variables quickly become a maintenance problem as command line tools evolve and in particular as functions are refactored. The cloudeng.io/cmdutil/flags package provides a means of defining flags as fields in a struct with a struct tag providing the flag name, default value and usage and is used to represent all flags.

subcmd builds on the standard flag package and mirrors its design but without requiring that flag.Parse or any of its global state be used.

Flags are represented by a FlagSet which encapsulates an underlying flag.FlagSet but with flag variables provided via cloudeng.io/cmdutil/flags.

The Command type associates a FlagSet with the function that implements that command as well as documenting the command. This 'runner' takes as an argument the struct used to store its flag values as well as the command line arguments; thus avoiding the need for global flag variables at the cost of a type assertion. A CommandSet is used to create the command hierarchy itself and finally the cmdset can be used to dispatch the appropriate command functions via cmdset.Dispatch or DispatchWithArgs.

type rangeFlags struct {
  From int `subcmd:"from,1,start value for a range"`
  To   int `subcmd:"to,2,end value for a range "`
}

func printRange(ctx context.Context, values interface{}, args []string) error {
  r := values.(*rangeFlags)
  fmt.Printf("%v..%v\n", r.From, r.To)
  return nil
}

func main() {
  ctx := context.Background()
  fs := subcmd.MustRegisteredFlags(&rangeFlags{})
  // Subcommands are created using subcmd.NewCommandLevel.
  cmd := subcmd.NewCommand("ranger", fs, printRange, subcmd.WithoutArguments())
  cmd.Document("print an integer range")
  cmdSet := subcmd.NewCommandSet(cmd)
  cmdSet.MustDispatch(ctx)
}

In addition it is possible to register 'global' flags that may be specified before any sub commands on invocation and also to wrap calls to any subcommand's runner function. The former is useful for setting common flags and the latter for acting on those flags and/or implementing common functionality such as profiling or initializing logging etc.

The FromYAML function provides a more convenient and readable means of creating a command tree than using the NewCommand and NewCommandSet functions directly. FromYAML reads a yaml specification of a command tree, its summary documentation and argument specification and calls NewCommand and NewCommandSet internally.

The returned CommandSetYAML type can then be used to 'decorate' the command tree with the runner functions and flag value instances. The YAML mechanism provides identical functionality to calling the functions directly.

The YAML specification is show below and reflects the tree structure of the command tree to be created.

	name: command-name
	summary: description of the command
	arguments:
	commands:
	  - name:
	    summary:
	    arguments:
	    commands:
   - name:
     summary:
     ...

The summary and argument values are used in calls in the Command.Document. The arguments: field is a list of the expected arguments that also defines the number of expected arguments.

  1. If the field is missing or the list is empty then no arguments are allowed.

  2. If the list contains n arguments then exactly that number of arguments is expected, unless, the last argument in the list is '...' in which case at least that number is expected. Similarly if an argument ends in '...' then at least the preceding number of arguments is expected.

  3. If there is a single item in the list and it is enclosed in [] (in a quoted string), then 0 or 1 arguments are expected.

To define a simple command line, with no sub-commands, specify only the name:, summary: and arguments: fields.

CommandSet.Dispatch implements support for requesting help information on the top level and subcommands. Running a command with sub-commands without specifying one of those sub-commands results in a 'usage' message showing summary information and the available subcommands. Help on a specific subcommand is available via ' help ' or for multi-level commands ' help '. The --help flag can be used to display information on a commands flags and arguments, eg: ' --help' or " --help".

Note that this package will never call flag.Parse and will not associate any flags with flag.CommandLine.

Functions

Func SanitizeYAML
func SanitizeYAML(spec string) string

SanitizeYAML replaces tabs with two spaces to make it easier to write YAML in go string literals (where most editors will always use tabs). This does not guarantee correct alignment when spaces and tabs are mixed arbitrarily.

Types

Type Command
type Command struct {
	// contains filtered or unexported fields
}

Command represents a single command.

Functions
func NewCommand(name string, flags *FlagSet, runner Runner, options ...CommandOption) *Command

NewCommand returns a new instance of Command.

func NewCommandLevel(name string, subcmds *CommandSet) *Command

NewCommandLevel returns a new instance of Command with subcommands.

Methods
func (cmd *Command) Document(description string, arguments ...string)

Document adds a description of the command and optionally descriptions of its arguments.

func (cmd *Command) Usage() string

Usage returns a string containing a 'usage' message for the command. It includes a summary of the command (including a list of any sub commands) its flags and arguments and the flag defaults.

Type CommandOption
type CommandOption func(*options)

CommandOption represents an option controlling the handling of a given command.

Functions
func AtLeastNArguments(n int) CommandOption

AtLeastNArguments specifies that the command takes at least N arguments.

func ExactlyNumArguments(n int) CommandOption

ExactlyNumArguments specifies that the command takes exactly the specified number of arguments.

func OptionalSingleArgument() CommandOption

OptionalSingleArg specifies that the command takes an optional single argument.

func WithoutArguments() CommandOption

WithoutArguments specifies that the command takes no arguments.

Type CommandSet
type CommandSet struct {
	// contains filtered or unexported fields
}

CommandSet represents a set of commands that are peers to each other, that is, the command line must specificy one of them.

Functions
func NewCommandSet(cmds ...*Command) *CommandSet

NewCommandSet creates a new command set.

Methods
func (cmds *CommandSet) Commands() []string

Commands returns the list of available commands.

func (cmds *CommandSet) Defaults(name string) string

Defaults returns the usage message and flag defaults.

func (cmds *CommandSet) Dispatch(ctx context.Context) error

Dispatch will dispatch the appropriate sub command or return an error.

func (cmds *CommandSet) DispatchWithArgs(ctx context.Context, usage string, args ...string) error

Dispatch determines which top level command has been requested, if any, parses the command line appropriately and then runs its associated function.

func (cmds *CommandSet) Document(doc string)

Document adds a description for the command set.

func (cmds *CommandSet) MustDispatch(ctx context.Context)

MustDispatch will dispatch the appropriate sub command or exit.

func (cmds *CommandSet) Output() io.Writer

Output is like flag.FlagSet.Output.

func (cmds *CommandSet) SetOutput(out io.Writer)

SetOutput is like flag.FlagSet.SetOutput.

func (cmds *CommandSet) Summary() string

Summary returns a summary of the command set that includes its top level documentation and a list of its sub-commands.

func (cmds *CommandSet) TopLevel(cmd *Command)
func (cmds *CommandSet) Usage(name string) string

Usage returns the usage message for the command set.

func (cmds *CommandSet) WithGlobalFlags(global *FlagSet)

WithGlobalFlags adds top-level/global flags that apply to all commands. They must be specified before a subcommand, ie: command * sub-command * args

func (cmds *CommandSet) WithMain(m Main)

WithMain arranges for Main to be called by Dispatch to wrap the call to the requested RunnerFunc.

Type CommandSetYAML
type CommandSetYAML struct {
	*CommandSet
	// contains filtered or unexported fields
}
Functions
func FromYAML(spec []byte) (*CommandSetYAML, error)

FromYAML parses a YAML specification of the command tree.

func FromYAMLTemplate(specTpl string, exts ...Extension) (*CommandSetYAML, []byte, error)

FromYAMLTemplate returns a CommandSetYAML using the expanded value of the supplied template and the supplied extensions.

func MustFromYAML(spec string) *CommandSetYAML

MustFromYAML is like FromYAML but will panic if the YAML spec is incorrectly defined. It calls SanitizeYAML on its input before calling FromYAML.

func MustFromYAMLTemplate(specTpl string, exts ...Extension) *CommandSetYAML

MustFromYAMLTemplate is like FromYAMLTemplate except that it panics on error. SanitzeYAML is called on the expanded template.

Methods
func (c *CommandSetYAML) AddExtensions() error

AddExtensions calls the Set method on each of the extensions.

func (c *CommandSetYAML) MustAddExtensions()

MustAddExtensions is like AddExtensions but panics on error.

func (c *CommandSetYAML) Set(names ...string) *CurrentCommand

Set looks up the command specified by names. Each sub-command in a multi-level command should be specified separately. The returned CurrentCommand should be used to set the Runner and FlagSet to associate with that command.

Type CurrentCommand
type CurrentCommand struct {
	// contains filtered or unexported fields
}
Methods
func (c *CurrentCommand) MustRunner(runner Runner, fs any)

MustRunner is like Runner but will panic on error.

func (c *CurrentCommand) MustRunnerAndFlagSet(runner Runner, fs *FlagSet)

MustRunnerAndFlagSet is like RunnerAndFlagSet but will panic on error.

func (c *CurrentCommand) MustRunnerAndFlags(runner Runner, fs *FlagSet)

Deprecated: Use MustRunnerAndFlagSet or MustRunner.

func (c *CurrentCommand) Runner(runner Runner, fs any, defaults ...any) error

Runner specifies the Runner and struct to use as a FlagSet for the currently 'set' command as returned by CommandSetYAML.Set.

func (c *CurrentCommand) RunnerAndFlagSet(runner Runner, fs *FlagSet) error

RunnerAndFlagset specifies the Runner and FlagSet for the currently 'set' command as returned by CommandSetYAML.Set.

func (c *CurrentCommand) RunnerAndFlags(runner Runner, fs *FlagSet) error

Deprecated: Use RunnerAndFlagSet or Runner.

Type Extension
type Extension interface {
	Name() string
	YAML() string
	Set(cmdSet *CommandSetYAML) error
}

Extension allows for extending a YAMLCommandSet with additional commands at runtime. Implementations of extension are used in conjunction with a templated version of the YAML command tree spec. The template can refer to an extension using the subcmdExtension function in a template pipeline:

  • name: command commands: {{range subcmdExtension "exensionName"}}{{.}} {{end}}

The extensionName is the name of the extension as returned by the Name method, and . refers to results of the YAML method split into single lines. Thus for the above example, the YAML method can return:

- name: c3.1 - name: c3.2

The template expansion ensures the correct indentation in the final YAML that's used to create the command tree.

In addition to adding the extension to the YAML used to create the command tree, the Set method is also used to add the extension's commands to the command set. The Set method is called by CommandSetYAML.AddExtensions which should itself be called before the command set is used.

Functions
func MergeExtensions(name string, exts ...Extension) Extension

MergeExtensions returns an extension that merges the supplied extensions. Calling the Set method on the returned extension will call the Set method on each of the supplied extensions. The YAML method returns the concatenation of the YAML methods of the supplied extensions in the order that they are specified.

func NewExtension(name, spec string, appendFn func(cmdSet *CommandSetYAML) error) Extension

NewExtension creates a new Extension with the specified name and spec. The name is used to refer to the extension in the YAML template.

Type FlagSet
type FlagSet struct {
	// contains filtered or unexported fields
}

FlagSet represents the name, description and flag values for a command.

Functions
func GlobalFlagSet() *FlagSet

GlobalFlagSet creates a new FlagSet that is to be used for global flags.

func MustRegisterFlagStruct(flagValues interface{}, valueDefaults map[string]interface{}, usageDefaults map[string]string) *FlagSet

MustRegisterFlagStruct is like RegisterFlagStruct except that it panics on encountering an error. Its use is encouraged over RegisterFlagStruct from within init functions.

func MustRegisteredFlagSet(flagValues interface{}, defaults ...interface{}) *FlagSet

MustRegisteredFlagSet is like RegisteredFlagSet but will panic if defaults contains inappopriate types for the value and usage defaults.

func NewFlagSet() *FlagSet

NewFlagSet returns a new instance of FlagSet.

func RegisterFlagStruct(flagValues interface{}, valueDefaults map[string]interface{}, usageDefaults map[string]string) (*FlagSet, error)

RegisterFlagStruct creates a new FlagSet and calls RegisterFlagStruct on it.

func RegisteredFlagSet(flagValues interface{}, defaults ...interface{}) (*FlagSet, error)

RegisteredFlagSet is a convenience function that creates a new FlagSet and calls RegisterFlagStruct on it. The valueDefaults and usageDefaults are extracted from the defaults variadic parameter. MustRegisteredFlagSet will panic if defaults contains inappopriate types for the value and usage defaults.

Methods
func (cf *FlagSet) IsSet(field interface{}) (string, bool)

IsSet returns true if the supplied flag variable's value has been set, either via a string literal in the struct or via the valueDefaults argument to RegisterFlagStruct.

func (cf *FlagSet) MustRegisterFlagStruct(flagValues interface{}, valueDefaults map[string]interface{}, usageDefaults map[string]string) *FlagSet

MustRegisterFlagStruct is like RegisterFlagStruct except that it panics on encountering an error. Its use is encouraged over RegisterFlagStruct from within init functions.

func (cf *FlagSet) RegisterFlagStruct(flagValues interface{}, valueDefaults map[string]interface{}, usageDefaults map[string]string) error

RegisterFlagStruct registers a struct, using flags.RegisterFlagsInStructWithSetMap. The struct tag must be 'subcomd'. The returned SetMap can be queried by the IsSet method.

Type Main
type Main func(ctx context.Context, cmdRunner func(ctx context.Context) error) error

Main is the type of the function that can be used to intercept a call to a Runner.

Type Runner
type Runner func(ctx context.Context, flagValues interface{}, args []string) error

Runner is the type of the function to be called to run a particular command.

Examples

ExampleCommandSet
ExampleCommandSetYAML_multiple
ExampleCommandSetYAML_toplevel

Documentation

Overview

Package subcmd provides a multi-level command facility of the following form:

Usage of <tool>
  <sub-command-1> <flags for sub-command-1> <args for sub-comand-1>
    <sub-command-2-1> <flags for sub-command-2-1> <args for sub-comand-2-1>
    ...
    <sub-command-2-2> <flags for sub-command-2-2> <args for sub-comand-2-2>
  ...
  <sub-command-n> <flags for sub-command-n> <args for sub-comand-n>

The primary motivation for this package was to avoid the need to use global variables to store flag values packages. Such global variables quickly become a maintenance problem as command line tools evolve and in particular as functions are refactored. The cloudeng.io/cmdutil/flags package provides a means of defining flags as fields in a struct with a struct tag providing the flag name, default value and usage and is used to represent all flags.

subcmd builds on the standard flag package and mirrors its design but without requiring that flag.Parse or any of its global state be used.

Flags are represented by a FlagSet which encapsulates an underlying flag.FlagSet but with flag variables provided via cloudeng.io/cmdutil/flags.

The Command type associates a FlagSet with the function that implements that command as well as documenting the command. This 'runner' takes as an argument the struct used to store its flag values as well as the command line arguments; thus avoiding the need for global flag variables at the cost of a type assertion. A CommandSet is used to create the command hierarchy itself and finally the cmdset can be used to dispatch the appropriate command functions via cmdset.Dispatch or DispatchWithArgs.

type rangeFlags struct {
  From int `subcmd:"from,1,start value for a range"`
  To   int `subcmd:"to,2,end value for a range "`
}

func printRange(ctx context.Context, values interface{}, args []string) error {
  r := values.(*rangeFlags)
  fmt.Printf("%v..%v\n", r.From, r.To)
  return nil
}

func main() {
  ctx := context.Background()
  fs := subcmd.MustRegisteredFlags(&rangeFlags{})
  // Subcommands are created using subcmd.NewCommandLevel.
  cmd := subcmd.NewCommand("ranger", fs, printRange, subcmd.WithoutArguments())
  cmd.Document("print an integer range")
  cmdSet := subcmd.NewCommandSet(cmd)
  cmdSet.MustDispatch(ctx)
}

In addition it is possible to register 'global' flags that may be specified before any sub commands on invocation and also to wrap calls to any subcommand's runner function. The former is useful for setting common flags and the latter for acting on those flags and/or implementing common functionality such as profiling or initializing logging etc.

The FromYAML function provides a more convenient and readable means of creating a command tree than using the NewCommand and NewCommandSet functions directly. FromYAML reads a yaml specification of a command tree, its summary documentation and argument specification and calls NewCommand and NewCommandSet internally.

The returned CommandSetYAML type can then be used to 'decorate' the command tree with the runner functions and flag value instances. The YAML mechanism provides identical functionality to calling the functions directly.

The YAML specification is show below and reflects the tree structure of the command tree to be created.

	name: command-name
	summary: description of the command
	arguments:
	commands:
	  - name:
	    summary:
	    arguments:
	    commands:
   - name:
     summary:
     ...

The summary and argument values are used in calls in the Command.Document. The arguments: field is a list of the expected arguments that also defines the number of expected arguments.

  1. If the field is missing or the list is empty then no arguments are allowed.

  2. If the list contains n arguments then exactly that number of arguments is expected, unless, the last argument in the list is '...' in which case at least that number is expected. Similarly if an argument ends in '...' then at least the preceding number of arguments is expected.

  3. If there is a single item in the list and it is enclosed in [] (in a quoted string), then 0 or 1 arguments are expected.

Note that the arguments may be structured into a short form name and a description, eg. arg - description, where ' - ' is used to separate the short form and description. The usage displayed will use the short form name to display a summary of the command line and the description will be detailed below, eg:

my-command <arg1> <arg2>
  <arg1> - description of arg1
  <arg2> - description of arg2

To define a simple command line, with no sub-commands, specify only the name:, summary: and arguments: fields.

CommandSet.Dispatch implements support for requesting help information on the top level and subcommands. Running a command with sub-commands without specifying one of those sub-commands results in a 'usage' message showing summary information and the available subcommands. Help on a specific subcommand is available via '<command> help <sub-command>' or for multi-level commands '<command> <sub-command> help <next-sub-command>'. The --help flag can be used to display information on a commands flags and arguments, eg: '<command> --help' or "<command> <sub-command> --help".

Note that this package will never call flag.Parse and will not associate any flags with flag.CommandLine.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func SanitizeYAML

func SanitizeYAML(spec string) string

SanitizeYAML replaces tabs with two spaces to make it easier to write YAML in go string literals (where most editors will always use tabs). This does not guarantee correct alignment when spaces and tabs are mixed arbitrarily.

Types

type Command

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

Command represents a single command.

func NewCommand

func NewCommand(name string, flags *FlagSet, runner Runner, options ...CommandOption) *Command

NewCommand returns a new instance of Command.

func NewCommandLevel

func NewCommandLevel(name string, subcmds *CommandSet) *Command

NewCommandLevel returns a new instance of Command with subcommands.

func (*Command) Document

func (cmd *Command) Document(description string, arguments ...string)

Document adds a description of the command and optionally descriptions of its arguments.

func (*Command) Usage

func (cmd *Command) Usage() string

Usage returns a string containing a 'usage' message for the command. It includes a summary of the command (including a list of any sub commands) its flags and arguments and the flag defaults.

type CommandOption

type CommandOption func(*options)

CommandOption represents an option controlling the handling of a given command.

func AtLeastNArguments

func AtLeastNArguments(n int) CommandOption

AtLeastNArguments specifies that the command takes at least N arguments.

func ExactlyNumArguments

func ExactlyNumArguments(n int) CommandOption

ExactlyNumArguments specifies that the command takes exactly the specified number of arguments.

func OptionalSingleArgument

func OptionalSingleArgument() CommandOption

OptionalSingleArg specifies that the command takes an optional single argument.

func WithoutArguments

func WithoutArguments() CommandOption

WithoutArguments specifies that the command takes no arguments.

type CommandSet

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

CommandSet represents a set of commands that are peers to each other, that is, the command line must specificy one of them.

Example
package main

import (
	"context"
	"fmt"

	"cloudeng.io/cmdutil/subcmd"
)

func main() {
	ctx := context.Background()
	type globalFlags struct {
		Verbosity int `subcmd:"v,0,debugging verbosity"`
	}
	var globalValues globalFlags
	type rangeFlags struct {
		From int `subcmd:"from,1,start value for a range"`
		To   int `subcmd:"to,2,end value for a range "`
	}
	printRange := func(_ context.Context, values interface{}, _ []string) error {
		r := values.(*rangeFlags)
		fmt.Printf("%v: %v..%v\n", globalValues.Verbosity, r.From, r.To)
		return nil
	}

	fs := subcmd.MustRegisterFlagStruct(&rangeFlags{}, nil, nil)
	// Subcommands are added using subcmd.NewCommandLevel.
	cmd := subcmd.NewCommand("ranger", fs, printRange, subcmd.WithoutArguments())
	cmd.Document("print an integer range")
	cmdSet := subcmd.NewCommandSet(cmd)
	globals := subcmd.NewFlagSet()
	globals.MustRegisterFlagStruct(&globalValues, nil, nil)
	cmdSet.WithGlobalFlags(globals)

	// Use cmdSet.Dispatch to access os.Args.
	fmt.Printf("%s", cmdSet.Usage("example-command"))
	fmt.Printf("%s", cmdSet.Defaults("example-command"))
	if err := cmdSet.DispatchWithArgs(ctx, "example-command", "ranger"); err != nil {
		panic(err)
	}
	if err := cmdSet.DispatchWithArgs(ctx, "example-command", "-v=3", "ranger", "--from=10", "--to=100"); err != nil {
		panic(err)
	}

}
Output:

Usage of example-command

 ranger - print an integer range
Usage of example-command

 ranger - print an integer range

global flags: [--v=0]
  -v int
    debugging verbosity

0: 1..2
3: 10..100

func NewCommandSet

func NewCommandSet(cmds ...*Command) *CommandSet

NewCommandSet creates a new command set.

func (*CommandSet) Commands

func (cmds *CommandSet) Commands() []string

Commands returns the list of available commands.

func (*CommandSet) Defaults

func (cmds *CommandSet) Defaults(name string) string

Defaults returns the usage message and flag defaults.

func (*CommandSet) Dispatch

func (cmds *CommandSet) Dispatch(ctx context.Context) error

Dispatch will dispatch the appropriate sub command or return an error.

func (*CommandSet) DispatchWithArgs

func (cmds *CommandSet) DispatchWithArgs(ctx context.Context, usage string, args ...string) error

Dispatch determines which top level command has been requested, if any, parses the command line appropriately and then runs its associated function.

func (*CommandSet) Document

func (cmds *CommandSet) Document(doc string)

Document adds a description for the command set.

func (*CommandSet) MustDispatch

func (cmds *CommandSet) MustDispatch(ctx context.Context)

MustDispatch will dispatch the appropriate sub command or exit.

func (*CommandSet) Output

func (cmds *CommandSet) Output() io.Writer

Output is like flag.FlagSet.Output.

func (*CommandSet) SetOutput

func (cmds *CommandSet) SetOutput(out io.Writer)

SetOutput is like flag.FlagSet.SetOutput.

func (*CommandSet) Summary

func (cmds *CommandSet) Summary() string

Summary returns a summary of the command set that includes its top level documentation and a list of its sub-commands.

func (*CommandSet) TopLevel

func (cmds *CommandSet) TopLevel(cmd *Command)

func (*CommandSet) Usage

func (cmds *CommandSet) Usage(name string) string

Usage returns the usage message for the command set.

func (*CommandSet) WithGlobalFlags

func (cmds *CommandSet) WithGlobalFlags(global *FlagSet)

WithGlobalFlags adds top-level/global flags that apply to all commands. They must be specified before a subcommand, ie: command <global-flags>* sub-command <sub-command-pflags>* args

func (*CommandSet) WithMain

func (cmds *CommandSet) WithMain(m Main)

WithMain arranges for Main to be called by Dispatch to wrap the call to the requested RunnerFunc.

type CommandSetYAML

type CommandSetYAML struct {
	*CommandSet
	// contains filtered or unexported fields
}
Example (Multiple)
package main

import (
	"context"
	"fmt"
	"os"
	"strings"

	"cloudeng.io/cmdutil/subcmd"
)

type exampleFlags struct {
	Flag1 int `subcmd:"flag1,12,flag1"`
}

type runner struct {
	name string
	out  *strings.Builder
}

func (r *runner) cmd(_ context.Context, values interface{}, args []string) error {
	fmt.Fprintf(r.out, "%v: flag: %v, args: %v\n", r.name, values.(*exampleFlags).Flag1, args)
	return nil
}

func main() {
	cmdSet := subcmd.MustFromYAML(`name: l0
summary: documentation for l0
commands:
  - name: l0.1
    summary: summary of l0.1
    arguments: # l0.1 expects exactly two arguments.
      - <arg1>
      - <arg2>
  - name: l0.2
    summary: l0.2 summary of l0.2
  - name: l1
    summary: summary of l1
    commands:
      - name: l1.1
        summary: describe l1.1
      - name: l1.2
        summary: describe l1.2
  - name: l2
    commands:
      - name: l2.1
        commands:
          - name: l2.1.1
`)

	out := &strings.Builder{}
	cmdSet.Set("l0.1").MustRunner(
		(&runner{name: "l0.1", out: out}).cmd,
		&exampleFlags{})

	cmdSet.Set("l0.2").MustRunner(
		(&runner{name: "l0.2", out: out}).cmd,
		&exampleFlags{})

	cmdSet.Set("l1", "l1.1").MustRunner(
		(&runner{name: "l1.2", out: out}).cmd,
		&exampleFlags{})

	cmdSet.Set("l1", "l1.2").MustRunner(
		(&runner{name: "l1.2", out: out}).cmd,
		&exampleFlags{})

	cmdSet.Set("l2", "l2.1", "l2.1.1").MustRunner(
		(&runner{name: "l1.2", out: out}).cmd,
		&exampleFlags{})

	if err := cmdSet.DispatchWithArgs(context.Background(), os.Args[0], "l0.1", "-flag1=3", "first-arg", "second-arg"); err != nil {
		panic(err)
	}

	if err := cmdSet.DispatchWithArgs(context.Background(), os.Args[0], "l1", "l1.2", "-flag1=6"); err != nil {
		panic(err)
	}

	fmt.Println(out.String())
}
Output:

l0.1: flag: 3, args: [first-arg second-arg]
l1.2: flag: 6, args: []
Example (Toplevel)
package main

import (
	"context"
	"fmt"
	"os"
	"strings"

	"cloudeng.io/cmdutil/subcmd"
)

type exampleFlags struct {
	Flag1 int `subcmd:"flag1,12,flag1"`
}

type runner struct {
	name string
	out  *strings.Builder
}

func (r *runner) cmd(_ context.Context, values interface{}, args []string) error {
	fmt.Fprintf(r.out, "%v: flag: %v, args: %v\n", r.name, values.(*exampleFlags).Flag1, args)
	return nil
}

func main() {
	cmdSet := subcmd.MustFromYAML(`name: toplevel
summary: overall documentation
  for toplevel
arguments:
  - "[arg - optional]"
`)

	out := &strings.Builder{}

	cmdSet.Set("toplevel").MustRunnerAndFlags(
		(&runner{name: "toplevel", out: out}).cmd,
		subcmd.MustRegisteredFlagSet(&exampleFlags{}))
	if err := cmdSet.DispatchWithArgs(context.Background(), os.Args[0], "-flag1=32", "single-arg"); err != nil {
		panic(err)
	}
	fmt.Println(out.String())
}
Output:

toplevel: flag: 32, args: [single-arg]

func FromYAML

func FromYAML(spec []byte) (*CommandSetYAML, error)

FromYAML parses a YAML specification of the command tree.

func FromYAMLTemplate

func FromYAMLTemplate(specTpl string, exts ...Extension) (*CommandSetYAML, []byte, error)

FromYAMLTemplate returns a CommandSetYAML using the expanded value of the supplied template and the supplied extensions.

func MustFromYAML

func MustFromYAML(spec string) *CommandSetYAML

MustFromYAML is like FromYAML but will panic if the YAML spec is incorrectly defined. It calls SanitizeYAML on its input before calling FromYAML.

func MustFromYAMLTemplate

func MustFromYAMLTemplate(specTpl string, exts ...Extension) *CommandSetYAML

MustFromYAMLTemplate is like FromYAMLTemplate except that it panics on error. SanitzeYAML is called on the expanded template.

func (*CommandSetYAML) AddExtensions

func (c *CommandSetYAML) AddExtensions() error

AddExtensions calls the Set method on each of the extensions.

func (*CommandSetYAML) MustAddExtensions

func (c *CommandSetYAML) MustAddExtensions()

MustAddExtensions is like AddExtensions but panics on error.

func (*CommandSetYAML) Set

func (c *CommandSetYAML) Set(names ...string) *CurrentCommand

Set looks up the command specified by names. Each sub-command in a multi-level command should be specified separately. The returned CurrentCommand should be used to set the Runner and FlagSet to associate with that command.

type CurrentCommand

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

func (*CurrentCommand) MustRunner

func (c *CurrentCommand) MustRunner(runner Runner, fs any)

MustRunner is like Runner but will panic on error.

func (*CurrentCommand) MustRunnerAndFlagSet

func (c *CurrentCommand) MustRunnerAndFlagSet(runner Runner, fs *FlagSet)

MustRunnerAndFlagSet is like RunnerAndFlagSet but will panic on error.

func (*CurrentCommand) MustRunnerAndFlags deprecated

func (c *CurrentCommand) MustRunnerAndFlags(runner Runner, fs *FlagSet)

Deprecated: Use MustRunnerAndFlagSet or MustRunner.

func (*CurrentCommand) Runner

func (c *CurrentCommand) Runner(runner Runner, fs any, defaults ...any) error

Runner specifies the Runner and struct to use as a FlagSet for the currently 'set' command as returned by CommandSetYAML.Set.

func (*CurrentCommand) RunnerAndFlagSet

func (c *CurrentCommand) RunnerAndFlagSet(runner Runner, fs *FlagSet) error

RunnerAndFlagset specifies the Runner and FlagSet for the currently 'set' command as returned by CommandSetYAML.Set.

func (*CurrentCommand) RunnerAndFlags deprecated

func (c *CurrentCommand) RunnerAndFlags(runner Runner, fs *FlagSet) error

Deprecated: Use RunnerAndFlagSet or Runner.

type Extension

type Extension interface {
	Name() string
	YAML() string
	Set(cmdSet *CommandSetYAML) error
}

Extension allows for extending a YAMLCommandSet with additional commands at runtime. Implementations of extension are used in conjunction with a templated version of the YAML command tree spec. The template can refer to an extension using the subcmdExtension function in a template pipeline:

  • name: command commands: {{range subcmdExtension "exensionName"}}{{.}} {{end}}

The extensionName is the name of the extension as returned by the Name method, and . refers to results of the YAML method split into single lines. Thus for the above example, the YAML method can return:

`- name: c3.1 - name: c3.2`

The template expansion ensures the correct indentation in the final YAML that's used to create the command tree.

In addition to adding the extension to the YAML used to create the command tree, the Set method is also used to add the extension's commands to the command set. The Set method is called by CommandSetYAML.AddExtensions which should itself be called before the command set is used.

func MergeExtensions

func MergeExtensions(name string, exts ...Extension) Extension

MergeExtensions returns an extension that merges the supplied extensions. Calling the Set method on the returned extension will call the Set method on each of the supplied extensions. The YAML method returns the concatenation of the YAML methods of the supplied extensions in the order that they are specified.

func NewExtension

func NewExtension(name, spec string, appendFn func(cmdSet *CommandSetYAML) error) Extension

NewExtension creates a new Extension with the specified name and spec. The name is used to refer to the extension in the YAML template.

type FlagSet

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

FlagSet represents the name, description and flag values for a command.

func GlobalFlagSet

func GlobalFlagSet() *FlagSet

GlobalFlagSet creates a new FlagSet that is to be used for global flags.

func MustRegisterFlagStruct

func MustRegisterFlagStruct(flagValues interface{}, valueDefaults map[string]interface{}, usageDefaults map[string]string) *FlagSet

MustRegisterFlagStruct is like RegisterFlagStruct except that it panics on encountering an error. Its use is encouraged over RegisterFlagStruct from within init functions.

func MustRegisteredFlagSet

func MustRegisteredFlagSet(flagValues interface{}, defaults ...interface{}) *FlagSet

MustRegisteredFlagSet is like RegisteredFlagSet but will panic if defaults contains inappopriate types for the value and usage defaults.

func NewFlagSet

func NewFlagSet() *FlagSet

NewFlagSet returns a new instance of FlagSet.

func RegisterFlagStruct

func RegisterFlagStruct(flagValues interface{}, valueDefaults map[string]interface{}, usageDefaults map[string]string) (*FlagSet, error)

RegisterFlagStruct creates a new FlagSet and calls RegisterFlagStruct on it.

func RegisteredFlagSet

func RegisteredFlagSet(flagValues interface{}, defaults ...interface{}) (*FlagSet, error)

RegisteredFlagSet is a convenience function that creates a new FlagSet and calls RegisterFlagStruct on it. The valueDefaults and usageDefaults are extracted from the defaults variadic parameter. MustRegisteredFlagSet will panic if defaults contains inappopriate types for the value and usage defaults.

func (*FlagSet) IsSet

func (cf *FlagSet) IsSet(field interface{}) (string, bool)

IsSet returns true if the supplied flag variable's value has been set, either via a string literal in the struct or via the valueDefaults argument to RegisterFlagStruct.

func (*FlagSet) MustRegisterFlagStruct

func (cf *FlagSet) MustRegisterFlagStruct(flagValues interface{}, valueDefaults map[string]interface{}, usageDefaults map[string]string) *FlagSet

MustRegisterFlagStruct is like RegisterFlagStruct except that it panics on encountering an error. Its use is encouraged over RegisterFlagStruct from within init functions.

func (*FlagSet) RegisterFlagStruct

func (cf *FlagSet) RegisterFlagStruct(flagValues interface{}, valueDefaults map[string]interface{}, usageDefaults map[string]string) error

RegisterFlagStruct registers a struct, using flags.RegisterFlagsInStructWithSetMap. The struct tag must be 'subcomd'. The returned SetMap can be queried by the IsSet method.

type Main

type Main func(ctx context.Context, cmdRunner func(ctx context.Context) error) error

Main is the type of the function that can be used to intercept a call to a Runner.

type Runner

type Runner func(ctx context.Context, flagValues interface{}, args []string) error

Runner is the type of the function to be called to run a particular command.

Jump to

Keyboard shortcuts

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