go-cli
is a package to build a CLI application. Support command/sub-commands.
Some applications are built using go-cli
including:
Table of Contents
- Installation
- Syntax for Command Line
- Getting Started
- Generate Help
- Generate Version
- Error Handler
- Contributing
- License
go-cli
is available using the standard go get command.
To install go-cli
, simply run:
go get github.com/subchen/go-cli/v3
// Long option
--flag // boolean flags, or flags with no option default values
--flag x // only on flags without a default value
--flag=x
// Short option
-x // boolean flags
-x 123
-x=123
-x123 // value is 123
// value wrapped by quote
-x="123"
-x='123'
// unordered in flags and arguments
arg1 -x 123 arg2 --test arg3 arg4
// stops parsing after the terminator `--`
-x 123 -- arg1 --not-a-flag arg3 arg4
A simple CLI application:
package main
import (
"fmt"
"os"
"github.com/subchen/go-cli/v3"
)
func main() {
app := cli.NewApp()
app.Name = "hello"
app.Version = "1.0.0"
app.Usage = "a hello world application."
app.Action = func(c *cli.Context) {
fmt.Println("Hello World!")
}
app.Run(os.Args)
}
Build and run our new CLI application
$ go build
$ ./hello
Hello World!
go-cli
also generates neat help text
$ ./hello --help
NAME:
hello - a hello world application.
USAGE:
hello [options] [arguments...]
VERSION:
1.0.0
OPTIONS:
--help print this help
--version print version information
You can lookup arguments by calling the Args
function on cli.Context
, e.g.:
app := cli.NewApp()
app.Action = func(c *cli.Context) {
name := c.Args()[0]
fmt.Printf("Hello %v\n", name)
}
app.Run(os.Args)
Setting and querying flags is simple.
app := cli.NewApp()
app.Flags = []*cli.Flag {
{
Name: "name",
Usage: "a name of user",
},
}
app.Action = func(c *cli.Context) {
name := c.GetString("name")
fmt.Printf("Hello %v\n", name)
}
app.Run(os.Args)
A bool flag can has a optional inline bool value.
&cli.Flag{
Name: "verbose",
Usage: "output verbose information",
IsBool: true,
},
The parsed arguments likes:
// valid
--verbose
--verbose=true
--verbose=false
// invalid
--verbose false
bool flag accepts 1,t,true,yes,on
as true, 0,f,false,no,off
as false.
You can bind a variable for a Flag.Value
, which will be set after parsed.
var name string
app := cli.NewApp()
app.Flags = []*cli.Flag {
{
Name: "name",
Usage: "a name of user",
Value: &name,
},
}
app.Action = func(c *cli.Context) {
fmt.Printf("Hello %v\n", name)
}
app.Run(os.Args)
Flag.Value
can accept a cli.Value
interface or a pointer of base type.
-
base type:
*string
*bool
*int
,*int8
,*int16
,*int32
,*int64
*uint
,*uint8
,*uint16
,*uint32
,*uint64
*float32
,*float64
*time.Time
,*time.Duration
,*time.Location
*net.IP
,*net.IPMask
,*net.IPNet
*url.URL
-
slice of base type:
*[]string
*[]int
,*[]uint
,*[]float64
*[]net.IP
,*[]net.IPNet
*[]url.URL
-
cli.Value:
type Value interface { String() string Set(string) error }
Note: If you set
*bool
asFlag.Value
, theFlag.IsBool
will be automaticallytrue
.
You can set multiply name in a flag, a short name, a long name, or multiple alias names.
&cli.Flag{
Name: "o, output, output-dir",
Usage: "A directory for output",
}
Then, results in help output like:
-o, --output, --output-dir value A directory for output
Sometimes it's useful to specify a flag's value within the usage string itself.
For example this:
&cli.Flag{
Name: "o, output",
Usage: "A directory for output",
Placeholder: "DIR",
}
Then, results in help output like:
-o DIR, --output DIR A directory for output
&cli.Flag{
Name: "o, output",
Usage: "A directory for output",
DefValue: "/tmp/",
}
You also can set a default value got from the Environment
&cli.Flag{
Name: "o, output",
Usage: "A directory for output",
EnvVar: "APP_OUTPUT_DIR",
}
The EnvVar
may also be given as a comma-delimited "cascade",
where the first environment variable that resolves is used as the default.
EnvVar: "APP_OUTPUT,APP_OUTPUT_DIR",
If a flag has a NoOptDefVal
and the flag is set on the command line without an option
the flag will be set to the NoOptDefVal
.
For example given:
&cli.Flag{
Name: "flagname",
DefValue: "123",
NoOptDefVal: "456",
Value: &val
}
Would result in something like
Parsed Arguments | Resulting Value |
---|---|
--flagname=000 | val=000 |
--flagname | val=456 |
[nothing] | val=123 |
Hidden flags
It is possible to mark a flag as hidden, meaning it will still function as normal, however will not show up in usage/help text.
&cli.Flag{
Name: "secretFlag",
Hidden: true,
}
Commands can be defined for a more git-like command line app.
package main
import (
"fmt"
"os"
"strings"
"github.com/subchen/go-cli/v3"
)
func main() {
app := cli.NewApp()
app.Name = "git"
app.Commands = []*cli.Command{
{
Name: "add",
Usage: "Add file contents to the index",
Action: func(c *cli.Context) {
fmt.Println("added files: ", strings.Join(c.Args(), ", "))
},
},
{
// alias name
Name: "commit, co",
Usage: "Record changes to the repository",
Flags: []*cli.Flag {
{
Name: "m, message",
Usage: "commit message",
},
},
Hidden: false,
Action: func(c *cli.Context) {
fmt.Println("commit message: ", c.GetString("m"))
},
},
}
app.SeeAlso = `https://github.com/subchen/go-cli
https://github.com/subchen/go-cli/wiki`
app.Run(os.Args)
}
Also, you can use sub-commands in a command.
The default help flag (--help
) is defined in cli.App
and cli.Command
.
All of the help text generation may be customized.
A help template is exposed as variable cli.HelpTemplate
, that can be override.
// Append copyright
cli.HelpTemplate = cli.HelpTemplate + "@2017 Your company, Inc.\n\n"
Or, you can rewrite a help using customized func.
app := cli.NewApp()
app.ShowHelp = func(c *cli.HelpContext) {
fmt.Println("this is my help generated.")
}
app.Run(os.Args)
The default version flag (--version
) is defined in cli.App
.
app := cli.NewApp()
app.Name = "hello"
app.Version = "1.0.0"
app.BuildInfo = &cli.BuildInfo{
GitBranch: "master",
GitCommit: "320279c1a9a6537cdfd1e526063f6a748bb1fec3",
GitRevCount: "1234",
Timestamp: "Sat May 13 19:53:08 UTC 2017",
}
app.Run(os.Args)
Then, ./hello --version
results like:
Name: hello
Version: 1.0.0
Patches: 1234
Git branch: master
Git commit: 320279c1a9a6537cdfd1e526063f6a748bb1fec3
Built: Sat May 13 19:53:08 UTC 2017
Go version: go1.8.1
OS/Arch: darwin/amd64
You can rewrite version output using customized func.
app := cli.NewApp()
app.ShowVersion = func(app *App) {
fmt.Println("Version: ", app.Version)
}
app.Run(os.Args)
go-cli
provides OnCommandNotFound
func to handle an error if command/sub-command is not found.
app := cli.NewApp()
app.Flags = ...
app.Commands = ...
app.OnCommandNotFound = func(c *cli.Context, command string) {
c.ShowError(fmt.Errorf("Command not found: %s", command))
}
app.Run(os.Args)
go-cli
provides OnActionPanic
func to handle an error if panic in action.
app := cli.NewApp()
app.Flags = ...
app.Commands = ...
app.OnActionPanic = func(c *cli.Context, err error) {
os.Stderr.WriteString(fmt.Sprintf("fatal: %v\n", err))
}
app.Run(os.Args)
Notes:
go-cli
will only output error message without golang error stacks if app.OnActionPanic is nil.
- Fork it
- Create your feature branch (git checkout -b my-new-feature)
- Commit your changes (git commit -am 'Add some feature')
- Push to the branch (git push origin my-new-feature)
- Create new Pull Request
Apache 2.0 license. See LICENSE