Skip to content
/ go-cli Public

A full-featured and easy to use command-line package

License

Notifications You must be signed in to change notification settings

subchen/go-cli

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Jan 2, 2021
e60b8d5 · Jan 2, 2021

History

73 Commits
Oct 29, 2019
Jan 2, 2021
May 13, 2017
Jan 2, 2021
Oct 29, 2019
May 25, 2018
May 24, 2018
May 24, 2018
Oct 29, 2019
May 24, 2018
May 24, 2018
May 24, 2018
May 25, 2018
May 25, 2018
May 25, 2018
May 13, 2017
May 13, 2017
May 13, 2017
May 13, 2017
May 13, 2017
May 13, 2017
May 13, 2017
May 13, 2017
May 13, 2017
May 13, 2017
May 24, 2018
May 24, 2018
May 13, 2017
May 13, 2017
May 24, 2018
May 13, 2017
May 13, 2017
May 24, 2018
May 24, 2017
May 13, 2017
May 24, 2017
May 13, 2017
May 13, 2017
May 13, 2017
May 13, 2017
May 13, 2017
May 13, 2017
May 24, 2017
May 24, 2017
May 24, 2018
Jan 2, 2021
Oct 29, 2019
Oct 29, 2019

Repository files navigation

go-cli

GoDoc Build Status Coverage Status Go Report Card Release License

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

go-cli is available using the standard go get command.

To install go-cli, simply run:

go get github.com/subchen/go-cli/v3

Syntax for Command Line

// 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

Getting Started

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

Arguments

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)

Flags

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)

Bool flag

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.

Value bind

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 as Flag.Value, the Flag.IsBool will be automatically true.

Short, Long, Alias Names

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

Placeholder

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

Default Value

&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",

NoOptDefVal

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

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.

Generate Help

The default help flag (--help) is defined in cli.App and cli.Command.

Customize help

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)

Generate Version

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

Customize version

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)

Error Handler

OnCommandNotFound

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)

OnActionPanic

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.

Contributing

  • 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

License

Apache 2.0 license. See LICENSE