cli

package
v0.0.0-...-b425644 Latest Latest
Warning

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

Go to latest
Published: Nov 14, 2019 License: Apache-2.0 Imports: 11 Imported by: 73

README

cli

A library to easily create command line interfaces. This is driven by the desire to have a simple but powerful way to generate those. The existing frameworks failed to deliver features for extended parsing of arguments.

The core ideas of cli are:

  • Have routes to actions (like in a web API). There is a fuzzy matching for routes, that allows for giving short cuts (like d s for do something), as long as the given short cuts are unambiguous.
  • Actions have parameters like options, flags and arguments. Each action is associated with a struct that has annotated fields. The annotations are used to fill the associated fields with the value given on the command line (or by the defaults). Action struct must implement the Runner interface.
  • Options are given via a handle in short or long form (-c vs. --config-file) and have a value.
  • Flags are options with a boolean value, that is set to true if the flag is given.
  • Arguments are given additionally with out a special handle. This is why order and existence are essential!
  • Actions can have hierarchies to facilitate reuse of code.

Examples

The following example creates a simple CLI action for running commands at a remote host (like example run on host -c "uname -a" 192.168.1.1 given the binary has the example name and the uname command should be executed on 192.168.1.1).

// Struct used to configure an action.
type ExampleRunner struct {
	Verbose bool   `cli:"type=opt short=v long=verbose"`               // Flag (boolean option) example. This is either set or not.
	Command string `cli:"type=opt short=c long=command required=true"` // Option that has a default value.
	Hosts   string `cli:"type=arg required=true"`                      // Argument with at least one required.
}

// Run the action. Called when the cli.Run function is called with a route matching the one of this action.
func (er *ExampleRunner) Run() error {
	// Called when action matches route.
	if er.Verbose {
		log.Printf("Going to execute %q at the following hosts: %v", er.Command, er.Hosts)
	}
	// [..] Executing the SSH command is left to the reader.
	return nil
}

// Basic example that shows how to register an action to a route and execute it.
func Example_basic() {
	router := NewRouter()
	router.Register("run/on/hosts", &ExampleRunner{}, "This is an example that pretends to run a given command on a set of hosts.")
	router.Run("run", "on", "host", "-v", "-c", "uname -a", "192.168.1.1")
	router.RunWithArgs() // Run with args given on the command line.
}

This example used the long notation of the annotation parser. The following would have the very same effect, but be much more concise:

// Struct used to configure an action.
type ExampleRunner struct {
	Verbose bool   `cli:"opt -v --verbose"`          // Flag (boolean option) example. This is either set or not.
	Command string `cli:"opt -c --command required"` // Option that has a default value.
	Hosts   string `cli:"arg required"`              // Argument with at least one required.
}

If an action doesn't need parameters it's also possible to directly register a function:

router.RegisterFunc("do/something", func() error { return fmt.Errorf("I should do something") }, "do something")

If there is only a single action for a program the router is not required and this action can be registered directly:

cli.RunActionWithArgs(&ExampleRunner{})

If you must know whether an option was set, or not you can use pointer values. Note that this has implications, as values must first be tested for nil (aka the option wasn't given on the command line) and must be dereferenced, to get the actual value. For booleans the flag mechanisms is deactivated, i.e. a value (true or false) must be given.

// Struct used to configure an action.
type ExampleRunner struct {
	Opt1 *string `cli:"opt -o"`
	Opt2 *int    `cli:"opt -i"`
	Opt3 *bool   `cli:"opt -t"`
}

Documentation

Overview

golang CLI parameter handling

This is dynport's version of a golang CLI handler. Given a struct that implements an interface with fields that have annotations, an CLI is built that can be run against a list of strings (taken from os.Args for example).

See the basic example on how to use this library with a router. The other example shows the short annotation notation and the direct usage of actions without a router.

The following constraints or special behaviors are to be taken into account:

  • Options (type "opt") are given in short or long form ("-h" vs. "--help"). Each option must have at least one modifier set.
  • Required options must be present. A default value is preset in the struct.
  • Options with a boolean value are internally handled as flags, i.e. presence of the flag indicates true.
  • Ordering of arguments is defined by the position in the action's struct (first come first serve).
  • Arguments (type "arg") may be variadic (type in the struct must be a slice), i.e. arbitrary can be given. If the argument is required, at least one value must be present. Only the last arguments can be variadic.
  • Non variadic arguments must always be given.
  • Routes use a fuzzy matching algorithm, i.e. for `do something` its sufficient to give `d s` as long as the fragments are not ambiguous.
Example (Basic)

Basic example that shows how to register an action to a route and execute it.

package main

import "fmt"

// Struct used to configure an action.
type ExampleRunner struct {
	Verbose bool   `cli:"type=opt short=v long=verbose"`               // Flag (boolean option) example. This is either set or not.
	Command string `cli:"type=opt short=c long=command required=true"` // Option that has a default value.
	Hosts   string `cli:"type=arg required=true"`                      // Argument with at least one required.
}

// Run the action. Called when the cli.Run function is called with a route matching the one of this action.
func (er *ExampleRunner) Run() error {
	// Called when action matches route.
	if er.Verbose {
		fmt.Fprintf(Stderr, "Going to execute %q at the following hosts: %v\n", er.Command, er.Hosts)
	}
	// [..] Executing the SSH command is left to the reader.
	return nil
}

// Basic example that shows how to register an action to a route and execute it.
func main() {
	router := NewRouter()
	router.Register("run/on/hosts", &ExampleRunner{}, "This is an example that pretends to run a given command on a set of hosts.")
	router.Run("run", "on", "host", "-v", "-c", "uname -a", "192.168.1.1")
	router.RunWithArgs() // Run with args given on the command line.
}
Output:

Example (ShortNotation)

Example that shows how to run a single action without using a router.

package main

import (
	"log"
)

// Struct used to configure an action.
type ExampleShortNotationRunner struct {
	Verbose bool   `cli:"opt -v --verbose"`          // Flag (boolean option) example. This is either set or not.
	Command string `cli:"opt -c --command required"` // Option that has a default value.
	Hosts   string `cli:"arg required"`              // Argument with at least one required.
}

// Run the action. Called when the cli.Run function is called with a route matching the one of this action.
func (er *ExampleShortNotationRunner) Run() error {
	// Called when action matches route.
	if er.Verbose {
		log.Printf("Going to execute %q at the following hosts: %v", er.Command, er.Hosts)
	}
	// [..] Executing the SSH command is left to the reader.
	return nil
}

// Example that shows how to run a single action without using a router.
func main() {
	RunAction(&ExampleShortNotationRunner{}, "-v", "-c", "uname -a", "192.168.1.1")
	RunActionWithArgs(&ExampleShortNotationRunner{})
}
Output:

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	ErrorNoRoute       = fmt.Errorf("no route matched")
	ErrorHelpRequested = fmt.Errorf("help requested")
)
View Source
var DefaultWriter io.Writer = os.Stderr
View Source
var Stderr io.Writer = os.Stderr

Functions

func RunAction

func RunAction(runner Runner, args ...string) (e error)

Run the given action with given arguments. This is useful for programms that don't need the routing features, i.e. only have one action to run. The example on the short notation uses this feature.

func RunActionWithArgs

func RunActionWithArgs(runner Runner) (e error)

Run the given action with the arguments given on the command line.

Types

type Router

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

The basic datastructure used in cli. Actions are added for different paths to a router via the "Register" and "RegisterFunc" methods. These actions can be executed using the "Run" and "RunWithArgs" methods.

func New

func New(routes ...func(*Router) error) (*Router, error)

func NewRouter

func NewRouter() *Router

Create a new router that will be used to register and run the actions of the application.

func (*Router) Register

func (r *Router) Register(path string, runner Runner, desc string)

Register the given action (some struct implementing the Runner interface) for the given route.

func (*Router) RegisterFunc

func (r *Router) RegisterFunc(path string, f func() error, desc string)

Register the given function as handler for the given route. This is a shortcut for actions that don't need options or arguments. A description can be provided as an optional argument.

func (*Router) Run

func (r *Router) Run(args ...string) (e error)

Run the given arguments against the registered actions, i.e. try to find a matching route and run the according action.

func (*Router) RunWithArgs

func (r *Router) RunWithArgs() (e error)

Run the arguments from the commandline (aka os.Args) against the registered actions, i.e. try to find a matching route and run the according action.

type RunFunc

type RunFunc func() error

func (RunFunc) Run

func (rf RunFunc) Run() error

type Runner

type Runner interface {
	Run() error
}

Interface that must be implemented by actions. This is interface is used by the RegisterAction function. The Run method of the implementing type will be called if the given route matches it.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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