├── .gitignore ├── .sail └── Dockerfile ├── LICENSE.md ├── README.md ├── command.go ├── command_test.go ├── doc.go ├── examples ├── simple │ └── main.go └── subcommands │ └── main.go ├── go.mod ├── go.sum ├── help.go └── run.go /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | -------------------------------------------------------------------------------- /.sail/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM codercom/ubuntu-dev-go 2 | RUN sudo apt-get install -y htop 3 | LABEL project_root "~/go/src/go.coder.com" 4 | 5 | # Modules break much of Go's tooling. 6 | ENV GO111MODULE=off 7 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2019 Coder 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cli 2 | 3 | A minimal, command-oriented CLI package. 4 | 5 | [![GoDoc](https://godoc.org/github.com/golang/gddo?status.svg)](https://godoc.org/go.coder.com/cli) 6 | 7 | ## Features 8 | 9 | - Very small, simple API. 10 | - Support for POSIX flags. 11 | - Only external dependency is [spf13/pflag](https://github.com/spf13/pflag). 12 | - Subcommands and subcommand aliases. 13 | - Auto-generated help. 14 | 15 | ## Install 16 | 17 | ```bash 18 | go get -u go.coder.com/cli 19 | ``` 20 | 21 | ## Examples 22 | 23 | See `examples/` for more. 24 | 25 | ### Simple CLI 26 | ```go 27 | package main 28 | 29 | import ( 30 | "fmt" 31 | 32 | "github.com/spf13/pflag" 33 | "go.coder.com/cli" 34 | ) 35 | 36 | type cmd struct { 37 | verbose bool 38 | } 39 | 40 | func (c *cmd) Run(fl *pflag.FlagSet) { 41 | if c.verbose { 42 | fmt.Println("verbose enabled") 43 | } 44 | fmt.Println("we run") 45 | } 46 | 47 | func (c *cmd) Spec() cli.CommandSpec { 48 | return cli.CommandSpec{ 49 | Name: "simple-example", 50 | Usage: "[flags]", 51 | Desc: `This is a simple example of the cli package.`, 52 | } 53 | } 54 | 55 | func (c *cmd) RegisterFlags(fl *pflag.FlagSet) { 56 | fl.BoolVar(&c.verbose, "v", false, "sets verbose mode") 57 | } 58 | 59 | func main() { 60 | cli.RunRoot(&cmd{}) 61 | } 62 | 63 | ``` 64 | renders a help like 65 | 66 | ``` 67 | Usage: simple-example [flags] 68 | 69 | This is a simple example of the cli package. 70 | 71 | simple-example flags: 72 | -v sets verbose mode (false) 73 | ``` 74 | 75 | ### Subcommands 76 | 77 | ```go 78 | package main 79 | 80 | import ( 81 | "fmt" 82 | 83 | "github.com/spf13/pflag" 84 | "go.coder.com/cli" 85 | ) 86 | 87 | type subcmd struct { 88 | } 89 | 90 | func (c *subcmd) Run(fl *pflag.FlagSet) { 91 | fmt.Println("subcommand invoked") 92 | } 93 | 94 | func (c *subcmd) Spec() cli.CommandSpec { 95 | return cli.CommandSpec{ 96 | Name: "sub", 97 | Usage: "", 98 | Aliases: []string{"s"}, 99 | Desc: `This is a simple subcommand.`, 100 | } 101 | } 102 | 103 | type cmd struct { 104 | } 105 | 106 | func (c *cmd) Run(fl *pflag.FlagSet) { 107 | // This root command has no default action, so print the help. 108 | fl.Usage() 109 | } 110 | 111 | func (c *cmd) Spec() cli.CommandSpec { 112 | return cli.CommandSpec{ 113 | Name: "subcommand", 114 | Usage: "[flags]", 115 | Desc: `This is a simple example of subcommands.`, 116 | } 117 | } 118 | 119 | func (c *cmd) Subcommands() []cli.Command { 120 | return []cli.Command{ 121 | &subcmd{}, 122 | } 123 | } 124 | 125 | func main() { 126 | cli.RunRoot(&cmd{}) 127 | } 128 | ``` 129 | 130 | renders a help like 131 | 132 | ``` 133 | Usage: subcommand [flags] 134 | 135 | This is a simple example of subcommands. 136 | 137 | Commands: 138 | s, sub - This is a simple subcommand. 139 | ``` 140 | -------------------------------------------------------------------------------- /command.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/spf13/pflag" 7 | ) 8 | 9 | // CommandSpec describes a Command's usage. 10 | // 11 | // It should not list flags. 12 | type CommandSpec struct { 13 | // Name is the name of the command. 14 | // It should be the leaf name of the entire command. E.g `run` for the full 15 | // command `sail run`. 16 | Name string 17 | // Usage is the command's usage string. 18 | // E.g "[flags] " 19 | Usage string 20 | // Desc is the description of the command. 21 | // The first line is used as an abbreviated description. 22 | Desc string 23 | // RawArgs indicates that flags should not be parsed, and they should be deferred 24 | // to the command. 25 | RawArgs bool 26 | // Hidden indicates that this command should not show up in it's parent's 27 | // subcommand help. 28 | Hidden bool 29 | // Aliases contains a list of alternative names that can be used for a particular command. 30 | Aliases []string 31 | } 32 | 33 | // ShortDesc returns the first line of Desc. 34 | func (c CommandSpec) ShortDesc() string { 35 | return strings.Split(c.Desc, "\n")[0] 36 | } 37 | 38 | // HasAliases evaluates whether particular command has any alternative names. 39 | func (c CommandSpec) HasAliases() bool { 40 | return len(c.Aliases) > 0 41 | } 42 | 43 | // Command describes a command or subcommand. 44 | type Command interface { 45 | // Spec returns metadata about the command. 46 | Spec() CommandSpec 47 | // Run invokes the command's main routine with parsed flags. 48 | Run(fl *pflag.FlagSet) 49 | } 50 | 51 | // ParentCommand is an optional interface for commands that have subcommands. 52 | // 53 | // A ParentCommand may pass itself into children as it creates them in order to 54 | // pass high-level configuration and state. 55 | type ParentCommand interface { 56 | Subcommands() []Command 57 | } 58 | 59 | // FlaggedCommand is an optional interface for commands that have flags. 60 | type FlaggedCommand interface { 61 | // RegisterFlags lets the command register flags which be sent to Handle. 62 | RegisterFlags(fl *pflag.FlagSet) 63 | } 64 | -------------------------------------------------------------------------------- /command_test.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "bytes" 5 | "os" 6 | "testing" 7 | 8 | "cdr.dev/slog/sloggers/slogtest/assert" 9 | "github.com/spf13/pflag" 10 | ) 11 | 12 | var subCmd = new(mockSubCmd) 13 | 14 | type ( 15 | // Mock for root/parent command. 16 | mockParentCmd struct{} 17 | // Mock subcommand with aliases and a nested sucommand of its own. 18 | mockSubCmd struct { 19 | buf *bytes.Buffer 20 | } 21 | // Mock subcommand with aliases and no nested subcommands. 22 | mockSubCmdNoNested struct{} 23 | ) 24 | 25 | func (c *mockParentCmd) Run(fl *pflag.FlagSet) {} 26 | 27 | func (c *mockParentCmd) Subcommands() []Command { 28 | return []Command{subCmd} 29 | } 30 | 31 | func (c *mockParentCmd) Spec() CommandSpec { 32 | return CommandSpec{ 33 | Name: "mockParentCmd", 34 | Usage: "Mock parent command usage.", 35 | Desc: "Mock parent command description.", 36 | } 37 | } 38 | 39 | func (c *mockSubCmd) Run(fl *pflag.FlagSet) { 40 | c.buf = new(bytes.Buffer) 41 | _, err := c.WriteString("success") 42 | if err != nil { 43 | fl.Usage() 44 | } 45 | } 46 | 47 | func (c *mockSubCmd) Subcommands() []Command { 48 | return []Command{new(mockSubCmd)} 49 | } 50 | 51 | func (c *mockSubCmd) WriteString(s string) (int, error) { 52 | return c.buf.WriteString(s) 53 | } 54 | 55 | func (c *mockSubCmd) Spec() CommandSpec { 56 | return CommandSpec{ 57 | Name: "mockSubCmd", 58 | Usage: "Test a subcommand.", 59 | Aliases: []string{"s", "sc", "sub"}, 60 | Desc: "A simple mock subcommand with aliases and its own subcommand.", 61 | } 62 | } 63 | 64 | func (c *mockSubCmdNoNested) Run(fl *pflag.FlagSet) {} 65 | 66 | func (c *mockSubCmdNoNested) Spec() CommandSpec { 67 | return CommandSpec{ 68 | Name: "mockSubCmdNoNested", 69 | Usage: "Used for help output tests.", 70 | Aliases: []string{"s", "sc", "sub"}, 71 | Desc: "A simple mock subcommand with aliases and no nested subcommands.", 72 | } 73 | } 74 | 75 | func TestSubCmdAliases(t *testing.T) { 76 | for _, alias := range []string{"s", "sc", "sub"} { 77 | t.Run(alias, func(t *testing.T) { 78 | // Setup command. 79 | cmd := new(mockParentCmd) 80 | os.Args = []string{cmd.Spec().Name, alias} 81 | // Run command. 82 | RunRoot(cmd) 83 | // If "success" isn't written into the buffer 84 | // then we failed to find the subcommand by alias. 85 | got := subCmd.buf.String() 86 | assert.Equal(t, t.Name(), "success", got) 87 | }) 88 | } 89 | } 90 | 91 | func TestCmdHelpOutput(t *testing.T) { 92 | t.Run(t.Name(), func(t *testing.T) { 93 | expected := `Usage: mockParentCmd Mock parent command usage. 94 | 95 | Description: Mock parent command description. 96 | 97 | Commands: 98 | s, sc, sub, mockSubCmd - A simple mock subcommand with aliases and its own subcommand. 99 | ` 100 | buf := new(bytes.Buffer) 101 | cmd := new(mockParentCmd) 102 | name := cmd.Spec().Name 103 | fl := pflag.NewFlagSet(name, pflag.ExitOnError) 104 | // If the help output doesn't contain the subcommand and 105 | // isn't formatted the way we expect the test will fail. 106 | renderHelp(buf, name, cmd, fl) 107 | got := buf.String() 108 | assert.Equal(t, t.Name(), expected, got) 109 | }) 110 | } 111 | 112 | func TestSubCmdHelpOutput(t *testing.T) { 113 | withNested := `Usage: mockSubCmd Test a subcommand. 114 | 115 | Aliases: s, sc, sub 116 | 117 | Description: A simple mock subcommand with aliases and its own subcommand. 118 | 119 | Commands: 120 | s, sc, sub, mockSubCmd - A simple mock subcommand with aliases and its own subcommand. 121 | ` 122 | 123 | noNested := `Usage: mockSubCmdNoNested Used for help output tests. 124 | 125 | Aliases: s, sc, sub 126 | 127 | Description: A simple mock subcommand with aliases and no nested subcommands. 128 | ` 129 | 130 | for _, test := range []struct { 131 | name, expected string 132 | cmd Command 133 | }{ 134 | { 135 | name: "subcmd w/nested subcmd.", 136 | expected: withNested, 137 | cmd: new(mockSubCmd), 138 | }, 139 | { 140 | name: "subcmd w/no nested subcmds.", 141 | expected: noNested, 142 | cmd: new(mockSubCmdNoNested), 143 | }, 144 | } { 145 | t.Run(test.name, func(t *testing.T) { 146 | buf := new(bytes.Buffer) 147 | name := test.cmd.Spec().Name 148 | fl := pflag.NewFlagSet(name, pflag.ExitOnError) 149 | // If the help output is not written to the buffer 150 | // in the format we expect then the test will fail. 151 | renderHelp(buf, name, test.cmd, fl) 152 | got := buf.String() 153 | assert.Equal(t, t.Name(), test.expected, got) 154 | }) 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Package cli provides a thin CLI abstraction around the standard flag package. 2 | // It is minimal, command-struct-oriented, and trades off "power" for flexibility 3 | // and clarity at the caller level. 4 | // 5 | // It pretends that Go's single dash (-flag) support doesn't exist, and renders 6 | // helps with --. 7 | // 8 | // Optional interface adherence can be asserted with a statement like 9 | // var _ interface { 10 | // cli.Command 11 | // cli.FlaggedCommand 12 | // cli.ParentCommand 13 | // } = new(rootCmd) 14 | package cli 15 | -------------------------------------------------------------------------------- /examples/simple/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/spf13/pflag" 7 | "go.coder.com/cli" 8 | ) 9 | 10 | type cmd struct { 11 | verbose bool 12 | } 13 | 14 | func (c *cmd) Run(fl *pflag.FlagSet) { 15 | if c.verbose { 16 | fmt.Println("verbose enabled") 17 | } 18 | fmt.Println("we run") 19 | } 20 | 21 | func (c *cmd) Spec() cli.CommandSpec { 22 | return cli.CommandSpec{ 23 | Name: "simple-example", 24 | Usage: "[flags]", 25 | Desc: `This is a simple example of the cli package.`, 26 | } 27 | } 28 | 29 | func (c *cmd) RegisterFlags(fl *pflag.FlagSet) { 30 | fl.BoolVarP(&c.verbose, "verbose", "v", false, "sets verbose mode") 31 | } 32 | 33 | func main() { 34 | cli.RunRoot(&cmd{}) 35 | } 36 | -------------------------------------------------------------------------------- /examples/subcommands/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/spf13/pflag" 7 | "go.coder.com/cli" 8 | ) 9 | 10 | type subcmd struct { 11 | } 12 | 13 | func (c *subcmd) Run(fl *pflag.FlagSet) { 14 | fmt.Println("subcommand invoked") 15 | } 16 | 17 | func (c *subcmd) Spec() cli.CommandSpec { 18 | return cli.CommandSpec{ 19 | Name: "sub", 20 | Usage: "", 21 | Desc: `This is a simple subcommand.`, 22 | } 23 | } 24 | 25 | type cmd struct { 26 | } 27 | 28 | func (c *cmd) Run(fl *pflag.FlagSet) { 29 | // This root command has no default action, so print the help. 30 | fl.Usage() 31 | } 32 | 33 | func (c *cmd) Spec() cli.CommandSpec { 34 | return cli.CommandSpec{ 35 | Name: "subcommand", 36 | Usage: "[flags]", 37 | Desc: `This is a simple example of subcommands.`, 38 | } 39 | } 40 | 41 | func (c *cmd) Subcommands() []cli.Command { 42 | return []cli.Command{ 43 | &subcmd{}, 44 | } 45 | } 46 | 47 | func main() { 48 | cli.RunRoot(&cmd{}) 49 | } 50 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module go.coder.com/cli 2 | 3 | go 1.12 4 | 5 | require ( 6 | cdr.dev/slog v1.3.0 7 | github.com/spf13/pflag v1.0.3 8 | github.com/stretchr/testify v1.6.1 // indirect 9 | ) 10 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cdr.dev/slog v1.3.0 h1:MYN1BChIaVEGxdS7I5cpdyMC0+WfJfK8BETAfzfLUGQ= 2 | cdr.dev/slog v1.3.0/go.mod h1:C5OL99WyuOK8YHZdYY57dAPN1jK2WJlCdq2VP6xeQns= 3 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 4 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 5 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= 6 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= 7 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= 8 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= 9 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= 10 | cloud.google.com/go v0.49.0 h1:CH+lkubJzcPYB1Ggupcq0+k8Ni2ILdG2lYjDIgavDBQ= 11 | cloud.google.com/go v0.49.0/go.mod h1:hGvAdzcWNbyuxS3nWhD7H2cIJxjRRTRLQVB0bdputVY= 12 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= 13 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= 14 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= 15 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= 16 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 17 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 18 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 19 | github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0= 20 | github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0= 21 | github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= 22 | github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U= 23 | github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI= 24 | github.com/alecthomas/chroma v0.7.0 h1:z+0HgTUmkpRDRz0SRSdMaqOLfJV4F+N1FPDZUZIDUzw= 25 | github.com/alecthomas/chroma v0.7.0/go.mod h1:1U/PfCsTALWWYHDnsIQkxEBM0+6LLe0v8+RSVMOwxeY= 26 | github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721 h1:JHZL0hZKJ1VENNfmXvHbgYlbUOvpzYzvy2aZU5gXVeo= 27 | github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0= 28 | github.com/alecthomas/kong v0.1.17-0.20190424132513-439c674f7ae0/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI= 29 | github.com/alecthomas/kong v0.2.1-0.20190708041108-0548c6b1afae/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI= 30 | github.com/alecthomas/kong-hcl v0.1.8-0.20190615233001-b21fea9723c8/go.mod h1:MRgZdU3vrFd05IQ89AxUZ0aYdF39BYoNFa324SodPCA= 31 | github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897 h1:p9Sln00KOTlrYkxI1zYWl1QLnEqAqEARBEYa8FQnQcY= 32 | github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ= 33 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 34 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 35 | github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E= 36 | github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ= 37 | github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk= 38 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 39 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 40 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 41 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 42 | github.com/dlclark/regexp2 v1.1.6/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= 43 | github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk= 44 | github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= 45 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 46 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 47 | github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= 48 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 49 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 50 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= 51 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 52 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 53 | github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9 h1:uHTyIjqVhYRhLbJ8nIiOJHkEZZ+5YoOsAbD3sk82NiE= 54 | github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 55 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 56 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 57 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= 58 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 59 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 60 | github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= 61 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 62 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 63 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 64 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 65 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 66 | github.com/google/go-cmp v0.3.2-0.20191216170541-340f1ebe299e h1:4WfjkTUTsO6siF8ghDQQk6t7x/FPsv3w6MXkc47do7Q= 67 | github.com/google/go-cmp v0.3.2-0.20191216170541-340f1ebe299e/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 68 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 69 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 70 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 71 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 72 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 73 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= 74 | github.com/gorilla/csrf v1.6.0/go.mod h1:7tSf8kmjNYr7IWDCYhd3U8Ck34iQ/Yw5CJu7bAkHEGI= 75 | github.com/gorilla/handlers v1.4.1/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= 76 | github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= 77 | github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= 78 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 79 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 80 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 81 | github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= 82 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 83 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 84 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 85 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 86 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 87 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 88 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 89 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 90 | github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= 91 | github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 92 | github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 93 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 94 | github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM= 95 | github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= 96 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 97 | github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E= 98 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 99 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 100 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 101 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 102 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 103 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 104 | github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= 105 | github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= 106 | github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= 107 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 108 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 109 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 110 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 111 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 112 | github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= 113 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 114 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 115 | github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= 116 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 117 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= 118 | go.opencensus.io v0.22.2 h1:75k/FF0Q2YM8QYo07VPddOLBslDt1MZOdEslOHvmzAs= 119 | go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 120 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 121 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 122 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 123 | golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 h1:ULYEB3JvPRE/IfO+9uO7vKV/xzVTO7XPAwm8xbf4w2g= 124 | golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 125 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 126 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 127 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 128 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= 129 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= 130 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 131 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 132 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 133 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 134 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 135 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 136 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 137 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 138 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 139 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 140 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 141 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 142 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 143 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 144 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 145 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 146 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 147 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 148 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 149 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 150 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 151 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 152 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 153 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 h1:efeOvDhwQ29Dj3SdAV/MJf8oukgn+8D8WgaCaRMchF8= 154 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 155 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 156 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 157 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 158 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 159 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 160 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 161 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 162 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 163 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 164 | golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 165 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 166 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 167 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 168 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 169 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 170 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 171 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 172 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 173 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 174 | golang.org/x/sys v0.0.0-20191210023423-ac6580df4449 h1:gSbV7h1NRL2G1xTg/owz62CST1oJBmxy4QpMMregXVQ= 175 | golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 176 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 177 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 178 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= 179 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 180 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 181 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 182 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 183 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 184 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 185 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 186 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 187 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 188 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 189 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 190 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 191 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 192 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 193 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 194 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 195 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 196 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 197 | golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 198 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 199 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 200 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 201 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 202 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= 203 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 204 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 205 | google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 206 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 207 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 208 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 209 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= 210 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 211 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 212 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 213 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 214 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 215 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 216 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 217 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= 218 | google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 219 | google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1 h1:aQktFqmDE2yjveXJlVIfslDFmFnUXSqG0i6KRcJAeMc= 220 | google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 221 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 222 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 223 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 224 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 225 | google.golang.org/grpc v1.25.1 h1:wdKvqQk7IttEw92GoRyKG2IDrUIpgpj6H6m81yfeMW0= 226 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 227 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 228 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 229 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 230 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 231 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 232 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 233 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 234 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 235 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 236 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 237 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 238 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 239 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= 240 | -------------------------------------------------------------------------------- /help.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "strings" 7 | "text/tabwriter" 8 | "unicode" 9 | "unicode/utf8" 10 | 11 | "github.com/spf13/pflag" 12 | ) 13 | 14 | func flagDashes(name string) string { 15 | if utf8.RuneCountInString(name) > 1 { 16 | return "--" 17 | } 18 | return "-" 19 | } 20 | 21 | // fmtDefValue adds quotes around default value strings that contain spaces so 22 | // the help representation matches what you would need to do when running a 23 | // command. 24 | func fmtDefValue(value interface{}) string { 25 | switch v := value.(type) { 26 | case string: 27 | if strings.IndexFunc(v, unicode.IsSpace) != -1 { 28 | return fmt.Sprintf(`"%v"`, v) 29 | } 30 | return v 31 | 32 | default: 33 | return fmt.Sprintf("%v", v) 34 | } 35 | } 36 | 37 | // \xFF is used to escape a \t so tabwriter ignores it. 38 | const tabEscape = "\xFF" 39 | 40 | func renderFlagHelp(fullName string, fl *pflag.FlagSet, w io.Writer) { 41 | if fl.HasFlags() { 42 | fmt.Fprintf(w, "\n%s flags:\n", fullName) 43 | fmt.Fprint(w, fl.FlagUsages()) 44 | } 45 | } 46 | 47 | // renderHelp generates a command's help page. 48 | func renderHelp(w io.Writer, fullName string, cmd Command, fl *pflag.FlagSet) { 49 | var b strings.Builder 50 | spec := cmd.Spec() 51 | fmt.Fprintf(&b, "Usage: %v %v\n\n", fullName, spec.Usage) 52 | 53 | // If the command has aliases, add them to the output as a comma-separated list. 54 | if spec.HasAliases() { 55 | fmt.Fprintf(&b, "Aliases: %s\n\n", strings.Join(spec.Aliases, ", ")) 56 | } 57 | // Print usage and description. 58 | fmt.Fprintf(w, "%sDescription: %s\n", b.String(), spec.Desc) 59 | 60 | tw := tabwriter.NewWriter(w, 0, 4, 2, ' ', tabwriter.StripEscape) 61 | defer tw.Flush() 62 | 63 | // Render flag help. 64 | renderFlagHelp(fullName, fl, tw) 65 | 66 | // Render subcommand summaries. 67 | pc, ok := cmd.(ParentCommand) 68 | if ok { 69 | if len(pc.Subcommands()) > 0 { 70 | // Give some space from flags. 71 | fmt.Fprintf(w, "\n") 72 | fmt.Fprint(w, "Commands:\n") 73 | } 74 | 75 | for _, cmd := range pc.Subcommands() { 76 | spec := cmd.Spec() 77 | 78 | if spec.Hidden { 79 | continue 80 | } 81 | 82 | allNames := strings.Join(append(spec.Aliases, spec.Name), ", ") 83 | 84 | fmt.Fprintf(tw, 85 | tabEscape+"\t"+tabEscape+"%v\t- %v\n", 86 | allNames, 87 | spec.ShortDesc(), 88 | ) 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /run.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "os" 5 | "strings" 6 | 7 | "github.com/spf13/pflag" 8 | ) 9 | 10 | type aliasMap map[Command][]string 11 | 12 | func appendParent(parent string, add string) string { 13 | return parent + add + " " 14 | } 15 | 16 | // splitArgs tries to split the args between the parent command's flags/args, and the subcommand's 17 | // flags/args. If a subcommand is found, the parent args, subcommand args, and the subcommand will 18 | // be returned. If a subcommand isn't found, the args will be returned as is, the subcommand args 19 | // will be empty, and the subcommand will be nil. 20 | func splitArgs(subCmds []Command, args []string, aliases aliasMap) (cmdArgs, subArgs []string, subCmd Command) { 21 | for i, arg := range args { 22 | if strings.HasPrefix(arg, "-") { 23 | continue 24 | } 25 | 26 | for _, subCommand := range subCmds { 27 | spec := subCommand.Spec() 28 | 29 | if spec.HasAliases() { 30 | for _, alias := range spec.Aliases { 31 | if arg == alias { 32 | return args[:i], args[i+1:], subCommand 33 | } 34 | } 35 | } 36 | 37 | if spec.Name == arg { 38 | return args[:i], args[i+1:], subCommand 39 | } 40 | } 41 | } 42 | 43 | return args, []string{}, nil 44 | } 45 | 46 | // Run sets up flags, helps, and executes the command with the provided 47 | // arguments. 48 | // 49 | // parents is the list of parent commands. 50 | // E.g the parent for `sail run hello` would be `sail run`. 51 | // 52 | // Use RunRoot if this package is managing the entire CLI. 53 | func Run(cmd Command, args []string, parent string) { 54 | name := parent + cmd.Spec().Name 55 | fl := pflag.NewFlagSet(name, pflag.ContinueOnError) 56 | // Ensure pflag library doesn't print usage for us automatically, 57 | // we'll override this below. 58 | fl.Usage = func() {} 59 | 60 | if fc, ok := cmd.(FlaggedCommand); ok { 61 | fc.RegisterFlags(fl) 62 | } 63 | 64 | if cmd.Spec().RawArgs { 65 | // Use `--` to return immediately when parsing the flags. 66 | args = append([]string{"--"}, args...) 67 | } 68 | 69 | var ( 70 | cmdArgs, subArgs []string 71 | subCmd Command 72 | ) 73 | 74 | pc, isParentCmd := cmd.(ParentCommand) 75 | if isParentCmd { 76 | aliases := make(aliasMap) 77 | subCmds := pc.Subcommands() 78 | 79 | for _, sc := range subCmds { 80 | spec := sc.Spec() 81 | 82 | if spec.HasAliases() { 83 | aliases[sc] = spec.Aliases 84 | } 85 | } 86 | 87 | cmdArgs, subArgs, subCmd = splitArgs(subCmds, args, aliases) 88 | if subCmd != nil { 89 | args = cmdArgs 90 | } 91 | } 92 | 93 | err := fl.Parse(args) 94 | // Reassign the usage now that we've parsed the args 95 | // so that we can render it manually. 96 | fl.Usage = func() { 97 | renderHelp(os.Stderr, name, cmd, fl) 98 | } 99 | if err != nil { 100 | fl.Usage() 101 | os.Exit(2) 102 | } 103 | 104 | // Route to subcommand. 105 | if isParentCmd && subCmd != nil { 106 | Run( 107 | subCmd, subArgs, 108 | appendParent(parent, cmd.Spec().Name), 109 | ) 110 | return 111 | } 112 | 113 | cmd.Run(fl) 114 | } 115 | 116 | // RunRoot calls Run with the process's arguments. 117 | func RunRoot(cmd Command) { 118 | Run(cmd, os.Args[1:], "") 119 | } 120 | --------------------------------------------------------------------------------