cli: github.com/turbinelabs/cli Index | Examples | Files | Directories

package cli

import "github.com/turbinelabs/cli"

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.

This example shows how to create a single-command CLI

Code:

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

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 main() {
    // 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())
// }

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 main

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 main() {
    // 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

Package Files

cli.go doc.go

Constants

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

type CLI Uses

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 Uses

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

New produces a CLI for the given command.Cmd

func NewWithSubCmds Uses

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 Uses

type ValidationFlag int
const (
    // Skips Validating that global and subcommand help text can
    // be generated.
    ValidateSkipHelpText ValidationFlag = iota
)

Directories

PathSynopsis
appThe app package provides a simple App struct to describe a command-line application, and a Usage interface, which describers the global and command-specific usage of the App.
commandThe command package provides an abstraction for a command-line application sub-command, a means to execute code when that sub-command is invoked, a means to report success/failure status of said code, and generic implementations of help and version sub-commands.
terminal

Package cli imports 14 packages (graph) and is imported by 2 packages. Updated 2018-11-22. Refresh now. Tools for package owners.