cli

package module
v0.7.1 Latest Latest
Warning

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

Go to latest
Published: May 1, 2020 License: MIT Imports: 11 Imported by: 1

README

cli

Linux, macOS and Windows GoDoc

About

This is a library that adds some CLI functionalities on top of Go's flag package while preserving the single dash Go-style flags. Some of those functionalities are:

  • Subcommands
  • Positional arguments
    • Both required and optional arguments
    • Repeating arguments
  • More robust help message
  • Correct handling of help flags
    • Print help to stdout when help is explicitly requested (via -h or -help options)
    • Print help to stderr when the CLI is misused (by requesting a bad command or argument)
  • Prefix errors with the program's name
  • Easy to set up (the whole CLI can be configured all at once)
Principles

This library enforces some principles that might not suit everybody's taste:

  • Go-style flags (single dash for both long and short options)
  • No flags with short name only
  • No conditional flags (internal parser is still Go's flag package)
  • No required flags (if it's required, make it an argument)
  • Flags must come right after its command, before args
  • Do not expose types from external packages
  • Boring (it's basically configuration plus your logic)

Really, if you're looking for GNU-style flags with the whole parsing kung fu, this library might not be for you. But don't worry, Go is full of awesome libraries out there for such purposes.

Example

Multiple subcommands with top-level configuration
package main

import (
	"fmt"
	"os"
	"strings"

	"github.com/gbrlsnchs/cli"
)

// appConfig is the program's configuration.
// It can be used by any command.
type appConfig struct {
	quiet bool
}

// copy returns a copy of cfg, thus not allowing the original configuration
// to be modified by registering functions.
// This is one way to simulate a const reference, which, although modifiable,
// won't interfere with the original instance, so modifying it is harmless.
func (cfg *appConfig) copy() appConfig { return *cfg }

// helloCmd says hello to someone. Toggle upper to scream.
type helloCmd struct {
	name  string
	upper bool
}

func (cmd *helloCmd) register(getcfg func() appConfig) cli.ExecFunc {
	return func(prg cli.Program) error {
		appcfg := getcfg()
		s := cmd.name
		if cmd.upper {
			s = strings.ToUpper(s)
		}
		if !appcfg.quiet {
			fmt.Fprintf(prg.Stdout(), "Hello, %s!\n", s)
		}
		return nil
	}
}

// concatCmd prints the concatenation of two words.
type concatCmd struct {
	first  string
	second string
}

func (cmd *concatCmd) register(getcfg func() appConfig) cli.ExecFunc {
	return func(prg cli.Program) error {
		appcfg := getcfg()
		if !appcfg.quiet {
			fmt.Fprintf(prg.Stdout(), "%s %s\n", cmd.first, cmd.second)
		}
		return nil
	}
}

// joinCmd prints a list of words joined by a separator.
type joinCmd struct {
	words []string
	sep   string
}

func (cmd *joinCmd) register(getcfg func() appConfig) cli.ExecFunc {
	return func(prg cli.Program) error {
		appcfg := getcfg()
		if !appcfg.quiet {
			s := strings.Join(cmd.words, cmd.sep)
			fmt.Fprintln(prg.Stdout(), s)
		}
		return nil
	}
}

// rootCmd is simply a store for commands, and also a way to
// initialize all of them at once by using rootCmd's zero value.
type rootCmd struct {
	hello  helloCmd
	concat concatCmd
	join   joinCmd
}

func main() {
	var (
		root   rootCmd
		appcfg appConfig
	)
	cmdl := cli.New(&cli.Command{
		Description: `This is a simple program that serves as an example for how to use package cli.

Its commands should not be taken seriously, since they do nothing really great, but serve well for demonstration.`,
		Options: map[string]cli.Option{
			"quiet": cli.BoolOption{
				OptionDetails: cli.OptionDetails{
					Description: "Turn output off.",
					Short:       'q',
				},
				Recipient: &appcfg.quiet,
			},
		},
		Subcommands: map[string]*cli.Command{
			"hello": {
				Description: "Say hello to someone.",
				Arg: cli.StringArg{
					Label:     "NAME",
					Required:  true,
					Recipient: &root.hello.name,
				},
				Options: map[string]cli.Option{
					"upper": cli.BoolOption{
						OptionDetails: cli.OptionDetails{
							Description: "Convert name to uppercase.",
						},
						Recipient: &root.hello.upper,
					},
				},
				Exec: root.hello.register(appcfg.copy),
			},
			"concat": {
				Description: "Concatenate two words.",
				Arg: cli.StringArg{
					Label:     "FIRST WORD",
					Required:  true,
					Recipient: &root.concat.first,
					Next: cli.StringArg{
						Label:     "LAST WORD",
						Required:  true,
						Recipient: &root.concat.second,
					},
				},
				Exec: root.concat.register(appcfg.copy),
			},
			"join": {
				Description: "Join strings together.",
				Arg: cli.RepeatingArg{
					Label:     "WORD",
					Required:  true,
					Recipient: &root.join.words,
				},
				Options: map[string]cli.Option{
					"separator": cli.StringOption{
						OptionDetails: cli.OptionDetails{
							Description: "Set a custom separator.",
							Short:       's',
							ArgLabel:    "SEPARATOR",
						},
						Recipient: &root.join.sep,
						DefValue:  ",",
					},
				},
				Exec: root.join.register(appcfg.copy),
			},
		},
	}, cli.Name("my-cmd"))
	code := cmdl.ParseAndRun(os.Args)
	os.Exit(code)
}
Outputs
Running the main program (prints help)

$ go run .examples/full_featured/main.go -h
This is a simple program that serves as an example for how to use
package cli.

Its commands should not be taken seriously, since they do nothing really
great, still they serve well for demonstration.

USAGE:
    my-cmd [OPTIONS] <COMMAND>

OPTIONS:
    -h, -help     Print this help message.
    -q, -quiet    Turn output off.

COMMANDS:
    concat    Concatenate two words.
    hello     Say hello to someone.
    join      Join strings together.

Help for the "hello" program

$ go run .examples/full_featured/main.go hello -h
Say hello to someone.

USAGE:
    hello [OPTIONS] <NAME>

OPTIONS:
    -h, -help     Print this help message.
        -upper    Convert name to uppercase.

Help for the "concat" program

$ go run .examples/full_featured/main.go concat -h
Concatenate two words.

USAGE:
    concat [OPTIONS] <FIRST WORD> <LAST WORD>

OPTIONS:
    -h, -help    Print this help message.

Help for the "join" program

$ go run .examples/full_featured/main.go join -h
Join strings together.

USAGE:
    join [OPTIONS] <WORD> [...]

OPTIONS:
    -h, -help                     Print this help message.
    -s, -separator <SEPARATOR>    Set a custom separator. (default: ",")

Documentation

Overview

Package cli provides CLI functionality on top of Go's flag package.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ErrorCode added in v0.4.0

func ErrorCode(c int) func(*CLI)

ErrorCode sets a different error code for a CLI. The default is 1.

func HelpDescription

func HelpDescription(s string) func(*CLI)

HelpDescription changes the default help description message of the CLI.

func MisuseCode added in v0.4.0

func MisuseCode(c int) func(*CLI)

MisuseCode sets a different misuse code for a CLI. The default is 2.

func Name added in v0.4.0

func Name(s string) func(*CLI)

Name sets a fixed name for the program. The default is the first string from parsed args.

func Stderr

func Stderr(w io.Writer) func(*CLI)

Stdout is a functional option for creating a CLI that sets w as stderr.

func Stdout

func Stdout(w io.Writer) func(*CLI)

Stdout is a functional option for creating a CLI that sets w as stdout.

Types

type Arg

type Arg interface {
	AppendTo(a *ArgList)
	WriteDoc(w io.Writer)
}

Arg is an interface for a positional argument. It can append an argument to an argument list and can write its own documentation to show on a command's usage instructions.

type ArgList

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

ArgList is an argument list that holds all arguments set by a command.

func (*ArgList) Append

func (a *ArgList) Append(name string, v ArgValue, required, repeat bool)

Append appends an argument to itself.

type ArgValue

type ArgValue interface {
	Set([]string) error
}

ArgValue is an interface to wrap the parsed arguments. Non-repeating arguments get a single argument slice passed to Set.

type BoolOption

type BoolOption struct {
	OptionDetails
	DefValue  bool
	Recipient *bool
}

BoolOption represents a boolean flag.

func (BoolOption) Define

func (fg BoolOption) Define(f *flag.FlagSet, name string)

Define implements Option by defining a boolean flag to f.

type CLI

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

CLI is a command-line interface wrapper that provides flags, positional arguments and subcommands.

For providing flags, it uses the flag package from the standard library. It overrides some features of the flag package in order to provide a prettier help message and to print help to the correct output depending on the situation, which is:

If the user explicitly ask for help by using either -h or -help, it prints help to stdout.

If the user makes a mistake by missing either a subcommand or a positional argument, it prints
help to stderr.

func New

func New(entry *Command, opts ...func(*CLI)) *CLI

New instantiates a new command-line interface with sane defaults, which have outputs set to os.Stdout and os.Stderr.

func (*CLI) ParseAndRun

func (cli *CLI) ParseAndRun(args []string) int

ParseAndRun parses arguments and runs a command, handling flags, subcommands and positional arguments according to configuration.

It returns a status code and correctly prints error messages followed by usage instructions when necessary.

func (*CLI) ParseAndRunContext

func (cli *CLI) ParseAndRunContext(ctx context.Context, args []string) int

ParseAndRunContext is just like ParseAndRun but accepts a custom context.

type Command

type Command struct {
	Description string              // Description describes what the command does.
	Exec        ExecFunc            // Exec is the function run by the command.
	Options     map[string]Option   // Options are the command's options (also known as flags).
	Subcommands map[string]*Command // Subcommands store the command's subcommands.
	Arg         Arg                 // Arg is a positional argument.
}

Command is a command line command.

If a command doesn't have an Exec function, it is treated as a help command, which prints help to stdout.

When a command has one or more subcommands, its Arg will be totally ignored.

type ExecFunc

type ExecFunc func(Program) error

ExecFunc is a function that receives a program information and may return an error, which will be printed to stderr.

type Int64Option

type Int64Option struct {
	OptionDetails
	DefValue  int64
	Recipient *int64
}

Int64Option represents a 64-bit integer flag.

func (Int64Option) Define

func (fg Int64Option) Define(f *flag.FlagSet, name string)

Define implements Option by defining a 64-bit integer flag to f.

type IntOption

type IntOption struct {
	OptionDetails
	DefValue  int
	Recipient *int
}

IntOption represents an integer flag.

func (IntOption) Define

func (fg IntOption) Define(f *flag.FlagSet, name string)

Define implements Option by defining an integer flag to f.

type Option

type Option interface {
	Define(f *flag.FlagSet, name string)
	WriteDoc(w io.Writer, name string)
}

Option is a type that is able to define its flags to a flag set and also print its own documentation.

type OptionDetails

type OptionDetails struct {
	Description string
	Short       byte
	ArgLabel    string
}

OptionDetails are common fields for an option, which are its details.

func (OptionDetails) WriteDoc

func (ff OptionDetails) WriteDoc(w io.Writer, name string)

WriteDoc writes to w a flag's description in a pretty way.

type Program

type Program interface {
	Name() string
	Stdout() io.Writer
	Stderr() io.Writer
}

Program carries information about a running command.

type RepeatingArg

type RepeatingArg struct {
	Label     string    // Label is for documentation purposes.
	Required  bool      // Required means one or more occurrences must happen.
	Recipient *[]string // Recipient is the pointer that will receive the parsed args.
}

RepeatingArg is a repeating argument. It can be empty when not required, or must occur one or more times when required.

func (RepeatingArg) AppendTo

func (arg RepeatingArg) AppendTo(a *ArgList)

AppendTo appends the argument as the last one in the list.

func (RepeatingArg) WriteDoc

func (arg RepeatingArg) WriteDoc(w io.Writer)

WriteDoc writes the argument's instruction to w.

type StringArg

type StringArg struct {
	Label     string  // Label is for documentation purposes.
	Required  bool    // Required triggers an error when the argument is not provided.
	Recipient *string // Recipient is the pointer to have the value set to.
	Next      Arg     // Next is the next positional argument.
}

StringArg is the most common type of argument, a simple string.

func (StringArg) AppendTo

func (arg StringArg) AppendTo(a *ArgList)

AppendTo appends the argument and recursively appends chained arguments.

func (StringArg) WriteDoc

func (arg StringArg) WriteDoc(w io.Writer)

WriteDoc writes the argument's instruction to w.

type StringOption

type StringOption struct {
	OptionDetails
	DefValue  string
	Recipient *string
}

StringOption represents a string flag.

func (StringOption) Define

func (fg StringOption) Define(f *flag.FlagSet, name string)

Define implements Option by defining a string flag to f.

func (StringOption) WriteDoc

func (fg StringOption) WriteDoc(w io.Writer, name string)

WriteDoc writes the standard flag documentation and also the default value when it's not an empty string to w.

type VarOption

type VarOption struct {
	OptionDetails
	Recipient flag.Value
}

VarOption represents a flag that implements flag.Value.

func (VarOption) Define

func (fg VarOption) Define(f *flag.FlagSet, name string)

Define implements Option by defining a flag that implements flag.Value to f.

Directories

Path Synopsis
Package clitest provides utilities for CLI testing.
Package clitest provides utilities for CLI testing.
Package cliutil adds some utility types for using in a CLI.
Package cliutil adds some utility types for using in a CLI.

Jump to

Keyboard shortcuts

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