Back to godoc.org
github.com/turbinelabs/cli

package cli

v0.0.0 (f808c62)
Latest Go to latest
Published: Oct 25, 2018 | License: Apache-2.0 | Module: github.com/turbinelabs/cli

Overview

The cli package provides a simple library for creating command-line applications. See http://github.com/turbinelabs/cli for a richer discussion of motivation and feature set. See the examples for basic usage.

Example (SingleCommand)

This example shows how to create a single-command CLI

Code:

// This package contains a trivial example use of the cli package
package cli_test

import (
	"fmt"
	"strconv"

	"github.com/turbinelabs/cli"
	"github.com/turbinelabs/cli/command"
	"github.com/turbinelabs/nonstdlib/flag/usage"
)

// The typical pattern is to provide a public Cmd() func. This function should
// initialize the command.Cmd, the command.Runner, and flags.
func Cmd() *command.Cmd {
	// typically the command.Runner is initialized only with internally-defined
	// state; all necessary external state should be provided via flags. One can
	// inline the initializaton of the command.Runner in the command.Cmd
	// initialization if no flags are necessary, but it's often convenient to
	// have a typed reference
	runner := &runner{}

	cmd := &command.Cmd{
		Name:        "adder",
		Summary:     "add a delimited string of integers together",
		Usage:       "[OPTIONS] <int>...",
		Description: "add a delimited string of integers together",
		Runner:      runner,
	}

	// The flag.FlagSet is a member of the command.Cmd, and the flag
	// value is a member of the command.Runner.
	cmd.Flags.BoolVar(&runner.verbose, "verbose", false, "Produce verbose output")

	// If we wrap flag.Required(...) around the usage string, Cmd.Run(...)
	// will fail if it is unspecified
	cmd.Flags.StringVar(&runner.thing, "thing", "", usage.Required("The thing"))

	return cmd
}

// The private command.Runner implementation should contain any state needed
// to execute the command. The values should be initialized via flags declared
// in the Cmd() function.
type runner struct {
	verbose bool
	thing   string
}

// Run does the actual work, based on state provided by flags, and the
// args remaining after the flags have been parsed.
func (f *runner) Run(cmd *command.Cmd, args []string) command.CmdErr {
	ints := []int{}
	sum := 0
	// argument validation should occur at the top of the function, and
	// errors should be reported via the cmd.BadInput or cmd.BadInputf methods.
	// In this case, the main work of the function is done at the same time.
	for _, arg := range args {
		i, err := strconv.Atoi(arg)
		if err != nil {
			return cmd.BadInputf("Bad integer: \"%s\": %s", arg, err)
		}
		ints = append(ints, i)
		sum += i
	}

	if f.verbose && len(ints) > 0 {
		fmt.Print(ints[0])
		for _, i := range ints[1:] {
			fmt.Print(" + ", i)
		}
		fmt.Print(" = ")
	}

	fmt.Printf(`The thing: %s, the sum: %d`, f.thing, sum)

	// In this case, there was no error. Errors should be returned via the
	// cmd.Error or cmd.Errorf methods.
	return command.NoError()
}

func mkSingleCmdCLI() cli.CLI {
	// make a new CLI passing the version string and a command.Cmd
	// while it's possible to add flags to the CLI, they are ignored; only the
	// Cmd's flags are presented to the user.
	return cli.New("1.0.2", Cmd())
}

// This example shows how to create a single-command CLI
func Example_singleCommand() {
	// this would be your main() function

	// run the Main function, which calls os.Exit with the appropriate exit status
	mkSingleCmdCLI().Main()
}

// Add the following to your tests to validate that there are no collisions
// between command flags and that help text can be generated without error:

// package main

// import (
// 	"testing"

// 	"github.com/turbinelabs/test/assert"
// )

// func TestCLI(t *testing.T) {
// 	assert.Nil(t, mkCLI().Validate())
// }
Example (SubCommands)

This example shows how to create a CLI with multiple sub-commands

Code:

// This package contains a trivial example use of the cli package
package cli_test

import (
	"fmt"
	"strings"

	"github.com/turbinelabs/cli"
	"github.com/turbinelabs/cli/command"
)

// The typical pattern is to provide a public CmdXYZ() func for each
// sub-command you wish to provide. This function should initialize the
// command.Cmd, the command.Runner, and flags.
func CmdSplit() *command.Cmd {
	// typically the command.Runner is initialized only with internally-defined
	// state; all necessary external state should be provided via flags. One can
	// inline the initializaton of the command.Runner in the command.Cmd
	// initialization if no flags are necessary, but it's often convenient to
	// have a typed reference
	runner := &splitRunner{}

	cmd := &command.Cmd{
		Name:        "split",
		Summary:     "split strings",
		Usage:       "[OPTIONS] <string>",
		Description: "split strings using the specified delimiter",
		Runner:      runner,
	}

	// The flag.FlagSet is a member of the command.Cmd, and the flag
	// value is a member of the command.Runner.
	cmd.Flags.StringVar(&runner.delim, "delim", ",", "The delimiter on which to split the string")

	return cmd
}

// The private command.Runner implementation should contain any state needed
// to execute the command. The values should be initialized via flags declared
// in the CmdXYZ() function.
type splitRunner struct {
	delim string
}

// Run does the actual work, based on state provided by flags, and the
// args remaining after the flags have been parsed.
func (f *splitRunner) Run(cmd *command.Cmd, args []string) command.CmdErr {
	// argument validation should occur at the top of the function, and
	// errors should be reported via the cmd.BadInput or cmd.BadInputf methods
	if len(args) < 1 {
		return cmd.BadInput("missing \"string\" argument.")
	}
	str := args[0]
	if globalFlags.verbose {
		fmt.Printf("Splitting \"%s\"\n", str)
	}
	split := strings.Split(str, f.delim)
	for i, term := range split {
		if globalFlags.verbose {
			fmt.Printf("[%d] ", i)
		}
		fmt.Println(term)
	}

	// In this case, there was no error. Errors should be returned via the
	// cmd.Error or cmd.Errorf methods.
	return command.NoError()
}

// A second command
func CmdJoin() *command.Cmd {
	runner := &joinRunner{}

	cmd := &command.Cmd{
		Name:        "join",
		Summary:     "join strings",
		Usage:       "[OPTIONS] <string>...",
		Description: "join strings using the specified delimiter",
		Runner:      runner,
	}

	cmd.Flags.StringVar(&runner.delim, "delim", ",", "The delimiter with which to join the strings")

	return cmd
}

// a second Runner
type joinRunner struct {
	delim string
}

func (f *joinRunner) Run(cmd *command.Cmd, args []string) command.CmdErr {
	if globalFlags.verbose {
		fmt.Printf("Joining \"%v\"\n", args)
	}
	joined := strings.Join(args, f.delim)
	fmt.Println(joined)

	return command.NoError()
}

// while not manditory, keeping globally-configured flags in a single struct
// makes it obvious where they came from at access time.
type globalFlagsT struct {
	verbose bool
}

var globalFlags = globalFlagsT{}

func mkSubCmdCLI() cli.CLI {
	// make a new CLI passing the description and version and one or more sub commands
	c := cli.NewWithSubCmds(
		"an example CLI for simple string operations",
		"1.2.3",
		CmdSplit(),
		CmdJoin(),
	)

	// Global flags can be used to modify global state
	c.Flags().BoolVar(&globalFlags.verbose, "verbose", false, "Produce verbose output")

	return c
}

// This example shows how to create a CLI with multiple sub-commands
func Example_subCommands() {
	// this would be your main() function

	// run the Main function, which calls os.Exit with the appropriate exit status
	mkSubCmdCLI().Main()
}

// Add the following to your tests to validate that there are no collisions
// between command flags:

// package main

// import (
// 	"testing"

// 	"github.com/turbinelabs/test/assert"
// )

// func TestCLI(t *testing.T) {
// 	assert.Nil(t, mkCLI().Validate())
// }

Index

Examples

Constants

const HelpSummary = "Show a list of commands or help for one command"
const VersionSummary = "Print the version and exit"

type CLI

type CLI interface {
	// Flags returns a pointer to the global flags for the CLI
	Flags() *flag.FlagSet
	// Set the flags
	SetFlags(*flag.FlagSet)

	// Main serves as the main() function for the CLI. It will parse
	// the command-line arguments and flags, call the appropriate sub-command,
	// and return exit status and output error messages as appropriate.
	Main()

	// Validate can be used to make sure the CLI is well-defined from within
	// unit tests. In particular it will validate that no two flags exist with
	// the same environment key. As a last-ditch effort, Validate will be called
	// at the start of Main. ValidationFlag values may be passed to alter the
	// level of validation performed.
	Validate(...ValidationFlag) error

	// Returns the CLI version data.
	Version() app.Version
}

A CLI represents a command-line application

func New

func New(version string, command *command.Cmd) CLI

New produces a CLI for the given command.Cmd

func NewWithSubCmds

func NewWithSubCmds(
	description string,
	version string,
	command1 *command.Cmd,
	commandsN ...*command.Cmd,
) CLI

NewWithSubCmds produces a CLI for the given app.App and with subcommands for the given command.Cmds.

type ValidationFlag

type ValidationFlag int
const (
	// Skips Validating that global and subcommand help text can
	// be generated.
	ValidateSkipHelpText ValidationFlag = iota
)
Documentation was rendered with GOOS=linux and GOARCH=amd64.

Jump to identifier

Keyboard shortcuts

? : This menu
f or F : Jump to identifier