GitHub - mkideal/cli: CLI - A package for building command line app with go (original) (raw)
Command line interface
Screenshot
Key features
- Lightweight and easy to use.
- Defines flag by tag, e.g. flag name(short or/and long), description, default value, password, prompt and so on.
- Type safety.
- Output looks very nice.
- Supports custom Validator.
- Supports slice and map as a flag.
- Supports any type as a flag field which implements cli.Decoder interface.
- Supports any type as a flag field which uses FlagParser.
- Suggestions for command.(e.g.
hl=>help, "veron" => "version"). - Supports default value for flag, even expression about env variable(e.g.
dft:"$HOME/dev"). - Supports editor like
git commitcommand.(See example 21 and 22)
API documentation
See godoc
Examples
- Example 1: Hello world
- Example 2: How to use flag
- Example 3: How to use required flag
- Example 4: How to use default flag
- Example 5: How to use slice
- Example 6: How to use map
- Example 7: Usage of force flag
- Example 8: Usage of child command
- Example 9: Auto help
- Example 10: Usage of Validator
- Example 11: Prompt and Password
- Example 12: How to use Decoder
- Example 13: Builtin Decoder: PidFile
- Example 14: Builtin Decoder: Time and Duration
- Example 15: Builtin Decoder: File
- Example 16: Parser
- Example 17: Builtin Parser: JSONFileParser
- Example 18: How to use custom parser
- Example 19: How to use Hooks
- Example 20: How to use Daemon
- Example 21: How to use Editor
- Example 22: Custom Editor
- Example 23: How to hide/disable/deprecate flag
Example 1: Hello
// main.go // This is a HelloWorld-like example
package main
import ( "os"
"github.com/mkideal/cli")
type argT struct {
Name string cli:"name" usage:"tell me your name"
}
func main() { os.Exit(cli.Run(new(argT), func(ctx *cli.Context) error { argv := ctx.Argv().(*argT) ctx.String("Hello, %s!\n", argv.Name) return nil })) }
$ go build -o hello $ ./hello --name Clipher Hello, Clipher!
Example 2: Flag
// main.go // This example show basic usage of flag
package main
import ( "os"
"github.com/mkideal/cli")
type argT struct {
cli.Helper
Port int cli:"p,port" usage:"short and long format flags both are supported"
X bool cli:"x" usage:"boolean type"
Y bool cli:"y" usage:"boolean type, too"
}
func main() { os.Exit(cli.Run(new(argT), func(ctx *cli.Context) error { argv := ctx.Argv().(*argT) ctx.String("port=%d, x=%v, y=%v\n", argv.Port, argv.X, argv.Y) return nil })) }
$ go build -o app $ ./app -h Options:
-h, --help display help information -p, --port short and long format flags both are supported -x boolean type -y boolean type, too $ ./app -p=8080 -x port=8080, x=true, y=false $ ./app -p 8080 -x=true port=8080, x=true, y=false $ ./app -p8080 -y true port=8080, x=false, y=true $ ./app --port=8080 -xy port=8080, x=true, y=true $ ./app --port 8080 -yx port=8080, x=true, y=true
Example 3: Required flag
// main.go // This example show how to use required flag
package main
import ( "os"
"github.com/mkideal/cli")
type argT struct {
cli.Helper
Id uint8 cli:"*id" usage:"this is a required flag, note the *"
}
func main() { os.Exit(cli.Run(new(argT), func(ctx *cli.Context) error { argv := ctx.Argv().(*argT) ctx.String("%d\n", argv.Id) return nil })) }
$ go build -o app $ ./app ERR! required argument --id missing $ ./app --id=2 2
Example 4: Default flag
// main.go // This example show how to use default flag
package main
import ( "os"
"github.com/mkideal/cli")
type argT struct {
cli.Helper
Basic int cli:"basic" usage:"basic usage of default" dft:"2"
Env string cli:"env" usage:"env variable as default" dft:"$HOME"
Expr int cli:"expr" usage:"expression as default" dft:"$BASE_PORT+1000"
DevDir string cli:"devdir" usage:"directory of developer" dft:"$HOME/dev"
}
func main() { os.Exit(cli.Run(new(argT), func(ctx *cli.Context) error { argv := ctx.Argv().(*argT) ctx.String("%d, %s, %d, %s\n", argv.Basic, argv.Env, argv.Expr, argv.DevDir) return nil })) }
$ go build -o app $ ./app -h Options:
-h, --help display help information --basic[=2] basic usage of default --env[=$HOME] env variable as default --expr[=$BASE_PORT+1000] expression as default --devdir[=$HOME/dev] directory of developer $ ./app 2, /Users/wang, 1000, /Users/wang/dev $ BASE_PORT=8000 ./app --basic=3 3, /Users/wang, 9000, /Users/wang/dev
Example 5: Slice
// main.go // This example show how to use slice as a flag
package main
import ( "os"
"github.com/mkideal/cli")
type argT struct {
// []bool, []int, []float32, ... supported too.
Friends []string cli:"F" usage:"my friends"
}
func main() { os.Exit(cli.Run(new(argT), func(ctx *cli.Context) error { ctx.JSONln(ctx.Argv()) return nil })) }
$ go build -o app $ ./app {"Friends":null} $ ./app -FAlice -FBob -F Charlie {"Friends":["Alice","Bob","Charlie"]}
Example 6: Map
// main.go // This example show how to use map as a flag
package main
import ( "os"
"github.com/mkideal/cli")
type argT struct {
Macros map[string]int cli:"D" usage:"define macros"
}
func main() { os.Exit(cli.Run(new(argT), func(ctx *cli.Context) error { ctx.JSONln(ctx.Argv()) return nil })) }
$ go build -o app
$ ./app
{"Macros":null}
$ ./app -Dx=not-a-number
ERR! not-a-number couldn't converted to an int value
$ ./app -Dx=1 -D y=2
{"Macros":{"x":1,"y":2}}
Example 7: Force flag
// main.go // This example show usage of force flag // Force flag has prefix !, and must be a boolean. // Will prevent validating flags if some force flag assigned true
package main
import ( "os"
"github.com/mkideal/cli")
type argT struct {
Version bool cli:"!v" usage:"force flag, note the !"
Required int cli:"*r" usage:"required flag"
}
func main() { os.Exit(cli.Run(new(argT), func(ctx *cli.Context) error { argv := ctx.Argv().(*argT) if argv.Version { ctx.String("v0.0.1\n") } return nil })) }
$ go build -o app $ ./app ERR! required argument -r missing
-v is a force flag, and assigned true, so ERR disappear.
$ ./app -v v0.0.1
Example 8: Child command
// main.go // This example demonstrates usage of child command
package main
import ( "fmt" "os"
"github.com/mkideal/cli")
func main() { if err := cli.Root(root, cli.Tree(help), cli.Tree(child), ).Run(os.Args[1:]); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } }
var help = cli.HelpCommand("display help information")
// root command
type rootT struct {
cli.Helper
Name string cli:"name" usage:"your name"
}
var root = &cli.Command{ Desc: "this is root command", // Argv is a factory function of argument object // ctx.Argv() is if Command.Argv == nil or Command.Argv() is nil Argv: func() interface{} { return new(rootT) }, Fn: func(ctx *cli.Context) error { argv := ctx.Argv().(*rootT) ctx.String("Hello, root command, I am %s\n", argv.Name) return nil }, }
// child command
type childT struct {
cli.Helper
Name string cli:"name" usage:"your name"
}
var child = &cli.Command{ Name: "child", Desc: "this is a child command", Argv: func() interface{} { return new(childT) }, Fn: func(ctx *cli.Context) error { argv := ctx.Argv().(*childT) ctx.String("Hello, child command, I am %s\n", argv.Name) return nil }, }
$ go build -o app
help for root
equivalent to "./app -h"
$ ./app help this is root command
Options:
-h, --help display help information --name your name
Commands: help display help information child this is a child command
help for specific command
equivalent to "./app child -h"
$ ./app help child this is a child command
Options:
-h, --help display help information --name your name
execute root command
$ ./app --name 123 Hello, root command, I am 123
execute child command
$ ./app child --name=123 Hello, child command, I am 123
something wrong, but got a suggestion.
$ ./app chd ERR! command chd not found Did you mean child?
Example 9: Auto help
// main.go // This example demonstrates cli.AutoHelper
package main
import ( "os"
"github.com/mkideal/cli")
type argT struct {
Help bool cli:"h,help" usage:"show help"
}
// AutoHelp implements cli.AutoHelper interface // NOTE: cli.Helper is a predefined type which implements cli.AutoHelper func (argv *argT) AutoHelp() bool { return argv.Help }
func main() { os.Exit(cli.Run(new(argT), func(ctx *cli.Context) error { return nil })) }
$ go build -o app $ ./app -h Options:
-h, --help show help
Try comment AutoHelp method and rerun it.
Example 10: Usage of Validator
// main.go // This example demonstrates how to utilize Validator
package main
import ( "fmt" "os"
"github.com/mkideal/cli")
type argT struct {
cli.Helper
Age int cli:"age" usage:"your age"
Gender string cli:"g,gender" usage:"your gender" dft:"male"
}
// Validate implements cli.Validator interface func (argv *argT) Validate(ctx *cli.Context) error { if argv.Age < 0 || argv.Age > 300 { return fmt.Errorf("age %d out of range", argv.Age) } if argv.Gender != "male" && argv.Gender != "female" { return fmt.Errorf("invalid gender %s", ctx.Color().Yellow(argv.Gender)) } return nil }
func main() { os.Exit(cli.Run(new(argT), func(ctx *cli.Context) error { ctx.JSONln(ctx.Argv()) return nil })) }
$ go build -o app $ ./app --age=-1 ERR! age -1 out of range $ ./app --age=1000 ERR! age 1000 out of range $ ./app -g balabala ERR! invalid gender balabala $ ./app --age 88 --gender female {"Help":false,"Age":88,"Gender":"female"}
Example 11: Prompt and Password
// main.go // This example introduce prompt and pw tag
package main
import ( "os"
"github.com/mkideal/cli")
type argT struct {
cli.Helper
Username string cli:"u,username" usage:"github account" prompt:"type github account"
Password string pw:"p,password" usage:"password of github account" prompt:"type the password"
}
func main() { os.Exit(cli.Run(new(argT), func(ctx *cli.Context) error { argv := ctx.Argv().(*argT) ctx.String("username=%s, password=%s\n", argv.Username, argv.Password) return nil })) }
$ go build -o app
$ ./app
type github account: hahaha # visible
type the password: # invisible because of pw tag
username=hahaha, password=123456
Example 12: Decoder
// main.go // This example show how to use decoder
package main
import ( "os" "strings"
"github.com/mkideal/cli")
type exampleDecoder struct { list []string }
// Decode implements cli.Decoder interface func (d *exampleDecoder) Decode(s string) error { d.list = strings.Split(s, ",") return nil }
type argT struct {
Example exampleDecoder cli:"d" usage:"example decoder"
}
func main() { os.Exit(cli.Run(new(argT), func(ctx *cli.Context) error { argv := ctx.Argv().(*argT) ctx.JSONln(argv.Example.list) return nil })) }
$ go build -o app $ ./app -d a,b,c ["a","b","c"]
Example 13: Pid file
// main.go // This example show how to use builtin Decoder: PidFile
package main
import ( "os"
"github.com/mkideal/cli"
clix "github.com/mkideal/cli/ext")
type argT struct {
cli.Helper
PidFile clix.PidFile cli:"pid" usage:"pid file" dft:"013-pidfile.pid"
}
func main() { os.Exit(cli.Run(new(argT), func(ctx *cli.Context) error { argv := ctx.Argv().(*argT)
if err := argv.PidFile.New(); err != nil {
return err
}
defer argv.PidFile.Remove()
return nil
}))}
Example 14: Time and Duration
// main.go // This example show how to use builtin Decoder: Time and Duration
package main
import ( "os"
"github.com/mkideal/cli"
clix "github.com/mkideal/cli/ext")
type argT struct {
Time clix.Time cli:"t" usage:"time"
Duration clix.Duration cli:"d" usage:"duration"
}
func main() { os.Exit(cli.Run(new(argT), func(ctx *cli.Context) error { argv := ctx.Argv().(*argT) ctx.String("time=%v, duration=%v\n", argv.Time, argv.Duration) return nil })) }
$ go build -o app $ ./app -t '2016-1-2 3:5' -d=10ms time=2016-01-02 03:05:00 +0800 CST, duration=10ms
Example 15: File
// main.go // This example show how to use builtin Decoder: File
package main
import ( "os"
"github.com/mkideal/cli"
clix "github.com/mkideal/cli/ext")
type argT struct {
Content clix.File cli:"f,file" usage:"read content from file or stdin"
}
func main() { os.Exit(cli.Run(new(argT), func(ctx *cli.Context) error { argv := ctx.Argv().(*argT) ctx.String(argv.Content.String()) return nil })) }
$ go build -o app
read from stdin
$ echo hello | ./app -f hello
read from file
$ echo hello > test.txt && ./app -f test.txt hello $ rm test.txt
Example 16: Parser
// main.go
// This example introduce Parser
// Parser is another way to use custom type of data.
// Unlike Decoder, Parser used to parse string according to specific rule,
// like json,yaml and so on.
//
// Builtin parsers:
// * json
// * jsonfile
package main
import ( "os"
"github.com/mkideal/cli")
type config struct { A string B int C bool }
type argT struct {
JSON config cli:"c,config" usage:"parse json string" parser:"json"
}
func main() { os.Exit(cli.Run(new(argT), func(ctx *cli.Context) error { argv := ctx.Argv().(*argT) ctx.JSONIndentln(argv.JSON, "", " ") return nil })) }
$ go build -o app $ ./app { "A": "", "B": 0, "C": false } $ ./app -c '{"A": "hello", "b": 22, "C": true}' { "A": "hello", "B": 22, "C": true }
Example 17: JSON file
// main.go // This example show how to use builtin parser: jsonfile // It's similar to json, but read string from file.
package main
import ( "os"
"github.com/mkideal/cli")
type config struct { A string B int C bool }
type argT struct {
JSON config cli:"c,config" usage:"parse json from file" parser:"jsonfile"
}
func main() { os.Exit(cli.Run(new(argT), func(ctx *cli.Context) error { argv := ctx.Argv().(*argT) ctx.JSONIndentln(argv.JSON, "", " ") return nil })) }
$ go build -o app $ echo '{"A": "hello", "b": 22, "C": true}' > test.json $ ./app -c test.json { "A": "hello", "B": 22, "C": true } $ rm test.json
Example 18: Custom parser
// main.go // This example demonstrates how to use custom parser
package main
import ( "os" "reflect"
"github.com/mkideal/cli")
type myParser struct { ptr interface{} }
func newMyParser(ptr interface{}) cli.FlagParser { return &myParser{ptr} }
// Parse implements FlagParser.Parse interface func (parser *myParser) Parse(s string) error { typ := reflect.TypeOf(parser.ptr) val := reflect.ValueOf(parser.ptr) if typ.Kind() == reflect.Ptr { kind := reflect.Indirect(val).Type().Kind() if kind == reflect.Struct { typElem, valElem := typ.Elem(), val.Elem() numField := valElem.NumField() for i := 0; i < numField; i++ { _, valField := typElem.Field(i), valElem.Field(i) if valField.Kind() == reflect.Int && valField.CanSet() { valField.SetInt(2) } if valField.Kind() == reflect.String && valField.CanSet() { valField.SetString("B") } } } } return nil }
type config struct { A int B string }
type argT struct {
Cfg config cli:"cfg" parser:"myparser"
}
func main() { // register parser factory function cli.RegisterFlagParser("myparser", newMyParser)
os.Exit(cli.Run(new(argT), func(ctx *cli.Context) error {
argv := ctx.Argv().(*argT)
ctx.String("%v\n", argv.Cfg)
return nil
}))}
$ go build -o app $ ./app {0 } $ ./app --cfg xxx {2 B}
Example 19: Hooks
// main.go // This example demonstrates how to use hooks
package main
import ( "fmt" "os"
"github.com/mkideal/cli")
func main() { if err := cli.Root(root, cli.Tree(child1), cli.Tree(child2), ).Run(os.Args[1:]); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } }
type argT struct {
Error bool cli:"e" usage:"return error"
}
var root = &cli.Command{ Name: "app", Argv: func() interface{} { return new(argT) }, OnRootBefore: func(ctx *cli.Context) error { ctx.String("OnRootBefore invoked\n") return nil }, OnRootAfter: func(ctx *cli.Context) error { ctx.String("OnRootAfter invoked\n") return nil }, Fn: func(ctx *cli.Context) error { ctx.String("exec root command\n") argv := ctx.Argv().(*argT) if argv.Error { return fmt.Errorf("root command returns error") } return nil }, }
var child1 = &cli.Command{ Name: "child1", Argv: func() interface{} { return new(argT) }, OnBefore: func(ctx *cli.Context) error { ctx.String("child1's OnBefore invoked\n") return nil }, OnAfter: func(ctx *cli.Context) error { ctx.String("child1's OnAfter invoked\n") return nil }, Fn: func(ctx *cli.Context) error { ctx.String("exec child1 command\n") argv := ctx.Argv().(*argT) if argv.Error { return fmt.Errorf("child1 command returns error") } return nil }, }
var child2 = &cli.Command{ Name: "child2", NoHook: true, Fn: func(ctx *cli.Context) error { ctx.String("exec child2 command\n") return nil }, }
$ go build -o app
OnRootBefore => Fn => OnRootAfter
$ ./app OnRootBefore invoked exec root command OnRootAfter invoked
OnBefore => OnRootBefore => Fn => OnRootAfter => OnAfter
$ ./app child1 child1 OnBefore invoked OnRootBefore invoked exec child1 command OnRootAfter invoked child1 OnAfter invoked
No hooks
$ ./app child2 exec child2 command
OnRootBefore => Fn --> Error
$ ./app -e OnRootBefore invoked exec root command root command returns error
OnBefore => OnRootBefore => Fn --> Error
$ ./app child1 -e child1 OnBefore invoked OnRootBefore invoked exec child1 command child1 command returns error
Example 20: Daemon
// main.go
// This example demonstrates how to use Daemon
package main
import ( "fmt" "os" "time"
"github.com/mkideal/cli")
type argT struct {
cli.Helper
Wait uint cli:"wait" usage:"seconds for waiting" dft:"10"
Error bool cli:"e" usage:"create an error"
}
const successResponsePrefix = "start ok"
func main() { if err := cli.Root(root, cli.Tree(daemon), ).Run(os.Args[1:]); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } }
var root = &cli.Command{ Argv: func() interface{} { return new(argT) }, Fn: func(ctx *cli.Context) error { argv := ctx.Argv().(*argT) if argv.Error { err := fmt.Errorf("occurs error") cli.DaemonResponse(err.Error()) return err } cli.DaemonResponse(successResponsePrefix) <-time.After(time.Duration(argv.Wait) * time.Second) return nil }, }
var daemon = &cli.Command{ Name: "daemon", Argv: func() interface{} { return new(argT) }, Fn: func(ctx *cli.Context) error { return cli.Daemon(ctx, successResponsePrefix) }, }
$ go build -o daemon-app $ ./daemone-app daemon start ok
Within 10 seconds, you will see process "./daemon-app"
$ ps | grep daemon-app 11913 ttys002 0:00.01 ./daemon-app 11915 ttys002 0:00.00 grep daemon-app
After 10 seconds
$ ps | grep daemon-app 11936 ttys002 0:00.00 grep daemon-app
try again with an error
$ ./daemon-app daemon -e occurs error $ ps | grep daemon-app 11936 ttys002 0:00.00 grep daemon-app
Example 21: Editor
// main.go
// This example demonstrates how to use editor. This similar to git commit
package main
import ( "os"
"github.com/mkideal/cli")
type argT struct {
cli.Helper
Msg string edit:"m" usage:"message"
}
func main() { os.Exit(cli.Run(new(argT), func(ctx *cli.Context) error { argv := ctx.Argv().(*argT) ctx.String("msg: %s", argv.Msg) return nil })) }
$ go build -o app
$ ./app -m "hello, editor"
msg: hello, editor
$ ./app # Then, launch a editor(default is vim) and type hello, editor, quit the editor
msg: hello, editor
Example 22: Custom Editor
// main.go // This example demonstrates specific editor.
package main
import ( "os"
"github.com/mkideal/cli")
type argT struct {
cli.Helper
Msg string edit:"m" usage:"message"
}
func main() { cli.GetEditor = func() (string, error) { if editor := os.Getenv("EDITOR"); editor != "" { return editor, nil } return cli.DefaultEditor, nil } os.Exit(cli.Run(new(argT), func(ctx *cli.Context) error { argv := ctx.Argv().(*argT) ctx.String("msg: %s", argv.Msg) return nil })) }
$ go build -o app
$ ./app -m "hello, editor"
msg: hello, editor
$ EDITOR=nano ./app # Then, launch nano and type hello, editor, quit the editor
msg: hello, editor
Example 23: Hide flag
// main.go // This example hides Gender and InternalUsage flags. package main
import ( "os"
"github.com/mkideal/cli")
type helloT struct {
cli.Helper
Name string cli:"name" usage:"tell me your name" dft:"world"
Gender string cli:"-" // deprecated
InternalUsage string cli:"-" // hide
Age uint8 cli:"a,age" usage:"tell me your age" dft:"100"
}
func main() { os.Exit(cli.Run(new(helloT), func(ctx *cli.Context) error { argv := ctx.Argv().(*helloT) ctx.String("Hello, %s! Your age is %d?\n", argv.Name, argv.Age) return nil })) }
$ go build -o app $ ./app -h Options:
-h, --help display help information --name[=world] tell me your name -a, --age[=100] tell me your age
