cli

package module
v0.8.1 Latest Latest
Warning

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

Go to latest
Published: Feb 9, 2024 License: MIT Imports: 18 Imported by: 9

README

cli build status Go Report Card GoDoc

Go package providing high-level constructs for command-line tools.

Motivation

The user interface of a program is a major contributor to its adoption and maintainability, however it is often overlooked as a second-class requirement. Developers often focus on the core functionalities of their programs and don't put as much time in designing and understanding how the program will be used.

The reality is that even when effort is spent on building powerful interfaces, the tooling available in Go can be a blocker to generalization of the practice.

The standard library does offer a package for parsing command line arguments, but it is limited to flags, and doesn't support loading configuration options from the environment, or building advanced UX with sub-commands.

Another popular package is spf13/cobra, which has been the to-go solution for most projects. This package is powerful but also very large, brings a lot of complexity to programs that use it, and can be very time consuming to navigate for developers.

We believed that creating powerful tools should be simple, that developers should be empowered to build programs that are safe to use and easy to evolve.

The segmentio/cli package was designed to have a minimal yet flexible API, making it easy to learn, and offering clear guidlines on how to build and evolve command line programs.

Command Line Interface

This section contains a couple of examples that showcase the features of the package. (For more, see the "examples" directory.)

Flags

This first example presents how to construct a command which accepts a --name flag:

package main

import (
	"fmt"

	"github.com/segmentio/cli"
)

func main() {
	type config struct {
		Name string `flag:"-n,--name" help:"Someone's name" default:"Luke"`
	}

	cli.Exec(cli.Command(func(config config) {
		fmt.Printf("hello %s!\n", config.Name)
	}))
}
$ ./example1 --help

Usage:
  example1 [options]

Options:
  -h, --help         Show this help message
  -n, --name string  Someone's name (default: Luke)

$ ./example1 --name Han
hello Han!

The key take away here is how flags are declared by the first argument of the function implementing the command. The segmentio/cli package implements a calling convention which maps the program arguments to the arguments of the function being called.

Default Values

The first example shows how to set a default value for a flag. If a flag is truly optional, then set its default value to "-"; when the flag isn't used, its field assumes its zero-value. When a flag does not have any default value defined, then it is required.

type config struct {
	// optional, default "Luke"
	Name     string `flag:"-n,--name"     help:"Someone's name"        default:"Luke"`
	// optional, no default
	Planet   string `flag:"-p,--planet"   help:"Someone's home planet" default:"-"`
	// required
	Greeting string `flag:"-g,--greeting" help:"Greeting word, such as hello"`
}
Hidden Flags

A hidden flag is not included in help text, making it undocumented but still usable.

	// optional, default "Leia", hidden
	Sibling  string `flag:"-s,--sibling"  help:"Secret family member"  default:"Leia" hidden:"true"`
Command Help Text

When the struct used for flags contains a field named _, its "help" tag defines the command's own help message. The field type is ignored.

type config struct {
	_    struct{} `help:"Greets someone from a galaxy far, far away"`
	Name string   `flag:"-n,--name" help:"Someone's name" default:"Luke"`
}
Positional Arguments

While the first argument of a command must always be a struct defining the set of accepted flags, the function may also define extra arguments which will be loaded from positional arguments:

package main

import (
	"fmt"

	"github.com/segmentio/cli"
)

func main() {
	type noflags struct{}

	cli.Exec(cli.Command(func(_ noflags, x, y int) {
		fmt.Println(x + y)
	}))
}
$ ./example2 --help

Usage:
  example2 [options] [int] [int]

Options:
  -h, --help  Show this help message
$ ./example2 1 2
3

The last function parameter may also be a slice which captures all remaining positional arguments:

package main

import (
	"fmt"

	"github.com/segmentio/cli"
)

func main() {
	type noflags struct{}

	cli.Exec(cli.Command(func(_ noflags, words []string) {
		for _, word := range words {
			fmt.Println(word)
		}
	}))
}
$ ./example3 --help

Usage:
  example3 [options] [string...]

Options:
  -h, --help  Show this help message

$ ./example3 hello world
hello
world
Child Commands

It is common for wrapper programs to accept an arbitrary command that they execute after performing some initializations. To reduce the risk of mixing the program's arguments and the arguments of its child-command, a "--" separator is employed as a delimiter between the two on the command line.

With the segmentio/cli package, this model is supported by adding a variadic list of string parameters to the command:

package main

import (
	"fmt"
	"strings"

	"github.com/segmentio/cli"
)

func main() {
	type noflags struct{}

	cli.Exec(cli.Command(func(_ noflags, args ...string) {
		fmt.Println("run:", strings.Join(args, " "))
	}))
}
$ ./example4 --help

Usage:
  example4 [options] -- [command]

Options:
  -h, --help  Show this help message

$ ./example4 -- echo hello world
run: echo hello world
Command Sets

Advanced tools often have a set of commands in a single program, each exposing a different feature of the tool (e.g. git checkout, git commit).

The segmentio/cli package supports constructing programs like these using the cli.CommandSet type. The next example showcases how to construct a program accepting three sub-commands:

package main

import (
	"fmt"

	"github.com/segmentio/cli"
)

func main() {
	type oneConfig struct {
		_ struct{} `help:"Usage text for command one"`
	}
	one := cli.Command(func(cfg oneConfig) {
		fmt.Println("1")
	})

	two := cli.Command(func() {
		fmt.Println("2")
	})

	three := cli.CommandSet{
		"_": cli.CommandFunc{
			Help: "Usage text for the command three",
		},
		"four": cli.Command(func() {
			fmt.Println("4")
		}),
		"five": cli.Command(func() {
			fmt.Println("4")
		}),
	}

	cli.Exec(cli.CommandSet{
		"one":   one,
		"two":   two,
		"three": three,
	})
}
$ ./example5 --help
Usage:
  example5 [command] [-h] [--help] ...

Commands:
  one    Usage text for command one
  three  Usage text for the 'three' command
  two

Options:
  -h, --help  Show this help message

$ ./example5 one
1

When the command set contains a value for the key "_", its function value's "Help" value defines the command set's own help message.

Environment Variables

While passing configuration options on the command line using flags and positional arguments provides great UX, it is also very common to use environment variables in configuration files like kubernetes templates.

Every long flag accepted by a command (flags starting with "--") can also be loaded from environment variables. The package maps environment variables to flags by prefixing it with the program name and converting the flag to upper-snake-case, for example:

> --verbose => ${PROGRAM}_VERBOSE

Testing Commands

Testing command line programs is often overlooked, because packages which facilitate loading program configurations often aren't designed with ease of testing in mind.

On the other hand, commands declared with the segmentio/cli package are easily testable using the cli.Call function, which combined with Go's support for testable examples, offer a great model for testing commands.

Using the first example, here is how we could write tests to validate the behavior of the command:

type config struct {
	Name string `flag:"-n,--name" help:"Someone's name" default:"Luke"`
}

var command = cli.Command(func(config config) {
	fmt.Printf("hello %s!\n", config.Name)
})
func Example_noArguments() {
	cli.Call(command)
	// Output: hello Luke!
}

func Example_withArgument() {
	cli.Call(command, "--name", "Han")
	// Output: hello Han!
}

Formatting Output

A lot of command line programs also output information to their caller, and often need to support multiple formats to be used in different conditions (called by an operator, used in a script for automation, etc...).

This formatting work is often tedious and redundant, so the segmentio/cli package exposes abstractions to help developers build tools which support multiple output formats:

type config struct {
	Output string `flag:"-o,--output" help:"Output format of the command" default:"text"`
}

type result struct {
	Name  string
	Value int
}

var command = cli.Command(func(config config) error {
	p, err := cli.Format(config.Output, os.Stdout)
	if err != nil {
		return err
	}
	defer p.Flush()

	...

	// Call p.Print one or more times to output content to stdout
	//
	// p.Print(v)
})

The package supports three formats out-of-the-box: text, json, and yaml.

In the text format, struct and map values are printed as table representations with a header being the name of the struct fields or the keys of the map. Other value types are simply printed one value per line.

All formats interpret the json struct tag to configure the names of the fields and the behavior of the formatting operation.

The text format also interprets fmt tags as carrying the formatting string passed in calls to functions of the fmt package.

Documentation

Overview

Package cli provides high-level tools for building command-line interfaces.

Index

Examples

Constants

This section is empty.

Variables

Err is used by the Exec and Call functions to print out errors returned by the commands they call out to.

Functions

func Call

func Call(cmd Function, args ...string) int

Call calls cmd with args and environment variables prefixed with the uppercased program name.

This function is often used to test commands in example programs with constructs like:

var command = cli.Command(func(config config) {
	...
})

Then in the test file:

func Example_command_with_option() {
	cli.Call(command, "--option", "value")
	// Output:
	// ...
}

func CallContext added in v0.2.0

func CallContext(ctx context.Context, cmd Function, args ...string) int

CallContext calls Call but with a specified context.Context.

func Exec

func Exec(cmd Function)

Exec delegate the program execution to cmd, then exits with the code returned by the function call.

A typical use case is for Exec to be the last statement of the main function of a program:

func main() {
	cli.Exec(cli.Command(func(config config) {
		...
	})
}

The Exec function never returns.

func ExecContext added in v0.2.0

func ExecContext(ctx context.Context, cmd Function)

ExecContext calls Exec but with a specified context.Context.

Types

type CommandFunc

type CommandFunc struct {
	// A short help message describing what the command does.
	Help string

	// A full description of the command.
	Desc string

	// The function that the command calls out to when invoked.
	//
	// See Command for details about the accepted signatures.
	Func interface{}

	// An optional usage string for this function. If set, then this replaces
	// the default one that shows the types (but not names) of arguments.
	Usage string

	// Set of options to not set from the environment
	// this is a more user-friendly-syntax than IgnoreEnvOptionMap
	// However, this is strictly for user input and should not be used in the cli code
	// Please use IgnoreEnvOptionMap internally
	IgnoreEnvOptions []string

	// Set of options to not set from the environment
	// This is to convert IgnoreEnvOptions field to a map for efficient lookups
	IgnoreEnvOptionsMap map[string]struct{}
	// contains filtered or unexported fields
}

CommandFunc is an implementation of the Function interface which calls out to a nested function when invoked.

func (*CommandFunc) Call

func (cmd *CommandFunc) Call(ctx context.Context, args, env []string) (int, error)

Call satisfies the Function interface.

See Command for the full documentation of how the Call method behaves.

func (*CommandFunc) Format

func (cmd *CommandFunc) Format(w fmt.State, v rune)

Format satisfies the fmt.Formatter interface. It recognizes the following verbs:

%s	outputs the usage information of the command
%v	outputs the full description of the command
%x	outputs the help message of the command

type CommandSet

type CommandSet map[string]Function

A CommandSet is used to construct a routing mechanism for named commands.

This model is often used in CLI tools to support a list of verbs, each routing the caller to a sub-functionality of the main command.

CommandSet satisfies the Function interface as well, making it possible to compose sub-commands recursively, for example:

cmd := cli.CommandSet{
	"top": cli.CommandSet{
		"sub-1": cli.Command(func() {
			...
		}),
		"sub-2": cli.Command(func() {
			...
		}),
	},
}

The sub-commands can be called with one of these invocations:

$ program top sub-1

$ program top sub-2
Example
package main

import (
	"fmt"

	"github.com/segmentio/cli"
)

func main() {
	help := cli.Command(func() {
		fmt.Println("help")
	})

	this := cli.Command(func() {
		fmt.Println("this")
	})

	that := cli.Command(func() {
		fmt.Println("that")
	})

	cmd := cli.CommandSet{
		"help": help,
		"do": cli.CommandSet{
			"this": this,
			"that": that,
		},
	}

	cli.Call(cmd, "help")
	cli.Call(cmd, "do", "this")
	cli.Call(cmd, "do", "that")

}
Output:

help
this
that
Example (Help)
package main

import (
	"os"

	"github.com/segmentio/cli"
)

func main() {
	type thisConfig struct {
		_     struct{} `help:"Call this command"`
		Path  string   `flag:"-p,--path"  help:"Path to some file" default:"file" env:"-"`
		Debug bool     `flag:"-d,--debug" help:"Enable debug mode"`
	}

	type thatConfig struct {
		_     struct{} `help:"Call that command"`
		Count int      `flag:"-n"         help:"Number of things"  default:"1"`
		Debug bool     `flag:"-d,--debug" help:"Enable debug mode"`
	}

	cmd := cli.CommandSet{
		"do": cli.CommandSet{
			"this": cli.Command(func(config thisConfig) {
				// ...
			}),
			"that": cli.Command(func(config thatConfig) {
				// ...
			}),
		},
	}

	cli.Err = os.Stdout
	cli.Call(cmd, "do", "--help")

}
Output:

Usage:
  do [command] [-h] [--help] ...

Commands:
  that  Call that command
  this  Call this command

Options:
  -h, --help  Show this help message
Example (Help2)
package main

import (
	"os"

	"github.com/segmentio/cli"
)

func main() {
	type thisConfig struct {
		_     struct{} `help:"Call this command"`
		Path  string   `flag:"-p,--path"  help:"Path to some file" default:"file" env:"-"`
		Debug bool     `flag:"-d,--debug" help:"Enable debug mode"`
	}

	type thatConfig struct {
		_     struct{} `help:"Call that command"`
		Count int      `flag:"-n"         help:"Number of things"  default:"1"`
		Debug bool     `flag:"-d,--debug" help:"Enable debug mode"`
	}

	cmd := cli.CommandSet{
		"do": cli.CommandSet{
			"this": cli.Command(func(config thisConfig) {
				// ...
			}),
			"that": cli.Command(func(config thatConfig) {
				// ...
			}),
		},
	}

	cli.Err = os.Stdout
	cli.Call(cmd, "do", "this", "-h")

}
Output:

Usage:
  do this [options]

Options:
  -d, --debug        Enable debug mode
  -h, --help         Show this help message
  -p, --path string  Path to some file (default: file)
Example (Option_after_command)
package main

import (
	"fmt"

	"github.com/segmentio/cli"
)

func main() {
	type config struct {
		String string `flag:"-f,--flag" default:"-"`
	}

	sub := cli.Command(func(config config) {
		fmt.Println(config.String)
	})

	cmd := cli.CommandSet{
		"sub": sub,
	}

	cli.Call(cmd, "sub", "-f=hello")

}
Output:

hello
Example (Option_before_command)
package main

import (
	"fmt"

	"github.com/segmentio/cli"
)

func main() {
	type config struct {
		String string `flag:"-f,--flag" default:"-"`
	}

	sub := cli.Command(func(config config) {
		fmt.Println(config.String)
	})

	cmd := cli.CommandSet{
		"sub": sub,
	}

	cli.Call(cmd, "-f=hello", "sub")

}
Output:

hello
Example (Usage_text)
package main

import (
	"fmt"
	"os"

	"github.com/segmentio/cli"
)

func main() {
	help := cli.Command(func() {
		fmt.Println("help")
	})

	doc := cli.Command(func() {
		fmt.Println("doc")
	})

	cover := cli.Command(func() {
		fmt.Println("cover")
	})

	cmd := cli.CommandSet{
		"help": help,
		"tool": cli.CommandSet{
			"_": &cli.CommandFunc{
				Help: "run specified go tool",
			},
			"cover": cover,
			"doc":   doc,
		},
	}

	cli.Err = os.Stdout
	cli.Call(cmd, "--help")

}
Output:

Usage:
  [command] [-h] [--help] ...

Commands:
  help
  tool  run specified go tool

Options:
  -h, --help  Show this help message

func (CommandSet) Call

func (cmds CommandSet) Call(ctx context.Context, args, env []string) (int, error)

Call dispatches the given arguments and environment variables to the sub-command named in the first non-option value in args. Finding the command separator "--" before a sub-command name results in an error.

The method returns a *Help (as an error) is the first argument is -h or --help, and a usage error if the first argument did not match any sub-command.

Call satisfies the Function interface.

func (CommandSet) Format

func (cmds CommandSet) Format(w fmt.State, v rune)

Format writes a human-readable representation of cmds to w, using v as the formatting verb to determine which property of the command set should be written.

The method supports the following formatting verbs:

%s	outputs the usage information of the command set
%v	outputs the full description of the command set

Format satisfies the fmt.Formatter interface.

type Flusher

type Flusher interface {
	Flush()
}

Flusher is an interface implemented by types that buffer content.

type Function

type Function interface {
	Call(ctx context.Context, args, env []string) (int, error)
}

The Function interface is implemented by commands that may be invoked with argument and environment variable lists.

Functions returns a status code intended to be the exit code of the program that called them as well as a non-nil error if the function call failed.

func Command

func Command(fn interface{}) Function

Command constructs a Function which delegates to the Go function passed as argument.

The argument is an interface{} because functions with multiple types are accepted.

The function may receive no arguments, indicating that the program delegating to the command is expected to be invoked with no arguments, for example:

cmd := cli.Command(func() {
	...
})

If the function accepts arguments, the first argument (except for an optional initial `context.Context`) must always be a struct type, which describes the set of options that are accepted by the command:

// Struct tags are used to declare the flags accepted by the command.
type config struct {
	Path    string `flag:"--path"       help:"Path to a text file" default:"file.txt"`
	Verbose bool   `flag:"-v,--verbose" help:"Enable verbose mode"`
}

cmd := cli.Command(func(config config) {
	...
})

Five keys are recognized in the struct tags: "flag", "env", "help", "default", and "hidden".

The "flag" struct tag is a comma-separated list of command line flags that map to the field. This tag is required.

The "env" struct tag optionally specifies the name of an environment variable whose value may provide a field value. When the tag is not specified, then environment variables corresponding to long command line flags may provide field values. A tag value of "-" disables this default behavior.

The "help" struct tag is a human-readable message describing what the field is used for.

The "default" struct tag provides the default value of the field when the argument was missing from the call to the command. Any flag which has no default value and isn't a boolean or a slice type must be passed when calling the command, otherwise a usage error is returned. The special default value "-" can be used to indicate that the option is not required and should assume its zero-value when omitted.

The "hidden" struct flag is a Boolean indicating if the field should be excluded from help text, essentially making it undocumented.

If the struct contains a field named `_`, the command will look for a "help" struct tag to define its own help message. Note that the type of the field is irrelevant, but it is common practice to use an empty struct.

The command always injects and handles the -h and --help flags, which can be used to request that call to the command return a help error to describe the configuration options of the command.

Every flag starting with a "--" may also be configured via an environment variable. The environment variable is matched by converting the flag name to a snakecase and uppercase format. Flags that should not be matched to environment variables must specify a struct tag env:"-" to disable the feature.

Each extra argument to the function is interpreted as a positional argument and decoded as such, for example:

// This command expects two integers as positional arguments.
cmd := cli.Command(func(config config, x, y int) {
	...
})

The last positional argument may be a slice, which consumes as many values as remained on the command invocation.

An extra variadic string parameter may be accepted by the function, which receives any extra arguments found after a "--" separator. This mechanism is often used by programs that spawn other programs to define the limits between the arguments of the first program, and the second command.

If the command is called with an invalid set of arguments, it returns a non-zero code and a usage error which describes the issue.

Example (BinaryUnmarshaler)
package main

import (
	"fmt"
	"net/url"

	"github.com/segmentio/cli"
)

func main() {
	type config struct {
		URL url.URL `flag:"--url" default:"http://localhost/"`
	}

	cmd := cli.Command(func(config config) {
		fmt.Println(config.URL.String())
	})

	cli.Call(cmd)
	cli.Call(cmd, "--url", "http://www.segment.com/")

}
Output:


http://localhost/
http://www.segment.com/
Example (Bool)
package main

import (
	"fmt"

	"github.com/segmentio/cli"
)

func main() {
	type config struct {
		Bool bool `flag:"-f,--flag"`
	}

	cmd := cli.Command(func(config config) {
		fmt.Println(config.Bool)
	})

	cli.Call(cmd)
	cli.Call(cmd, "-f")
	cli.Call(cmd, "--flag")
	cli.Call(cmd, "-f=false")
	cli.Call(cmd, "--flag=false")
	cli.Call(cmd, "-f=true")
	cli.Call(cmd, "--flag=true")

}
Output:

false
true
true
false
false
true
true
Example (Context)
package main

import (
	"context"
	"fmt"

	"github.com/segmentio/cli"
)

func main() {
	ctx := context.Background()

	cmd := cli.Command(func(ctx context.Context) {
		if ctx == context.TODO() {
			fmt.Println("context.TODO()")
		} else {
			fmt.Println("context.Background()")
		}
	})

	cli.Call(cmd)
	cli.CallContext(ctx, cmd)
}
Output:

context.TODO()
context.Background()
Example (Context_args)
package main

import (
	"context"
	"fmt"

	"github.com/segmentio/cli"
)

func main() {
	ctx := context.TODO()

	type config struct{}

	cmd := cli.Command(func(ctx context.Context, config config, args []string) {
		fmt.Println(args)
	})

	cli.CallContext(ctx, cmd, "hello", "world")
}
Output:

[hello world]
Example (Context_config)
package main

import (
	"context"
	"fmt"

	"github.com/segmentio/cli"
)

func main() {
	ctx := context.TODO()

	type config struct{}

	cmd := cli.Command(func(ctx context.Context, config config) {
		fmt.Println("hello world")
	})

	cli.CallContext(ctx, cmd)
}
Output:

hello world
Example (Default)
package main

import (
	"fmt"

	"github.com/segmentio/cli"
)

func main() {
	type config struct {
		Path string `flag:"-p,--path" default:"file.txt" env:"-"`
	}

	cmd := cli.Command(func(config config) {
		fmt.Println(config.Path)
	})

	cli.Call(cmd)
}
Output:

file.txt
Example (Duration)
package main

import (
	"fmt"
	"time"

	"github.com/segmentio/cli"
)

func main() {
	type config struct {
		Duration time.Duration `flag:"-f,--flag" default:"-"`
	}

	cmd := cli.Command(func(config config) {
		fmt.Println(config.Duration)
	})

	cli.Call(cmd)
	cli.Call(cmd, "-f=1ms")
	cli.Call(cmd, "--flag=2s")
	cli.Call(cmd, "-f", "3m")
	cli.Call(cmd, "--flag", "4h")

}
Output:

0s
1ms
2s
3m0s
4h0m0s
Example (Embedded_struct)
package main

import (
	"fmt"

	"github.com/segmentio/cli"
)

func main() {
	type embed struct {
		AnotherString string `flag:"--another-string" default:"b"`
	}

	type config struct {
		String string `flag:"--string" default:"a"`
		embed
	}

	cmd := cli.Command(func(config config) {
		fmt.Println(config.String, config.AnotherString)
	})

	cli.Call(cmd)
	cli.Call(cmd, "--string", "A")
	cli.Call(cmd, "--another-string", "B")
	cli.Call(cmd, "--string", "A", "--another-string", "B")

}
Output:

a b
A b
a B
A B
Example (Environment)
package main

import (
	"fmt"
	"os"

	"github.com/segmentio/cli"
)

func main() {
	type config struct {
		String string `flag:"-f,--flag" default:"-"`
	}

	// If you don't specify the name using NamedCommand, it defaults
	// to the binary name. In this test, the name must correspond to the prefix
	// of the environment variable.
	cmd := cli.NamedCommand("prog", cli.Command(func(config config) {
		fmt.Println(config.String)
	}))

	os.Setenv("PROG_FLAG", "hello world")
	cli.Err = os.Stdout
	cli.Call(cmd)
}
Output:

hello world
Example (Float)
package main

import (
	"fmt"

	"github.com/segmentio/cli"
)

func main() {
	type config struct {
		Float float64 `flag:"-f,--flag" default:"-"`
	}

	cmd := cli.Command(func(config config) {
		fmt.Println(config.Float)
	})

	cli.Call(cmd)
	cli.Call(cmd, "-f=1")
	cli.Call(cmd, "--flag=2")
	cli.Call(cmd, "-f", "3")
	cli.Call(cmd, "--flag", "4")

}
Output:

0
1
2
3
4
Example (Help)
package main

import (
	"os"

	"github.com/segmentio/cli"
)

func main() {
	type config struct {
		Path  string `flag:"--path"     help:"Path to some file" default:"file" env:"-"`
		Debug bool   `flag:"-d,--debug" help:"Enable debug mode"`
	}

	cmd := cli.CommandSet{
		"do": cli.Command(func(config config) {
			// ...
		}),
	}

	cli.Err = os.Stdout
	cli.Call(cmd, "do", "-h")

}
Output:

Usage:
  do [options]

Options:
  -d, --debug        Enable debug mode
  -h, --help         Show this help message
      --path string  Path to some file (default: file)
Example (HelpContext)
package main

import (
	"context"
	"os"

	"github.com/segmentio/cli"
)

func main() {
	type config struct {
		Path  string `flag:"--path"     help:"Path to some file" default:"file" env:"-"`
		Debug bool   `flag:"-d,--debug" help:"Enable debug mode"`
	}

	cmd := cli.CommandSet{
		"do": cli.Command(func(ctx context.Context, config config) {
			// ...
		}),
	}

	cli.Err = os.Stdout
	cli.CallContext(context.Background(), cmd, "do", "-h")

}
Output:

Usage:
  do [options]

Options:
  -d, --debug        Enable debug mode
  -h, --help         Show this help message
      --path string  Path to some file (default: file)
Example (Int)
package main

import (
	"fmt"

	"github.com/segmentio/cli"
)

func main() {
	type config struct {
		Int int `flag:"-f,--flag" default:"-"`
	}

	cmd := cli.Command(func(config config) {
		fmt.Println(config.Int)
	})

	cli.Call(cmd)
	cli.Call(cmd, "-f=1")
	cli.Call(cmd, "--flag=2")
	cli.Call(cmd, "-f", "3")
	cli.Call(cmd, "--flag", "4")

}
Output:

0
1
2
3
4
Example (Positional_arguments)
package main

import (
	"fmt"

	"github.com/segmentio/cli"
)

func main() {
	type config struct{}

	cmd := cli.Command(func(config config, x, y int) {
		fmt.Println(x, y)
	})

	cli.Call(cmd, "10", "42")
}
Output:

10 42
Example (Positional_arguments_slice)
package main

import (
	"fmt"

	"github.com/segmentio/cli"
)

func main() {
	type config struct{}

	cmd := cli.Command(func(config config, paths []string) {
		fmt.Println(paths)
	})

	cli.Call(cmd, "file1.txt", "file2.txt", "file3.txt")
}
Output:

[file1.txt file2.txt file3.txt]
Example (Required)
package main

import (
	"fmt"
	"os"

	"github.com/segmentio/cli"
)

func main() {
	type config struct {
		Path string `flag:"-p,--path" env:"-"`
	}

	cmd := cli.Command(func(config config) {
		fmt.Println(config.Path)
	})

	cli.Err = os.Stdout
	cli.Call(cmd)
}
Output:

Usage:
  [options]

Options:
  -h, --help         Show this help message
  -p, --path string

Error:
  missing required flag: "--path"
Example (Slice)
package main

import (
	"fmt"

	"github.com/segmentio/cli"
)

func main() {
	type config struct {
		// Slice types in the configuration struct means the flag can be
		// passed multiple times.
		Input []string `flag:"-f,--flag"`
	}

	cmd := cli.Command(func(config config) {
		fmt.Println(config.Input)
	})

	cli.Call(cmd)
	cli.Call(cmd, "-f=file1", "--flag=file2", "--flag", "file3")

}
Output:

[]
[file1 file2 file3]
Example (SpacesInFlag)
package main

import (
	"fmt"

	"github.com/segmentio/cli"
)

func main() {
	type config struct {
		String string `flag:"-f, --flag" default:"-"`
	}

	cmd := cli.Command(func(config config) {
		fmt.Println(config.String)
	})

	cli.Call(cmd)

	cli.Call(cmd, "-f=short")
	cli.Call(cmd, "--flag", "hello world")

}
Output:

short
hello world
Example (String)
package main

import (
	"fmt"

	"github.com/segmentio/cli"
)

func main() {
	type config struct {
		String string `flag:"-f,--flag" default:"-"`
	}

	cmd := cli.Command(func(config config) {
		fmt.Println(config.String)
	})

	cli.Call(cmd)

	cli.Call(cmd, "-f=")
	cli.Call(cmd, "-f=short")
	cli.Call(cmd, "-f", "")
	cli.Call(cmd, "-f", "hello world")

	cli.Call(cmd, "--flag=")
	cli.Call(cmd, "--flag=long")
	cli.Call(cmd, "--flag", "")
	cli.Call(cmd, "--flag", "hello world")

}
Output:


short

hello world

long

hello world
Example (TextUnmarshaler)
package main

import (
	"fmt"

	"github.com/segmentio/cli"
)

type unmarshaler []byte

func (u *unmarshaler) UnmarshalText(b []byte) error {
	*u = b
	return nil
}

func main() {
	type config struct {
		Input unmarshaler `flag:"-f,--flag" default:"-"`
	}

	cmd := cli.Command(func(config config) {
		fmt.Println(string(config.Input))
	})

	cli.Call(cmd)
	cli.Call(cmd, "--flag", "hello world")

}
Output:


hello world
Example (Time)
package main

import (
	"fmt"
	"time"

	"github.com/segmentio/cli"
)

func main() {
	type config struct {
		Time time.Time `flag:"-f,--flag" default:"-"`
	}

	cmd := cli.Command(func(config config) {
		fmt.Println(config.Time.Unix())
	})

	cli.Call(cmd)
	cli.Call(cmd, "-f=Mon, 02 Jan 2006 15:04:05 UTC")
	cli.Call(cmd, "--flag=Mon, 02 Jan 2006 15:04:05 UTC")
	cli.Call(cmd, "-f", "Mon, 02 Jan 2006 15:04:05 UTC")
	cli.Call(cmd, "--flag", "Mon, 02 Jan 2006 15:04:05 UTC")

}
Output:

-62135596800
1136214245
1136214245
1136214245
1136214245
Example (Uint)
package main

import (
	"fmt"

	"github.com/segmentio/cli"
)

func main() {
	type config struct {
		Uint uint `flag:"-f,--flag" default:"-"`
	}

	cmd := cli.Command(func(config config) {
		fmt.Println(config.Uint)
	})

	cli.Call(cmd)
	cli.Call(cmd, "-f=1")
	cli.Call(cmd, "--flag=2")
	cli.Call(cmd, "-f", "3")
	cli.Call(cmd, "--flag", "4")

}
Output:

0
1
2
3
4
Example (Usage)
package main

import (
	"os"

	"github.com/segmentio/cli"
)

func main() {
	type config struct {
		Count int  `flag:"-n"         help:"Number of things"  default:"1"`
		Debug bool `flag:"-d,--debug" help:"Enable debug mode"`
	}

	cmd := cli.CommandSet{
		"do": cli.Command(func(config config) {
			// ...
		}),
	}

	cli.Err = os.Stdout
	cli.Call(cmd, "do", "-n", "abc")

}
Output:

Usage:
  do [options]

Options:
  -d, --debug  Enable debug mode
  -h, --help   Show this help message
  -n int       Number of things (default: 1)

Error:
  decoding "-n": strconv.ParseInt: parsing "abc": invalid syntax
Example (With_sub_command)
package main

import (
	"fmt"

	"github.com/segmentio/cli"
)

func main() {
	type config struct{}

	cmd := cli.Command(func(config config, sub ...string) {
		fmt.Println(sub)
	})

	cli.Call(cmd, "--", "curl", "https://segment.com")
}
Output:

[curl https://segment.com]

func NamedCommand

func NamedCommand(name string, cmd Function) Function

NamedCommand constructs a command which carries the name passed as argument and delegate execution to cmd.

type Help

type Help struct {
	Cmd Function
}

Help values are returned by commands to indicate to the caller that it was called with a configuration that requested a help message rather than executing the command. This type satisfies the error interface.

func (*Help) Error

func (h *Help) Error() string

Error satisfies the error interface.

func (*Help) Format

func (h *Help) Format(w fmt.State, v rune)

Format satisfies the fmt.Formatter interface, print the help message for the command carried by h.

type PrintFlusher

type PrintFlusher interface {
	Printer
	Flusher
}

PrintFlusher is an interface implemented by printers that may buffer content until they are flushed.

func Format

func Format(format string, output io.Writer) (PrintFlusher, error)

Format returns a Printer which formats printed values.

Typical usage looks like this:

p, err := cli.Format(config.Format, os.Stdout)
if err != nil {
	return err
}
defer p.Flush()
...
p.Print(v1)
p.Print(v2)
p.Print(v3)

The package supports three formats: text, json, and yaml. All formats einterpret the `json` struct tag to configure the names of the fields and the behavior of the formatting operation.

The text format also interprets `fmt` tags as carrying the formatting string passed in calls to functions of the `fmt` package.

If the format name is not supported, the function returns a usage error.

Example (Json)
package main

import (
	"os"

	"github.com/segmentio/cli"
)

func main() {
	cmd := cli.Command(func() error {
		p, err := cli.Format("json", os.Stdout)
		if err != nil {
			return err
		}
		defer p.Flush()

		p.Print(struct {
			Message string
		}{"Hello World!"})
		return nil
	})

	cli.Call(cmd)
}
Output:

{
  "Message": "Hello World!"
}
Example (Text_map)
package main

import (
	"os"

	"github.com/segmentio/cli"
)

func main() {
	cmd := cli.Command(func() error {
		p, err := cli.Format("text", os.Stdout)
		if err != nil {
			return err
		}
		defer p.Flush()

		p.Print(map[string]interface{}{
			"ID":    "1234",
			"Name":  "A",
			"Value": 1,
		})

		p.Print(map[string]interface{}{
			"ID":    "5678",
			"Name":  "B",
			"Value": 2,
		})

		p.Print(map[string]interface{}{
			"ID":    "9012",
			"Name":  "C",
			"Value": 3,
		})
		return nil
	})

	cli.Call(cmd)
}
Output:

ID    NAME  VALUE
1234  A     1
5678  B     2
9012  C     3
Example (Text_string)
package main

import (
	"os"

	"github.com/segmentio/cli"
)

func main() {
	cmd := cli.Command(func() error {
		p, err := cli.Format("text", os.Stdout)
		if err != nil {
			return err
		}
		defer p.Flush()

		p.Print("hello")
		p.Print("world")
		return nil
	})

	cli.Call(cmd)
}
Output:

hello
world
Example (Text_struct)
package main

import (
	"os"

	"github.com/segmentio/cli"
)

func main() {
	cmd := cli.Command(func() error {
		p, err := cli.Format("text", os.Stdout)
		if err != nil {
			return err
		}
		defer p.Flush()

		type output struct {
			ID    string
			Name  string `fmt:"%q"`
			Value int    `fmt:"% 5d"`
		}

		p.Print(output{"1234", "A", 1})
		p.Print(output{"5678", "B", 2})
		p.Print(output{"9012", "C", 3})
		return nil
	})

	cli.Call(cmd)
}
Output:

ID    NAME  VALUE
1234  "A"       1
5678  "B"       2
9012  "C"       3
Example (Yaml)
package main

import (
	"os"

	"github.com/segmentio/cli"
)

func main() {
	cmd := cli.Command(func() error {
		p, err := cli.Format("yaml", os.Stdout)
		if err != nil {
			return err
		}
		defer p.Flush()

		type output struct {
			Value int `json:"value"`
		}

		p.Print(output{1})
		p.Print(output{2})
		p.Print(output{3})
		return nil
	})

	cli.Call(cmd)
}
Output:

value: 1
---
value: 2
---
value: 3

func FormatList added in v0.2.4

func FormatList(format string, output io.Writer) (PrintFlusher, error)

FormatList returns a Printer which formats lists of printed values.

Typical usage looks like this:

p, err := cli.FormatList(config.Format, os.Stdout)
if err != nil {
	return err
}
defer p.Flush()
...
p.Print(v1)
p.Print(v2)
p.Print(v3)

The package supports three formats: text, json, and yaml. All formats einterpret the `json` struct tag to configure the names of the fields and the behavior of the formatting operation.

The text format also interprets `fmt` tags as carrying the formatting string passed in calls to functions of the `fmt` package.

If the format name is not supported, the function returns a usage error.

Example (Json)
package main

import (
	"os"

	"github.com/segmentio/cli"
)

func main() {
	cmd := cli.Command(func() error {
		p, err := cli.FormatList("json", os.Stdout)
		if err != nil {
			return err
		}
		defer p.Flush()

		p.Print(struct {
			Message string
		}{"Hello World!"})
		return nil
	})

	cli.Call(cmd)
}
Output:

[
  {
    "Message": "Hello World!"
  }
]
Example (Yaml)
package main

import (
	"os"

	"github.com/segmentio/cli"
)

func main() {
	cmd := cli.Command(func() error {
		p, err := cli.FormatList("yaml", os.Stdout)
		if err != nil {
			return err
		}
		defer p.Flush()

		type output struct {
			Value int `json:"value"`
		}

		p.Print(output{1})
		p.Print(output{2})
		p.Print(output{3})
		return nil
	})

	cli.Call(cmd)
}
Output:

- value: 1
- value: 2
- value: 3

type Printer

type Printer interface {
	Print(interface{})
}

Printer is an interface implemented for high-level printing formats.

type Usage

type Usage struct {
	Cmd Function
	Err error
}

Usage values are returned by commands to indicate that the combination of arguments and environment variables they were called with was invalid. This type satisfies the error interface.

func (*Usage) Error

func (u *Usage) Error() string

Error satisfies the error interface.

func (*Usage) Format

func (u *Usage) Format(w fmt.State, v rune)

Format satisfies the fmt.Formatter interface, print the usage message for the command carried by u.

func (*Usage) Unwrap

func (u *Usage) Unwrap() error

Unwrap satisfies the errors wrapper interface.

Directories

Path Synopsis
Package human provides types that support parsing and formatting human-friendly representations of values in various units.
Package human provides types that support parsing and formatting human-friendly representations of values in various units.

Jump to

Keyboard shortcuts

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