From 4f2877d4122ad6a2632aa2bd3edfb103a3130a18 Mon Sep 17 00:00:00 2001 From: umarcor <38422348+umarcor@users.noreply.github.com> Date: Tue, 17 Sep 2019 04:08:40 +0200 Subject: [PATCH 01/56] clean(travis): remove shellcheck from before_install (#947) --- .travis.yml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 38b85f49..fca1e694 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,13 +18,10 @@ matrix: go: 1.12.x script: diff -u <(echo -n) <(gofmt -d -s .) -before_install: - - mkdir -p bin - - curl -Lso bin/shellcheck https://github.com/caarlos0/shellcheck-docker/releases/download/v0.6.0/shellcheck - - chmod +x bin/shellcheck - - go get -u github.com/kyoh86/richgo +before_install: go get -u github.com/kyoh86/richgo + script: - - PATH=$PATH:$PWD/bin richgo test -v ./... + - richgo test -v ./... - go build - if [ -z $NOVET ]; then diff -u <(echo -n) <(go vet . 2>&1 | grep -vE 'ExampleCommand|bash_completions.*Fprint'); From 606aa5792c27e9e4404c19c211c4ae8200ee84ef Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Tue, 17 Sep 2019 04:17:19 +0200 Subject: [PATCH 02/56] Used goimports to fix import order (#912) Keeps the list sorted and prevents future merge conflicts. From 19cf35ea77e5981f8e8b90a897af621f2be864f6 Mon Sep 17 00:00:00 2001 From: umarcor <38422348+umarcor@users.noreply.github.com> Date: Tue, 17 Sep 2019 17:02:42 +0200 Subject: [PATCH 03/56] fix: ensure that testproject is removed even after a failure (#948) * fix: ensure that testproject is removed even after a failure * fix: defer licenseFile * style: simply defer os.RemoveAll * cobra/cmd: add getProject test func --- cobra/cmd/add_test.go | 21 ++------------------- cobra/cmd/init_test.go | 23 ++++++++++------------- cobra/cmd/project.go | 1 + 3 files changed, 13 insertions(+), 32 deletions(-) diff --git a/cobra/cmd/add_test.go b/cobra/cmd/add_test.go index 0de1d221..de92fcea 100644 --- a/cobra/cmd/add_test.go +++ b/cobra/cmd/add_test.go @@ -7,31 +7,14 @@ import ( ) func TestGoldenAddCmd(t *testing.T) { - - wd, _ := os.Getwd() command := &Command{ CmdName: "test", CmdParent: parentName, - Project: &Project{ - AbsolutePath: fmt.Sprintf("%s/testproject", wd), - Legal: getLicense(), - Copyright: copyrightLine(), - - // required to init - AppName: "testproject", - PkgName: "github.com/spf13/testproject", - Viper: true, - }, + Project: getProject(), } + defer os.RemoveAll(command.AbsolutePath) - // init project first command.Project.Create() - defer func() { - if _, err := os.Stat(command.AbsolutePath); err == nil { - os.RemoveAll(command.AbsolutePath) - } - }() - if err := command.Create(); err != nil { t.Fatal(err) } diff --git a/cobra/cmd/init_test.go b/cobra/cmd/init_test.go index 9540b2d3..8ee39106 100644 --- a/cobra/cmd/init_test.go +++ b/cobra/cmd/init_test.go @@ -7,29 +7,26 @@ import ( "testing" ) -func TestGoldenInitCmd(t *testing.T) { - +func getProject() *Project { wd, _ := os.Getwd() - project := &Project{ + return &Project{ AbsolutePath: fmt.Sprintf("%s/testproject", wd), - PkgName: "github.com/spf13/testproject", Legal: getLicense(), Copyright: copyrightLine(), - Viper: true, AppName: "testproject", + PkgName: "github.com/spf13/testproject", + Viper: true, } +} - err := project.Create() - if err != nil { +func TestGoldenInitCmd(t *testing.T) { + project := getProject() + defer os.RemoveAll(project.AbsolutePath) + + if err := project.Create(); err != nil { t.Fatal(err) } - defer func() { - if _, err := os.Stat(project.AbsolutePath); err == nil { - os.RemoveAll(project.AbsolutePath) - } - }() - expectedFiles := []string{"LICENSE", "main.go", "cmd/root.go"} for _, f := range expectedFiles { generatedFile := fmt.Sprintf("%s/%s", project.AbsolutePath, f) diff --git a/cobra/cmd/project.go b/cobra/cmd/project.go index a53893cc..ecd783d0 100644 --- a/cobra/cmd/project.go +++ b/cobra/cmd/project.go @@ -75,6 +75,7 @@ func (p *Project) createLicenseFile() error { if err != nil { return err } + defer licenseFile.Close() licenseTemplate := template.Must(template.New("license").Parse(p.Legal.Text)) return licenseTemplate.Execute(licenseFile, data) From 3745fcd26232890015d35e0398b59e26f0a85130 Mon Sep 17 00:00:00 2001 From: Joshua Harshman Date: Thu, 3 Oct 2019 15:53:10 -0600 Subject: [PATCH 04/56] add goreportcard to readme (#971) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 649e2e2f..3c4daa5e 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ etc. [![Build Status](https://travis-ci.org/spf13/cobra.svg "Travis CI status")](https://travis-ci.org/spf13/cobra) [![CircleCI status](https://circleci.com/gh/spf13/cobra.png?circle-token=:circle-token "CircleCI status")](https://circleci.com/gh/spf13/cobra) [![GoDoc](https://godoc.org/github.com/spf13/cobra?status.svg)](https://godoc.org/github.com/spf13/cobra) +[![Go Report Card](https://goreportcard.com/badge/github.com/spf13/cobra)](https://goreportcard.com/report/github.com/spf13/cobra) # Table of Contents From 48e6ac4718b3f139d3bb28ace54986ca6889d1ca Mon Sep 17 00:00:00 2001 From: Joshua Harshman Date: Mon, 7 Oct 2019 15:31:10 -0600 Subject: [PATCH 05/56] update doc w/ newer cmd/root.go example (#973) --- README.md | 93 +++++++++++++++++++++++++++++++++---------------------- 1 file changed, 56 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index 3c4daa5e..b58eacff 100644 --- a/README.md +++ b/README.md @@ -210,53 +210,72 @@ You will additionally define flags and handle configuration in your init() funct For example cmd/root.go: ```go -import ( - "fmt" - "os" +package cmd - homedir "github.com/mitchellh/go-homedir" - "github.com/spf13/cobra" - "github.com/spf13/viper" +import ( + "fmt" + + homedir "github.com/mitchellh/go-homedir" + "github.com/spf13/cobra" + "github.com/spf13/viper" ) -var cfgFile string +var ( + // Used for flags. + cfgFile string + userLicense string + + rootCmd = &cobra.Command{ + Use: "cobra", + Short: "A generator for Cobra based Applications", + Long: `Cobra is a CLI library for Go that empowers applications. +This application is a tool to generate the needed files +to quickly create a Cobra application.`, + } +) + +// Execute executes the root command. +func Execute() error { + return rootCmd.Execute() +} func init() { - cobra.OnInitialize(initConfig) - rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.cobra.yaml)") - rootCmd.PersistentFlags().StringVarP(&projectBase, "projectbase", "b", "", "base project directory eg. github.com/spf13/") - rootCmd.PersistentFlags().StringP("author", "a", "YOUR NAME", "Author name for copyright attribution") - rootCmd.PersistentFlags().StringVarP(&userLicense, "license", "l", "", "Name of license for the project (can provide `licensetext` in config)") - rootCmd.PersistentFlags().Bool("viper", true, "Use Viper for configuration") - viper.BindPFlag("author", rootCmd.PersistentFlags().Lookup("author")) - viper.BindPFlag("projectbase", rootCmd.PersistentFlags().Lookup("projectbase")) - viper.BindPFlag("useViper", rootCmd.PersistentFlags().Lookup("viper")) - viper.SetDefault("author", "NAME HERE ") - viper.SetDefault("license", "apache") + cobra.OnInitialize(initConfig) + + rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.cobra.yaml)") + rootCmd.PersistentFlags().StringP("author", "a", "YOUR NAME", "author name for copyright attribution") + rootCmd.PersistentFlags().StringVarP(&userLicense, "license", "l", "", "name of license for the project") + rootCmd.PersistentFlags().Bool("viper", true, "use Viper for configuration") + viper.BindPFlag("author", rootCmd.PersistentFlags().Lookup("author")) + viper.BindPFlag("useViper", rootCmd.PersistentFlags().Lookup("viper")) + viper.SetDefault("author", "NAME HERE ") + viper.SetDefault("license", "apache") + + rootCmd.AddCommand(addCmd) + rootCmd.AddCommand(initCmd) } func initConfig() { - // Don't forget to read config either from cfgFile or from home directory! - if cfgFile != "" { - // Use config file from the flag. - viper.SetConfigFile(cfgFile) - } else { - // Find home directory. - home, err := homedir.Dir() - if err != nil { - fmt.Println(err) - os.Exit(1) - } + if cfgFile != "" { + // Use config file from the flag. + viper.SetConfigFile(cfgFile) + } else { + // Find home directory. + home, err := homedir.Dir() + if err != nil { + er(err) + } - // Search config in home directory with name ".cobra" (without extension). - viper.AddConfigPath(home) - viper.SetConfigName(".cobra") - } + // Search config in home directory with name ".cobra" (without extension). + viper.AddConfigPath(home) + viper.SetConfigName(".cobra") + } - if err := viper.ReadInConfig(); err != nil { - fmt.Println("Can't read config:", err) - os.Exit(1) - } + viper.AutomaticEnv() + + if err := viper.ReadInConfig(); err == nil { + fmt.Println("Using config file:", viper.ConfigFileUsed()) + } } ``` From c022f6fabd6ec400cb7227be78bc651d9423be35 Mon Sep 17 00:00:00 2001 From: XuZvvHYmZfYdWJNRunkJ <3367239+JBrVJxsc@users.noreply.github.com> Date: Mon, 7 Oct 2019 14:41:51 -0700 Subject: [PATCH 06/56] Update README.md (#944) I think it meant to be `Echo` here instead of `Print` since the command is called `cmdEcho`. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b58eacff..834f2878 100644 --- a/README.md +++ b/README.md @@ -482,7 +482,7 @@ For many years people have printed back to the screen.`, Echo works a lot like print, except it has a child command.`, Args: cobra.MinimumNArgs(1), Run: func(cmd *cobra.Command, args []string) { - fmt.Println("Print: " + strings.Join(args, " ")) + fmt.Println("Echo: " + strings.Join(args, " ")) }, } From 8a4b46fadf756f30eff047abf2f8edba4eac6fef Mon Sep 17 00:00:00 2001 From: umarcor <38422348+umarcor@users.noreply.github.com> Date: Mon, 14 Oct 2019 05:11:37 +0200 Subject: [PATCH 07/56] update viper to v1.4.0 (#953) --- go.mod | 3 +- go.sum | 110 +++++++++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 104 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 9a9eb65a..14b400ec 100644 --- a/go.mod +++ b/go.mod @@ -3,11 +3,10 @@ module github.com/spf13/cobra go 1.12 require ( - github.com/BurntSushi/toml v0.3.1 // indirect github.com/cpuguy83/go-md2man v1.0.10 github.com/inconshreveable/mousetrap v1.0.0 github.com/mitchellh/go-homedir v1.1.0 github.com/spf13/pflag v1.0.3 - github.com/spf13/viper v1.3.2 + github.com/spf13/viper v1.4.0 gopkg.in/yaml.v2 v2.2.2 ) diff --git a/go.sum b/go.sum index 9761f4d0..2906e346 100644 --- a/go.sum +++ b/go.sum @@ -1,31 +1,89 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= @@ -34,18 +92,56 @@ github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9 github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/viper v1.3.2 h1:VUFqw5KcqRf7i70GOzW7N+Q7+gxVBkSSqiXB12+JQ4M= -github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU= +github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= -golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a h1:1n5lsVfiQW3yfsRGu98756EH1YthsFqr/5mxHduZW2A= -golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= From 77e4d5aecc4d34e58f72e5a1c4a5a13ef55e6f44 Mon Sep 17 00:00:00 2001 From: Peter Fern Date: Sun, 20 Oct 2019 09:17:41 +1100 Subject: [PATCH 08/56] Update md2man to v2.0.0 (#977) Fixes #805 --- doc/man_docs.go | 2 +- go.mod | 2 +- go.sum | 10 ++++++---- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/doc/man_docs.go b/doc/man_docs.go index 4a062339..8c7fba44 100644 --- a/doc/man_docs.go +++ b/doc/man_docs.go @@ -24,7 +24,7 @@ import ( "strings" "time" - "github.com/cpuguy83/go-md2man/md2man" + "github.com/cpuguy83/go-md2man/v2/md2man" "github.com/spf13/cobra" "github.com/spf13/pflag" ) diff --git a/go.mod b/go.mod index 14b400ec..dea1030b 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/spf13/cobra go 1.12 require ( - github.com/cpuguy83/go-md2man v1.0.10 + github.com/cpuguy83/go-md2man/v2 v2.0.0 github.com/inconshreveable/mousetrap v1.0.0 github.com/mitchellh/go-homedir v1.1.0 github.com/spf13/pflag v1.0.3 diff --git a/go.sum b/go.sum index 2906e346..3aaa2ac0 100644 --- a/go.sum +++ b/go.sum @@ -14,8 +14,8 @@ github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= -github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= @@ -79,8 +79,10 @@ github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= -github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= -github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= From b04b5bfc50cbb6c036d2115ed55ea1bccdaf82a9 Mon Sep 17 00:00:00 2001 From: Stefano Guerrini Date: Mon, 2 Dec 2019 14:04:30 +0100 Subject: [PATCH 09/56] substitute wrong word in md_docs (#998) --- doc/md_docs.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/md_docs.md b/doc/md_docs.md index 56ce9fe8..5c870625 100644 --- a/doc/md_docs.md +++ b/doc/md_docs.md @@ -1,6 +1,6 @@ # Generating Markdown Docs For Your Own cobra.Command -Generating man pages from a cobra command is incredibly easy. An example is as follows: +Generating Markdown pages from a cobra command is incredibly easy. An example is as follows: ```go package main From 0d9d2d46f3099574b6d524c3d8834dd592a80fb3 Mon Sep 17 00:00:00 2001 From: Marc Khouzam Date: Mon, 23 Dec 2019 13:38:24 -0500 Subject: [PATCH 10/56] Revert change so help is printed on stdout again (#1004) Fixes #1002 For backwards compatibility reasons, and to follow the need of https://github.com/kubernetes/kubernetes/pull/26077#issuecomment-230818900 the help message should be printed on stdout. Signed-off-by: Marc Khouzam --- command.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/command.go b/command.go index 42e500de..ab3cf69a 100644 --- a/command.go +++ b/command.go @@ -372,7 +372,9 @@ func (c *Command) HelpFunc() func(*Command, []string) { } return func(c *Command, a []string) { c.mergePersistentFlags() - err := tmpl(c.OutOrStderr(), c.HelpTemplate(), c) + // The help should be sent to stdout + // See https://github.com/spf13/cobra/issues/1002 + err := tmpl(c.OutOrStdout(), c.HelpTemplate(), c) if err != nil { c.Println(err) } From 447f182a9da843c53fa3dd6acfbd043a857e152e Mon Sep 17 00:00:00 2001 From: Joshua Harshman Date: Mon, 23 Dec 2019 13:51:40 -0700 Subject: [PATCH 11/56] format tpl/main.go templates (#980) --- cobra/cmd/testdata/main.go.golden | 2 +- cobra/cmd/testdata/root.go.golden | 90 +++++++++++++--------------- cobra/tpl/main.go | 99 +++++++++++++++---------------- 3 files changed, 92 insertions(+), 99 deletions(-) diff --git a/cobra/cmd/testdata/main.go.golden b/cobra/cmd/testdata/main.go.golden index 4ad570c5..f43a6c1c 100644 --- a/cobra/cmd/testdata/main.go.golden +++ b/cobra/cmd/testdata/main.go.golden @@ -18,5 +18,5 @@ package main import "github.com/spf13/testproject/cmd" func main() { - cmd.Execute() + cmd.Execute() } diff --git a/cobra/cmd/testdata/root.go.golden b/cobra/cmd/testdata/root.go.golden index d3b889ba..afc668ad 100644 --- a/cobra/cmd/testdata/root.go.golden +++ b/cobra/cmd/testdata/root.go.golden @@ -16,82 +16,76 @@ limitations under the License. package cmd import ( - "fmt" - "os" - "github.com/spf13/cobra" - - homedir "github.com/mitchellh/go-homedir" - "github.com/spf13/viper" + "fmt" + "github.com/spf13/cobra" + "os" + homedir "github.com/mitchellh/go-homedir" + "github.com/spf13/viper" ) - var cfgFile string - // rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ - Use: "testproject", - Short: "A brief description of your application", - Long: `A longer description that spans multiple lines and likely contains + Use: "testproject", + Short: "A brief description of your application", + Long: `A longer description that spans multiple lines and likely contains examples and usage of using your application. For example: Cobra is a CLI library for Go that empowers applications. This application is a tool to generate the needed files to quickly create a Cobra application.`, - // Uncomment the following line if your bare application - // has an action associated with it: - // Run: func(cmd *cobra.Command, args []string) { }, + // Uncomment the following line if your bare application + // has an action associated with it: + // Run: func(cmd *cobra.Command, args []string) { }, } // Execute adds all child commands to the root command and sets flags appropriately. // This is called by main.main(). It only needs to happen once to the rootCmd. func Execute() { - if err := rootCmd.Execute(); err != nil { - fmt.Println(err) - os.Exit(1) - } + if err := rootCmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(1) + } } func init() { - cobra.OnInitialize(initConfig) + cobra.OnInitialize(initConfig) - // Here you will define your flags and configuration settings. - // Cobra supports persistent flags, which, if defined here, - // will be global for your application. + // Here you will define your flags and configuration settings. + // Cobra supports persistent flags, which, if defined here, + // will be global for your application. - rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.testproject.yaml)") + rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.testproject.yaml)") - - // Cobra also supports local flags, which will only run - // when this action is called directly. - rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") + // Cobra also supports local flags, which will only run + // when this action is called directly. + rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") } - // initConfig reads in config file and ENV variables if set. func initConfig() { - if cfgFile != "" { - // Use config file from the flag. - viper.SetConfigFile(cfgFile) - } else { - // Find home directory. - home, err := homedir.Dir() - if err != nil { - fmt.Println(err) - os.Exit(1) - } + if cfgFile != "" { + // Use config file from the flag. + viper.SetConfigFile(cfgFile) + } else { + // Find home directory. + home, err := homedir.Dir() + if err != nil { + fmt.Println(err) + os.Exit(1) + } - // Search config in home directory with name ".testproject" (without extension). - viper.AddConfigPath(home) - viper.SetConfigName(".testproject") - } + // Search config in home directory with name ".testproject" (without extension). + viper.AddConfigPath(home) + viper.SetConfigName(".testproject") + } - viper.AutomaticEnv() // read in environment variables that match + viper.AutomaticEnv() // read in environment variables that match - // If a config file is found, read it in. - if err := viper.ReadInConfig(); err == nil { - fmt.Println("Using config file:", viper.ConfigFileUsed()) - } + // If a config file is found, read it in. + if err := viper.ReadInConfig(); err == nil { + fmt.Println("Using config file:", viper.ConfigFileUsed()) + } } - diff --git a/cobra/tpl/main.go b/cobra/tpl/main.go index 5e5a0fae..4348e561 100644 --- a/cobra/tpl/main.go +++ b/cobra/tpl/main.go @@ -10,7 +10,7 @@ package main import "{{ .PkgName }}/cmd" func main() { - cmd.Execute() + cmd.Execute() } `) } @@ -23,88 +23,87 @@ func RootTemplate() []byte { package cmd import ( - "fmt" - "os" - "github.com/spf13/cobra" + "fmt" + "github.com/spf13/cobra" + "os" {{ if .Viper }} - homedir "github.com/mitchellh/go-homedir" - "github.com/spf13/viper" -{{ end }} + homedir "github.com/mitchellh/go-homedir" + "github.com/spf13/viper" +{{ end -}} ) -{{ if .Viper }} +{{ if .Viper -}} var cfgFile string -{{ end }} +{{- end }} // rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ - Use: "{{ .AppName }}", - Short: "A brief description of your application", - Long: ` + "`" + `A longer description that spans multiple lines and likely contains + Use: "{{ .AppName }}", + Short: "A brief description of your application", + Long: ` + "`" + `A longer description that spans multiple lines and likely contains examples and usage of using your application. For example: Cobra is a CLI library for Go that empowers applications. This application is a tool to generate the needed files to quickly create a Cobra application.` + "`" + `, - // Uncomment the following line if your bare application - // has an action associated with it: - // Run: func(cmd *cobra.Command, args []string) { }, + // Uncomment the following line if your bare application + // has an action associated with it: + // Run: func(cmd *cobra.Command, args []string) { }, } // Execute adds all child commands to the root command and sets flags appropriately. // This is called by main.main(). It only needs to happen once to the rootCmd. func Execute() { - if err := rootCmd.Execute(); err != nil { - fmt.Println(err) - os.Exit(1) - } + if err := rootCmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(1) + } } func init() { {{- if .Viper }} - cobra.OnInitialize(initConfig) + cobra.OnInitialize(initConfig) {{ end }} - // Here you will define your flags and configuration settings. - // Cobra supports persistent flags, which, if defined here, - // will be global for your application. + // Here you will define your flags and configuration settings. + // Cobra supports persistent flags, which, if defined here, + // will be global for your application. {{ if .Viper }} - rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.{{ .AppName }}.yaml)") + rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.{{ .AppName }}.yaml)") {{ else }} - // rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.{{ .AppName }}.yaml)") + // rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.{{ .AppName }}.yaml)") {{ end }} - - // Cobra also supports local flags, which will only run - // when this action is called directly. - rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") + // Cobra also supports local flags, which will only run + // when this action is called directly. + rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") } -{{ if .Viper }} +{{ if .Viper -}} // initConfig reads in config file and ENV variables if set. func initConfig() { - if cfgFile != "" { - // Use config file from the flag. - viper.SetConfigFile(cfgFile) - } else { - // Find home directory. - home, err := homedir.Dir() - if err != nil { - fmt.Println(err) - os.Exit(1) - } + if cfgFile != "" { + // Use config file from the flag. + viper.SetConfigFile(cfgFile) + } else { + // Find home directory. + home, err := homedir.Dir() + if err != nil { + fmt.Println(err) + os.Exit(1) + } - // Search config in home directory with name ".{{ .AppName }}" (without extension). - viper.AddConfigPath(home) - viper.SetConfigName(".{{ .AppName }}") - } + // Search config in home directory with name ".{{ .AppName }}" (without extension). + viper.AddConfigPath(home) + viper.SetConfigName(".{{ .AppName }}") + } - viper.AutomaticEnv() // read in environment variables that match + viper.AutomaticEnv() // read in environment variables that match - // If a config file is found, read it in. - if err := viper.ReadInConfig(); err == nil { - fmt.Println("Using config file:", viper.ConfigFileUsed()) - } + // If a config file is found, read it in. + if err := viper.ReadInConfig(); err == nil { + fmt.Println("Using config file:", viper.ConfigFileUsed()) + } } -{{ end }} +{{- end }} `) } From bf268956645942d536f2faef6d53f1951f9522a4 Mon Sep 17 00:00:00 2001 From: Marc Khouzam Date: Thu, 26 Dec 2019 12:55:42 -0500 Subject: [PATCH 12/56] Fix regression when calling *_custom_func (#1001) PR #889 introduced a regression where the global variable $c is no longer set when *custom_func is called. This is because $c is re-used by mistake in the read loop. This PR simply changes the name of the variable used in the loop. Signed-off-by: Marc Khouzam --- bash_completions.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/bash_completions.go b/bash_completions.go index 03ddda27..1e0e25cf 100644 --- a/bash_completions.go +++ b/bash_completions.go @@ -61,6 +61,7 @@ __%[1]s_contains_word() __%[1]s_handle_reply() { __%[1]s_debug "${FUNCNAME[0]}" + local comp case $cur in -*) if [[ $(type -t compopt) = "builtin" ]]; then @@ -72,8 +73,8 @@ __%[1]s_handle_reply() else allflags=("${flags[*]} ${two_word_flags[*]}") fi - while IFS='' read -r c; do - COMPREPLY+=("$c") + while IFS='' read -r comp; do + COMPREPLY+=("$comp") done < <(compgen -W "${allflags[*]}" -- "$cur") if [[ $(type -t compopt) = "builtin" ]]; then [[ "${COMPREPLY[0]}" == *= ]] || compopt +o nospace @@ -124,13 +125,13 @@ __%[1]s_handle_reply() if [[ ${#must_have_one_flag[@]} -ne 0 ]]; then completions+=("${must_have_one_flag[@]}") fi - while IFS='' read -r c; do - COMPREPLY+=("$c") + while IFS='' read -r comp; do + COMPREPLY+=("$comp") done < <(compgen -W "${completions[*]}" -- "$cur") if [[ ${#COMPREPLY[@]} -eq 0 && ${#noun_aliases[@]} -gt 0 && ${#must_have_one_noun[@]} -ne 0 ]]; then - while IFS='' read -r c; do - COMPREPLY+=("$c") + while IFS='' read -r comp; do + COMPREPLY+=("$comp") done < <(compgen -W "${noun_aliases[*]}" -- "$cur") fi From 89c7ffb5129bebd58cd68878c13af2144a5791f3 Mon Sep 17 00:00:00 2001 From: Nickolas Kraus Date: Mon, 6 Jan 2020 12:10:57 -0600 Subject: [PATCH 13/56] Bump year on golden files (#1010) --- cobra/cmd/testdata/main.go.golden | 2 +- cobra/cmd/testdata/root.go.golden | 2 +- cobra/cmd/testdata/test.go.golden | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cobra/cmd/testdata/main.go.golden b/cobra/cmd/testdata/main.go.golden index f43a6c1c..0bf75c5a 100644 --- a/cobra/cmd/testdata/main.go.golden +++ b/cobra/cmd/testdata/main.go.golden @@ -1,5 +1,5 @@ /* -Copyright © 2019 NAME HERE +Copyright © 2020 NAME HERE Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/cobra/cmd/testdata/root.go.golden b/cobra/cmd/testdata/root.go.golden index afc668ad..1db829c7 100644 --- a/cobra/cmd/testdata/root.go.golden +++ b/cobra/cmd/testdata/root.go.golden @@ -1,5 +1,5 @@ /* -Copyright © 2019 NAME HERE +Copyright © 2020 NAME HERE Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/cobra/cmd/testdata/test.go.golden b/cobra/cmd/testdata/test.go.golden index fb8e0fa9..3eef95fe 100644 --- a/cobra/cmd/testdata/test.go.golden +++ b/cobra/cmd/testdata/test.go.golden @@ -1,5 +1,5 @@ /* -Copyright © 2019 NAME HERE +Copyright © 2020 NAME HERE Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. From 21cab29ef902d36da074e8c940437c9f536b2283 Mon Sep 17 00:00:00 2001 From: Andrea Falzetti Date: Wed, 19 Feb 2020 16:20:06 +0000 Subject: [PATCH 14/56] fix: undefined er (#1039) --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 834f2878..2f8175bc 100644 --- a/README.md +++ b/README.md @@ -214,6 +214,7 @@ package cmd import ( "fmt" + "os" homedir "github.com/mitchellh/go-homedir" "github.com/spf13/cobra" @@ -255,6 +256,11 @@ func init() { rootCmd.AddCommand(initCmd) } +func er(msg interface{}) { + fmt.Println("Error:", msg) + os.Exit(1) +} + func initConfig() { if cfgFile != "" { // Use config file from the flag. From 0da06874266c88228b8f14615396a1f6bfc90ed7 Mon Sep 17 00:00:00 2001 From: Alexandr Burdiyan Date: Thu, 20 Feb 2020 07:29:50 +0100 Subject: [PATCH 15/56] Add support for context.Context --- command.go | 30 ++++++++++++++++++++-- command_test.go | 67 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 2 deletions(-) diff --git a/command.go b/command.go index ab3cf69a..fb60ebd9 100644 --- a/command.go +++ b/command.go @@ -17,6 +17,7 @@ package cobra import ( "bytes" + "context" "errors" "fmt" "io" @@ -143,9 +144,11 @@ type Command struct { // TraverseChildren parses flags on all parents before executing child command. TraverseChildren bool - //FParseErrWhitelist flag parse errors to be ignored + // FParseErrWhitelist flag parse errors to be ignored FParseErrWhitelist FParseErrWhitelist + ctx context.Context + // commands is the list of commands supported by this program. commands []*Command // parent is a parent command for this command. @@ -205,6 +208,12 @@ type Command struct { errWriter io.Writer } +// Context returns underlying command context. If command wasn't +// executed with ExecuteContext Context returns Background context. +func (c *Command) Context() context.Context { + return c.ctx +} + // SetArgs sets arguments for the command. It is set to os.Args[1:] by default, if desired, can be overridden // particularly useful when testing. func (c *Command) SetArgs(a []string) { @@ -862,6 +871,13 @@ func (c *Command) preRun() { } } +// ExecuteContext is the same as Execute(), but sets the ctx on the command. +// Retrieve ctx by calling cmd.Context() inside your *Run lifecycle functions. +func (c *Command) ExecuteContext(ctx context.Context) error { + c.ctx = ctx + return c.Execute() +} + // Execute uses the args (os.Args[1:] by default) // and run through the command tree finding appropriate matches // for commands and then corresponding flags. @@ -872,6 +888,10 @@ func (c *Command) Execute() error { // ExecuteC executes the command. func (c *Command) ExecuteC() (cmd *Command, err error) { + if c.ctx == nil { + c.ctx = context.Background() + } + // Regardless of what command execute is called on, run on Root only if c.HasParent() { return c.Root().ExecuteC() @@ -916,6 +936,12 @@ func (c *Command) ExecuteC() (cmd *Command, err error) { cmd.commandCalledAs.name = cmd.Name() } + // We have to pass global context to children command + // if context is present on the parent command. + if cmd.ctx == nil { + cmd.ctx = c.ctx + } + err = cmd.execute(flags) if err != nil { // Always show help if requested, even if SilenceErrors is in @@ -1560,7 +1586,7 @@ func (c *Command) ParseFlags(args []string) error { beforeErrorBufLen := c.flagErrorBuf.Len() c.mergePersistentFlags() - //do it here after merging all flags and just before parse + // do it here after merging all flags and just before parse c.Flags().ParseErrorsWhitelist = flag.ParseErrorsWhitelist(c.FParseErrWhitelist) err := c.Flags().Parse(args) diff --git a/command_test.go b/command_test.go index b26bd4ab..05a3c0f2 100644 --- a/command_test.go +++ b/command_test.go @@ -2,6 +2,7 @@ package cobra import ( "bytes" + "context" "fmt" "os" "reflect" @@ -18,6 +19,16 @@ func executeCommand(root *Command, args ...string) (output string, err error) { return output, err } +func executeCommandWithContext(ctx context.Context, root *Command, args ...string) (output string, err error) { + buf := new(bytes.Buffer) + root.SetOutput(buf) + root.SetArgs(args) + + err = root.ExecuteContext(ctx) + + return buf.String(), err +} + func executeCommandC(root *Command, args ...string) (c *Command, output string, err error) { buf := new(bytes.Buffer) root.SetOutput(buf) @@ -135,6 +146,62 @@ func TestSubcommandExecuteC(t *testing.T) { } } +func TestExecuteContext(t *testing.T) { + ctx := context.TODO() + + ctxRun := func(cmd *Command, args []string) { + if cmd.Context() != ctx { + t.Errorf("Command %q must have context when called with ExecuteContext", cmd.Use) + } + } + + rootCmd := &Command{Use: "root", Run: ctxRun, PreRun: ctxRun} + childCmd := &Command{Use: "child", Run: ctxRun, PreRun: ctxRun} + granchildCmd := &Command{Use: "grandchild", Run: ctxRun, PreRun: ctxRun} + + childCmd.AddCommand(granchildCmd) + rootCmd.AddCommand(childCmd) + + if _, err := executeCommandWithContext(ctx, rootCmd, ""); err != nil { + t.Errorf("Root command must not fail: %+v", err) + } + + if _, err := executeCommandWithContext(ctx, rootCmd, "child"); err != nil { + t.Errorf("Subcommand must not fail: %+v", err) + } + + if _, err := executeCommandWithContext(ctx, rootCmd, "child", "grandchild"); err != nil { + t.Errorf("Command child must not fail: %+v", err) + } +} + +func TestExecute_NoContext(t *testing.T) { + run := func(cmd *Command, args []string) { + if cmd.Context() != context.Background() { + t.Errorf("Command %s must have background context", cmd.Use) + } + } + + rootCmd := &Command{Use: "root", Run: run, PreRun: run} + childCmd := &Command{Use: "child", Run: run, PreRun: run} + granchildCmd := &Command{Use: "grandchild", Run: run, PreRun: run} + + childCmd.AddCommand(granchildCmd) + rootCmd.AddCommand(childCmd) + + if _, err := executeCommand(rootCmd, ""); err != nil { + t.Errorf("Root command must not fail: %+v", err) + } + + if _, err := executeCommand(rootCmd, "child"); err != nil { + t.Errorf("Subcommand must not fail: %+v", err) + } + + if _, err := executeCommand(rootCmd, "child", "grandchild"); err != nil { + t.Errorf("Command child must not fail: %+v", err) + } +} + func TestRootUnknownCommandSilenced(t *testing.T) { rootCmd := &Command{Use: "root", Run: emptyRun} rootCmd.SilenceErrors = true From 3c2624538b7d0935103b37a9313661ffaad30d46 Mon Sep 17 00:00:00 2001 From: Daniel Esponda Date: Thu, 20 Feb 2020 12:42:29 -0600 Subject: [PATCH 16/56] Correct documentation for InOrStdin (#929) Documentation on function is incorrect --- command.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/command.go b/command.go index fb60ebd9..dc677532 100644 --- a/command.go +++ b/command.go @@ -309,7 +309,7 @@ func (c *Command) ErrOrStderr() io.Writer { return c.getErr(os.Stderr) } -// InOrStdin returns output to stderr +// InOrStdin returns input to stdin func (c *Command) InOrStdin() io.Reader { return c.getIn(os.Stdin) } From 39cf99f556817dc31535a3fbc8a60c7fbd70ba56 Mon Sep 17 00:00:00 2001 From: Joshua Harshman Date: Thu, 20 Feb 2020 12:25:38 -0700 Subject: [PATCH 17/56] leverage makefile to run build tasks (#976) remove circle ci --- .circleci/config.yml | 53 -------------------------------------------- .gitignore | 2 +- .travis.yml | 25 +++++++++++---------- Makefile | 36 ++++++++++++++++++++++++++++++ cobra/Makefile | 23 +++++++++++++++++++ 5 files changed, 73 insertions(+), 66 deletions(-) delete mode 100644 .circleci/config.yml create mode 100644 Makefile create mode 100644 cobra/Makefile diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 81944643..00000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,53 +0,0 @@ -version: 2 - -references: - workspace: &workspace - /go/src/github.com/spf13/cobra - - run_tests: &run_tests - run: - name: "All Commands" - command: | - mkdir -p bin - curl -Lso bin/shellcheck https://github.com/caarlos0/shellcheck-docker/releases/download/v0.4.6/shellcheck - chmod +x bin/shellcheck - go get -t -v ./... - PATH=$PATH:$PWD/bin go test -v ./... - go build - if [ -z $NOVET ]; then - diff -u <(echo -n) <(go vet . 2>&1 | grep -vE 'ExampleCommand|bash_completions.*Fprint'); - fi - -jobs: - go-current: - docker: - - image: circleci/golang:1.12 - working_directory: *workspace - steps: - - checkout - - *run_tests - - run: - name: "Check formatting" - command: diff -u <(echo -n) <(gofmt -d -s .) - go-previous: - docker: - - image: circleci/golang:1.11 - working_directory: *workspace - steps: - - checkout - - *run_tests - go-latest: - docker: - - image: circleci/golang:latest - working_directory: *workspace - steps: - - checkout - - *run_tests - -workflows: - version: 2 - main: - jobs: - - go-current - - go-previous - - go-latest diff --git a/.gitignore b/.gitignore index b2b848e7..c7b459e4 100644 --- a/.gitignore +++ b/.gitignore @@ -32,8 +32,8 @@ Session.vim tags *.exe -cobra cobra.test +bin .idea/ *.iml diff --git a/.travis.yml b/.travis.yml index fca1e694..a9bd4e54 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,26 +3,27 @@ language: go stages: - diff - test + - build go: - - 1.10.x - - 1.11.x - 1.12.x + - 1.13.x - tip +before_install: + - go get -u github.com/kyoh86/richgo + - go get -u github.com/mitchellh/gox + matrix: allow_failures: - go: tip include: - stage: diff - go: 1.12.x - script: diff -u <(echo -n) <(gofmt -d -s .) + go: 1.13.x + script: make fmt + - stage: build + go: 1.13.x + script: make cobra_generator -before_install: go get -u github.com/kyoh86/richgo - -script: - - richgo test -v ./... - - go build - - if [ -z $NOVET ]; then - diff -u <(echo -n) <(go vet . 2>&1 | grep -vE 'ExampleCommand|bash_completions.*Fprint'); - fi +script: + - make test diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..e9740d1e --- /dev/null +++ b/Makefile @@ -0,0 +1,36 @@ +BIN="./bin" +SRC=$(shell find . -name "*.go") + +ifeq (, $(shell which richgo)) +$(warning "could not find richgo in $(PATH), run: go get github.com/kyoh86/richgo") +endif + +.PHONY: fmt vet test cobra_generator install_deps clean + +default: all + +all: fmt vet test cobra_generator + +fmt: + $(info ******************** checking formatting ********************) + @test -z $(shell gofmt -l $(SRC)) || (gofmt -d $(SRC); exit 1) + +test: install_deps vet + $(info ******************** running tests ********************) + richgo test -v ./... + +cobra_generator: install_deps + $(info ******************** building generator ********************) + mkdir -p $(BIN) + make -C cobra all + +install_deps: + $(info ******************** downloading dependencies ********************) + go get -v ./... + +vet: + $(info ******************** vetting ********************) + go vet ./... + +clean: + rm -rf $(BIN) diff --git a/cobra/Makefile b/cobra/Makefile new file mode 100644 index 00000000..4995000d --- /dev/null +++ b/cobra/Makefile @@ -0,0 +1,23 @@ +XC_OS="linux darwin" +XC_ARCH="amd64" +XC_PARALLEL="2" +BIN="../bin" +SRC=$(shell find . -name "*.go") + +ifeq (, $(shell which gox)) +$(warning "could not find gox in $(PATH), run: go get github.com/mitchellh/gox") +endif + +.PHONY: all build + +default: all + +all: build + +build: + gox \ + -os=$(XC_OS) \ + -arch=$(XC_ARCH) \ + -parallel=$(XC_PARALLEL) \ + -output=$(BIN)/{{.Dir}}_{{.OS}}_{{.Arch}} \ + ; From 95f2f73ed97e57387762620175ccdc277a8597a0 Mon Sep 17 00:00:00 2001 From: Dave Henderson Date: Fri, 28 Feb 2020 13:13:40 -0500 Subject: [PATCH 18/56] Add short version flag -v when not otherwise set (#996) Signed-off-by: Dave Henderson --- command.go | 9 +++- command_test.go | 115 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+), 2 deletions(-) diff --git a/command.go b/command.go index dc677532..55ddc50f 100644 --- a/command.go +++ b/command.go @@ -84,7 +84,8 @@ type Command struct { // Version defines the version for this command. If this value is non-empty and the command does not // define a "version" flag, a "version" boolean flag will be added to the command and, if specified, - // will print content of the "Version" variable. + // will print content of the "Version" variable. A shorthand "v" flag will also be added if the + // command does not define one. Version string // The *Run functions are executed in the following order: @@ -1033,7 +1034,11 @@ func (c *Command) InitDefaultVersionFlag() { } else { usage += c.Name() } - c.Flags().Bool("version", false, usage) + if c.Flags().ShorthandLookup("v") == nil { + c.Flags().BoolP("version", "v", false, usage) + } else { + c.Flags().Bool("version", false, usage) + } } } diff --git a/command_test.go b/command_test.go index 05a3c0f2..12155937 100644 --- a/command_test.go +++ b/command_test.go @@ -921,6 +921,52 @@ func TestVersionFlagExecuted(t *testing.T) { checkStringContains(t, output, "root version 1.0.0") } +func TestVersionFlagExecutedWithNoName(t *testing.T) { + rootCmd := &Command{Version: "1.0.0", Run: emptyRun} + + output, err := executeCommand(rootCmd, "--version", "arg1") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + checkStringContains(t, output, "version 1.0.0") +} + +func TestShortAndLongVersionFlagInHelp(t *testing.T) { + rootCmd := &Command{Use: "root", Version: "1.0.0", Run: emptyRun} + + output, err := executeCommand(rootCmd, "--help") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + checkStringContains(t, output, "-v, --version") +} + +func TestLongVersionFlagOnlyInHelpWhenShortPredefined(t *testing.T) { + rootCmd := &Command{Use: "root", Version: "1.0.0", Run: emptyRun} + rootCmd.Flags().StringP("foo", "v", "", "not a version flag") + + output, err := executeCommand(rootCmd, "--help") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + checkStringOmits(t, output, "-v, --version") + checkStringContains(t, output, "--version") +} + +func TestShorthandVersionFlagExecuted(t *testing.T) { + rootCmd := &Command{Use: "root", Version: "1.0.0", Run: emptyRun} + + output, err := executeCommand(rootCmd, "-v", "arg1") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + checkStringContains(t, output, "root version 1.0.0") +} + func TestVersionTemplate(t *testing.T) { rootCmd := &Command{Use: "root", Version: "1.0.0", Run: emptyRun} rootCmd.SetVersionTemplate(`customized version: {{.Version}}`) @@ -933,6 +979,18 @@ func TestVersionTemplate(t *testing.T) { checkStringContains(t, output, "customized version: 1.0.0") } +func TestShorthandVersionTemplate(t *testing.T) { + rootCmd := &Command{Use: "root", Version: "1.0.0", Run: emptyRun} + rootCmd.SetVersionTemplate(`customized version: {{.Version}}`) + + output, err := executeCommand(rootCmd, "-v", "arg1") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + checkStringContains(t, output, "customized version: 1.0.0") +} + func TestVersionFlagExecutedOnSubcommand(t *testing.T) { rootCmd := &Command{Use: "root", Version: "1.0.0"} rootCmd.AddCommand(&Command{Use: "sub", Run: emptyRun}) @@ -945,6 +1003,18 @@ func TestVersionFlagExecutedOnSubcommand(t *testing.T) { checkStringContains(t, output, "root version 1.0.0") } +func TestShorthandVersionFlagExecutedOnSubcommand(t *testing.T) { + rootCmd := &Command{Use: "root", Version: "1.0.0"} + rootCmd.AddCommand(&Command{Use: "sub", Run: emptyRun}) + + output, err := executeCommand(rootCmd, "-v", "sub") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + checkStringContains(t, output, "root version 1.0.0") +} + func TestVersionFlagOnlyAddedToRoot(t *testing.T) { rootCmd := &Command{Use: "root", Version: "1.0.0", Run: emptyRun} rootCmd.AddCommand(&Command{Use: "sub", Run: emptyRun}) @@ -957,6 +1027,18 @@ func TestVersionFlagOnlyAddedToRoot(t *testing.T) { checkStringContains(t, err.Error(), "unknown flag: --version") } +func TestShortVersionFlagOnlyAddedToRoot(t *testing.T) { + rootCmd := &Command{Use: "root", Version: "1.0.0", Run: emptyRun} + rootCmd.AddCommand(&Command{Use: "sub", Run: emptyRun}) + + _, err := executeCommand(rootCmd, "sub", "-v") + if err == nil { + t.Errorf("Expected error") + } + + checkStringContains(t, err.Error(), "unknown shorthand flag: 'v' in -v") +} + func TestVersionFlagOnlyExistsIfVersionNonEmpty(t *testing.T) { rootCmd := &Command{Use: "root", Run: emptyRun} @@ -967,6 +1049,39 @@ func TestVersionFlagOnlyExistsIfVersionNonEmpty(t *testing.T) { checkStringContains(t, err.Error(), "unknown flag: --version") } +func TestShorthandVersionFlagOnlyExistsIfVersionNonEmpty(t *testing.T) { + rootCmd := &Command{Use: "root", Run: emptyRun} + + _, err := executeCommand(rootCmd, "-v") + if err == nil { + t.Errorf("Expected error") + } + checkStringContains(t, err.Error(), "unknown shorthand flag: 'v' in -v") +} + +func TestShorthandVersionFlagOnlyAddedIfShorthandNotDefined(t *testing.T) { + rootCmd := &Command{Use: "root", Run: emptyRun, Version: "1.2.3"} + rootCmd.Flags().StringP("notversion", "v", "", "not a version flag") + + _, err := executeCommand(rootCmd, "-v") + if err == nil { + t.Errorf("Expected error") + } + check(t, rootCmd.Flags().ShorthandLookup("v").Name, "notversion") + checkStringContains(t, err.Error(), "flag needs an argument: 'v' in -v") +} + +func TestShorthandVersionFlagOnlyAddedIfVersionNotDefined(t *testing.T) { + rootCmd := &Command{Use: "root", Run: emptyRun, Version: "1.2.3"} + rootCmd.Flags().Bool("version", false, "a different kind of version flag") + + _, err := executeCommand(rootCmd, "-v") + if err == nil { + t.Errorf("Expected error") + } + checkStringContains(t, err.Error(), "unknown shorthand flag: 'v' in -v") +} + func TestUsageIsNotPrintedTwice(t *testing.T) { var cmd = &Command{Use: "root"} var sub = &Command{Use: "sub"} From 6607e6b8603f56adb027298ee6695e06ffb3a819 Mon Sep 17 00:00:00 2001 From: Joshua Harshman Date: Fri, 27 Mar 2020 14:38:32 -0600 Subject: [PATCH 19/56] Partial Revert of #922 (#1068) Issue Reference: https://github.com/spf13/cobra/issues/1056 https://github.com/spf13/cobra/pull/922 introduced a new error type that emitted when a command was not runnable. This caused all commands w/o a run function set to error w/ that message and a status code of 1. This change reverts the addition of that new error. Similar functionality can be accomplished by leveraging RunE. --- command.go | 13 +------------ command_test.go | 4 ++-- 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/command.go b/command.go index 55ddc50f..fdac9d27 100644 --- a/command.go +++ b/command.go @@ -18,7 +18,6 @@ package cobra import ( "bytes" "context" - "errors" "fmt" "io" "os" @@ -29,8 +28,6 @@ import ( flag "github.com/spf13/pflag" ) -var ErrSubCommandRequired = errors.New("subcommand is required") - // FParseErrWhitelist configures Flag parse errors to be ignored type FParseErrWhitelist flag.ParseErrorsWhitelist @@ -801,7 +798,7 @@ func (c *Command) execute(a []string) (err error) { } if !c.Runnable() { - return ErrSubCommandRequired + return flag.ErrHelp } c.preRun() @@ -952,14 +949,6 @@ func (c *Command) ExecuteC() (cmd *Command, err error) { return cmd, nil } - // If command wasn't runnable, show full help, but do return the error. - // This will result in apps by default returning a non-success exit code, but also gives them the option to - // handle specially. - if err == ErrSubCommandRequired { - cmd.HelpFunc()(cmd, args) - return cmd, err - } - // If root command has SilentErrors flagged, // all subcommands should respect it if !cmd.SilenceErrors && !c.SilenceErrors { diff --git a/command_test.go b/command_test.go index 12155937..abee7ba7 100644 --- a/command_test.go +++ b/command_test.go @@ -903,8 +903,8 @@ func TestHelpExecutedOnNonRunnableChild(t *testing.T) { rootCmd.AddCommand(childCmd) output, err := executeCommand(rootCmd, "child") - if err != ErrSubCommandRequired { - t.Errorf("Expected error") + if err != nil { + t.Errorf("Unexpected error: %v", err) } checkStringContains(t, output, childCmd.Long) From f62883520e343a2f99f0326b04b7bf9a41dc0d98 Mon Sep 17 00:00:00 2001 From: Kanji Yomoda Date: Thu, 2 Apr 2020 01:25:22 +0900 Subject: [PATCH 20/56] Replace deprecated SetOutput func with SetOut and SetErr in test (#1053) --- cobra/cmd/golden_test.go | 6 ++++-- command_test.go | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/cobra/cmd/golden_test.go b/cobra/cmd/golden_test.go index 9010caa1..e4055f33 100644 --- a/cobra/cmd/golden_test.go +++ b/cobra/cmd/golden_test.go @@ -13,8 +13,10 @@ var update = flag.Bool("update", false, "update .golden files") func init() { // Mute commands. - addCmd.SetOutput(new(bytes.Buffer)) - initCmd.SetOutput(new(bytes.Buffer)) + addCmd.SetOut(new(bytes.Buffer)) + addCmd.SetErr(new(bytes.Buffer)) + initCmd.SetOut(new(bytes.Buffer)) + initCmd.SetErr(new(bytes.Buffer)) } // ensureLF converts any \r\n to \n diff --git a/command_test.go b/command_test.go index abee7ba7..8ec52f44 100644 --- a/command_test.go +++ b/command_test.go @@ -21,7 +21,8 @@ func executeCommand(root *Command, args ...string) (output string, err error) { func executeCommandWithContext(ctx context.Context, root *Command, args ...string) (output string, err error) { buf := new(bytes.Buffer) - root.SetOutput(buf) + root.SetOut(buf) + root.SetErr(buf) root.SetArgs(args) err = root.ExecuteContext(ctx) @@ -31,7 +32,8 @@ func executeCommandWithContext(ctx context.Context, root *Command, args ...strin func executeCommandC(root *Command, args ...string) (c *Command, output string, err error) { buf := new(bytes.Buffer) - root.SetOutput(buf) + root.SetOut(buf) + root.SetErr(buf) root.SetArgs(args) c, err = root.ExecuteC() From bbffa3aa944332b9d9f642d5fa0a551dc8b3abb5 Mon Sep 17 00:00:00 2001 From: Joshua Harshman Date: Wed, 1 Apr 2020 10:27:47 -0600 Subject: [PATCH 21/56] rm circle ci badge (#1073) --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 2f8175bc..a95b04f7 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,6 @@ Many of the most widely used Go projects are built using Cobra, such as: etc. [![Build Status](https://travis-ci.org/spf13/cobra.svg "Travis CI status")](https://travis-ci.org/spf13/cobra) -[![CircleCI status](https://circleci.com/gh/spf13/cobra.png?circle-token=:circle-token "CircleCI status")](https://circleci.com/gh/spf13/cobra) [![GoDoc](https://godoc.org/github.com/spf13/cobra?status.svg)](https://godoc.org/github.com/spf13/cobra) [![Go Report Card](https://goreportcard.com/badge/github.com/spf13/cobra)](https://goreportcard.com/report/github.com/spf13/cobra) From d88d9a09e9c65d0974864d85e7af363feb2a86b3 Mon Sep 17 00:00:00 2001 From: John Calabrese Date: Thu, 2 Apr 2020 11:26:08 -0400 Subject: [PATCH 22/56] Add Labeler Actions (#1074) * adds labels to PRs this will label PRs such that the maintainers have visibility into the breadth of the changes being suggested * Create labeler.yml * add all shell completions to labeler any changes to and autocomplete for any shell will now be auto labelled as impacting the autocompletion functionality --- .github/labeler.yml | 12 ++++++++++++ .github/workflows/label.yml | 19 +++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 .github/labeler.yml create mode 100644 .github/workflows/label.yml diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 00000000..a4982bf3 --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,12 @@ +# changes to documentation +"area/documentation": doc/**/* + +# changes to the core lib package +"area/lib": ./*.go + +# changes to the zsh completion +"area/*sh completion": + - ./zsh_* + - ./shell_* + - ./powershell_* + - ./bash_* diff --git a/.github/workflows/label.yml b/.github/workflows/label.yml new file mode 100644 index 00000000..e90b599b --- /dev/null +++ b/.github/workflows/label.yml @@ -0,0 +1,19 @@ +# This workflow will triage pull requests and apply a label based on the +# paths that are modified in the pull request. +# +# To use this workflow, you will need to set up a .github/labeler.yml +# file with configuration. For more information, see: +# https://github.com/actions/labeler/blob/master/README.md + +name: Labeler +on: [pull_request] + +jobs: + label: + + runs-on: ubuntu-latest + + steps: + - uses: actions/labeler@v2 + with: + repo-token: "${{ secrets.GITHUB_TOKEN }}" From 138b98f39dedc35ae01b827fb9bceacaf30b0880 Mon Sep 17 00:00:00 2001 From: John Calabrese Date: Thu, 2 Apr 2020 12:55:31 -0400 Subject: [PATCH 23/56] add support for autolabel stale PR (#1075) * add support for autolabel stale PR automatically labels stale PRs * stale label is kind/stale updates the stale label to reflect existing project labels * adds a relevant stale message for both PRs and issues there is now a more descriptive and relevant message when the action marks something stale * no auto-close on stale issues/prs uses the exempt flag to ignore issues already marked stale. this will allow us to auto label stale, and then ignore them so they do not get auto closed. --- .github/workflows/stale.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 .github/workflows/stale.yml diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 00000000..890ed012 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,21 @@ +name: Mark stale issues and pull requests + +on: + schedule: + - cron: "0 0 * * *" + +jobs: + stale: + + runs-on: ubuntu-latest + + steps: + - uses: actions/stale@v1 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + stale-issue-message: 'This issue is being marked as stale due to a long period of inactivity' + stale-pr-message: 'This PR is being marked as stale due to a long period of inactivity' + stale-issue-label: 'kind/stale' + stale-pr-label: 'kind/stale' + exempt-issue-label: 'kind/stale' + exempt-pr-label: 'kind/stale' From b80aeb17fc46362ff9cea51437a719322f8965ac Mon Sep 17 00:00:00 2001 From: Marc Khouzam Date: Fri, 3 Apr 2020 15:43:43 -0400 Subject: [PATCH 24/56] Add support for custom completions in Go (#1035) This commit allows programs using Cobra to code their custom completions in Go instead of Bash. The new ValidArgsFunction field is added for commands, similarly to ValidArgs. For flags, the new function Command.RegisterFlagCompletionFunc() is added. When either of the above functions is used, the bash completion script will call the new hidden command '__complete', passing it all command-line arguments. The '__complete' command will call the function specified by Command.ValidArgsFunction or by Command.RegisterFlagCompletionFunc to obtain completions from the program itself. Signed-off-by: Marc Khouzam --- bash_completions.go | 86 ++++++++- bash_completions.md | 235 ++++++++++++++++------ command.go | 7 + custom_completions.go | 299 ++++++++++++++++++++++++++++ custom_completions_test.go | 385 +++++++++++++++++++++++++++++++++++++ 5 files changed, 955 insertions(+), 57 deletions(-) create mode 100644 custom_completions.go create mode 100644 custom_completions_test.go diff --git a/bash_completions.go b/bash_completions.go index 1e0e25cf..d23f96b8 100644 --- a/bash_completions.go +++ b/bash_completions.go @@ -58,6 +58,67 @@ __%[1]s_contains_word() return 1 } +__%[1]s_handle_go_custom_completion() +{ + __%[1]s_debug "${FUNCNAME[0]}: cur is ${cur}, words[*] is ${words[*]}, #words[@] is ${#words[@]}" + + local out requestComp lastParam lastChar comp directive args + + # Prepare the command to request completions for the program. + # Calling ${words[0]} instead of directly %[1]s allows to handle aliases + args=("${words[@]:1}") + requestComp="${words[0]} %[2]s ${args[*]}" + + lastParam=${words[$((${#words[@]}-1))]} + lastChar=${lastParam:$((${#lastParam}-1)):1} + __%[1]s_debug "${FUNCNAME[0]}: lastParam ${lastParam}, lastChar ${lastChar}" + + if [ -z "${cur}" ] && [ "${lastChar}" != "=" ]; then + # If the last parameter is complete (there is a space following it) + # We add an extra empty parameter so we can indicate this to the go method. + __%[1]s_debug "${FUNCNAME[0]}: Adding extra empty parameter" + requestComp="${requestComp} \"\"" + fi + + __%[1]s_debug "${FUNCNAME[0]}: calling ${requestComp}" + # Use eval to handle any environment variables and such + out=$(eval "${requestComp}" 2>/dev/null) + + # Extract the directive integer at the very end of the output following a colon (:) + directive=${out##*:} + # Remove the directive + out=${out%%:*} + if [ "${directive}" = "${out}" ]; then + # There is not directive specified + directive=0 + fi + __%[1]s_debug "${FUNCNAME[0]}: the completion directive is: ${directive}" + __%[1]s_debug "${FUNCNAME[0]}: the completions are: ${out[*]}" + + if [ $((directive & %[3]d)) -ne 0 ]; then + # Error code. No completion. + __%[1]s_debug "${FUNCNAME[0]}: received error from custom completion go code" + return + else + if [ $((directive & %[4]d)) -ne 0 ]; then + if [[ $(type -t compopt) = "builtin" ]]; then + __%[1]s_debug "${FUNCNAME[0]}: activating no space" + compopt -o nospace + fi + fi + if [ $((directive & %[5]d)) -ne 0 ]; then + if [[ $(type -t compopt) = "builtin" ]]; then + __%[1]s_debug "${FUNCNAME[0]}: activating no file completion" + compopt +o default + fi + fi + + while IFS='' read -r comp; do + COMPREPLY+=("$comp") + done < <(compgen -W "${out[*]}" -- "$cur") + fi +} + __%[1]s_handle_reply() { __%[1]s_debug "${FUNCNAME[0]}" @@ -121,6 +182,10 @@ __%[1]s_handle_reply() completions=("${commands[@]}") if [[ ${#must_have_one_noun[@]} -ne 0 ]]; then completions=("${must_have_one_noun[@]}") + elif [[ -n "${has_completion_function}" ]]; then + # if a go completion function is provided, defer to that function + completions=() + __%[1]s_handle_go_custom_completion fi if [[ ${#must_have_one_flag[@]} -ne 0 ]]; then completions+=("${must_have_one_flag[@]}") @@ -279,7 +344,7 @@ __%[1]s_handle_word() __%[1]s_handle_word } -`, name)) +`, name, CompRequestCmd, BashCompDirectiveError, BashCompDirectiveNoSpace, BashCompDirectiveNoFileComp)) } func writePostscript(buf *bytes.Buffer, name string) { @@ -304,6 +369,7 @@ func writePostscript(buf *bytes.Buffer, name string) { local commands=("%[1]s") local must_have_one_flag=() local must_have_one_noun=() + local has_completion_function local last_command local nouns=() @@ -404,7 +470,22 @@ func writeLocalNonPersistentFlag(buf *bytes.Buffer, flag *pflag.Flag) { buf.WriteString(fmt.Sprintf(format, name)) } +// Setup annotations for go completions for registered flags +func prepareCustomAnnotationsForFlags(cmd *Command) { + for flag := range flagCompletionFunctions { + // Make sure the completion script calls the __*_go_custom_completion function for + // every registered flag. We need to do this here (and not when the flag was registered + // for completion) so that we can know the root command name for the prefix + // of ___go_custom_completion + if flag.Annotations == nil { + flag.Annotations = map[string][]string{} + } + flag.Annotations[BashCompCustom] = []string{fmt.Sprintf("__%[1]s_handle_go_custom_completion", cmd.Root().Name())} + } +} + func writeFlags(buf *bytes.Buffer, cmd *Command) { + prepareCustomAnnotationsForFlags(cmd) buf.WriteString(` flags=() two_word_flags=() local_nonpersistent_flags=() @@ -469,6 +550,9 @@ func writeRequiredNouns(buf *bytes.Buffer, cmd *Command) { for _, value := range cmd.ValidArgs { buf.WriteString(fmt.Sprintf(" must_have_one_noun+=(%q)\n", value)) } + if cmd.ValidArgsFunction != nil { + buf.WriteString(" has_completion_function=1\n") + } } func writeCmdAliases(buf *bytes.Buffer, cmd *Command) { diff --git a/bash_completions.md b/bash_completions.md index 4ac61ee1..db675972 100644 --- a/bash_completions.md +++ b/bash_completions.md @@ -56,7 +56,147 @@ func main() { `out.sh` will get you completions of subcommands and flags. Copy it to `/etc/bash_completion.d/` as described [here](https://debian-administration.org/article/316/An_introduction_to_bash_completion_part_1) and reset your terminal to use autocompletion. If you make additional annotations to your code, you can get even more intelligent and flexible behavior. -## Creating your own custom functions +## Have the completions code complete your 'nouns' + +### Static completion of nouns + +This method allows you to provide a pre-defined list of completion choices for your nouns using the `validArgs` field. +For example, if you want `kubectl get [tab][tab]` to show a list of valid "nouns" you have to set them. Simplified code from `kubectl get` looks like: + +```go +validArgs []string = { "pod", "node", "service", "replicationcontroller" } + +cmd := &cobra.Command{ + Use: "get [(-o|--output=)json|yaml|template|...] (RESOURCE [NAME] | RESOURCE/NAME ...)", + Short: "Display one or many resources", + Long: get_long, + Example: get_example, + Run: func(cmd *cobra.Command, args []string) { + err := RunGet(f, out, cmd, args) + util.CheckErr(err) + }, + ValidArgs: validArgs, +} +``` + +Notice we put the "ValidArgs" on the "get" subcommand. Doing so will give results like + +```bash +# kubectl get [tab][tab] +node pod replicationcontroller service +``` + +### Plural form and shortcuts for nouns + +If your nouns have a number of aliases, you can define them alongside `ValidArgs` using `ArgAliases`: + +```go +argAliases []string = { "pods", "nodes", "services", "svc", "replicationcontrollers", "rc" } + +cmd := &cobra.Command{ + ... + ValidArgs: validArgs, + ArgAliases: argAliases +} +``` + +The aliases are not shown to the user on tab completion, but they are accepted as valid nouns by +the completion algorithm if entered manually, e.g. in: + +```bash +# kubectl get rc [tab][tab] +backend frontend database +``` + +Note that without declaring `rc` as an alias, the completion algorithm would show the list of nouns +in this example again instead of the replication controllers. + +### Dynamic completion of nouns + +In some cases it is not possible to provide a list of possible completions in advance. Instead, the list of completions must be determined at execution-time. Cobra provides two ways of defining such dynamic completion of nouns. Note that both these methods can be used along-side each other as long as they are not both used for the same command. + +#### 1. Custom completions of nouns written in Go + +In a similar fashion as for static completions, you can use the `ValidArgsFunction` field to provide a Go function that Cobra will execute when it needs the list of completion choices for the nouns of a command. Note that either `ValidArgs` or `ValidArgsFunction` can be used for a single cobra command, but not both. +Simplified code from `helm status` looks like: + +```go +cmd := &cobra.Command{ + Use: "status RELEASE_NAME", + Short: "Display the status of the named release", + Long: status_long, + RunE: func(cmd *cobra.Command, args []string) { + RunGet(args[0]) + }, + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.BashCompDirective) { + if len(args) != 0 { + return nil, cobra.BashCompDirectiveNoFileComp + } + return getReleasesFromCluster(toComplete), cobra.BashCompDirectiveNoFileComp + }, +} +``` +Where `getReleasesFromCluster()` is a Go function that obtains the list of current Helm releases running on the Kubernetes cluster. +Notice we put the `ValidArgsFunction` on the `status` subcommand. Let's assume the Helm releases on the cluster are: `harbor`, `notary`, `rook` and `thanos` then this dynamic completion will give results like + +```bash +# helm status [tab][tab] +harbor notary rook thanos +``` +You may have noticed the use of `cobra.BashCompDirective`. These directives are bit fields allowing to control some shell completion behaviors for your particular completion. You can combine them with the bit-or operator such as `cobra.BashCompDirectiveNoSpace | cobra.BashCompDirectiveNoFileComp` +```go +// Indicates an error occurred and completions should be ignored. +BashCompDirectiveError +// Indicates that the shell should not add a space after the completion, +// even if there is a single completion provided. +BashCompDirectiveNoSpace +// Indicates that the shell should not provide file completion even when +// no completion is provided. +// This currently does not work for zsh or bash < 4 +BashCompDirectiveNoFileComp +// Indicates that the shell will perform its default behavior after completions +// have been provided (this implies !BashCompDirectiveNoSpace && !BashCompDirectiveNoFileComp). +BashCompDirectiveDefault +``` + +When using the `ValidArgsFunction`, Cobra will call your registered function after having parsed all flags and arguments provided in the command-line. You therefore don't need to do this parsing yourself. For example, when a user calls `helm status --namespace my-rook-ns [tab][tab]`, Cobra will call your registered `ValidArgsFunction` after having parsed the `--namespace` flag, as it would have done when calling the `RunE` function. + +##### Debugging + +Cobra achieves dynamic completions written in Go through the use of a hidden command called by the completion script. To debug your Go completion code, you can call this hidden command directly: +```bash +# helm __complete status har +harbor +:4 +Completion ended with directive: BashCompDirectiveNoFileComp # This is on stderr +``` +***Important:*** If the noun to complete is empty, you must pass an empty parameter to the `__complete` command: +```bash +# helm __complete status "" +harbor +notary +rook +thanos +:4 +Completion ended with directive: BashCompDirectiveNoFileComp # This is on stderr +``` +Calling the `__complete` command directly allows you to run the Go debugger to troubleshoot your code. You can also add printouts to your code; Cobra provides the following functions to use for printouts in Go completion code: +```go +// Prints to the completion script debug file (if BASH_COMP_DEBUG_FILE +// is set to a file path) and optionally prints to stderr. +cobra.CompDebug(msg string, printToStdErr bool) { +cobra.CompDebugln(msg string, printToStdErr bool) + +// Prints to the completion script debug file (if BASH_COMP_DEBUG_FILE +// is set to a file path) and to stderr. +cobra.CompError(msg string) +cobra.CompErrorln(msg string) +``` +***Important:*** You should **not** leave traces that print to stdout in your completion code as they will be interpreted as completion choices by the completion script. Instead, use the cobra-provided debugging traces functions mentioned above. + +#### 2. Custom completions of nouns written in Bash + +This method allows you to inject bash functions into the completion script. Those bash functions are responsible for providing the completion choices for your own completions. Some more actual code that works in kubernetes: @@ -111,58 +251,6 @@ Find more information at https://github.com/GoogleCloudPlatform/kubernetes.`, The `BashCompletionFunction` option is really only valid/useful on the root command. Doing the above will cause `__kubectl_custom_func()` (`___custom_func()`) to be called when the built in processor was unable to find a solution. In the case of kubernetes a valid command might look something like `kubectl get pod [mypod]`. If you type `kubectl get pod [tab][tab]` the `__kubectl_customc_func()` will run because the cobra.Command only understood "kubectl" and "get." `__kubectl_custom_func()` will see that the cobra.Command is "kubectl_get" and will thus call another helper `__kubectl_get_resource()`. `__kubectl_get_resource` will look at the 'nouns' collected. In our example the only noun will be `pod`. So it will call `__kubectl_parse_get pod`. `__kubectl_parse_get` will actually call out to kubernetes and get any pods. It will then set `COMPREPLY` to valid pods! -## Have the completions code complete your 'nouns' - -In the above example "pod" was assumed to already be typed. But if you want `kubectl get [tab][tab]` to show a list of valid "nouns" you have to set them. Simplified code from `kubectl get` looks like: - -```go -validArgs []string = { "pod", "node", "service", "replicationcontroller" } - -cmd := &cobra.Command{ - Use: "get [(-o|--output=)json|yaml|template|...] (RESOURCE [NAME] | RESOURCE/NAME ...)", - Short: "Display one or many resources", - Long: get_long, - Example: get_example, - Run: func(cmd *cobra.Command, args []string) { - err := RunGet(f, out, cmd, args) - util.CheckErr(err) - }, - ValidArgs: validArgs, -} -``` - -Notice we put the "ValidArgs" on the "get" subcommand. Doing so will give results like - -```bash -# kubectl get [tab][tab] -node pod replicationcontroller service -``` - -## Plural form and shortcuts for nouns - -If your nouns have a number of aliases, you can define them alongside `ValidArgs` using `ArgAliases`: - -```go -argAliases []string = { "pods", "nodes", "services", "svc", "replicationcontrollers", "rc" } - -cmd := &cobra.Command{ - ... - ValidArgs: validArgs, - ArgAliases: argAliases -} -``` - -The aliases are not shown to the user on tab completion, but they are accepted as valid nouns by -the completion algorithm if entered manually, e.g. in: - -```bash -# kubectl get rc [tab][tab] -backend frontend database -``` - -Note that without declaring `rc` as an alias, the completion algorithm would show the list of nouns -in this example again instead of the replication controllers. - ## Mark flags as required Most of the time completions will only show subcommands. But if a flag is required to make a subcommand work, you probably want it to show up when the user types [tab][tab]. Marking a flag as 'Required' is incredibly easy. @@ -211,8 +299,43 @@ So while there are many other files in the CWD it only shows me subdirs and thos # Specify custom flag completion -Similar to the filename completion and filtering using cobra.BashCompFilenameExt, you can specify -a custom flag completion function with cobra.BashCompCustom: +As for nouns, Cobra provides two ways of defining dynamic completion of flags. Note that both these methods can be used along-side each other as long as they are not both used for the same flag. + +## 1. Custom completions of flags written in Go + +To provide a Go function that Cobra will execute when it needs the list of completion choices for a flag, you must register the function in the following manner: + +```go +flagName := "output" +cmd.RegisterFlagCompletionFunc(flagName, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.BashCompDirective) { + return []string{"json", "table", "yaml"}, cobra.BashCompDirectiveDefault +}) +``` +Notice that calling `RegisterFlagCompletionFunc()` is done through the `command` with which the flag is associated. In our example this dynamic completion will give results like so: + +```bash +# helm status --output [tab][tab] +json table yaml +``` + +### Debugging + +You can also easily debug your Go completion code for flags: +```bash +# helm __complete status --output "" +json +table +yaml +:4 +Completion ended with directive: BashCompDirectiveNoFileComp # This is on stderr +``` +***Important:*** You should **not** leave traces that print to stdout in your completion code as they will be interpreted as completion choices by the completion script. Instead, use the cobra-provided debugging traces functions mentioned in the above section. + +## 2. Custom completions of flags written in Bash + +Alternatively, you can use bash code for flag custom completion. Similar to the filename +completion and filtering using `cobra.BashCompFilenameExt`, you can specify +a custom flag completion bash function with `cobra.BashCompCustom`: ```go annotation := make(map[string][]string) @@ -226,7 +349,7 @@ a custom flag completion function with cobra.BashCompCustom: cmd.Flags().AddFlag(flag) ``` -In addition add the `__handle_namespace_flag` implementation in the `BashCompletionFunction` +In addition add the `__kubectl_get_namespaces` implementation in the `BashCompletionFunction` value, e.g.: ```bash diff --git a/command.go b/command.go index fdac9d27..cf02313f 100644 --- a/command.go +++ b/command.go @@ -57,6 +57,10 @@ type Command struct { // ValidArgs is list of all valid non-flag arguments that are accepted in bash completions ValidArgs []string + // ValidArgsFunction is an optional function that provides valid non-flag arguments for bash completion. + // It is a dynamic version of using ValidArgs. + // Only one of ValidArgs and ValidArgsFunction can be used for a command. + ValidArgsFunction func(cmd *Command, args []string, toComplete string) ([]string, BashCompDirective) // Expected arguments Args PositionalArgs @@ -911,6 +915,9 @@ func (c *Command) ExecuteC() (cmd *Command, err error) { args = os.Args[1:] } + // initialize the hidden command to be used for bash completion + c.initCompleteCmd(args) + var flags []string if c.TraverseChildren { cmd, flags, err = c.Traverse(args) diff --git a/custom_completions.go b/custom_completions.go new file mode 100644 index 00000000..64c79015 --- /dev/null +++ b/custom_completions.go @@ -0,0 +1,299 @@ +package cobra + +import ( + "errors" + "fmt" + "os" + "strings" + + "github.com/spf13/pflag" +) + +// CompRequestCmd is the name of the hidden command that is used to request +// completion results from the program. It is used by the shell completion script. +const CompRequestCmd = "__complete" + +// Global map of flag completion functions. +var flagCompletionFunctions = map[*pflag.Flag]func(cmd *Command, args []string, toComplete string) ([]string, BashCompDirective){} + +// BashCompDirective is a bit map representing the different behaviors the shell +// can be instructed to have once completions have been provided. +type BashCompDirective int + +const ( + // BashCompDirectiveError indicates an error occurred and completions should be ignored. + BashCompDirectiveError BashCompDirective = 1 << iota + + // BashCompDirectiveNoSpace indicates that the shell should not add a space + // after the completion even if there is a single completion provided. + BashCompDirectiveNoSpace + + // BashCompDirectiveNoFileComp indicates that the shell should not provide + // file completion even when no completion is provided. + // This currently does not work for zsh or bash < 4 + BashCompDirectiveNoFileComp + + // BashCompDirectiveDefault indicates to let the shell perform its default + // behavior after completions have been provided. + BashCompDirectiveDefault BashCompDirective = 0 +) + +// RegisterFlagCompletionFunc should be called to register a function to provide completion for a flag. +func (c *Command) RegisterFlagCompletionFunc(flagName string, f func(cmd *Command, args []string, toComplete string) ([]string, BashCompDirective)) error { + flag := c.Flag(flagName) + if flag == nil { + return fmt.Errorf("RegisterFlagCompletionFunc: flag '%s' does not exist", flagName) + } + if _, exists := flagCompletionFunctions[flag]; exists { + return fmt.Errorf("RegisterFlagCompletionFunc: flag '%s' already registered", flagName) + } + flagCompletionFunctions[flag] = f + return nil +} + +// Returns a string listing the different directive enabled in the specified parameter +func (d BashCompDirective) string() string { + var directives []string + if d&BashCompDirectiveError != 0 { + directives = append(directives, "BashCompDirectiveError") + } + if d&BashCompDirectiveNoSpace != 0 { + directives = append(directives, "BashCompDirectiveNoSpace") + } + if d&BashCompDirectiveNoFileComp != 0 { + directives = append(directives, "BashCompDirectiveNoFileComp") + } + if len(directives) == 0 { + directives = append(directives, "BashCompDirectiveDefault") + } + + if d > BashCompDirectiveError+BashCompDirectiveNoSpace+BashCompDirectiveNoFileComp { + return fmt.Sprintf("ERROR: unexpected BashCompDirective value: %d", d) + } + return strings.Join(directives, ", ") +} + +// Adds a special hidden command that can be used to request custom completions. +func (c *Command) initCompleteCmd(args []string) { + completeCmd := &Command{ + Use: fmt.Sprintf("%s [command-line]", CompRequestCmd), + DisableFlagsInUseLine: true, + Hidden: true, + DisableFlagParsing: true, + Args: MinimumNArgs(1), + Short: "Request shell completion choices for the specified command-line", + Long: fmt.Sprintf("%[2]s is a special command that is used by the shell completion logic\n%[1]s", + "to request completion choices for the specified command-line.", CompRequestCmd), + Run: func(cmd *Command, args []string) { + finalCmd, completions, directive, err := cmd.getCompletions(args) + if err != nil { + CompErrorln(err.Error()) + // Keep going for multiple reasons: + // 1- There could be some valid completions even though there was an error + // 2- Even without completions, we need to print the directive + } + + for _, comp := range completions { + // Print each possible completion to stdout for the completion script to consume. + fmt.Fprintln(finalCmd.OutOrStdout(), comp) + } + + if directive > BashCompDirectiveError+BashCompDirectiveNoSpace+BashCompDirectiveNoFileComp { + directive = BashCompDirectiveDefault + } + + // As the last printout, print the completion directive for the completion script to parse. + // The directive integer must be that last character following a single colon (:). + // The completion script expects : + fmt.Fprintf(finalCmd.OutOrStdout(), ":%d\n", directive) + + // Print some helpful info to stderr for the user to understand. + // Output from stderr must be ignored by the completion script. + fmt.Fprintf(finalCmd.ErrOrStderr(), "Completion ended with directive: %s\n", directive.string()) + }, + } + c.AddCommand(completeCmd) + subCmd, _, err := c.Find(args) + if err != nil || subCmd.Name() != CompRequestCmd { + // Only create this special command if it is actually being called. + // This reduces possible side-effects of creating such a command; + // for example, having this command would cause problems to a + // cobra program that only consists of the root command, since this + // command would cause the root command to suddenly have a subcommand. + c.RemoveCommand(completeCmd) + } +} + +func (c *Command) getCompletions(args []string) (*Command, []string, BashCompDirective, error) { + var completions []string + + // The last argument, which is not completely typed by the user, + // should not be part of the list of arguments + toComplete := args[len(args)-1] + trimmedArgs := args[:len(args)-1] + + // Find the real command for which completion must be performed + finalCmd, finalArgs, err := c.Root().Find(trimmedArgs) + if err != nil { + // Unable to find the real command. E.g., someInvalidCmd + return c, completions, BashCompDirectiveDefault, fmt.Errorf("Unable to find a command for arguments: %v", trimmedArgs) + } + + var flag *pflag.Flag + if !finalCmd.DisableFlagParsing { + // We only do flag completion if we are allowed to parse flags + // This is important for commands which have requested to do their own flag completion. + flag, finalArgs, toComplete, err = checkIfFlagCompletion(finalCmd, finalArgs, toComplete) + if err != nil { + // Error while attempting to parse flags + return finalCmd, completions, BashCompDirectiveDefault, err + } + } + + // Parse the flags and extract the arguments to prepare for calling the completion function + if err = finalCmd.ParseFlags(finalArgs); err != nil { + return finalCmd, completions, BashCompDirectiveDefault, fmt.Errorf("Error while parsing flags from args %v: %s", finalArgs, err.Error()) + } + + // We only remove the flags from the arguments if DisableFlagParsing is not set. + // This is important for commands which have requested to do their own flag completion. + if !finalCmd.DisableFlagParsing { + finalArgs = finalCmd.Flags().Args() + } + + // Find the completion function for the flag or command + var completionFn func(cmd *Command, args []string, toComplete string) ([]string, BashCompDirective) + if flag != nil { + completionFn = flagCompletionFunctions[flag] + } else { + completionFn = finalCmd.ValidArgsFunction + } + if completionFn == nil { + // Go custom completion not supported/needed for this flag or command + return finalCmd, completions, BashCompDirectiveDefault, nil + } + + // Call the registered completion function to get the completions + comps, directive := completionFn(finalCmd, finalArgs, toComplete) + completions = append(completions, comps...) + return finalCmd, completions, directive, nil +} + +func checkIfFlagCompletion(finalCmd *Command, args []string, lastArg string) (*pflag.Flag, []string, string, error) { + var flagName string + trimmedArgs := args + flagWithEqual := false + if isFlagArg(lastArg) { + if index := strings.Index(lastArg, "="); index >= 0 { + flagName = strings.TrimLeft(lastArg[:index], "-") + lastArg = lastArg[index+1:] + flagWithEqual = true + } else { + return nil, nil, "", errors.New("Unexpected completion request for flag") + } + } + + if len(flagName) == 0 { + if len(args) > 0 { + prevArg := args[len(args)-1] + if isFlagArg(prevArg) { + // Only consider the case where the flag does not contain an =. + // If the flag contains an = it means it has already been fully processed, + // so we don't need to deal with it here. + if index := strings.Index(prevArg, "="); index < 0 { + flagName = strings.TrimLeft(prevArg, "-") + + // Remove the uncompleted flag or else there could be an error created + // for an invalid value for that flag + trimmedArgs = args[:len(args)-1] + } + } + } + } + + if len(flagName) == 0 { + // Not doing flag completion + return nil, trimmedArgs, lastArg, nil + } + + flag := findFlag(finalCmd, flagName) + if flag == nil { + // Flag not supported by this command, nothing to complete + err := fmt.Errorf("Subcommand '%s' does not support flag '%s'", finalCmd.Name(), flagName) + return nil, nil, "", err + } + + if !flagWithEqual { + if len(flag.NoOptDefVal) != 0 { + // We had assumed dealing with a two-word flag but the flag is a boolean flag. + // In that case, there is no value following it, so we are not really doing flag completion. + // Reset everything to do noun completion. + trimmedArgs = args + flag = nil + } + } + + return flag, trimmedArgs, lastArg, nil +} + +func findFlag(cmd *Command, name string) *pflag.Flag { + flagSet := cmd.Flags() + if len(name) == 1 { + // First convert the short flag into a long flag + // as the cmd.Flag() search only accepts long flags + if short := flagSet.ShorthandLookup(name); short != nil { + name = short.Name + } else { + set := cmd.InheritedFlags() + if short = set.ShorthandLookup(name); short != nil { + name = short.Name + } else { + return nil + } + } + } + return cmd.Flag(name) +} + +// CompDebug prints the specified string to the same file as where the +// completion script prints its logs. +// Note that completion printouts should never be on stdout as they would +// be wrongly interpreted as actual completion choices by the completion script. +func CompDebug(msg string, printToStdErr bool) { + msg = fmt.Sprintf("[Debug] %s", msg) + + // Such logs are only printed when the user has set the environment + // variable BASH_COMP_DEBUG_FILE to the path of some file to be used. + if path := os.Getenv("BASH_COMP_DEBUG_FILE"); path != "" { + f, err := os.OpenFile(path, + os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err == nil { + defer f.Close() + f.WriteString(msg) + } + } + + if printToStdErr { + // Must print to stderr for this not to be read by the completion script. + fmt.Fprintf(os.Stderr, msg) + } +} + +// CompDebugln prints the specified string with a newline at the end +// to the same file as where the completion script prints its logs. +// Such logs are only printed when the user has set the environment +// variable BASH_COMP_DEBUG_FILE to the path of some file to be used. +func CompDebugln(msg string, printToStdErr bool) { + CompDebug(fmt.Sprintf("%s\n", msg), printToStdErr) +} + +// CompError prints the specified completion message to stderr. +func CompError(msg string) { + msg = fmt.Sprintf("[Error] %s", msg) + CompDebug(msg, true) +} + +// CompErrorln prints the specified completion message to stderr with a newline at the end. +func CompErrorln(msg string) { + CompError(fmt.Sprintf("%s\n", msg)) +} diff --git a/custom_completions_test.go b/custom_completions_test.go new file mode 100644 index 00000000..61d214d6 --- /dev/null +++ b/custom_completions_test.go @@ -0,0 +1,385 @@ +package cobra + +import ( + "bytes" + "strings" + "testing" +) + +func validArgsFunc(cmd *Command, args []string, toComplete string) ([]string, BashCompDirective) { + if len(args) != 0 { + return nil, BashCompDirectiveNoFileComp + } + + var completions []string + for _, comp := range []string{"one", "two"} { + if strings.HasPrefix(comp, toComplete) { + completions = append(completions, comp) + } + } + return completions, BashCompDirectiveDefault +} + +func validArgsFunc2(cmd *Command, args []string, toComplete string) ([]string, BashCompDirective) { + if len(args) != 0 { + return nil, BashCompDirectiveNoFileComp + } + + var completions []string + for _, comp := range []string{"three", "four"} { + if strings.HasPrefix(comp, toComplete) { + completions = append(completions, comp) + } + } + return completions, BashCompDirectiveDefault +} + +func TestValidArgsFuncSingleCmd(t *testing.T) { + rootCmd := &Command{ + Use: "root", + ValidArgsFunction: validArgsFunc, + Run: emptyRun, + } + + // Test completing an empty string + output, err := executeCommand(rootCmd, CompRequestCmd, "") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected := strings.Join([]string{ + "one", + "two", + ":0", + "Completion ended with directive: BashCompDirectiveDefault", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Check completing with a prefix + output, err = executeCommand(rootCmd, CompRequestCmd, "t") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + "two", + ":0", + "Completion ended with directive: BashCompDirectiveDefault", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } +} + +func TestValidArgsFuncSingleCmdInvalidArg(t *testing.T) { + rootCmd := &Command{ + Use: "root", + // If we don't specify a value for Args, this test fails. + // This is only true for a root command without any subcommands, and is caused + // by the fact that the __complete command becomes a subcommand when there should not be one. + // The problem is in the implementation of legacyArgs(). + Args: MinimumNArgs(1), + ValidArgsFunction: validArgsFunc, + Run: emptyRun, + } + + // Check completing with wrong number of args + output, err := executeCommand(rootCmd, CompRequestCmd, "unexpectedArg", "t") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected := strings.Join([]string{ + ":4", + "Completion ended with directive: BashCompDirectiveNoFileComp", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } +} + +func TestValidArgsFuncChildCmds(t *testing.T) { + rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun} + child1Cmd := &Command{ + Use: "child1", + ValidArgsFunction: validArgsFunc, + Run: emptyRun, + } + child2Cmd := &Command{ + Use: "child2", + ValidArgsFunction: validArgsFunc2, + Run: emptyRun, + } + rootCmd.AddCommand(child1Cmd, child2Cmd) + + // Test completion of first sub-command with empty argument + output, err := executeCommand(rootCmd, CompRequestCmd, "child1", "") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected := strings.Join([]string{ + "one", + "two", + ":0", + "Completion ended with directive: BashCompDirectiveDefault", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Test completion of first sub-command with a prefix to complete + output, err = executeCommand(rootCmd, CompRequestCmd, "child1", "t") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + "two", + ":0", + "Completion ended with directive: BashCompDirectiveDefault", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Check completing with wrong number of args + output, err = executeCommand(rootCmd, CompRequestCmd, "child1", "unexpectedArg", "t") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + ":4", + "Completion ended with directive: BashCompDirectiveNoFileComp", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Test completion of second sub-command with empty argument + output, err = executeCommand(rootCmd, CompRequestCmd, "child2", "") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + "three", + "four", + ":0", + "Completion ended with directive: BashCompDirectiveDefault", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + output, err = executeCommand(rootCmd, CompRequestCmd, "child2", "t") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + "three", + ":0", + "Completion ended with directive: BashCompDirectiveDefault", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Check completing with wrong number of args + output, err = executeCommand(rootCmd, CompRequestCmd, "child2", "unexpectedArg", "t") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + ":4", + "Completion ended with directive: BashCompDirectiveNoFileComp", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } +} + +func TestValidArgsFuncAliases(t *testing.T) { + rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun} + child := &Command{ + Use: "child", + Aliases: []string{"son", "daughter"}, + ValidArgsFunction: validArgsFunc, + Run: emptyRun, + } + rootCmd.AddCommand(child) + + // Test completion of first sub-command with empty argument + output, err := executeCommand(rootCmd, CompRequestCmd, "son", "") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected := strings.Join([]string{ + "one", + "two", + ":0", + "Completion ended with directive: BashCompDirectiveDefault", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Test completion of first sub-command with a prefix to complete + output, err = executeCommand(rootCmd, CompRequestCmd, "daughter", "t") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + "two", + ":0", + "Completion ended with directive: BashCompDirectiveDefault", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Check completing with wrong number of args + output, err = executeCommand(rootCmd, CompRequestCmd, "son", "unexpectedArg", "t") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + ":4", + "Completion ended with directive: BashCompDirectiveNoFileComp", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } +} + +func TestValidArgsFuncInScript(t *testing.T) { + rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun} + child := &Command{ + Use: "child", + ValidArgsFunction: validArgsFunc, + Run: emptyRun, + } + rootCmd.AddCommand(child) + + buf := new(bytes.Buffer) + rootCmd.GenBashCompletion(buf) + output := buf.String() + + check(t, output, "has_completion_function=1") +} + +func TestNoValidArgsFuncInScript(t *testing.T) { + rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun} + child := &Command{ + Use: "child", + Run: emptyRun, + } + rootCmd.AddCommand(child) + + buf := new(bytes.Buffer) + rootCmd.GenBashCompletion(buf) + output := buf.String() + + checkOmit(t, output, "has_completion_function=1") +} + +func TestFlagCompletionInGo(t *testing.T) { + rootCmd := &Command{ + Use: "root", + Run: emptyRun, + } + rootCmd.Flags().IntP("introot", "i", -1, "help message for flag introot") + rootCmd.RegisterFlagCompletionFunc("introot", func(cmd *Command, args []string, toComplete string) ([]string, BashCompDirective) { + completions := []string{} + for _, comp := range []string{"1", "2", "10"} { + if strings.HasPrefix(comp, toComplete) { + completions = append(completions, comp) + } + } + return completions, BashCompDirectiveDefault + }) + rootCmd.Flags().String("filename", "", "Enter a filename") + rootCmd.RegisterFlagCompletionFunc("filename", func(cmd *Command, args []string, toComplete string) ([]string, BashCompDirective) { + completions := []string{} + for _, comp := range []string{"file.yaml", "myfile.json", "file.xml"} { + if strings.HasPrefix(comp, toComplete) { + completions = append(completions, comp) + } + } + return completions, BashCompDirectiveNoSpace | BashCompDirectiveNoFileComp + }) + + // Test completing an empty string + output, err := executeCommand(rootCmd, CompRequestCmd, "--introot", "") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected := strings.Join([]string{ + "1", + "2", + "10", + ":0", + "Completion ended with directive: BashCompDirectiveDefault", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Check completing with a prefix + output, err = executeCommand(rootCmd, CompRequestCmd, "--introot", "1") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + "1", + "10", + ":0", + "Completion ended with directive: BashCompDirectiveDefault", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Test completing an empty string + output, err = executeCommand(rootCmd, CompRequestCmd, "--filename", "") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + "file.yaml", + "myfile.json", + "file.xml", + ":6", + "Completion ended with directive: BashCompDirectiveNoSpace, BashCompDirectiveNoFileComp", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Check completing with a prefix + output, err = executeCommand(rootCmd, CompRequestCmd, "--filename", "f") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + "file.yaml", + "file.xml", + ":6", + "Completion ended with directive: BashCompDirectiveNoSpace, BashCompDirectiveNoFileComp", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } +} From b84ef40338051c81c2916fe90c7e73e6e5583182 Mon Sep 17 00:00:00 2001 From: Marc Khouzam Date: Mon, 6 Apr 2020 13:28:44 -0400 Subject: [PATCH 25/56] Rename BashCompDirectives to ShellCompDirectives (#1082) Since the completion directives will be used for all shells, and that these names will be consumed by users, this is a more appropriate name. Signed-off-by: Marc Khouzam --- bash_completions.go | 2 +- bash_completions.md | 28 ++++++------- command.go | 2 +- custom_completions.go | 70 +++++++++++++++---------------- custom_completions_test.go | 84 +++++++++++++++++++------------------- 5 files changed, 93 insertions(+), 93 deletions(-) diff --git a/bash_completions.go b/bash_completions.go index d23f96b8..b4098f4a 100644 --- a/bash_completions.go +++ b/bash_completions.go @@ -344,7 +344,7 @@ __%[1]s_handle_word() __%[1]s_handle_word } -`, name, CompRequestCmd, BashCompDirectiveError, BashCompDirectiveNoSpace, BashCompDirectiveNoFileComp)) +`, name, ShellCompRequestCmd, ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp)) } func writePostscript(buf *bytes.Buffer, name string) { diff --git a/bash_completions.md b/bash_completions.md index db675972..e1590e1b 100644 --- a/bash_completions.md +++ b/bash_completions.md @@ -128,11 +128,11 @@ cmd := &cobra.Command{ RunE: func(cmd *cobra.Command, args []string) { RunGet(args[0]) }, - ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.BashCompDirective) { + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if len(args) != 0 { - return nil, cobra.BashCompDirectiveNoFileComp + return nil, cobra.ShellCompDirectiveNoFileComp } - return getReleasesFromCluster(toComplete), cobra.BashCompDirectiveNoFileComp + return getReleasesFromCluster(toComplete), cobra.ShellCompDirectiveNoFileComp }, } ``` @@ -143,20 +143,20 @@ Notice we put the `ValidArgsFunction` on the `status` subcommand. Let's assume t # helm status [tab][tab] harbor notary rook thanos ``` -You may have noticed the use of `cobra.BashCompDirective`. These directives are bit fields allowing to control some shell completion behaviors for your particular completion. You can combine them with the bit-or operator such as `cobra.BashCompDirectiveNoSpace | cobra.BashCompDirectiveNoFileComp` +You may have noticed the use of `cobra.ShellCompDirective`. These directives are bit fields allowing to control some shell completion behaviors for your particular completion. You can combine them with the bit-or operator such as `cobra.ShellCompDirectiveNoSpace | cobra.ShellCompDirectiveNoFileComp` ```go // Indicates an error occurred and completions should be ignored. -BashCompDirectiveError +ShellCompDirectiveError // Indicates that the shell should not add a space after the completion, // even if there is a single completion provided. -BashCompDirectiveNoSpace +ShellCompDirectiveNoSpace // Indicates that the shell should not provide file completion even when // no completion is provided. // This currently does not work for zsh or bash < 4 -BashCompDirectiveNoFileComp +ShellCompDirectiveNoFileComp // Indicates that the shell will perform its default behavior after completions -// have been provided (this implies !BashCompDirectiveNoSpace && !BashCompDirectiveNoFileComp). -BashCompDirectiveDefault +// have been provided (this implies !ShellCompDirectiveNoSpace && !ShellCompDirectiveNoFileComp). +ShellCompDirectiveDefault ``` When using the `ValidArgsFunction`, Cobra will call your registered function after having parsed all flags and arguments provided in the command-line. You therefore don't need to do this parsing yourself. For example, when a user calls `helm status --namespace my-rook-ns [tab][tab]`, Cobra will call your registered `ValidArgsFunction` after having parsed the `--namespace` flag, as it would have done when calling the `RunE` function. @@ -168,7 +168,7 @@ Cobra achieves dynamic completions written in Go through the use of a hidden com # helm __complete status har harbor :4 -Completion ended with directive: BashCompDirectiveNoFileComp # This is on stderr +Completion ended with directive: ShellCompDirectiveNoFileComp # This is on stderr ``` ***Important:*** If the noun to complete is empty, you must pass an empty parameter to the `__complete` command: ```bash @@ -178,7 +178,7 @@ notary rook thanos :4 -Completion ended with directive: BashCompDirectiveNoFileComp # This is on stderr +Completion ended with directive: ShellCompDirectiveNoFileComp # This is on stderr ``` Calling the `__complete` command directly allows you to run the Go debugger to troubleshoot your code. You can also add printouts to your code; Cobra provides the following functions to use for printouts in Go completion code: ```go @@ -307,8 +307,8 @@ To provide a Go function that Cobra will execute when it needs the list of compl ```go flagName := "output" -cmd.RegisterFlagCompletionFunc(flagName, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.BashCompDirective) { - return []string{"json", "table", "yaml"}, cobra.BashCompDirectiveDefault +cmd.RegisterFlagCompletionFunc(flagName, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return []string{"json", "table", "yaml"}, cobra.ShellCompDirectiveDefault }) ``` Notice that calling `RegisterFlagCompletionFunc()` is done through the `command` with which the flag is associated. In our example this dynamic completion will give results like so: @@ -327,7 +327,7 @@ json table yaml :4 -Completion ended with directive: BashCompDirectiveNoFileComp # This is on stderr +Completion ended with directive: ShellCompDirectiveNoFileComp # This is on stderr ``` ***Important:*** You should **not** leave traces that print to stdout in your completion code as they will be interpreted as completion choices by the completion script. Instead, use the cobra-provided debugging traces functions mentioned in the above section. diff --git a/command.go b/command.go index cf02313f..88e6ed77 100644 --- a/command.go +++ b/command.go @@ -60,7 +60,7 @@ type Command struct { // ValidArgsFunction is an optional function that provides valid non-flag arguments for bash completion. // It is a dynamic version of using ValidArgs. // Only one of ValidArgs and ValidArgsFunction can be used for a command. - ValidArgsFunction func(cmd *Command, args []string, toComplete string) ([]string, BashCompDirective) + ValidArgsFunction func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) // Expected arguments Args PositionalArgs diff --git a/custom_completions.go b/custom_completions.go index 64c79015..81f1bc38 100644 --- a/custom_completions.go +++ b/custom_completions.go @@ -9,37 +9,37 @@ import ( "github.com/spf13/pflag" ) -// CompRequestCmd is the name of the hidden command that is used to request +// ShellCompRequestCmd is the name of the hidden command that is used to request // completion results from the program. It is used by the shell completion script. -const CompRequestCmd = "__complete" +const ShellCompRequestCmd = "__complete" // Global map of flag completion functions. -var flagCompletionFunctions = map[*pflag.Flag]func(cmd *Command, args []string, toComplete string) ([]string, BashCompDirective){} +var flagCompletionFunctions = map[*pflag.Flag]func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective){} -// BashCompDirective is a bit map representing the different behaviors the shell +// ShellCompDirective is a bit map representing the different behaviors the shell // can be instructed to have once completions have been provided. -type BashCompDirective int +type ShellCompDirective int const ( - // BashCompDirectiveError indicates an error occurred and completions should be ignored. - BashCompDirectiveError BashCompDirective = 1 << iota + // ShellCompDirectiveError indicates an error occurred and completions should be ignored. + ShellCompDirectiveError ShellCompDirective = 1 << iota - // BashCompDirectiveNoSpace indicates that the shell should not add a space + // ShellCompDirectiveNoSpace indicates that the shell should not add a space // after the completion even if there is a single completion provided. - BashCompDirectiveNoSpace + ShellCompDirectiveNoSpace - // BashCompDirectiveNoFileComp indicates that the shell should not provide + // ShellCompDirectiveNoFileComp indicates that the shell should not provide // file completion even when no completion is provided. // This currently does not work for zsh or bash < 4 - BashCompDirectiveNoFileComp + ShellCompDirectiveNoFileComp - // BashCompDirectiveDefault indicates to let the shell perform its default + // ShellCompDirectiveDefault indicates to let the shell perform its default // behavior after completions have been provided. - BashCompDirectiveDefault BashCompDirective = 0 + ShellCompDirectiveDefault ShellCompDirective = 0 ) // RegisterFlagCompletionFunc should be called to register a function to provide completion for a flag. -func (c *Command) RegisterFlagCompletionFunc(flagName string, f func(cmd *Command, args []string, toComplete string) ([]string, BashCompDirective)) error { +func (c *Command) RegisterFlagCompletionFunc(flagName string, f func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective)) error { flag := c.Flag(flagName) if flag == nil { return fmt.Errorf("RegisterFlagCompletionFunc: flag '%s' does not exist", flagName) @@ -52,23 +52,23 @@ func (c *Command) RegisterFlagCompletionFunc(flagName string, f func(cmd *Comman } // Returns a string listing the different directive enabled in the specified parameter -func (d BashCompDirective) string() string { +func (d ShellCompDirective) string() string { var directives []string - if d&BashCompDirectiveError != 0 { - directives = append(directives, "BashCompDirectiveError") + if d&ShellCompDirectiveError != 0 { + directives = append(directives, "ShellCompDirectiveError") } - if d&BashCompDirectiveNoSpace != 0 { - directives = append(directives, "BashCompDirectiveNoSpace") + if d&ShellCompDirectiveNoSpace != 0 { + directives = append(directives, "ShellCompDirectiveNoSpace") } - if d&BashCompDirectiveNoFileComp != 0 { - directives = append(directives, "BashCompDirectiveNoFileComp") + if d&ShellCompDirectiveNoFileComp != 0 { + directives = append(directives, "ShellCompDirectiveNoFileComp") } if len(directives) == 0 { - directives = append(directives, "BashCompDirectiveDefault") + directives = append(directives, "ShellCompDirectiveDefault") } - if d > BashCompDirectiveError+BashCompDirectiveNoSpace+BashCompDirectiveNoFileComp { - return fmt.Sprintf("ERROR: unexpected BashCompDirective value: %d", d) + if d > ShellCompDirectiveError+ShellCompDirectiveNoSpace+ShellCompDirectiveNoFileComp { + return fmt.Sprintf("ERROR: unexpected ShellCompDirective value: %d", d) } return strings.Join(directives, ", ") } @@ -76,14 +76,14 @@ func (d BashCompDirective) string() string { // Adds a special hidden command that can be used to request custom completions. func (c *Command) initCompleteCmd(args []string) { completeCmd := &Command{ - Use: fmt.Sprintf("%s [command-line]", CompRequestCmd), + Use: fmt.Sprintf("%s [command-line]", ShellCompRequestCmd), DisableFlagsInUseLine: true, Hidden: true, DisableFlagParsing: true, Args: MinimumNArgs(1), Short: "Request shell completion choices for the specified command-line", Long: fmt.Sprintf("%[2]s is a special command that is used by the shell completion logic\n%[1]s", - "to request completion choices for the specified command-line.", CompRequestCmd), + "to request completion choices for the specified command-line.", ShellCompRequestCmd), Run: func(cmd *Command, args []string) { finalCmd, completions, directive, err := cmd.getCompletions(args) if err != nil { @@ -98,8 +98,8 @@ func (c *Command) initCompleteCmd(args []string) { fmt.Fprintln(finalCmd.OutOrStdout(), comp) } - if directive > BashCompDirectiveError+BashCompDirectiveNoSpace+BashCompDirectiveNoFileComp { - directive = BashCompDirectiveDefault + if directive > ShellCompDirectiveError+ShellCompDirectiveNoSpace+ShellCompDirectiveNoFileComp { + directive = ShellCompDirectiveDefault } // As the last printout, print the completion directive for the completion script to parse. @@ -114,7 +114,7 @@ func (c *Command) initCompleteCmd(args []string) { } c.AddCommand(completeCmd) subCmd, _, err := c.Find(args) - if err != nil || subCmd.Name() != CompRequestCmd { + if err != nil || subCmd.Name() != ShellCompRequestCmd { // Only create this special command if it is actually being called. // This reduces possible side-effects of creating such a command; // for example, having this command would cause problems to a @@ -124,7 +124,7 @@ func (c *Command) initCompleteCmd(args []string) { } } -func (c *Command) getCompletions(args []string) (*Command, []string, BashCompDirective, error) { +func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDirective, error) { var completions []string // The last argument, which is not completely typed by the user, @@ -136,7 +136,7 @@ func (c *Command) getCompletions(args []string) (*Command, []string, BashCompDir finalCmd, finalArgs, err := c.Root().Find(trimmedArgs) if err != nil { // Unable to find the real command. E.g., someInvalidCmd - return c, completions, BashCompDirectiveDefault, fmt.Errorf("Unable to find a command for arguments: %v", trimmedArgs) + return c, completions, ShellCompDirectiveDefault, fmt.Errorf("Unable to find a command for arguments: %v", trimmedArgs) } var flag *pflag.Flag @@ -146,13 +146,13 @@ func (c *Command) getCompletions(args []string) (*Command, []string, BashCompDir flag, finalArgs, toComplete, err = checkIfFlagCompletion(finalCmd, finalArgs, toComplete) if err != nil { // Error while attempting to parse flags - return finalCmd, completions, BashCompDirectiveDefault, err + return finalCmd, completions, ShellCompDirectiveDefault, err } } // Parse the flags and extract the arguments to prepare for calling the completion function if err = finalCmd.ParseFlags(finalArgs); err != nil { - return finalCmd, completions, BashCompDirectiveDefault, fmt.Errorf("Error while parsing flags from args %v: %s", finalArgs, err.Error()) + return finalCmd, completions, ShellCompDirectiveDefault, fmt.Errorf("Error while parsing flags from args %v: %s", finalArgs, err.Error()) } // We only remove the flags from the arguments if DisableFlagParsing is not set. @@ -162,7 +162,7 @@ func (c *Command) getCompletions(args []string) (*Command, []string, BashCompDir } // Find the completion function for the flag or command - var completionFn func(cmd *Command, args []string, toComplete string) ([]string, BashCompDirective) + var completionFn func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) if flag != nil { completionFn = flagCompletionFunctions[flag] } else { @@ -170,7 +170,7 @@ func (c *Command) getCompletions(args []string) (*Command, []string, BashCompDir } if completionFn == nil { // Go custom completion not supported/needed for this flag or command - return finalCmd, completions, BashCompDirectiveDefault, nil + return finalCmd, completions, ShellCompDirectiveDefault, nil } // Call the registered completion function to get the completions diff --git a/custom_completions_test.go b/custom_completions_test.go index 61d214d6..ea6a6d92 100644 --- a/custom_completions_test.go +++ b/custom_completions_test.go @@ -6,9 +6,9 @@ import ( "testing" ) -func validArgsFunc(cmd *Command, args []string, toComplete string) ([]string, BashCompDirective) { +func validArgsFunc(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { if len(args) != 0 { - return nil, BashCompDirectiveNoFileComp + return nil, ShellCompDirectiveNoFileComp } var completions []string @@ -17,12 +17,12 @@ func validArgsFunc(cmd *Command, args []string, toComplete string) ([]string, Ba completions = append(completions, comp) } } - return completions, BashCompDirectiveDefault + return completions, ShellCompDirectiveDefault } -func validArgsFunc2(cmd *Command, args []string, toComplete string) ([]string, BashCompDirective) { +func validArgsFunc2(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { if len(args) != 0 { - return nil, BashCompDirectiveNoFileComp + return nil, ShellCompDirectiveNoFileComp } var completions []string @@ -31,7 +31,7 @@ func validArgsFunc2(cmd *Command, args []string, toComplete string) ([]string, B completions = append(completions, comp) } } - return completions, BashCompDirectiveDefault + return completions, ShellCompDirectiveDefault } func TestValidArgsFuncSingleCmd(t *testing.T) { @@ -42,7 +42,7 @@ func TestValidArgsFuncSingleCmd(t *testing.T) { } // Test completing an empty string - output, err := executeCommand(rootCmd, CompRequestCmd, "") + output, err := executeCommand(rootCmd, ShellCompRequestCmd, "") if err != nil { t.Errorf("Unexpected error: %v", err) } @@ -51,14 +51,14 @@ func TestValidArgsFuncSingleCmd(t *testing.T) { "one", "two", ":0", - "Completion ended with directive: BashCompDirectiveDefault", ""}, "\n") + "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") if output != expected { t.Errorf("expected: %q, got: %q", expected, output) } // Check completing with a prefix - output, err = executeCommand(rootCmd, CompRequestCmd, "t") + output, err = executeCommand(rootCmd, ShellCompRequestCmd, "t") if err != nil { t.Errorf("Unexpected error: %v", err) } @@ -66,7 +66,7 @@ func TestValidArgsFuncSingleCmd(t *testing.T) { expected = strings.Join([]string{ "two", ":0", - "Completion ended with directive: BashCompDirectiveDefault", ""}, "\n") + "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") if output != expected { t.Errorf("expected: %q, got: %q", expected, output) @@ -86,14 +86,14 @@ func TestValidArgsFuncSingleCmdInvalidArg(t *testing.T) { } // Check completing with wrong number of args - output, err := executeCommand(rootCmd, CompRequestCmd, "unexpectedArg", "t") + output, err := executeCommand(rootCmd, ShellCompRequestCmd, "unexpectedArg", "t") if err != nil { t.Errorf("Unexpected error: %v", err) } expected := strings.Join([]string{ ":4", - "Completion ended with directive: BashCompDirectiveNoFileComp", ""}, "\n") + "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") if output != expected { t.Errorf("expected: %q, got: %q", expected, output) @@ -115,7 +115,7 @@ func TestValidArgsFuncChildCmds(t *testing.T) { rootCmd.AddCommand(child1Cmd, child2Cmd) // Test completion of first sub-command with empty argument - output, err := executeCommand(rootCmd, CompRequestCmd, "child1", "") + output, err := executeCommand(rootCmd, ShellCompRequestCmd, "child1", "") if err != nil { t.Errorf("Unexpected error: %v", err) } @@ -124,14 +124,14 @@ func TestValidArgsFuncChildCmds(t *testing.T) { "one", "two", ":0", - "Completion ended with directive: BashCompDirectiveDefault", ""}, "\n") + "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") if output != expected { t.Errorf("expected: %q, got: %q", expected, output) } // Test completion of first sub-command with a prefix to complete - output, err = executeCommand(rootCmd, CompRequestCmd, "child1", "t") + output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child1", "t") if err != nil { t.Errorf("Unexpected error: %v", err) } @@ -139,28 +139,28 @@ func TestValidArgsFuncChildCmds(t *testing.T) { expected = strings.Join([]string{ "two", ":0", - "Completion ended with directive: BashCompDirectiveDefault", ""}, "\n") + "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") if output != expected { t.Errorf("expected: %q, got: %q", expected, output) } // Check completing with wrong number of args - output, err = executeCommand(rootCmd, CompRequestCmd, "child1", "unexpectedArg", "t") + output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child1", "unexpectedArg", "t") if err != nil { t.Errorf("Unexpected error: %v", err) } expected = strings.Join([]string{ ":4", - "Completion ended with directive: BashCompDirectiveNoFileComp", ""}, "\n") + "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") if output != expected { t.Errorf("expected: %q, got: %q", expected, output) } // Test completion of second sub-command with empty argument - output, err = executeCommand(rootCmd, CompRequestCmd, "child2", "") + output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child2", "") if err != nil { t.Errorf("Unexpected error: %v", err) } @@ -169,13 +169,13 @@ func TestValidArgsFuncChildCmds(t *testing.T) { "three", "four", ":0", - "Completion ended with directive: BashCompDirectiveDefault", ""}, "\n") + "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") if output != expected { t.Errorf("expected: %q, got: %q", expected, output) } - output, err = executeCommand(rootCmd, CompRequestCmd, "child2", "t") + output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child2", "t") if err != nil { t.Errorf("Unexpected error: %v", err) } @@ -183,21 +183,21 @@ func TestValidArgsFuncChildCmds(t *testing.T) { expected = strings.Join([]string{ "three", ":0", - "Completion ended with directive: BashCompDirectiveDefault", ""}, "\n") + "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") if output != expected { t.Errorf("expected: %q, got: %q", expected, output) } // Check completing with wrong number of args - output, err = executeCommand(rootCmd, CompRequestCmd, "child2", "unexpectedArg", "t") + output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child2", "unexpectedArg", "t") if err != nil { t.Errorf("Unexpected error: %v", err) } expected = strings.Join([]string{ ":4", - "Completion ended with directive: BashCompDirectiveNoFileComp", ""}, "\n") + "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") if output != expected { t.Errorf("expected: %q, got: %q", expected, output) @@ -215,7 +215,7 @@ func TestValidArgsFuncAliases(t *testing.T) { rootCmd.AddCommand(child) // Test completion of first sub-command with empty argument - output, err := executeCommand(rootCmd, CompRequestCmd, "son", "") + output, err := executeCommand(rootCmd, ShellCompRequestCmd, "son", "") if err != nil { t.Errorf("Unexpected error: %v", err) } @@ -224,14 +224,14 @@ func TestValidArgsFuncAliases(t *testing.T) { "one", "two", ":0", - "Completion ended with directive: BashCompDirectiveDefault", ""}, "\n") + "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") if output != expected { t.Errorf("expected: %q, got: %q", expected, output) } // Test completion of first sub-command with a prefix to complete - output, err = executeCommand(rootCmd, CompRequestCmd, "daughter", "t") + output, err = executeCommand(rootCmd, ShellCompRequestCmd, "daughter", "t") if err != nil { t.Errorf("Unexpected error: %v", err) } @@ -239,21 +239,21 @@ func TestValidArgsFuncAliases(t *testing.T) { expected = strings.Join([]string{ "two", ":0", - "Completion ended with directive: BashCompDirectiveDefault", ""}, "\n") + "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") if output != expected { t.Errorf("expected: %q, got: %q", expected, output) } // Check completing with wrong number of args - output, err = executeCommand(rootCmd, CompRequestCmd, "son", "unexpectedArg", "t") + output, err = executeCommand(rootCmd, ShellCompRequestCmd, "son", "unexpectedArg", "t") if err != nil { t.Errorf("Unexpected error: %v", err) } expected = strings.Join([]string{ ":4", - "Completion ended with directive: BashCompDirectiveNoFileComp", ""}, "\n") + "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") if output != expected { t.Errorf("expected: %q, got: %q", expected, output) @@ -297,28 +297,28 @@ func TestFlagCompletionInGo(t *testing.T) { Run: emptyRun, } rootCmd.Flags().IntP("introot", "i", -1, "help message for flag introot") - rootCmd.RegisterFlagCompletionFunc("introot", func(cmd *Command, args []string, toComplete string) ([]string, BashCompDirective) { + rootCmd.RegisterFlagCompletionFunc("introot", func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { completions := []string{} for _, comp := range []string{"1", "2", "10"} { if strings.HasPrefix(comp, toComplete) { completions = append(completions, comp) } } - return completions, BashCompDirectiveDefault + return completions, ShellCompDirectiveDefault }) rootCmd.Flags().String("filename", "", "Enter a filename") - rootCmd.RegisterFlagCompletionFunc("filename", func(cmd *Command, args []string, toComplete string) ([]string, BashCompDirective) { + rootCmd.RegisterFlagCompletionFunc("filename", func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { completions := []string{} for _, comp := range []string{"file.yaml", "myfile.json", "file.xml"} { if strings.HasPrefix(comp, toComplete) { completions = append(completions, comp) } } - return completions, BashCompDirectiveNoSpace | BashCompDirectiveNoFileComp + return completions, ShellCompDirectiveNoSpace | ShellCompDirectiveNoFileComp }) // Test completing an empty string - output, err := executeCommand(rootCmd, CompRequestCmd, "--introot", "") + output, err := executeCommand(rootCmd, ShellCompRequestCmd, "--introot", "") if err != nil { t.Errorf("Unexpected error: %v", err) } @@ -328,14 +328,14 @@ func TestFlagCompletionInGo(t *testing.T) { "2", "10", ":0", - "Completion ended with directive: BashCompDirectiveDefault", ""}, "\n") + "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") if output != expected { t.Errorf("expected: %q, got: %q", expected, output) } // Check completing with a prefix - output, err = executeCommand(rootCmd, CompRequestCmd, "--introot", "1") + output, err = executeCommand(rootCmd, ShellCompRequestCmd, "--introot", "1") if err != nil { t.Errorf("Unexpected error: %v", err) } @@ -344,14 +344,14 @@ func TestFlagCompletionInGo(t *testing.T) { "1", "10", ":0", - "Completion ended with directive: BashCompDirectiveDefault", ""}, "\n") + "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") if output != expected { t.Errorf("expected: %q, got: %q", expected, output) } // Test completing an empty string - output, err = executeCommand(rootCmd, CompRequestCmd, "--filename", "") + output, err = executeCommand(rootCmd, ShellCompRequestCmd, "--filename", "") if err != nil { t.Errorf("Unexpected error: %v", err) } @@ -361,14 +361,14 @@ func TestFlagCompletionInGo(t *testing.T) { "myfile.json", "file.xml", ":6", - "Completion ended with directive: BashCompDirectiveNoSpace, BashCompDirectiveNoFileComp", ""}, "\n") + "Completion ended with directive: ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp", ""}, "\n") if output != expected { t.Errorf("expected: %q, got: %q", expected, output) } // Check completing with a prefix - output, err = executeCommand(rootCmd, CompRequestCmd, "--filename", "f") + output, err = executeCommand(rootCmd, ShellCompRequestCmd, "--filename", "f") if err != nil { t.Errorf("Unexpected error: %v", err) } @@ -377,7 +377,7 @@ func TestFlagCompletionInGo(t *testing.T) { "file.yaml", "file.xml", ":6", - "Completion ended with directive: BashCompDirectiveNoSpace, BashCompDirectiveNoFileComp", ""}, "\n") + "Completion ended with directive: ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp", ""}, "\n") if output != expected { t.Errorf("expected: %q, got: %q", expected, output) From 47414285f9d9c47e163ee6777b024c3d8fb71dcb Mon Sep 17 00:00:00 2001 From: Julien Date: Mon, 6 Apr 2020 19:30:17 +0200 Subject: [PATCH 26/56] Add Github CLI to list of projects using Cobra (#1034) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a95b04f7..9d799342 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ Many of the most widely used Go projects are built using Cobra, such as: [mattermost-server](https://github.com/mattermost/mattermost-server), [Gardener](https://github.com/gardener/gardenctl), [Linkerd](https://linkerd.io/), +[Github CLI](https://github.com/cli/cli) etc. [![Build Status](https://travis-ci.org/spf13/cobra.svg "Travis CI status")](https://travis-ci.org/spf13/cobra) From 7fead4bf3b209fb6e08df01188a7dbc2ac3103bf Mon Sep 17 00:00:00 2001 From: John McBride Date: Mon, 6 Apr 2020 11:36:04 -0600 Subject: [PATCH 27/56] Remove/replace SetOutput on Command - deprecated (#1078) Replace SetOutput on Command - deprecated --- command_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/command_test.go b/command_test.go index 8ec52f44..ec8e2aef 100644 --- a/command_test.go +++ b/command_test.go @@ -1814,7 +1814,8 @@ func (tc *calledAsTestcase) test(t *testing.T) { parent.SetArgs(tc.args) output := new(bytes.Buffer) - parent.SetOutput(output) + parent.SetOut(output) + parent.SetErr(output) parent.Execute() From a684a6d7f5e37385d954dd3b5a14fc6912c6ab9d Mon Sep 17 00:00:00 2001 From: Marc Khouzam Date: Fri, 10 Apr 2020 15:56:28 -0400 Subject: [PATCH 28/56] Fish completion using Go completion (#1048) Signed-off-by: Marc Khouzam --- args.go | 10 +- bash_completions.go | 5 +- bash_completions.md | 4 + custom_completions.go | 91 +++++++- custom_completions_test.go | 466 ++++++++++++++++++++++++++++--------- fish_completions.go | 172 ++++++++++++++ fish_completions.md | 7 + 7 files changed, 640 insertions(+), 115 deletions(-) create mode 100644 fish_completions.go create mode 100644 fish_completions.md diff --git a/args.go b/args.go index c4d820b8..70e9b262 100644 --- a/args.go +++ b/args.go @@ -2,6 +2,7 @@ package cobra import ( "fmt" + "strings" ) type PositionalArgs func(cmd *Command, args []string) error @@ -34,8 +35,15 @@ func NoArgs(cmd *Command, args []string) error { // OnlyValidArgs returns an error if any args are not in the list of ValidArgs. func OnlyValidArgs(cmd *Command, args []string) error { if len(cmd.ValidArgs) > 0 { + // Remove any description that may be included in ValidArgs. + // A description is following a tab character. + var validArgs []string + for _, v := range cmd.ValidArgs { + validArgs = append(validArgs, strings.Split(v, "\t")[0]) + } + for _, v := range args { - if !stringInSlice(v, cmd.ValidArgs) { + if !stringInSlice(v, validArgs) { return fmt.Errorf("invalid argument %q for %q%s", v, cmd.CommandPath(), cmd.findSuggestions(args[0])) } } diff --git a/bash_completions.go b/bash_completions.go index b4098f4a..1e27188c 100644 --- a/bash_completions.go +++ b/bash_completions.go @@ -344,7 +344,7 @@ __%[1]s_handle_word() __%[1]s_handle_word } -`, name, ShellCompRequestCmd, ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp)) +`, name, ShellCompNoDescRequestCmd, ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp)) } func writePostscript(buf *bytes.Buffer, name string) { @@ -548,6 +548,9 @@ func writeRequiredNouns(buf *bytes.Buffer, cmd *Command) { buf.WriteString(" must_have_one_noun=()\n") sort.Sort(sort.StringSlice(cmd.ValidArgs)) for _, value := range cmd.ValidArgs { + // Remove any description that may be included following a tab character. + // Descriptions are not supported by bash completion. + value = strings.Split(value, "\t")[0] buf.WriteString(fmt.Sprintf(" must_have_one_noun+=(%q)\n", value)) } if cmd.ValidArgsFunction != nil { diff --git a/bash_completions.md b/bash_completions.md index e1590e1b..e61a3a65 100644 --- a/bash_completions.md +++ b/bash_completions.md @@ -115,6 +115,8 @@ in this example again instead of the replication controllers. In some cases it is not possible to provide a list of possible completions in advance. Instead, the list of completions must be determined at execution-time. Cobra provides two ways of defining such dynamic completion of nouns. Note that both these methods can be used along-side each other as long as they are not both used for the same command. +**Note**: *Custom Completions written in Go* will automatically work for other shell-completion scripts (e.g., Fish shell), while *Custom Completions written in Bash* will only work for Bash shell-completion. It is therefore recommended to use *Custom Completions written in Go*. + #### 1. Custom completions of nouns written in Go In a similar fashion as for static completions, you can use the `ValidArgsFunction` field to provide a Go function that Cobra will execute when it needs the list of completion choices for the nouns of a command. Note that either `ValidArgs` or `ValidArgsFunction` can be used for a single cobra command, but not both. @@ -301,6 +303,8 @@ So while there are many other files in the CWD it only shows me subdirs and thos As for nouns, Cobra provides two ways of defining dynamic completion of flags. Note that both these methods can be used along-side each other as long as they are not both used for the same flag. +**Note**: *Custom Completions written in Go* will automatically work for other shell-completion scripts (e.g., Fish shell), while *Custom Completions written in Bash* will only work for Bash shell-completion. It is therefore recommended to use *Custom Completions written in Go*. + ## 1. Custom completions of flags written in Go To provide a Go function that Cobra will execute when it needs the list of completion choices for a flag, you must register the function in the following manner: diff --git a/custom_completions.go b/custom_completions.go index 81f1bc38..ba57327c 100644 --- a/custom_completions.go +++ b/custom_completions.go @@ -9,9 +9,14 @@ import ( "github.com/spf13/pflag" ) -// ShellCompRequestCmd is the name of the hidden command that is used to request -// completion results from the program. It is used by the shell completion script. -const ShellCompRequestCmd = "__complete" +const ( + // ShellCompRequestCmd is the name of the hidden command that is used to request + // completion results from the program. It is used by the shell completion scripts. + ShellCompRequestCmd = "__complete" + // ShellCompNoDescRequestCmd is the name of the hidden command that is used to request + // completion results without their description. It is used by the shell completion scripts. + ShellCompNoDescRequestCmd = "__completeNoDesc" +) // Global map of flag completion functions. var flagCompletionFunctions = map[*pflag.Flag]func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective){} @@ -77,6 +82,7 @@ func (d ShellCompDirective) string() string { func (c *Command) initCompleteCmd(args []string) { completeCmd := &Command{ Use: fmt.Sprintf("%s [command-line]", ShellCompRequestCmd), + Aliases: []string{ShellCompNoDescRequestCmd}, DisableFlagsInUseLine: true, Hidden: true, DisableFlagParsing: true, @@ -93,7 +99,12 @@ func (c *Command) initCompleteCmd(args []string) { // 2- Even without completions, we need to print the directive } + noDescriptions := (cmd.CalledAs() == ShellCompNoDescRequestCmd) for _, comp := range completions { + if noDescriptions { + // Remove any description that may be included following a tab character. + comp = strings.Split(comp, "\t")[0] + } // Print each possible completion to stdout for the completion script to consume. fmt.Fprintln(finalCmd.OutOrStdout(), comp) } @@ -139,6 +150,27 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi return c, completions, ShellCompDirectiveDefault, fmt.Errorf("Unable to find a command for arguments: %v", trimmedArgs) } + // When doing completion of a flag name, as soon as an argument starts with + // a '-' we know it is a flag. We cannot use isFlagArg() here as it requires + // the flag to be complete + if len(toComplete) > 0 && toComplete[0] == '-' && !strings.Contains(toComplete, "=") { + // We are completing a flag name + finalCmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) { + completions = append(completions, getFlagNameCompletions(flag, toComplete)...) + }) + finalCmd.InheritedFlags().VisitAll(func(flag *pflag.Flag) { + completions = append(completions, getFlagNameCompletions(flag, toComplete)...) + }) + + directive := ShellCompDirectiveDefault + if len(completions) > 0 { + if strings.HasSuffix(completions[0], "=") { + directive = ShellCompDirectiveNoSpace + } + } + return finalCmd, completions, directive, nil + } + var flag *pflag.Flag if !finalCmd.DisableFlagParsing { // We only do flag completion if we are allowed to parse flags @@ -150,6 +182,33 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi } } + if flag == nil { + // Complete subcommand names + for _, subCmd := range finalCmd.Commands() { + if subCmd.IsAvailableCommand() && strings.HasPrefix(subCmd.Name(), toComplete) { + completions = append(completions, fmt.Sprintf("%s\t%s", subCmd.Name(), subCmd.Short)) + } + } + + if len(finalCmd.ValidArgs) > 0 { + // Always complete ValidArgs, even if we are completing a subcommand name. + // This is for commands that have both subcommands and ValidArgs. + for _, validArg := range finalCmd.ValidArgs { + if strings.HasPrefix(validArg, toComplete) { + completions = append(completions, validArg) + } + } + + // If there are ValidArgs specified (even if they don't match), we stop completion. + // Only one of ValidArgs or ValidArgsFunction can be used for a single command. + return finalCmd, completions, ShellCompDirectiveNoFileComp, nil + } + + // Always let the logic continue so as to add any ValidArgsFunction completions, + // even if we already found sub-commands. + // This is for commands that have subcommands but also specify a ValidArgsFunction. + } + // Parse the flags and extract the arguments to prepare for calling the completion function if err = finalCmd.ParseFlags(finalArgs); err != nil { return finalCmd, completions, ShellCompDirectiveDefault, fmt.Errorf("Error while parsing flags from args %v: %s", finalArgs, err.Error()) @@ -179,6 +238,32 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi return finalCmd, completions, directive, nil } +func getFlagNameCompletions(flag *pflag.Flag, toComplete string) []string { + if nonCompletableFlag(flag) { + return []string{} + } + + var completions []string + flagName := "--" + flag.Name + if strings.HasPrefix(flagName, toComplete) { + // Flag without the = + completions = append(completions, fmt.Sprintf("%s\t%s", flagName, flag.Usage)) + + if len(flag.NoOptDefVal) == 0 { + // Flag requires a value, so it can be suffixed with = + flagName += "=" + completions = append(completions, fmt.Sprintf("%s\t%s", flagName, flag.Usage)) + } + } + + flagName = "-" + flag.Shorthand + if len(flag.Shorthand) > 0 && strings.HasPrefix(flagName, toComplete) { + completions = append(completions, fmt.Sprintf("%s\t%s", flagName, flag.Usage)) + } + + return completions +} + func checkIfFlagCompletion(finalCmd *Command, args []string, lastArg string) (*pflag.Flag, []string, string, error) { var flagName string trimmedArgs := args diff --git a/custom_completions_test.go b/custom_completions_test.go index ea6a6d92..86154839 100644 --- a/custom_completions_test.go +++ b/custom_completions_test.go @@ -12,7 +12,7 @@ func validArgsFunc(cmd *Command, args []string, toComplete string) ([]string, Sh } var completions []string - for _, comp := range []string{"one", "two"} { + for _, comp := range []string{"one\tThe first", "two\tThe second"} { if strings.HasPrefix(comp, toComplete) { completions = append(completions, comp) } @@ -26,7 +26,7 @@ func validArgsFunc2(cmd *Command, args []string, toComplete string) ([]string, S } var completions []string - for _, comp := range []string{"three", "four"} { + for _, comp := range []string{"three\tThe third", "four\tThe fourth"} { if strings.HasPrefix(comp, toComplete) { completions = append(completions, comp) } @@ -42,7 +42,7 @@ func TestValidArgsFuncSingleCmd(t *testing.T) { } // Test completing an empty string - output, err := executeCommand(rootCmd, ShellCompRequestCmd, "") + output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "") if err != nil { t.Errorf("Unexpected error: %v", err) } @@ -58,7 +58,7 @@ func TestValidArgsFuncSingleCmd(t *testing.T) { } // Check completing with a prefix - output, err = executeCommand(rootCmd, ShellCompRequestCmd, "t") + output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "t") if err != nil { t.Errorf("Unexpected error: %v", err) } @@ -86,7 +86,7 @@ func TestValidArgsFuncSingleCmdInvalidArg(t *testing.T) { } // Check completing with wrong number of args - output, err := executeCommand(rootCmd, ShellCompRequestCmd, "unexpectedArg", "t") + output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "unexpectedArg", "t") if err != nil { t.Errorf("Unexpected error: %v", err) } @@ -115,7 +115,7 @@ func TestValidArgsFuncChildCmds(t *testing.T) { rootCmd.AddCommand(child1Cmd, child2Cmd) // Test completion of first sub-command with empty argument - output, err := executeCommand(rootCmd, ShellCompRequestCmd, "child1", "") + output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "child1", "") if err != nil { t.Errorf("Unexpected error: %v", err) } @@ -131,7 +131,7 @@ func TestValidArgsFuncChildCmds(t *testing.T) { } // Test completion of first sub-command with a prefix to complete - output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child1", "t") + output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "child1", "t") if err != nil { t.Errorf("Unexpected error: %v", err) } @@ -145,6 +145,339 @@ func TestValidArgsFuncChildCmds(t *testing.T) { t.Errorf("expected: %q, got: %q", expected, output) } + // Check completing with wrong number of args + output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "child1", "unexpectedArg", "t") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + ":4", + "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Test completion of second sub-command with empty argument + output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "child2", "") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + "three", + "four", + ":0", + "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "child2", "t") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + "three", + ":0", + "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Check completing with wrong number of args + output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "child2", "unexpectedArg", "t") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + ":4", + "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } +} + +func TestValidArgsFuncAliases(t *testing.T) { + rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun} + child := &Command{ + Use: "child", + Aliases: []string{"son", "daughter"}, + ValidArgsFunction: validArgsFunc, + Run: emptyRun, + } + rootCmd.AddCommand(child) + + // Test completion of first sub-command with empty argument + output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "son", "") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected := strings.Join([]string{ + "one", + "two", + ":0", + "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Test completion of first sub-command with a prefix to complete + output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "daughter", "t") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + "two", + ":0", + "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Check completing with wrong number of args + output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "son", "unexpectedArg", "t") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + ":4", + "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } +} + +func TestValidArgsFuncInBashScript(t *testing.T) { + rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun} + child := &Command{ + Use: "child", + ValidArgsFunction: validArgsFunc, + Run: emptyRun, + } + rootCmd.AddCommand(child) + + buf := new(bytes.Buffer) + rootCmd.GenBashCompletion(buf) + output := buf.String() + + check(t, output, "has_completion_function=1") +} + +func TestNoValidArgsFuncInBashScript(t *testing.T) { + rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun} + child := &Command{ + Use: "child", + Run: emptyRun, + } + rootCmd.AddCommand(child) + + buf := new(bytes.Buffer) + rootCmd.GenBashCompletion(buf) + output := buf.String() + + checkOmit(t, output, "has_completion_function=1") +} + +func TestCompleteCmdInBashScript(t *testing.T) { + rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun} + child := &Command{ + Use: "child", + ValidArgsFunction: validArgsFunc, + Run: emptyRun, + } + rootCmd.AddCommand(child) + + buf := new(bytes.Buffer) + rootCmd.GenBashCompletion(buf) + output := buf.String() + + check(t, output, ShellCompNoDescRequestCmd) +} + +func TestCompleteNoDesCmdInFishScript(t *testing.T) { + rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun} + child := &Command{ + Use: "child", + ValidArgsFunction: validArgsFunc, + Run: emptyRun, + } + rootCmd.AddCommand(child) + + buf := new(bytes.Buffer) + rootCmd.GenFishCompletion(buf, false) + output := buf.String() + + check(t, output, ShellCompNoDescRequestCmd) +} + +func TestCompleteCmdInFishScript(t *testing.T) { + rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun} + child := &Command{ + Use: "child", + ValidArgsFunction: validArgsFunc, + Run: emptyRun, + } + rootCmd.AddCommand(child) + + buf := new(bytes.Buffer) + rootCmd.GenFishCompletion(buf, true) + output := buf.String() + + check(t, output, ShellCompRequestCmd) + checkOmit(t, output, ShellCompNoDescRequestCmd) +} + +func TestFlagCompletionInGo(t *testing.T) { + rootCmd := &Command{ + Use: "root", + Run: emptyRun, + } + rootCmd.Flags().IntP("introot", "i", -1, "help message for flag introot") + rootCmd.RegisterFlagCompletionFunc("introot", func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { + completions := []string{} + for _, comp := range []string{"1\tThe first", "2\tThe second", "10\tThe tenth"} { + if strings.HasPrefix(comp, toComplete) { + completions = append(completions, comp) + } + } + return completions, ShellCompDirectiveDefault + }) + rootCmd.Flags().String("filename", "", "Enter a filename") + rootCmd.RegisterFlagCompletionFunc("filename", func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { + completions := []string{} + for _, comp := range []string{"file.yaml\tYAML format", "myfile.json\tJSON format", "file.xml\tXML format"} { + if strings.HasPrefix(comp, toComplete) { + completions = append(completions, comp) + } + } + return completions, ShellCompDirectiveNoSpace | ShellCompDirectiveNoFileComp + }) + + // Test completing an empty string + output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--introot", "") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected := strings.Join([]string{ + "1", + "2", + "10", + ":0", + "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Check completing with a prefix + output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--introot", "1") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + "1", + "10", + ":0", + "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Test completing an empty string + output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--filename", "") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + "file.yaml", + "myfile.json", + "file.xml", + ":6", + "Completion ended with directive: ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Check completing with a prefix + output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--filename", "f") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + "file.yaml", + "file.xml", + ":6", + "Completion ended with directive: ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } +} + +func TestValidArgsFuncChildCmdsWithDesc(t *testing.T) { + rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun} + child1Cmd := &Command{ + Use: "child1", + ValidArgsFunction: validArgsFunc, + Run: emptyRun, + } + child2Cmd := &Command{ + Use: "child2", + ValidArgsFunction: validArgsFunc2, + Run: emptyRun, + } + rootCmd.AddCommand(child1Cmd, child2Cmd) + + // Test completion of first sub-command with empty argument + output, err := executeCommand(rootCmd, ShellCompRequestCmd, "child1", "") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected := strings.Join([]string{ + "one\tThe first", + "two\tThe second", + ":0", + "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Test completion of first sub-command with a prefix to complete + output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child1", "t") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + "two\tThe second", + ":0", + "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + // Check completing with wrong number of args output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child1", "unexpectedArg", "t") if err != nil { @@ -166,8 +499,8 @@ func TestValidArgsFuncChildCmds(t *testing.T) { } expected = strings.Join([]string{ - "three", - "four", + "three\tThe third", + "four\tThe fourth", ":0", "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") @@ -181,7 +514,7 @@ func TestValidArgsFuncChildCmds(t *testing.T) { } expected = strings.Join([]string{ - "three", + "three\tThe third", ":0", "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") @@ -204,94 +537,7 @@ func TestValidArgsFuncChildCmds(t *testing.T) { } } -func TestValidArgsFuncAliases(t *testing.T) { - rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun} - child := &Command{ - Use: "child", - Aliases: []string{"son", "daughter"}, - ValidArgsFunction: validArgsFunc, - Run: emptyRun, - } - rootCmd.AddCommand(child) - - // Test completion of first sub-command with empty argument - output, err := executeCommand(rootCmd, ShellCompRequestCmd, "son", "") - if err != nil { - t.Errorf("Unexpected error: %v", err) - } - - expected := strings.Join([]string{ - "one", - "two", - ":0", - "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") - - if output != expected { - t.Errorf("expected: %q, got: %q", expected, output) - } - - // Test completion of first sub-command with a prefix to complete - output, err = executeCommand(rootCmd, ShellCompRequestCmd, "daughter", "t") - if err != nil { - t.Errorf("Unexpected error: %v", err) - } - - expected = strings.Join([]string{ - "two", - ":0", - "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") - - if output != expected { - t.Errorf("expected: %q, got: %q", expected, output) - } - - // Check completing with wrong number of args - output, err = executeCommand(rootCmd, ShellCompRequestCmd, "son", "unexpectedArg", "t") - if err != nil { - t.Errorf("Unexpected error: %v", err) - } - - expected = strings.Join([]string{ - ":4", - "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") - - if output != expected { - t.Errorf("expected: %q, got: %q", expected, output) - } -} - -func TestValidArgsFuncInScript(t *testing.T) { - rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun} - child := &Command{ - Use: "child", - ValidArgsFunction: validArgsFunc, - Run: emptyRun, - } - rootCmd.AddCommand(child) - - buf := new(bytes.Buffer) - rootCmd.GenBashCompletion(buf) - output := buf.String() - - check(t, output, "has_completion_function=1") -} - -func TestNoValidArgsFuncInScript(t *testing.T) { - rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun} - child := &Command{ - Use: "child", - Run: emptyRun, - } - rootCmd.AddCommand(child) - - buf := new(bytes.Buffer) - rootCmd.GenBashCompletion(buf) - output := buf.String() - - checkOmit(t, output, "has_completion_function=1") -} - -func TestFlagCompletionInGo(t *testing.T) { +func TestFlagCompletionInGoWithDesc(t *testing.T) { rootCmd := &Command{ Use: "root", Run: emptyRun, @@ -299,7 +545,7 @@ func TestFlagCompletionInGo(t *testing.T) { rootCmd.Flags().IntP("introot", "i", -1, "help message for flag introot") rootCmd.RegisterFlagCompletionFunc("introot", func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { completions := []string{} - for _, comp := range []string{"1", "2", "10"} { + for _, comp := range []string{"1\tThe first", "2\tThe second", "10\tThe tenth"} { if strings.HasPrefix(comp, toComplete) { completions = append(completions, comp) } @@ -309,7 +555,7 @@ func TestFlagCompletionInGo(t *testing.T) { rootCmd.Flags().String("filename", "", "Enter a filename") rootCmd.RegisterFlagCompletionFunc("filename", func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { completions := []string{} - for _, comp := range []string{"file.yaml", "myfile.json", "file.xml"} { + for _, comp := range []string{"file.yaml\tYAML format", "myfile.json\tJSON format", "file.xml\tXML format"} { if strings.HasPrefix(comp, toComplete) { completions = append(completions, comp) } @@ -324,9 +570,9 @@ func TestFlagCompletionInGo(t *testing.T) { } expected := strings.Join([]string{ - "1", - "2", - "10", + "1\tThe first", + "2\tThe second", + "10\tThe tenth", ":0", "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") @@ -341,8 +587,8 @@ func TestFlagCompletionInGo(t *testing.T) { } expected = strings.Join([]string{ - "1", - "10", + "1\tThe first", + "10\tThe tenth", ":0", "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") @@ -357,9 +603,9 @@ func TestFlagCompletionInGo(t *testing.T) { } expected = strings.Join([]string{ - "file.yaml", - "myfile.json", - "file.xml", + "file.yaml\tYAML format", + "myfile.json\tJSON format", + "file.xml\tXML format", ":6", "Completion ended with directive: ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp", ""}, "\n") @@ -374,8 +620,8 @@ func TestFlagCompletionInGo(t *testing.T) { } expected = strings.Join([]string{ - "file.yaml", - "file.xml", + "file.yaml\tYAML format", + "file.xml\tXML format", ":6", "Completion ended with directive: ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp", ""}, "\n") diff --git a/fish_completions.go b/fish_completions.go new file mode 100644 index 00000000..c83609c8 --- /dev/null +++ b/fish_completions.go @@ -0,0 +1,172 @@ +package cobra + +import ( + "bytes" + "fmt" + "io" + "os" +) + +func genFishComp(buf *bytes.Buffer, name string, includeDesc bool) { + compCmd := ShellCompRequestCmd + if !includeDesc { + compCmd = ShellCompNoDescRequestCmd + } + buf.WriteString(fmt.Sprintf("# fish completion for %-36s -*- shell-script -*-\n", name)) + buf.WriteString(fmt.Sprintf(` +function __%[1]s_debug + set file "$BASH_COMP_DEBUG_FILE" + if test -n "$file" + echo "$argv" >> $file + end +end + +function __%[1]s_perform_completion + __%[1]s_debug "Starting __%[1]s_perform_completion with: $argv" + + set args (string split -- " " "$argv") + set lastArg "$args[-1]" + + __%[1]s_debug "args: $args" + __%[1]s_debug "last arg: $lastArg" + + set emptyArg "" + if test -z "$lastArg" + __%[1]s_debug "Setting emptyArg" + set emptyArg \"\" + end + __%[1]s_debug "emptyArg: $emptyArg" + + set requestComp "$args[1] %[2]s $args[2..-1] $emptyArg" + __%[1]s_debug "Calling $requestComp" + + set results (eval $requestComp 2> /dev/null) + set comps $results[1..-2] + set directiveLine $results[-1] + + # For Fish, when completing a flag with an = (e.g., -n=) + # completions must be prefixed with the flag + set flagPrefix (string match -r -- '-.*=' "$lastArg") + + __%[1]s_debug "Comps: $comps" + __%[1]s_debug "DirectiveLine: $directiveLine" + __%[1]s_debug "flagPrefix: $flagPrefix" + + for comp in $comps + printf "%%s%%s\n" "$flagPrefix" "$comp" + end + + printf "%%s\n" "$directiveLine" +end + +# This function does three things: +# 1- Obtain the completions and store them in the global __%[1]s_comp_results +# 2- Set the __%[1]s_comp_do_file_comp flag if file completion should be performed +# and unset it otherwise +# 3- Return true if the completion results are not empty +function __%[1]s_prepare_completions + # Start fresh + set --erase __%[1]s_comp_do_file_comp + set --erase __%[1]s_comp_results + + # Check if the command-line is already provided. This is useful for testing. + if not set --query __%[1]s_comp_commandLine + set __%[1]s_comp_commandLine (commandline) + end + __%[1]s_debug "commandLine is: $__%[1]s_comp_commandLine" + + set results (__%[1]s_perform_completion "$__%[1]s_comp_commandLine") + set --erase __%[1]s_comp_commandLine + __%[1]s_debug "Completion results: $results" + + if test -z "$results" + __%[1]s_debug "No completion, probably due to a failure" + # Might as well do file completion, in case it helps + set --global __%[1]s_comp_do_file_comp 1 + return 0 + end + + set directive (string sub --start 2 $results[-1]) + set --global __%[1]s_comp_results $results[1..-2] + + __%[1]s_debug "Completions are: $__%[1]s_comp_results" + __%[1]s_debug "Directive is: $directive" + + if test -z "$directive" + set directive 0 + end + + set compErr (math (math --scale 0 $directive / %[3]d) %% 2) + if test $compErr -eq 1 + __%[1]s_debug "Received error directive: aborting." + # Might as well do file completion, in case it helps + set --global __%[1]s_comp_do_file_comp 1 + return 0 + end + + set nospace (math (math --scale 0 $directive / %[4]d) %% 2) + set nofiles (math (math --scale 0 $directive / %[5]d) %% 2) + + __%[1]s_debug "nospace: $nospace, nofiles: $nofiles" + + # Important not to quote the variable for count to work + set numComps (count $__%[1]s_comp_results) + __%[1]s_debug "numComps: $numComps" + + if test $numComps -eq 1; and test $nospace -ne 0 + # To support the "nospace" directive we trick the shell + # by outputting an extra, longer completion. + __%[1]s_debug "Adding second completion to perform nospace directive" + set --append __%[1]s_comp_results $__%[1]s_comp_results[1]. + end + + if test $numComps -eq 0; and test $nofiles -eq 0 + __%[1]s_debug "Requesting file completion" + set --global __%[1]s_comp_do_file_comp 1 + end + + # If we don't want file completion, we must return true even if there + # are no completions found. This is because fish will perform the last + # completion command, even if its condition is false, if no other + # completion command was triggered + return (not set --query __%[1]s_comp_do_file_comp) +end + +# Remove any pre-existing completions for the program since we will be handling all of them +# TODO this cleanup is not sufficient. Fish completions are only loaded once the user triggers +# them, so the below deletion will not work as it is run too early. What else can we do? +complete -c %[1]s -e + +# The order in which the below two lines are defined is very important so that __%[1]s_prepare_completions +# is called first. It is __%[1]s_prepare_completions that sets up the __%[1]s_comp_do_file_comp variable. +# +# This completion will be run second as complete commands are added FILO. +# It triggers file completion choices when __%[1]s_comp_do_file_comp is set. +complete -c %[1]s -n 'set --query __%[1]s_comp_do_file_comp' + +# This completion will be run first as complete commands are added FILO. +# The call to __%[1]s_prepare_completions will setup both __%[1]s_comp_results abd __%[1]s_comp_do_file_comp. +# It provides the program's completion choices. +complete -c %[1]s -n '__%[1]s_prepare_completions' -f -a '$__%[1]s_comp_results' + +`, name, compCmd, ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp)) +} + +// GenFishCompletion generates fish completion file and writes to the passed writer. +func (c *Command) GenFishCompletion(w io.Writer, includeDesc bool) error { + buf := new(bytes.Buffer) + genFishComp(buf, c.Name(), includeDesc) + _, err := buf.WriteTo(w) + return err +} + +// GenFishCompletionFile generates fish completion file. +func (c *Command) GenFishCompletionFile(filename string, includeDesc bool) error { + outFile, err := os.Create(filename) + if err != nil { + return err + } + defer outFile.Close() + + return c.GenFishCompletion(outFile, includeDesc) +} diff --git a/fish_completions.md b/fish_completions.md new file mode 100644 index 00000000..6bfe5f88 --- /dev/null +++ b/fish_completions.md @@ -0,0 +1,7 @@ +## Generating Fish Completions for your own cobra.Command + +Cobra supports native Fish completions generated from the root `cobra.Command`. You can use the `command.GenFishCompletion()` or `command.GenFishCompletionFile()` functions. You must provide these functions with a parameter indicating if the completions should be annotated with a description; Cobra will provide the description automatically based on usage information. You can choose to make this option configurable by your users. + +### Limitations + +* Custom completions implemented using the `ValidArgsFunction` and `RegisterFlagCompletionFunc()` are supported automatically but the ones implemented in Bash scripting are not. From 8c638d3f9077b692f7d12a212a74be98326d11a2 Mon Sep 17 00:00:00 2001 From: Amit Botzer Date: Sat, 11 Apr 2020 00:30:10 +0300 Subject: [PATCH 29/56] Fixed typo. (#1087) Changed 'applicaton' to 'application'. --- cobra/cmd/init.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cobra/cmd/init.go b/cobra/cmd/init.go index dcf5ada4..edddc2cf 100644 --- a/cobra/cmd/init.go +++ b/cobra/cmd/init.go @@ -67,7 +67,7 @@ Init will not use an existing directory with contents.`, er(err) } - fmt.Printf("Your Cobra applicaton is ready at\n%s\n", project.AbsolutePath) + fmt.Printf("Your Cobra application is ready at\n%s\n", project.AbsolutePath) }, } ) From 090d94f4743e6590e11c154c45ccbca01a6eaf9a Mon Sep 17 00:00:00 2001 From: Warren Fernandes Date: Thu, 16 Apr 2020 11:00:24 -0600 Subject: [PATCH 30/56] Move projects using Cobra (#1090) Since there are so many projects using Cobra, we created a separate document to list them --- README.md | 28 +++------------------------- projects_using_cobra.md | 25 +++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 25 deletions(-) create mode 100644 projects_using_cobra.md diff --git a/README.md b/README.md index 9d799342..a3713660 100644 --- a/README.md +++ b/README.md @@ -2,31 +2,9 @@ Cobra is both a library for creating powerful modern CLI applications as well as a program to generate applications and command files. -Many of the most widely used Go projects are built using Cobra, such as: -[Kubernetes](http://kubernetes.io/), -[Hugo](http://gohugo.io), -[rkt](https://github.com/coreos/rkt), -[etcd](https://github.com/coreos/etcd), -[Moby (former Docker)](https://github.com/moby/moby), -[Docker (distribution)](https://github.com/docker/distribution), -[OpenShift](https://www.openshift.com/), -[Delve](https://github.com/derekparker/delve), -[GopherJS](http://www.gopherjs.org/), -[CockroachDB](http://www.cockroachlabs.com/), -[Bleve](http://www.blevesearch.com/), -[ProjectAtomic (enterprise)](http://www.projectatomic.io/), -[Giant Swarm's gsctl](https://github.com/giantswarm/gsctl), -[Nanobox](https://github.com/nanobox-io/nanobox)/[Nanopack](https://github.com/nanopack), -[rclone](http://rclone.org/), -[nehm](https://github.com/bogem/nehm), -[Pouch](https://github.com/alibaba/pouch), -[Istio](https://istio.io), -[Prototool](https://github.com/uber/prototool), -[mattermost-server](https://github.com/mattermost/mattermost-server), -[Gardener](https://github.com/gardener/gardenctl), -[Linkerd](https://linkerd.io/), -[Github CLI](https://github.com/cli/cli) -etc. +Cobra is used in many Go projects such as [Kubernetes](http://kubernetes.io/), +[Hugo](https://gohugo.io), and [Github CLI](https://github.com/cli/cli) to +name a few. [This list](./projects_using_cobra.md) contains a more extensive list of projects using Cobra. [![Build Status](https://travis-ci.org/spf13/cobra.svg "Travis CI status")](https://travis-ci.org/spf13/cobra) [![GoDoc](https://godoc.org/github.com/spf13/cobra?status.svg)](https://godoc.org/github.com/spf13/cobra) diff --git a/projects_using_cobra.md b/projects_using_cobra.md new file mode 100644 index 00000000..58dd6394 --- /dev/null +++ b/projects_using_cobra.md @@ -0,0 +1,25 @@ +## Projects using Cobra + +- [Bleve](http://www.blevesearch.com/) +- [CockroachDB](http://www.cockroachlabs.com/) +- [Delve](https://github.com/derekparker/delve) +- [Docker (distribution)](https://github.com/docker/distribution) +- [Gardener](https://github.com/gardener/gardenctl) +- [Giant Swarm's gsctl](https://github.com/giantswarm/gsctl) +- [Github CLI](https://github.com/cli/cli) +- [GopherJS](http://www.gopherjs.org/) +- [Hugo](https://gohugo.io) +- [Istio](https://istio.io) +- [Kubernetes](http://kubernetes.io/) +- [Linkerd](https://linkerd.io/) +- [Mattermost-server](https://github.com/mattermost/mattermost-server) +- [Moby (former Docker)](https://github.com/moby/moby) +- [Nanobox](https://github.com/nanobox-io/nanobox)/[Nanopack](https://github.com/nanopack) +- [OpenShift](https://www.openshift.com/) +- [Pouch](https://github.com/alibaba/pouch) +- [ProjectAtomic (enterprise)](http://www.projectatomic.io/) +- [Prototool](https://github.com/uber/prototool) +- [Rclone](https://rclone.org/) +- [etcd](https://github.com/coreos/etcd) +- [nehm](https://github.com/bogem/nehm) +- [rkt](https://github.com/coreos/rkt) From 11ba63fc3bc37818c471fc7912783e6909da4645 Mon Sep 17 00:00:00 2001 From: Marc Khouzam Date: Sat, 25 Apr 2020 11:03:04 -0400 Subject: [PATCH 31/56] Add Helm as project using Cobra (#1103) Signed-off-by: Marc Khouzam --- projects_using_cobra.md | 1 + 1 file changed, 1 insertion(+) diff --git a/projects_using_cobra.md b/projects_using_cobra.md index 58dd6394..aa791077 100644 --- a/projects_using_cobra.md +++ b/projects_using_cobra.md @@ -8,6 +8,7 @@ - [Giant Swarm's gsctl](https://github.com/giantswarm/gsctl) - [Github CLI](https://github.com/cli/cli) - [GopherJS](http://www.gopherjs.org/) +- [Helm](https://helm.sh) - [Hugo](https://gohugo.io) - [Istio](https://istio.io) - [Kubernetes](http://kubernetes.io/) From 44d55fb4d308b9ee64b7b189421730595300339a Mon Sep 17 00:00:00 2001 From: Warren Fernandes Date: Tue, 28 Apr 2020 11:49:46 -0600 Subject: [PATCH 32/56] Fix cobra command README (#1106) Describes the behavior of custom LICENSE generation using properties in ~/.cobra.yml --- cobra/README.md | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/cobra/README.md b/cobra/README.md index c360fdac..0f718c2b 100644 --- a/cobra/README.md +++ b/cobra/README.md @@ -88,12 +88,17 @@ author: Steve Francia license: MIT ``` +You can also use built-in licenses. For example, **GPLv2**, **GPLv3**, **LGPL**, +**AGPL**, **MIT**, **2-Clause BSD** or **3-Clause BSD**. + You can specify no license by setting `license` to `none` or you can specify a custom license: ```yaml +author: Steve Francia +year: 2020 license: - header: This file is part of {{ .appName }}. + header: This file is part of CLI application foo. text: | {{ .copyright }} @@ -102,5 +107,23 @@ license: master my life. ``` -You can also use built-in licenses. For example, **GPLv2**, **GPLv3**, **LGPL**, -**AGPL**, **MIT**, **2-Clause BSD** or **3-Clause BSD**. +In the above custom license configuration the `copyright` line in the License +text is generated from the `author` and `year` properties. The content of the +`LICENSE` file is + +``` +Copyright © 2020 Steve Francia + +This is my license. There are many like it, but this one is mine. +My license is my best friend. It is my life. I must master it as I must +master my life. +``` + +The `header` property is used as the license header files. No interpolation is +done. This is the example of the go file header. +``` +/* +Copyright © 2020 Steve Francia +This file is part of CLI application foo. +*/ +``` From 41fd44e1df83ad6acd8e14b304d5f2a2ae1d62f5 Mon Sep 17 00:00:00 2001 From: Warren Fernandes Date: Tue, 28 Apr 2020 16:59:18 -0600 Subject: [PATCH 33/56] Fix cobra init cmd help documentation (#1108) Adds tests for other cases where we do not expect projects to be created for absolute paths and relative paths in GOPATH. --- cobra/cmd/init.go | 63 ++++++++++++++++++---------------- cobra/cmd/init_test.go | 77 ++++++++++++++++++++++++++++++++++++------ 2 files changed, 99 insertions(+), 41 deletions(-) diff --git a/cobra/cmd/init.go b/cobra/cmd/init.go index edddc2cf..504a4785 100644 --- a/cobra/cmd/init.go +++ b/cobra/cmd/init.go @@ -32,42 +32,17 @@ var ( Long: `Initialize (cobra init) will create a new application, with a license and the appropriate structure for a Cobra-based CLI application. - * If a name is provided, it will be created in the current directory; + * If a name is provided, a directory with that name will be created in the current directory; * If no name is provided, the current directory will be assumed; - * If a relative path is provided, it will be created inside $GOPATH - (e.g. github.com/spf13/hugo); - * If an absolute path is provided, it will be created; - * If the directory already exists but is empty, it will be used. +`, -Init will not use an existing directory with contents.`, + Run: func(_ *cobra.Command, args []string) { - Run: func(cmd *cobra.Command, args []string) { - - wd, err := os.Getwd() + projectPath, err := initializeProject(args) if err != nil { er(err) } - - if len(args) > 0 { - if args[0] != "." { - wd = fmt.Sprintf("%s/%s", wd, args[0]) - } - } - - project := &Project{ - AbsolutePath: wd, - PkgName: pkgName, - Legal: getLicense(), - Copyright: copyrightLine(), - Viper: viper.GetBool("useViper"), - AppName: path.Base(pkgName), - } - - if err := project.Create(); err != nil { - er(err) - } - - fmt.Printf("Your Cobra application is ready at\n%s\n", project.AbsolutePath) + fmt.Printf("Your Cobra application is ready at\n%s\n", projectPath) }, } ) @@ -76,3 +51,31 @@ func init() { initCmd.Flags().StringVar(&pkgName, "pkg-name", "", "fully qualified pkg name") initCmd.MarkFlagRequired("pkg-name") } + +func initializeProject(args []string) (string, error) { + wd, err := os.Getwd() + if err != nil { + return "", err + } + + if len(args) > 0 { + if args[0] != "." { + wd = fmt.Sprintf("%s/%s", wd, args[0]) + } + } + + project := &Project{ + AbsolutePath: wd, + PkgName: pkgName, + Legal: getLicense(), + Copyright: copyrightLine(), + Viper: viper.GetBool("useViper"), + AppName: path.Base(pkgName), + } + + if err := project.Create(); err != nil { + return "", err + } + + return project.AbsolutePath, nil +} diff --git a/cobra/cmd/init_test.go b/cobra/cmd/init_test.go index 8ee39106..c4b3f09a 100644 --- a/cobra/cmd/init_test.go +++ b/cobra/cmd/init_test.go @@ -2,9 +2,12 @@ package cmd import ( "fmt" + "io/ioutil" "os" "path/filepath" "testing" + + "github.com/spf13/viper" ) func getProject() *Project { @@ -20,20 +23,72 @@ func getProject() *Project { } func TestGoldenInitCmd(t *testing.T) { - project := getProject() - defer os.RemoveAll(project.AbsolutePath) - if err := project.Create(); err != nil { + dir, err := ioutil.TempDir("", "cobra-init") + if err != nil { t.Fatal(err) } + defer os.RemoveAll(dir) - expectedFiles := []string{"LICENSE", "main.go", "cmd/root.go"} - for _, f := range expectedFiles { - generatedFile := fmt.Sprintf("%s/%s", project.AbsolutePath, f) - goldenFile := fmt.Sprintf("testdata/%s.golden", filepath.Base(f)) - err := compareFiles(generatedFile, goldenFile) - if err != nil { - t.Fatal(err) - } + tests := []struct { + name string + args []string + pkgName string + expectErr bool + }{ + { + name: "successfully creates a project with name", + args: []string{"testproject"}, + pkgName: "github.com/spf13/testproject", + expectErr: false, + }, + { + name: "returns error when passing an absolute path for project", + args: []string{dir}, + pkgName: "github.com/spf13/testproject", + expectErr: true, + }, + { + name: "returns error when passing an relative path for project", + args: []string{"github.com/spf13/testproject"}, + pkgName: "github.com/spf13/testproject", + expectErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + initCmd.Flags().Set("pkg-name", tt.pkgName) + viper.Set("useViper", true) + projectPath, err := initializeProject(tt.args) + defer func() { + if projectPath != "" { + os.RemoveAll(projectPath) + } + }() + + if !tt.expectErr && err != nil { + t.Fatalf("did not expect an error, got %s", err) + } + if tt.expectErr { + if err == nil { + t.Fatal("expected an error but got none") + } else { + // got an expected error nothing more to do + return + } + } + + expectedFiles := []string{"LICENSE", "main.go", "cmd/root.go"} + for _, f := range expectedFiles { + generatedFile := fmt.Sprintf("%s/%s", projectPath, f) + goldenFile := fmt.Sprintf("testdata/%s.golden", filepath.Base(f)) + err := compareFiles(generatedFile, goldenFile) + if err != nil { + t.Fatal(err) + } + } + }) } } From e392f3204db0445f1f4ad47448b04d8c8ad04c2a Mon Sep 17 00:00:00 2001 From: John McBride Date: Wed, 29 Apr 2020 11:15:55 -0600 Subject: [PATCH 34/56] Man pages won't have auto gen tag when option is disabled (#1104) * Man pages wont have auto gen tag when option is disabled - this addresses #741 * Add documentation for doc generation and a changelog --- CHANGELOG.md | 22 ++++++++++++++++++++++ README.md | 6 +----- doc/README.md | 12 ++++++++++++ doc/man_docs.go | 6 +++--- doc/man_docs_test.go | 2 ++ 5 files changed, 40 insertions(+), 8 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 doc/README.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..742d6d6e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,22 @@ +# Cobra Changelog + +## Pending +* Fix man page doc generation - no auto generated tag when `cmd.DisableAutoGenTag = true` @jpmcb + +## v1.0.0 +Announcing v1.0.0 of Cobra. 🎉 +**Notable Changes** +* Fish completion (including support for Go custom completion) @marckhouzam +* API (urgent): Rename BashCompDirectives to ShellCompDirectives @marckhouzam +* Remove/replace SetOutput on Command - deprecated @jpmcb +* add support for autolabel stale PR @xchapter7x +* Add Labeler Actions @xchapter7x +* Custom completions coded in Go (instead of Bash) @marckhouzam +* Partial Revert of #922 @jharshman +* Add Makefile to project @jharshman +* Correct documentation for InOrStdin @desponda +* Apply formatting to templates @jharshman +* Revert change so help is printed on stdout again @marckhouzam +* Update md2man to v2.0.0 @pdf +* update viper to v1.4.0 @umarcor +* Update cmd/root.go example in README.md @jharshman diff --git a/README.md b/README.md index a3713660..a932dd74 100644 --- a/README.md +++ b/README.md @@ -718,11 +718,7 @@ Run 'kubectl help' for usage. ## Generating documentation for your command -Cobra can generate documentation based on subcommands, flags, etc. in the following formats: - -- [Markdown](doc/md_docs.md) -- [ReStructured Text](doc/rest_docs.md) -- [Man Page](doc/man_docs.md) +Cobra can generate documentation based on subcommands, flags, etc. Read more about it in the [docs generation documentation](doc/README.md). ## Generating bash completions diff --git a/doc/README.md b/doc/README.md new file mode 100644 index 00000000..6ea4eb66 --- /dev/null +++ b/doc/README.md @@ -0,0 +1,12 @@ +# Documentation generation + +- [Man page docs](./man_docs.md) +- [Markdown docs](./md_docs.md) +- [Rest docs](./rest_docs.md) +- [Yaml docs](./yaml_docs.md) + +## Options +### `DisableAutoGenTag` +You may set `cmd.DisableAutoGenTag = true` +to _entirely_ remove the auto generated string "Auto generated by spf13/cobra..." +from any documentation source. diff --git a/doc/man_docs.go b/doc/man_docs.go index 8c7fba44..b29a6778 100644 --- a/doc/man_docs.go +++ b/doc/man_docs.go @@ -105,7 +105,7 @@ func GenMan(cmd *cobra.Command, header *GenManHeader, w io.Writer) error { if header == nil { header = &GenManHeader{} } - if err := fillHeader(header, cmd.CommandPath()); err != nil { + if err := fillHeader(header, cmd.CommandPath(), cmd.DisableAutoGenTag); err != nil { return err } @@ -114,7 +114,7 @@ func GenMan(cmd *cobra.Command, header *GenManHeader, w io.Writer) error { return err } -func fillHeader(header *GenManHeader, name string) error { +func fillHeader(header *GenManHeader, name string, disableAutoGen bool) error { if header.Title == "" { header.Title = strings.ToUpper(strings.Replace(name, " ", "\\-", -1)) } @@ -133,7 +133,7 @@ func fillHeader(header *GenManHeader, name string) error { header.Date = &now } header.date = (*header.Date).Format("Jan 2006") - if header.Source == "" { + if header.Source == "" && !disableAutoGen { header.Source = "Auto generated by spf13/cobra" } return nil diff --git a/doc/man_docs_test.go b/doc/man_docs_test.go index 2c400f5d..ee9b8753 100644 --- a/doc/man_docs_test.go +++ b/doc/man_docs_test.go @@ -101,6 +101,8 @@ func TestGenManNoGenTag(t *testing.T) { unexpected := translate("#HISTORY") checkStringOmits(t, output, unexpected) + unexpected = translate("Auto generated by spf13/cobra") + checkStringOmits(t, output, unexpected) } func TestGenManSeeAlso(t *testing.T) { From f8fdd173832c65ef5c7d97a8bccb32f1ba567038 Mon Sep 17 00:00:00 2001 From: Marc Khouzam Date: Thu, 7 May 2020 17:04:14 -0400 Subject: [PATCH 35/56] Complete command names even if ValidArgs present (#1088) Signed-off-by: Marc Khouzam --- bash_completions.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bash_completions.go b/bash_completions.go index 1e27188c..d8341cc4 100644 --- a/bash_completions.go +++ b/bash_completions.go @@ -181,10 +181,9 @@ __%[1]s_handle_reply() local completions completions=("${commands[@]}") if [[ ${#must_have_one_noun[@]} -ne 0 ]]; then - completions=("${must_have_one_noun[@]}") + completions+=("${must_have_one_noun[@]}") elif [[ -n "${has_completion_function}" ]]; then # if a go completion function is provided, defer to that function - completions=() __%[1]s_handle_go_custom_completion fi if [[ ${#must_have_one_flag[@]} -ne 0 ]]; then From a7aaa7cfaca5ed151b2fcabb14e7efd8b466e4ef Mon Sep 17 00:00:00 2001 From: John Calabrese Date: Thu, 7 May 2020 21:02:17 -0400 Subject: [PATCH 36/56] replace labeler action with periodic-labeler (#1097) * replace labeler action with periodic-labeler this replaces the default labeler as it does not label PRs from forks properly. with periodic-labeler it should apply labels on a cron to any PR and resolve the below bug: https://github.com/spf13/cobra/issues/1092 --- .github/workflows/label.yml | 19 ------------------- .github/workflows/periodic-labeler.yml | 17 +++++++++++++++++ 2 files changed, 17 insertions(+), 19 deletions(-) delete mode 100644 .github/workflows/label.yml create mode 100644 .github/workflows/periodic-labeler.yml diff --git a/.github/workflows/label.yml b/.github/workflows/label.yml deleted file mode 100644 index e90b599b..00000000 --- a/.github/workflows/label.yml +++ /dev/null @@ -1,19 +0,0 @@ -# This workflow will triage pull requests and apply a label based on the -# paths that are modified in the pull request. -# -# To use this workflow, you will need to set up a .github/labeler.yml -# file with configuration. For more information, see: -# https://github.com/actions/labeler/blob/master/README.md - -name: Labeler -on: [pull_request] - -jobs: - label: - - runs-on: ubuntu-latest - - steps: - - uses: actions/labeler@v2 - with: - repo-token: "${{ secrets.GITHUB_TOKEN }}" diff --git a/.github/workflows/periodic-labeler.yml b/.github/workflows/periodic-labeler.yml new file mode 100644 index 00000000..dfbb8e48 --- /dev/null +++ b/.github/workflows/periodic-labeler.yml @@ -0,0 +1,17 @@ +--- +name: Pull request labeler +on: + schedule: + - cron: '*/5 * * * *' +jobs: + labeler: + runs-on: ubuntu-latest + steps: + # if we are to change the labeler version from v0.0.2 + # we must review the code for that version + # to make sure there are no leaks or exploits + - uses: paulfantom/periodic-labeler@v0.0.2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_REPOSITORY: ${{ github.repository }} + LABEL_MAPPINGS_FILE: .github/labeler.yml From aa5badda62c6d673904230b93555ec2e960fa487 Mon Sep 17 00:00:00 2001 From: Stefan Majer Date: Fri, 8 May 2020 03:13:20 +0200 Subject: [PATCH 37/56] Metal Stack CLI is using cobra as well (#1094) --- projects_using_cobra.md | 1 + 1 file changed, 1 insertion(+) diff --git a/projects_using_cobra.md b/projects_using_cobra.md index aa791077..ced51f68 100644 --- a/projects_using_cobra.md +++ b/projects_using_cobra.md @@ -14,6 +14,7 @@ - [Kubernetes](http://kubernetes.io/) - [Linkerd](https://linkerd.io/) - [Mattermost-server](https://github.com/mattermost/mattermost-server) +- [Metal Stack CLI](https://github.com/metal-stack/metalctl) - [Moby (former Docker)](https://github.com/moby/moby) - [Nanobox](https://github.com/nanobox-io/nanobox)/[Nanopack](https://github.com/nanopack) - [OpenShift](https://www.openshift.com/) From 5155946348eed0f79a76f7743407c0c933e3b5f0 Mon Sep 17 00:00:00 2001 From: Marc Khouzam Date: Thu, 7 May 2020 21:18:16 -0400 Subject: [PATCH 38/56] Ignore required flags when DisableFlagParsing (#1095) When a command request to DisableFlagParsing, it should not fail due to a missing required flag. In fact, such a check will always fail since flags weren't parsed! Signed-off-by: Marc Khouzam --- command.go | 4 ++++ command_test.go | 31 +++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/command.go b/command.go index 88e6ed77..5b81f61d 100644 --- a/command.go +++ b/command.go @@ -979,6 +979,10 @@ func (c *Command) ValidateArgs(args []string) error { } func (c *Command) validateRequiredFlags() error { + if c.DisableFlagParsing { + return nil + } + flags := c.Flags() missingFlagNames := []string{} flags.VisitAll(func(pflag *flag.Flag) { diff --git a/command_test.go b/command_test.go index ec8e2aef..16cc41b4 100644 --- a/command_test.go +++ b/command_test.go @@ -785,6 +785,37 @@ func TestPersistentRequiredFlags(t *testing.T) { } } +func TestPersistentRequiredFlagsWithDisableFlagParsing(t *testing.T) { + // Make sure a required persistent flag does not break + // commands that disable flag parsing + + parent := &Command{Use: "parent", Run: emptyRun} + parent.PersistentFlags().Bool("foo", false, "") + flag := parent.PersistentFlags().Lookup("foo") + parent.MarkPersistentFlagRequired("foo") + + child := &Command{Use: "child", Run: emptyRun} + child.DisableFlagParsing = true + + parent.AddCommand(child) + + if _, err := executeCommand(parent, "--foo", "child"); err != nil { + t.Errorf("Unexpected error: %v", err) + } + + // Reset the flag or else it will remember the state from the previous command + flag.Changed = false + if _, err := executeCommand(parent, "child", "--foo"); err != nil { + t.Errorf("Unexpected error: %v", err) + } + + // Reset the flag or else it will remember the state from the previous command + flag.Changed = false + if _, err := executeCommand(parent, "child"); err != nil { + t.Errorf("Unexpected error: %v", err) + } +} + func TestInitHelpFlagMergesFlags(t *testing.T) { usage := "custom flag" rootCmd := &Command{Use: "root"} From 94a87a7b83bda98e918cf7a4793f41372a5e4139 Mon Sep 17 00:00:00 2001 From: Joshua Harshman Date: Fri, 8 May 2020 09:53:33 -0600 Subject: [PATCH 39/56] disable periodic labeler (#1112) --- .github/labeler.yml | 12 ------------ .github/workflows/periodic-labeler.yml | 17 ----------------- 2 files changed, 29 deletions(-) delete mode 100644 .github/labeler.yml delete mode 100644 .github/workflows/periodic-labeler.yml diff --git a/.github/labeler.yml b/.github/labeler.yml deleted file mode 100644 index a4982bf3..00000000 --- a/.github/labeler.yml +++ /dev/null @@ -1,12 +0,0 @@ -# changes to documentation -"area/documentation": doc/**/* - -# changes to the core lib package -"area/lib": ./*.go - -# changes to the zsh completion -"area/*sh completion": - - ./zsh_* - - ./shell_* - - ./powershell_* - - ./bash_* diff --git a/.github/workflows/periodic-labeler.yml b/.github/workflows/periodic-labeler.yml deleted file mode 100644 index dfbb8e48..00000000 --- a/.github/workflows/periodic-labeler.yml +++ /dev/null @@ -1,17 +0,0 @@ ---- -name: Pull request labeler -on: - schedule: - - cron: '*/5 * * * *' -jobs: - labeler: - runs-on: ubuntu-latest - steps: - # if we are to change the labeler version from v0.0.2 - # we must review the code for that version - # to make sure there are no leaks or exploits - - uses: paulfantom/periodic-labeler@v0.0.2 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GITHUB_REPOSITORY: ${{ github.repository }} - LABEL_MAPPINGS_FILE: .github/labeler.yml From 1d3ac910d4161a1485f9352a0b9750e2e0ae6248 Mon Sep 17 00:00:00 2001 From: Vaibhav Kaushik Date: Fri, 29 May 2020 21:47:41 +0530 Subject: [PATCH 40/56] Update projects_using_cobra.md (#1113) * Removing deprecated projects * Update etcd website --- projects_using_cobra.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/projects_using_cobra.md b/projects_using_cobra.md index ced51f68..de450338 100644 --- a/projects_using_cobra.md +++ b/projects_using_cobra.md @@ -4,6 +4,7 @@ - [CockroachDB](http://www.cockroachlabs.com/) - [Delve](https://github.com/derekparker/delve) - [Docker (distribution)](https://github.com/docker/distribution) +- [Etcd](https://etcd.io/) - [Gardener](https://github.com/gardener/gardenctl) - [Giant Swarm's gsctl](https://github.com/giantswarm/gsctl) - [Github CLI](https://github.com/cli/cli) @@ -22,6 +23,4 @@ - [ProjectAtomic (enterprise)](http://www.projectatomic.io/) - [Prototool](https://github.com/uber/prototool) - [Rclone](https://rclone.org/) -- [etcd](https://github.com/coreos/etcd) -- [nehm](https://github.com/bogem/nehm) -- [rkt](https://github.com/coreos/rkt) +- [Skaffold](https://skaffold.dev/) From ed7b60e2987d2cd54d6a391379e33effdacdadf9 Mon Sep 17 00:00:00 2001 From: "Alessandro (Ale) Segala" <43508+ItalyPaleAle@users.noreply.github.com> Date: Mon, 15 Jun 2020 15:51:03 -0700 Subject: [PATCH 41/56] YAML documentation contains "Usage" (#1037) --- doc/yaml_docs.go | 5 +++++ doc/yaml_docs_test.go | 11 +++++++++++ 2 files changed, 16 insertions(+) diff --git a/doc/yaml_docs.go b/doc/yaml_docs.go index ea00af07..96e6ad72 100644 --- a/doc/yaml_docs.go +++ b/doc/yaml_docs.go @@ -37,6 +37,7 @@ type cmdDoc struct { Name string Synopsis string `yaml:",omitempty"` Description string `yaml:",omitempty"` + Usage string `yaml:",omitempty"` Options []cmdOption `yaml:",omitempty"` InheritedOptions []cmdOption `yaml:"inherited_options,omitempty"` Example string `yaml:",omitempty"` @@ -98,6 +99,10 @@ func GenYamlCustom(cmd *cobra.Command, w io.Writer, linkHandler func(string) str yamlDoc.Synopsis = forceMultiLine(cmd.Short) yamlDoc.Description = forceMultiLine(cmd.Long) + if cmd.Runnable() { + yamlDoc.Usage = cmd.UseLine() + } + if len(cmd.Example) > 0 { yamlDoc.Example = cmd.Example } diff --git a/doc/yaml_docs_test.go b/doc/yaml_docs_test.go index c5a63594..d08fa4f8 100644 --- a/doc/yaml_docs_test.go +++ b/doc/yaml_docs_test.go @@ -57,6 +57,17 @@ func TestGenYamlTree(t *testing.T) { } } +func TestGenYamlDocRunnable(t *testing.T) { + // Testing a runnable command: should contain the "usage" field + buf := new(bytes.Buffer) + if err := GenYaml(rootCmd, buf); err != nil { + t.Fatal(err) + } + output := buf.String() + + checkStringContains(t, output, "usage: "+rootCmd.Use) +} + func BenchmarkGenYamlToFile(b *testing.B) { file, err := ioutil.TempFile("", "") if err != nil { From 04318720db1743b8488c86b2f7dca6d9663cb2f2 Mon Sep 17 00:00:00 2001 From: Marc Khouzam Date: Tue, 16 Jun 2020 16:49:26 -0400 Subject: [PATCH 42/56] Add completion for help command (#1136) * Don't exclude 'help' from bash completions Fixes #1000. * Provide completion for the help command 1- Show 'help' as a possible completion 2- Provide completions for the help command itself Signed-off-by: Marc Khouzam Co-authored-by: Zaven Muradyan --- bash_completions.go | 4 +-- command.go | 20 ++++++++++- custom_completions.go | 8 +++-- custom_completions_test.go | 68 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 94 insertions(+), 6 deletions(-) diff --git a/bash_completions.go b/bash_completions.go index d8341cc4..f5623993 100644 --- a/bash_completions.go +++ b/bash_completions.go @@ -389,7 +389,7 @@ fi func writeCommands(buf *bytes.Buffer, cmd *Command) { buf.WriteString(" commands=()\n") for _, c := range cmd.Commands() { - if !c.IsAvailableCommand() || c == cmd.helpCommand { + if !c.IsAvailableCommand() && c != cmd.helpCommand { continue } buf.WriteString(fmt.Sprintf(" commands+=(%q)\n", c.Name())) @@ -582,7 +582,7 @@ func writeArgAliases(buf *bytes.Buffer, cmd *Command) { func gen(buf *bytes.Buffer, cmd *Command) { for _, c := range cmd.Commands() { - if !c.IsAvailableCommand() || c == cmd.helpCommand { + if !c.IsAvailableCommand() && c != cmd.helpCommand { continue } gen(buf, c) diff --git a/command.go b/command.go index 5b81f61d..5f1caccc 100644 --- a/command.go +++ b/command.go @@ -1056,7 +1056,25 @@ func (c *Command) InitDefaultHelpCmd() { Short: "Help about any command", Long: `Help provides help for any command in the application. Simply type ` + c.Name() + ` help [path to command] for full details.`, - + ValidArgsFunction: func(c *Command, args []string, toComplete string) ([]string, ShellCompDirective) { + var completions []string + cmd, _, e := c.Root().Find(args) + if e != nil { + return nil, ShellCompDirectiveNoFileComp + } + if cmd == nil { + // Root help command. + cmd = c.Root() + } + for _, subCmd := range cmd.Commands() { + if subCmd.IsAvailableCommand() || subCmd == cmd.helpCommand { + if strings.HasPrefix(subCmd.Name(), toComplete) { + completions = append(completions, fmt.Sprintf("%s\t%s", subCmd.Name(), subCmd.Short)) + } + } + } + return completions, ShellCompDirectiveNoFileComp + }, Run: func(c *Command, args []string) { cmd, _, e := c.Root().Find(args) if cmd == nil || e != nil { diff --git a/custom_completions.go b/custom_completions.go index ba57327c..47cc9f5a 100644 --- a/custom_completions.go +++ b/custom_completions.go @@ -183,10 +183,12 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi } if flag == nil { - // Complete subcommand names + // Complete subcommand names, including the help command for _, subCmd := range finalCmd.Commands() { - if subCmd.IsAvailableCommand() && strings.HasPrefix(subCmd.Name(), toComplete) { - completions = append(completions, fmt.Sprintf("%s\t%s", subCmd.Name(), subCmd.Short)) + if subCmd.IsAvailableCommand() || subCmd == finalCmd.helpCommand { + if strings.HasPrefix(subCmd.Name(), toComplete) { + completions = append(completions, fmt.Sprintf("%s\t%s", subCmd.Name(), subCmd.Short)) + } } } diff --git a/custom_completions_test.go b/custom_completions_test.go index 86154839..fe15263c 100644 --- a/custom_completions_test.go +++ b/custom_completions_test.go @@ -629,3 +629,71 @@ func TestFlagCompletionInGoWithDesc(t *testing.T) { t.Errorf("expected: %q, got: %q", expected, output) } } + +func TestCompleteHelp(t *testing.T) { + rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun} + child1Cmd := &Command{ + Use: "child1", + Run: emptyRun, + } + child2Cmd := &Command{ + Use: "child2", + Run: emptyRun, + } + rootCmd.AddCommand(child1Cmd, child2Cmd) + + child3Cmd := &Command{ + Use: "child3", + Run: emptyRun, + } + child1Cmd.AddCommand(child3Cmd) + + // Test that completion includes the help command + output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected := strings.Join([]string{ + "child1", + "child2", + "help", + ":0", + "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Test sub-commands are completed on first level of help command + output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "help", "") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + "child1", + "child2", + "help", // " help help" is a valid command, so should be completed + ":4", + "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Test sub-commands are completed on first level of help command + output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "help", "child1", "") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + "child3", + ":4", + "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } +} From 2c5a0d300f8ba1cd142776508519aba999b5e77f Mon Sep 17 00:00:00 2001 From: Marc Khouzam Date: Mon, 29 Jun 2020 15:52:14 -0400 Subject: [PATCH 43/56] Extend Go completions and revamp zsh comp (#1070) (#1070) Replace the current Zsh completion with a Zsh completion solution based on Go completions. This allows to support custom completions (based on Go completions), but also to standardize the behavior of completion across all shells. Also, add support to Go completions for the bash completion annotations: BashCompFilenameExt (including Command.MarkFlagFilename() family) - still supported by zsh BashCompSubdirsInDir - now supported by zsh BashCompOneRequiredFlag (including Command.MarkFlagRequired() family) - now supported by zsh and fish Finally, remove the suggestin of the = form of flag completion. The = form is supported, but it will not be suggested to avoid having duplicated suggestions. --- README.md | 14 +- bash_completions.go | 42 +- bash_completions.md | 306 +-------- custom_completions.go | 307 ++++++--- custom_completions_test.go | 1238 +++++++++++++++++++++++++++++++++++- fish_completions.go | 48 +- fish_completions.md | 7 +- powershell_completions.md | 2 + shell_completions.go | 53 +- shell_completions.md | 429 +++++++++++++ zsh_completions.go | 523 ++++++--------- zsh_completions.md | 76 ++- zsh_completions_test.go | 475 -------------- 13 files changed, 2265 insertions(+), 1255 deletions(-) create mode 100644 shell_completions.md delete mode 100644 zsh_completions_test.go diff --git a/README.md b/README.md index a932dd74..1484240d 100644 --- a/README.md +++ b/README.md @@ -28,8 +28,7 @@ name a few. [This list](./projects_using_cobra.md) contains a more extensive lis * [PreRun and PostRun Hooks](#prerun-and-postrun-hooks) * [Suggestions when "unknown command" happens](#suggestions-when-unknown-command-happens) * [Generating documentation for your command](#generating-documentation-for-your-command) - * [Generating bash completions](#generating-bash-completions) - * [Generating zsh completions](#generating-zsh-completions) + * [Generating shell completions](#generating-shell-completions) - [Contributing](#contributing) - [License](#license) @@ -50,7 +49,7 @@ Cobra provides: * Intelligent suggestions (`app srver`... did you mean `app server`?) * Automatic help generation for commands and flags * Automatic help flag recognition of `-h`, `--help`, etc. -* Automatically generated bash autocomplete for your application +* Automatically generated shell autocomplete for your application (bash, zsh, fish, powershell) * Automatically generated man pages for your application * Command aliases so you can change things without breaking them * The flexibility to define your own help, usage, etc. @@ -720,14 +719,9 @@ Run 'kubectl help' for usage. Cobra can generate documentation based on subcommands, flags, etc. Read more about it in the [docs generation documentation](doc/README.md). -## Generating bash completions +## Generating shell completions -Cobra can generate a bash-completion file. If you add more information to your command, these completions can be amazingly powerful and flexible. Read more about it in [Bash Completions](bash_completions.md). - -## Generating zsh completions - -Cobra can generate zsh-completion file. Read more about it in -[Zsh Completions](zsh_completions.md). +Cobra can generate a shell-completion file for the following shells: Bash, Zsh, Fish, Powershell. If you add more information to your commands, these completions can be amazingly powerful and flexible. Read more about it in [Shell Completions](shell_completions.md). # Contributing diff --git a/bash_completions.go b/bash_completions.go index f5623993..ab428ccb 100644 --- a/bash_completions.go +++ b/bash_completions.go @@ -62,6 +62,12 @@ __%[1]s_handle_go_custom_completion() { __%[1]s_debug "${FUNCNAME[0]}: cur is ${cur}, words[*] is ${words[*]}, #words[@] is ${#words[@]}" + local shellCompDirectiveError=%[3]d + local shellCompDirectiveNoSpace=%[4]d + local shellCompDirectiveNoFileComp=%[5]d + local shellCompDirectiveFilterFileExt=%[6]d + local shellCompDirectiveFilterDirs=%[7]d + local out requestComp lastParam lastChar comp directive args # Prepare the command to request completions for the program. @@ -95,24 +101,50 @@ __%[1]s_handle_go_custom_completion() __%[1]s_debug "${FUNCNAME[0]}: the completion directive is: ${directive}" __%[1]s_debug "${FUNCNAME[0]}: the completions are: ${out[*]}" - if [ $((directive & %[3]d)) -ne 0 ]; then + if [ $((directive & shellCompDirectiveError)) -ne 0 ]; then # Error code. No completion. __%[1]s_debug "${FUNCNAME[0]}: received error from custom completion go code" return else - if [ $((directive & %[4]d)) -ne 0 ]; then + if [ $((directive & shellCompDirectiveNoSpace)) -ne 0 ]; then if [[ $(type -t compopt) = "builtin" ]]; then __%[1]s_debug "${FUNCNAME[0]}: activating no space" compopt -o nospace fi fi - if [ $((directive & %[5]d)) -ne 0 ]; then + if [ $((directive & shellCompDirectiveNoFileComp)) -ne 0 ]; then if [[ $(type -t compopt) = "builtin" ]]; then __%[1]s_debug "${FUNCNAME[0]}: activating no file completion" compopt +o default fi fi + fi + if [ $((directive & shellCompDirectiveFilterFileExt)) -ne 0 ]; then + # File extension filtering + local fullFilter filter filteringCmd + # Do not use quotes around the $out variable or else newline + # characters will be kept. + for filter in ${out[*]}; do + fullFilter+="$filter|" + done + + filteringCmd="_filedir $fullFilter" + __%[1]s_debug "File filtering command: $filteringCmd" + $filteringCmd + elif [ $((directive & shellCompDirectiveFilterDirs)) -ne 0 ]; then + # File completion for directories only + local subDir + # Use printf to strip any trailing newline + subdir=$(printf "%%s" "${out[0]}") + if [ -n "$subdir" ]; then + __%[1]s_debug "Listing directories in $subdir" + __%[1]s_handle_subdirs_in_dir_flag "$subdir" + else + __%[1]s_debug "Listing directories in ." + _filedir -d + fi + else while IFS='' read -r comp; do COMPREPLY+=("$comp") done < <(compgen -W "${out[*]}" -- "$cur") @@ -343,7 +375,9 @@ __%[1]s_handle_word() __%[1]s_handle_word } -`, name, ShellCompNoDescRequestCmd, ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp)) +`, name, ShellCompNoDescRequestCmd, + ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp, + ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs)) } func writePostscript(buf *bytes.Buffer, name string) { diff --git a/bash_completions.md b/bash_completions.md index e61a3a65..a82d5bb8 100644 --- a/bash_completions.md +++ b/bash_completions.md @@ -1,206 +1,14 @@ -# Generating Bash Completions For Your Own cobra.Command +# Generating Bash Completions For Your cobra.Command -If you are using the generator you can create a completion command by running +Please refer to [Shell Completions](shell_completions.md) for details. -```bash -cobra add completion -``` +## Bash legacy dynamic completions -Update the help text show how to install the bash_completion Linux show here [Kubectl docs show mac options](https://kubernetes.io/docs/tasks/tools/install-kubectl/#enabling-shell-autocompletion) +For backwards-compatibility, Cobra still supports its legacy dynamic completion solution (described below). Unlike the `ValidArgsFunction` solution, the legacy solution will only work for Bash shell-completion and not for other shells. This legacy solution can be used along-side `ValidArgsFunction` and `RegisterFlagCompletionFunc()`, as long as both solutions are not used for the same command. This provides a path to gradually migrate from the legacy solution to the new solution. -Writing the shell script to stdout allows the most flexible use. +The legacy solution allows you to inject bash functions into the bash completion script. Those bash functions are responsible for providing the completion choices for your own completions. -```go -// completionCmd represents the completion command -var completionCmd = &cobra.Command{ - Use: "completion", - Short: "Generates bash completion scripts", - Long: `To load completion run - -. <(bitbucket completion) - -To configure your bash shell to load completions for each session add to your bashrc - -# ~/.bashrc or ~/.profile -. <(bitbucket completion) -`, - Run: func(cmd *cobra.Command, args []string) { - rootCmd.GenBashCompletion(os.Stdout); - }, -} -``` - -**Note:** The cobra generator may include messages printed to stdout for example if the config file is loaded, this will break the auto complete script - - -## Example from kubectl - -Generating bash completions from a cobra command is incredibly easy. An actual program which does so for the kubernetes kubectl binary is as follows: - -```go -package main - -import ( - "io/ioutil" - "os" - - "k8s.io/kubernetes/pkg/kubectl/cmd" - "k8s.io/kubernetes/pkg/kubectl/cmd/util" -) - -func main() { - kubectl := cmd.NewKubectlCommand(util.NewFactory(nil), os.Stdin, ioutil.Discard, ioutil.Discard) - kubectl.GenBashCompletionFile("out.sh") -} -``` - -`out.sh` will get you completions of subcommands and flags. Copy it to `/etc/bash_completion.d/` as described [here](https://debian-administration.org/article/316/An_introduction_to_bash_completion_part_1) and reset your terminal to use autocompletion. If you make additional annotations to your code, you can get even more intelligent and flexible behavior. - -## Have the completions code complete your 'nouns' - -### Static completion of nouns - -This method allows you to provide a pre-defined list of completion choices for your nouns using the `validArgs` field. -For example, if you want `kubectl get [tab][tab]` to show a list of valid "nouns" you have to set them. Simplified code from `kubectl get` looks like: - -```go -validArgs []string = { "pod", "node", "service", "replicationcontroller" } - -cmd := &cobra.Command{ - Use: "get [(-o|--output=)json|yaml|template|...] (RESOURCE [NAME] | RESOURCE/NAME ...)", - Short: "Display one or many resources", - Long: get_long, - Example: get_example, - Run: func(cmd *cobra.Command, args []string) { - err := RunGet(f, out, cmd, args) - util.CheckErr(err) - }, - ValidArgs: validArgs, -} -``` - -Notice we put the "ValidArgs" on the "get" subcommand. Doing so will give results like - -```bash -# kubectl get [tab][tab] -node pod replicationcontroller service -``` - -### Plural form and shortcuts for nouns - -If your nouns have a number of aliases, you can define them alongside `ValidArgs` using `ArgAliases`: - -```go -argAliases []string = { "pods", "nodes", "services", "svc", "replicationcontrollers", "rc" } - -cmd := &cobra.Command{ - ... - ValidArgs: validArgs, - ArgAliases: argAliases -} -``` - -The aliases are not shown to the user on tab completion, but they are accepted as valid nouns by -the completion algorithm if entered manually, e.g. in: - -```bash -# kubectl get rc [tab][tab] -backend frontend database -``` - -Note that without declaring `rc` as an alias, the completion algorithm would show the list of nouns -in this example again instead of the replication controllers. - -### Dynamic completion of nouns - -In some cases it is not possible to provide a list of possible completions in advance. Instead, the list of completions must be determined at execution-time. Cobra provides two ways of defining such dynamic completion of nouns. Note that both these methods can be used along-side each other as long as they are not both used for the same command. - -**Note**: *Custom Completions written in Go* will automatically work for other shell-completion scripts (e.g., Fish shell), while *Custom Completions written in Bash* will only work for Bash shell-completion. It is therefore recommended to use *Custom Completions written in Go*. - -#### 1. Custom completions of nouns written in Go - -In a similar fashion as for static completions, you can use the `ValidArgsFunction` field to provide a Go function that Cobra will execute when it needs the list of completion choices for the nouns of a command. Note that either `ValidArgs` or `ValidArgsFunction` can be used for a single cobra command, but not both. -Simplified code from `helm status` looks like: - -```go -cmd := &cobra.Command{ - Use: "status RELEASE_NAME", - Short: "Display the status of the named release", - Long: status_long, - RunE: func(cmd *cobra.Command, args []string) { - RunGet(args[0]) - }, - ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - if len(args) != 0 { - return nil, cobra.ShellCompDirectiveNoFileComp - } - return getReleasesFromCluster(toComplete), cobra.ShellCompDirectiveNoFileComp - }, -} -``` -Where `getReleasesFromCluster()` is a Go function that obtains the list of current Helm releases running on the Kubernetes cluster. -Notice we put the `ValidArgsFunction` on the `status` subcommand. Let's assume the Helm releases on the cluster are: `harbor`, `notary`, `rook` and `thanos` then this dynamic completion will give results like - -```bash -# helm status [tab][tab] -harbor notary rook thanos -``` -You may have noticed the use of `cobra.ShellCompDirective`. These directives are bit fields allowing to control some shell completion behaviors for your particular completion. You can combine them with the bit-or operator such as `cobra.ShellCompDirectiveNoSpace | cobra.ShellCompDirectiveNoFileComp` -```go -// Indicates an error occurred and completions should be ignored. -ShellCompDirectiveError -// Indicates that the shell should not add a space after the completion, -// even if there is a single completion provided. -ShellCompDirectiveNoSpace -// Indicates that the shell should not provide file completion even when -// no completion is provided. -// This currently does not work for zsh or bash < 4 -ShellCompDirectiveNoFileComp -// Indicates that the shell will perform its default behavior after completions -// have been provided (this implies !ShellCompDirectiveNoSpace && !ShellCompDirectiveNoFileComp). -ShellCompDirectiveDefault -``` - -When using the `ValidArgsFunction`, Cobra will call your registered function after having parsed all flags and arguments provided in the command-line. You therefore don't need to do this parsing yourself. For example, when a user calls `helm status --namespace my-rook-ns [tab][tab]`, Cobra will call your registered `ValidArgsFunction` after having parsed the `--namespace` flag, as it would have done when calling the `RunE` function. - -##### Debugging - -Cobra achieves dynamic completions written in Go through the use of a hidden command called by the completion script. To debug your Go completion code, you can call this hidden command directly: -```bash -# helm __complete status har -harbor -:4 -Completion ended with directive: ShellCompDirectiveNoFileComp # This is on stderr -``` -***Important:*** If the noun to complete is empty, you must pass an empty parameter to the `__complete` command: -```bash -# helm __complete status "" -harbor -notary -rook -thanos -:4 -Completion ended with directive: ShellCompDirectiveNoFileComp # This is on stderr -``` -Calling the `__complete` command directly allows you to run the Go debugger to troubleshoot your code. You can also add printouts to your code; Cobra provides the following functions to use for printouts in Go completion code: -```go -// Prints to the completion script debug file (if BASH_COMP_DEBUG_FILE -// is set to a file path) and optionally prints to stderr. -cobra.CompDebug(msg string, printToStdErr bool) { -cobra.CompDebugln(msg string, printToStdErr bool) - -// Prints to the completion script debug file (if BASH_COMP_DEBUG_FILE -// is set to a file path) and to stderr. -cobra.CompError(msg string) -cobra.CompErrorln(msg string) -``` -***Important:*** You should **not** leave traces that print to stdout in your completion code as they will be interpreted as completion choices by the completion script. Instead, use the cobra-provided debugging traces functions mentioned above. - -#### 2. Custom completions of nouns written in Bash - -This method allows you to inject bash functions into the completion script. Those bash functions are responsible for providing the completion choices for your own completions. - -Some more actual code that works in kubernetes: +Some code that works in kubernetes: ```bash const ( @@ -253,93 +61,7 @@ Find more information at https://github.com/GoogleCloudPlatform/kubernetes.`, The `BashCompletionFunction` option is really only valid/useful on the root command. Doing the above will cause `__kubectl_custom_func()` (`___custom_func()`) to be called when the built in processor was unable to find a solution. In the case of kubernetes a valid command might look something like `kubectl get pod [mypod]`. If you type `kubectl get pod [tab][tab]` the `__kubectl_customc_func()` will run because the cobra.Command only understood "kubectl" and "get." `__kubectl_custom_func()` will see that the cobra.Command is "kubectl_get" and will thus call another helper `__kubectl_get_resource()`. `__kubectl_get_resource` will look at the 'nouns' collected. In our example the only noun will be `pod`. So it will call `__kubectl_parse_get pod`. `__kubectl_parse_get` will actually call out to kubernetes and get any pods. It will then set `COMPREPLY` to valid pods! -## Mark flags as required - -Most of the time completions will only show subcommands. But if a flag is required to make a subcommand work, you probably want it to show up when the user types [tab][tab]. Marking a flag as 'Required' is incredibly easy. - -```go -cmd.MarkFlagRequired("pod") -cmd.MarkFlagRequired("container") -``` - -and you'll get something like - -```bash -# kubectl exec [tab][tab][tab] --c --container= -p --pod= -``` - -# Specify valid filename extensions for flags that take a filename - -In this example we use --filename= and expect to get a json or yaml file as the argument. To make this easier we annotate the --filename flag with valid filename extensions. - -```go - annotations := []string{"json", "yaml", "yml"} - annotation := make(map[string][]string) - annotation[cobra.BashCompFilenameExt] = annotations - - flag := &pflag.Flag{ - Name: "filename", - Shorthand: "f", - Usage: usage, - Value: value, - DefValue: value.String(), - Annotations: annotation, - } - cmd.Flags().AddFlag(flag) -``` - -Now when you run a command with this filename flag you'll get something like - -```bash -# kubectl create -f -test/ example/ rpmbuild/ -hello.yml test.json -``` - -So while there are many other files in the CWD it only shows me subdirs and those with valid extensions. - -# Specify custom flag completion - -As for nouns, Cobra provides two ways of defining dynamic completion of flags. Note that both these methods can be used along-side each other as long as they are not both used for the same flag. - -**Note**: *Custom Completions written in Go* will automatically work for other shell-completion scripts (e.g., Fish shell), while *Custom Completions written in Bash* will only work for Bash shell-completion. It is therefore recommended to use *Custom Completions written in Go*. - -## 1. Custom completions of flags written in Go - -To provide a Go function that Cobra will execute when it needs the list of completion choices for a flag, you must register the function in the following manner: - -```go -flagName := "output" -cmd.RegisterFlagCompletionFunc(flagName, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - return []string{"json", "table", "yaml"}, cobra.ShellCompDirectiveDefault -}) -``` -Notice that calling `RegisterFlagCompletionFunc()` is done through the `command` with which the flag is associated. In our example this dynamic completion will give results like so: - -```bash -# helm status --output [tab][tab] -json table yaml -``` - -### Debugging - -You can also easily debug your Go completion code for flags: -```bash -# helm __complete status --output "" -json -table -yaml -:4 -Completion ended with directive: ShellCompDirectiveNoFileComp # This is on stderr -``` -***Important:*** You should **not** leave traces that print to stdout in your completion code as they will be interpreted as completion choices by the completion script. Instead, use the cobra-provided debugging traces functions mentioned in the above section. - -## 2. Custom completions of flags written in Bash - -Alternatively, you can use bash code for flag custom completion. Similar to the filename -completion and filtering using `cobra.BashCompFilenameExt`, you can specify -a custom flag completion bash function with `cobra.BashCompCustom`: +Similarly, for flags: ```go annotation := make(map[string][]string) @@ -367,17 +89,3 @@ __kubectl_get_namespaces() fi } ``` -# Using bash aliases for commands - -You can also configure the `bash aliases` for the commands and they will also support completions. - -```bash -alias aliasname=origcommand -complete -o default -F __start_origcommand aliasname - -# and now when you run `aliasname` completion will make -# suggestions as it did for `origcommand`. - -$) aliasname -completion firstcommand secondcommand -``` diff --git a/custom_completions.go b/custom_completions.go index 47cc9f5a..c25c03e4 100644 --- a/custom_completions.go +++ b/custom_completions.go @@ -1,7 +1,6 @@ package cobra import ( - "errors" "fmt" "os" "strings" @@ -38,8 +37,29 @@ const ( // This currently does not work for zsh or bash < 4 ShellCompDirectiveNoFileComp + // ShellCompDirectiveFilterFileExt indicates that the provided completions + // should be used as file extension filters. + // For flags, using Command.MarkFlagFilename() and Command.MarkPersistentFlagFilename() + // is a shortcut to using this directive explicitly. The BashCompFilenameExt + // annotation can also be used to obtain the same behavior for flags. + ShellCompDirectiveFilterFileExt + + // ShellCompDirectiveFilterDirs indicates that only directory names should + // be provided in file completion. To request directory names within another + // directory, the returned completions should specify the directory within + // which to search. The BashCompSubdirsInDir annotation can be used to + // obtain the same behavior but only for flags. + ShellCompDirectiveFilterDirs + + // =========================================================================== + + // All directives using iota should be above this one. + // For internal use. + shellCompDirectiveMaxValue + // ShellCompDirectiveDefault indicates to let the shell perform its default // behavior after completions have been provided. + // This one must be last to avoid messing up the iota count. ShellCompDirectiveDefault ShellCompDirective = 0 ) @@ -68,11 +88,17 @@ func (d ShellCompDirective) string() string { if d&ShellCompDirectiveNoFileComp != 0 { directives = append(directives, "ShellCompDirectiveNoFileComp") } + if d&ShellCompDirectiveFilterFileExt != 0 { + directives = append(directives, "ShellCompDirectiveFilterFileExt") + } + if d&ShellCompDirectiveFilterDirs != 0 { + directives = append(directives, "ShellCompDirectiveFilterDirs") + } if len(directives) == 0 { directives = append(directives, "ShellCompDirectiveDefault") } - if d > ShellCompDirectiveError+ShellCompDirectiveNoSpace+ShellCompDirectiveNoFileComp { + if d >= shellCompDirectiveMaxValue { return fmt.Sprintf("ERROR: unexpected ShellCompDirective value: %d", d) } return strings.Join(directives, ", ") @@ -105,11 +131,19 @@ func (c *Command) initCompleteCmd(args []string) { // Remove any description that may be included following a tab character. comp = strings.Split(comp, "\t")[0] } + + // Finally trim the completion. This is especially important to get rid + // of a trailing tab when there are no description following it. + // For example, a sub-command without a description should not be completed + // with a tab at the end (or else zsh will show a -- following it + // although there is no description). + comp = strings.TrimSpace(comp) + // Print each possible completion to stdout for the completion script to consume. fmt.Fprintln(finalCmd.OutOrStdout(), comp) } - if directive > ShellCompDirectiveError+ShellCompDirectiveNoSpace+ShellCompDirectiveNoFileComp { + if directive >= shellCompDirectiveMaxValue { directive = ShellCompDirectiveDefault } @@ -136,8 +170,6 @@ func (c *Command) initCompleteCmd(args []string) { } func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDirective, error) { - var completions []string - // The last argument, which is not completely typed by the user, // should not be part of the list of arguments toComplete := args[len(args)-1] @@ -147,81 +179,158 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi finalCmd, finalArgs, err := c.Root().Find(trimmedArgs) if err != nil { // Unable to find the real command. E.g., someInvalidCmd - return c, completions, ShellCompDirectiveDefault, fmt.Errorf("Unable to find a command for arguments: %v", trimmedArgs) + return c, []string{}, ShellCompDirectiveDefault, fmt.Errorf("Unable to find a command for arguments: %v", trimmedArgs) + } + + // Check if we are doing flag value completion before parsing the flags. + // This is important because if we are completing a flag value, we need to also + // remove the flag name argument from the list of finalArgs or else the parsing + // could fail due to an invalid value (incomplete) for the flag. + flag, finalArgs, toComplete, err := checkIfFlagCompletion(finalCmd, finalArgs, toComplete) + if err != nil { + // Error while attempting to parse flags + return finalCmd, []string{}, ShellCompDirectiveDefault, err + } + + // Parse the flags early so we can check if required flags are set + if err = finalCmd.ParseFlags(finalArgs); err != nil { + return finalCmd, []string{}, ShellCompDirectiveDefault, fmt.Errorf("Error while parsing flags from args %v: %s", finalArgs, err.Error()) + } + + if flag != nil { + // Check if we are completing a flag value subject to annotations + if validExts, present := flag.Annotations[BashCompFilenameExt]; present { + if len(validExts) != 0 { + // File completion filtered by extensions + return finalCmd, validExts, ShellCompDirectiveFilterFileExt, nil + } + + // The annotation requests simple file completion. There is no reason to do + // that since it is the default behavior anyway. Let's ignore this annotation + // in case the program also registered a completion function for this flag. + // Even though it is a mistake on the program's side, let's be nice when we can. + } + + if subDir, present := flag.Annotations[BashCompSubdirsInDir]; present { + if len(subDir) == 1 { + // Directory completion from within a directory + return finalCmd, subDir, ShellCompDirectiveFilterDirs, nil + } + // Directory completion + return finalCmd, []string{}, ShellCompDirectiveFilterDirs, nil + } } // When doing completion of a flag name, as soon as an argument starts with // a '-' we know it is a flag. We cannot use isFlagArg() here as it requires - // the flag to be complete - if len(toComplete) > 0 && toComplete[0] == '-' && !strings.Contains(toComplete, "=") { - // We are completing a flag name - finalCmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) { - completions = append(completions, getFlagNameCompletions(flag, toComplete)...) - }) - finalCmd.InheritedFlags().VisitAll(func(flag *pflag.Flag) { - completions = append(completions, getFlagNameCompletions(flag, toComplete)...) - }) + // the flag name to be complete + if flag == nil && len(toComplete) > 0 && toComplete[0] == '-' && !strings.Contains(toComplete, "=") { + var completions []string - directive := ShellCompDirectiveDefault - if len(completions) > 0 { - if strings.HasSuffix(completions[0], "=") { - directive = ShellCompDirectiveNoSpace + // First check for required flags + completions = completeRequireFlags(finalCmd, toComplete) + + // If we have not found any required flags, only then can we show regular flags + if len(completions) == 0 { + doCompleteFlags := func(flag *pflag.Flag) { + if !flag.Changed || + strings.Contains(flag.Value.Type(), "Slice") || + strings.Contains(flag.Value.Type(), "Array") { + // If the flag is not already present, or if it can be specified multiple times (Array or Slice) + // we suggest it as a completion + completions = append(completions, getFlagNameCompletions(flag, toComplete)...) + } } + + // We cannot use finalCmd.Flags() because we may not have called ParsedFlags() for commands + // that have set DisableFlagParsing; it is ParseFlags() that merges the inherited and + // non-inherited flags. + finalCmd.InheritedFlags().VisitAll(func(flag *pflag.Flag) { + doCompleteFlags(flag) + }) + finalCmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) { + doCompleteFlags(flag) + }) + } + + directive := ShellCompDirectiveNoFileComp + if len(completions) == 1 && strings.HasSuffix(completions[0], "=") { + // If there is a single completion, the shell usually adds a space + // after the completion. We don't want that if the flag ends with an = + directive = ShellCompDirectiveNoSpace } return finalCmd, completions, directive, nil } - var flag *pflag.Flag - if !finalCmd.DisableFlagParsing { - // We only do flag completion if we are allowed to parse flags - // This is important for commands which have requested to do their own flag completion. - flag, finalArgs, toComplete, err = checkIfFlagCompletion(finalCmd, finalArgs, toComplete) - if err != nil { - // Error while attempting to parse flags - return finalCmd, completions, ShellCompDirectiveDefault, err - } - } - - if flag == nil { - // Complete subcommand names, including the help command - for _, subCmd := range finalCmd.Commands() { - if subCmd.IsAvailableCommand() || subCmd == finalCmd.helpCommand { - if strings.HasPrefix(subCmd.Name(), toComplete) { - completions = append(completions, fmt.Sprintf("%s\t%s", subCmd.Name(), subCmd.Short)) - } - } - } - - if len(finalCmd.ValidArgs) > 0 { - // Always complete ValidArgs, even if we are completing a subcommand name. - // This is for commands that have both subcommands and ValidArgs. - for _, validArg := range finalCmd.ValidArgs { - if strings.HasPrefix(validArg, toComplete) { - completions = append(completions, validArg) - } - } - - // If there are ValidArgs specified (even if they don't match), we stop completion. - // Only one of ValidArgs or ValidArgsFunction can be used for a single command. - return finalCmd, completions, ShellCompDirectiveNoFileComp, nil - } - - // Always let the logic continue so as to add any ValidArgsFunction completions, - // even if we already found sub-commands. - // This is for commands that have subcommands but also specify a ValidArgsFunction. - } - - // Parse the flags and extract the arguments to prepare for calling the completion function - if err = finalCmd.ParseFlags(finalArgs); err != nil { - return finalCmd, completions, ShellCompDirectiveDefault, fmt.Errorf("Error while parsing flags from args %v: %s", finalArgs, err.Error()) - } - // We only remove the flags from the arguments if DisableFlagParsing is not set. // This is important for commands which have requested to do their own flag completion. if !finalCmd.DisableFlagParsing { finalArgs = finalCmd.Flags().Args() } + var completions []string + directive := ShellCompDirectiveDefault + if flag == nil { + // Check if there are any local, non-persistent flags on the command-line + foundLocalNonPersistentFlag := false + localNonPersistentFlags := finalCmd.LocalNonPersistentFlags() + finalCmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) { + if localNonPersistentFlags.Lookup(flag.Name) != nil && flag.Changed { + foundLocalNonPersistentFlag = true + } + }) + + // Complete subcommand names, including the help command + if len(finalArgs) == 0 && !foundLocalNonPersistentFlag { + // We only complete sub-commands if: + // - there are no arguments on the command-line and + // - there are no local, non-peristent flag on the command-line + for _, subCmd := range finalCmd.Commands() { + if subCmd.IsAvailableCommand() || subCmd == finalCmd.helpCommand { + if strings.HasPrefix(subCmd.Name(), toComplete) { + completions = append(completions, fmt.Sprintf("%s\t%s", subCmd.Name(), subCmd.Short)) + } + directive = ShellCompDirectiveNoFileComp + } + } + } + + // Complete required flags even without the '-' prefix + completions = append(completions, completeRequireFlags(finalCmd, toComplete)...) + + // Always complete ValidArgs, even if we are completing a subcommand name. + // This is for commands that have both subcommands and ValidArgs. + if len(finalCmd.ValidArgs) > 0 { + if len(finalArgs) == 0 { + // ValidArgs are only for the first argument + for _, validArg := range finalCmd.ValidArgs { + if strings.HasPrefix(validArg, toComplete) { + completions = append(completions, validArg) + } + } + directive = ShellCompDirectiveNoFileComp + + // If no completions were found within commands or ValidArgs, + // see if there are any ArgAliases that should be completed. + if len(completions) == 0 { + for _, argAlias := range finalCmd.ArgAliases { + if strings.HasPrefix(argAlias, toComplete) { + completions = append(completions, argAlias) + } + } + } + } + + // If there are ValidArgs specified (even if they don't match), we stop completion. + // Only one of ValidArgs or ValidArgsFunction can be used for a single command. + return finalCmd, completions, directive, nil + } + + // Let the logic continue so as to add any ValidArgsFunction completions, + // even if we already found sub-commands. + // This is for commands that have subcommands but also specify a ValidArgsFunction. + } + // Find the completion function for the flag or command var completionFn func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) if flag != nil { @@ -229,14 +338,14 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi } else { completionFn = finalCmd.ValidArgsFunction } - if completionFn == nil { - // Go custom completion not supported/needed for this flag or command - return finalCmd, completions, ShellCompDirectiveDefault, nil + if completionFn != nil { + // Go custom completion defined for this flag or command. + // Call the registered completion function to get the completions. + var comps []string + comps, directive = completionFn(finalCmd, finalArgs, toComplete) + completions = append(completions, comps...) } - // Call the registered completion function to get the completions - comps, directive := completionFn(finalCmd, finalArgs, toComplete) - completions = append(completions, comps...) return finalCmd, completions, directive, nil } @@ -251,11 +360,18 @@ func getFlagNameCompletions(flag *pflag.Flag, toComplete string) []string { // Flag without the = completions = append(completions, fmt.Sprintf("%s\t%s", flagName, flag.Usage)) - if len(flag.NoOptDefVal) == 0 { - // Flag requires a value, so it can be suffixed with = - flagName += "=" - completions = append(completions, fmt.Sprintf("%s\t%s", flagName, flag.Usage)) - } + // Why suggest both long forms: --flag and --flag= ? + // This forces the user to *always* have to type either an = or a space after the flag name. + // Let's be nice and avoid making users have to do that. + // Since boolean flags and shortname flags don't show the = form, let's go that route and never show it. + // The = form will still work, we just won't suggest it. + // This also makes the list of suggested flags shorter as we avoid all the = forms. + // + // if len(flag.NoOptDefVal) == 0 { + // // Flag requires a value, so it can be suffixed with = + // flagName += "=" + // completions = append(completions, fmt.Sprintf("%s\t%s", flagName, flag.Usage)) + // } } flagName = "-" + flag.Shorthand @@ -266,17 +382,54 @@ func getFlagNameCompletions(flag *pflag.Flag, toComplete string) []string { return completions } +func completeRequireFlags(finalCmd *Command, toComplete string) []string { + var completions []string + + doCompleteRequiredFlags := func(flag *pflag.Flag) { + if _, present := flag.Annotations[BashCompOneRequiredFlag]; present { + if !flag.Changed { + // If the flag is not already present, we suggest it as a completion + completions = append(completions, getFlagNameCompletions(flag, toComplete)...) + } + } + } + + // We cannot use finalCmd.Flags() because we may not have called ParsedFlags() for commands + // that have set DisableFlagParsing; it is ParseFlags() that merges the inherited and + // non-inherited flags. + finalCmd.InheritedFlags().VisitAll(func(flag *pflag.Flag) { + doCompleteRequiredFlags(flag) + }) + finalCmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) { + doCompleteRequiredFlags(flag) + }) + + return completions +} + func checkIfFlagCompletion(finalCmd *Command, args []string, lastArg string) (*pflag.Flag, []string, string, error) { + if finalCmd.DisableFlagParsing { + // We only do flag completion if we are allowed to parse flags + // This is important for commands which have requested to do their own flag completion. + return nil, args, lastArg, nil + } + var flagName string trimmedArgs := args flagWithEqual := false - if isFlagArg(lastArg) { + + // When doing completion of a flag name, as soon as an argument starts with + // a '-' we know it is a flag. We cannot use isFlagArg() here as that function + // requires the flag name to be complete + if len(lastArg) > 0 && lastArg[0] == '-' { if index := strings.Index(lastArg, "="); index >= 0 { + // Flag with an = flagName = strings.TrimLeft(lastArg[:index], "-") lastArg = lastArg[index+1:] flagWithEqual = true } else { - return nil, nil, "", errors.New("Unexpected completion request for flag") + // Normal flag completion + return nil, args, lastArg, nil } } diff --git a/custom_completions_test.go b/custom_completions_test.go index fe15263c..66b3e638 100644 --- a/custom_completions_test.go +++ b/custom_completions_test.go @@ -34,6 +34,1106 @@ func validArgsFunc2(cmd *Command, args []string, toComplete string) ([]string, S return completions, ShellCompDirectiveDefault } +func TestCmdNameCompletionInGo(t *testing.T) { + rootCmd := &Command{ + Use: "root", + Run: emptyRun, + } + childCmd1 := &Command{ + Use: "firstChild", + Short: "First command", + Run: emptyRun, + } + childCmd2 := &Command{ + Use: "secondChild", + Run: emptyRun, + } + hiddenCmd := &Command{ + Use: "testHidden", + Hidden: true, // Not completed + Run: emptyRun, + } + deprecatedCmd := &Command{ + Use: "testDeprecated", + Deprecated: "deprecated", // Not completed + Run: emptyRun, + } + aliasedCmd := &Command{ + Use: "aliased", + Short: "A command with aliases", + Aliases: []string{"testAlias", "testSynonym"}, // Not completed + Run: emptyRun, + } + + rootCmd.AddCommand(childCmd1, childCmd2, hiddenCmd, deprecatedCmd, aliasedCmd) + + // Test that sub-command names are completed + output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected := strings.Join([]string{ + "aliased", + "firstChild", + "help", + "secondChild", + ":4", + "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Test that sub-command names are completed with prefix + output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "s") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + "secondChild", + ":4", + "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Test that even with no valid sub-command matches, hidden, deprecated and + // aliases are not completed + output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "test") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + ":4", + "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Test that sub-command names are completed with description + output, err = executeCommand(rootCmd, ShellCompRequestCmd, "") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + "aliased\tA command with aliases", + "firstChild\tFirst command", + "help\tHelp about any command", + "secondChild", + ":4", + "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } +} + +func TestNoCmdNameCompletionInGo(t *testing.T) { + rootCmd := &Command{ + Use: "root", + Run: emptyRun, + } + childCmd1 := &Command{ + Use: "childCmd1", + Short: "First command", + Args: MinimumNArgs(0), + Run: emptyRun, + } + rootCmd.AddCommand(childCmd1) + childCmd1.PersistentFlags().StringP("persistent", "p", "", "persistent flag") + persistentFlag := childCmd1.PersistentFlags().Lookup("persistent") + childCmd1.Flags().StringP("nonPersistent", "n", "", "non-persistent flag") + nonPersistentFlag := childCmd1.Flags().Lookup("nonPersistent") + + childCmd2 := &Command{ + Use: "childCmd2", + Run: emptyRun, + } + childCmd1.AddCommand(childCmd2) + + // Test that sub-command names are not completed if there is an argument already + output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "childCmd1", "arg1", "") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected := strings.Join([]string{ + ":0", + "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Test that sub-command names are not completed if a local non-persistent flag is present + output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "childCmd1", "--nonPersistent", "value", "") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + // Reset the flag for the next command + nonPersistentFlag.Changed = false + + expected = strings.Join([]string{ + ":0", + "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Test that sub-command names are not completed if a local non-persistent short flag is present + output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "childCmd1", "-n", "value", "") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + // Reset the flag for the next command + nonPersistentFlag.Changed = false + + expected = strings.Join([]string{ + ":0", + "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Test that sub-command names are completed with a persistent flag + output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "childCmd1", "--persistent", "value", "") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + // Reset the flag for the next command + persistentFlag.Changed = false + + expected = strings.Join([]string{ + "childCmd2", + ":4", + "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Test that sub-command names are completed with a persistent short flag + output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "childCmd1", "-p", "value", "") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + // Reset the flag for the next command + persistentFlag.Changed = false + + expected = strings.Join([]string{ + "childCmd2", + ":4", + "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } +} + +func TestValidArgsCompletionInGo(t *testing.T) { + rootCmd := &Command{ + Use: "root", + ValidArgs: []string{"one", "two", "three"}, + Args: MinimumNArgs(1), + } + + // Test that validArgs are completed + output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected := strings.Join([]string{ + "one", + "two", + "three", + ":4", + "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Test that validArgs are completed with prefix + output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "o") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + "one", + ":4", + "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Test that validArgs don't repeat + output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "one", "") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + ":0", + "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } +} + +func TestValidArgsAndCmdCompletionInGo(t *testing.T) { + rootCmd := &Command{ + Use: "root", + ValidArgs: []string{"one", "two"}, + Run: emptyRun, + } + + childCmd := &Command{ + Use: "thechild", + Run: emptyRun, + } + + rootCmd.AddCommand(childCmd) + + // Test that both sub-commands and validArgs are completed + output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected := strings.Join([]string{ + "help", + "thechild", + "one", + "two", + ":4", + "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Test that both sub-commands and validArgs are completed with prefix + output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "t") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + "thechild", + "two", + ":4", + "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } +} + +func TestValidArgsFuncAndCmdCompletionInGo(t *testing.T) { + rootCmd := &Command{ + Use: "root", + ValidArgsFunction: validArgsFunc, + Run: emptyRun, + } + + childCmd := &Command{ + Use: "thechild", + Short: "The child command", + Run: emptyRun, + } + + rootCmd.AddCommand(childCmd) + + // Test that both sub-commands and validArgsFunction are completed + output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected := strings.Join([]string{ + "help", + "thechild", + "one", + "two", + ":0", + "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Test that both sub-commands and validArgs are completed with prefix + output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "t") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + "thechild", + "two", + ":0", + "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Test that both sub-commands and validArgs are completed with description + output, err = executeCommand(rootCmd, ShellCompRequestCmd, "t") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + "thechild\tThe child command", + "two\tThe second", + ":0", + "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } +} + +func TestFlagNameCompletionInGo(t *testing.T) { + rootCmd := &Command{ + Use: "root", + Run: emptyRun, + } + childCmd := &Command{ + Use: "childCmd", + Run: emptyRun, + } + rootCmd.AddCommand(childCmd) + + rootCmd.Flags().IntP("first", "f", -1, "first flag") + rootCmd.PersistentFlags().BoolP("second", "s", false, "second flag") + childCmd.Flags().String("subFlag", "", "sub flag") + + // Test that flag names are not shown if the user has not given the '-' prefix + output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected := strings.Join([]string{ + "childCmd", + "help", + ":4", + "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Test that flag names are completed + output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + "--first", + "-f", + "--second", + "-s", + ":4", + "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Test that flag names are completed when a prefix is given + output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--f") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + "--first", + ":4", + "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Test that flag names are completed in a sub-cmd + output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "childCmd", "-") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + "--second", + "-s", + "--subFlag", + ":4", + "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } +} + +func TestFlagNameCompletionInGoWithDesc(t *testing.T) { + rootCmd := &Command{ + Use: "root", + Run: emptyRun, + } + childCmd := &Command{ + Use: "childCmd", + Short: "first command", + Run: emptyRun, + } + rootCmd.AddCommand(childCmd) + + rootCmd.Flags().IntP("first", "f", -1, "first flag") + rootCmd.PersistentFlags().BoolP("second", "s", false, "second flag") + childCmd.Flags().String("subFlag", "", "sub flag") + + // Test that flag names are not shown if the user has not given the '-' prefix + output, err := executeCommand(rootCmd, ShellCompRequestCmd, "") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected := strings.Join([]string{ + "childCmd\tfirst command", + "help\tHelp about any command", + ":4", + "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Test that flag names are completed + output, err = executeCommand(rootCmd, ShellCompRequestCmd, "-") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + "--first\tfirst flag", + "-f\tfirst flag", + "--second\tsecond flag", + "-s\tsecond flag", + ":4", + "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Test that flag names are completed when a prefix is given + output, err = executeCommand(rootCmd, ShellCompRequestCmd, "--f") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + "--first\tfirst flag", + ":4", + "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Test that flag names are completed in a sub-cmd + output, err = executeCommand(rootCmd, ShellCompRequestCmd, "childCmd", "-") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + "--second\tsecond flag", + "-s\tsecond flag", + "--subFlag\tsub flag", + ":4", + "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } +} + +func TestFlagNameCompletionRepeat(t *testing.T) { + rootCmd := &Command{ + Use: "root", + Run: emptyRun, + } + childCmd := &Command{ + Use: "childCmd", + Short: "first command", + Run: emptyRun, + } + rootCmd.AddCommand(childCmd) + + rootCmd.Flags().IntP("first", "f", -1, "first flag") + firstFlag := rootCmd.Flags().Lookup("first") + rootCmd.Flags().BoolP("second", "s", false, "second flag") + secondFlag := rootCmd.Flags().Lookup("second") + rootCmd.Flags().StringArrayP("array", "a", nil, "array flag") + arrayFlag := rootCmd.Flags().Lookup("array") + rootCmd.Flags().IntSliceP("slice", "l", nil, "slice flag") + sliceFlag := rootCmd.Flags().Lookup("slice") + rootCmd.Flags().BoolSliceP("bslice", "b", nil, "bool slice flag") + bsliceFlag := rootCmd.Flags().Lookup("bslice") + + // Test that flag names are not repeated unless they are an array or slice + output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--first", "1", "--") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + // Reset the flag for the next command + firstFlag.Changed = false + + expected := strings.Join([]string{ + "--array", + "--bslice", + "--second", + "--slice", + ":4", + "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Test that flag names are not repeated unless they are an array or slice + output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--first", "1", "--second=false", "--") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + // Reset the flag for the next command + firstFlag.Changed = false + secondFlag.Changed = false + + expected = strings.Join([]string{ + "--array", + "--bslice", + "--slice", + ":4", + "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Test that flag names are not repeated unless they are an array or slice + output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--slice", "1", "--slice=2", "--array", "val", "--bslice", "true", "--") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + // Reset the flag for the next command + sliceFlag.Changed = false + arrayFlag.Changed = false + bsliceFlag.Changed = false + + expected = strings.Join([]string{ + "--array", + "--bslice", + "--first", + "--second", + "--slice", + ":4", + "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Test that flag names are not repeated unless they are an array or slice, using shortname + output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-l", "1", "-l=2", "-a", "val", "-") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + // Reset the flag for the next command + sliceFlag.Changed = false + arrayFlag.Changed = false + + expected = strings.Join([]string{ + "--array", + "-a", + "--bslice", + "-b", + "--first", + "-f", + "--second", + "-s", + "--slice", + "-l", + ":4", + "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Test that flag names are not repeated unless they are an array or slice, using shortname with prefix + output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-l", "1", "-l=2", "-a", "val", "-a") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + // Reset the flag for the next command + sliceFlag.Changed = false + arrayFlag.Changed = false + + expected = strings.Join([]string{ + "-a", + ":4", + "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } +} + +func TestRequiredFlagNameCompletionInGo(t *testing.T) { + rootCmd := &Command{ + Use: "root", + ValidArgs: []string{"realArg"}, + Run: emptyRun, + } + childCmd := &Command{ + Use: "childCmd", + ValidArgsFunction: func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { + return []string{"subArg"}, ShellCompDirectiveNoFileComp + }, + Run: emptyRun, + } + rootCmd.AddCommand(childCmd) + + rootCmd.Flags().IntP("requiredFlag", "r", -1, "required flag") + rootCmd.MarkFlagRequired("requiredFlag") + requiredFlag := rootCmd.Flags().Lookup("requiredFlag") + + rootCmd.PersistentFlags().IntP("requiredPersistent", "p", -1, "required persistent") + rootCmd.MarkPersistentFlagRequired("requiredPersistent") + requiredPersistent := rootCmd.PersistentFlags().Lookup("requiredPersistent") + + rootCmd.Flags().StringP("release", "R", "", "Release name") + + childCmd.Flags().BoolP("subRequired", "s", false, "sub required flag") + childCmd.MarkFlagRequired("subRequired") + childCmd.Flags().BoolP("subNotRequired", "n", false, "sub not required flag") + + // Test that a required flag is suggested even without the - prefix + output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected := strings.Join([]string{ + "childCmd", + "help", + "--requiredFlag", + "-r", + "--requiredPersistent", + "-p", + "realArg", + ":4", + "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Test that a required flag is suggested without other flags when using the '-' prefix + output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + "--requiredFlag", + "-r", + "--requiredPersistent", + "-p", + ":4", + "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Test that if no required flag matches, the normal flags are suggested + output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--relea") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + "--release", + ":4", + "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Test required flags for sub-commands + output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "childCmd", "") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + "--requiredPersistent", + "-p", + "--subRequired", + "-s", + "subArg", + ":4", + "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "childCmd", "-") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + "--requiredPersistent", + "-p", + "--subRequired", + "-s", + ":4", + "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "childCmd", "--subNot") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + "--subNotRequired", + ":4", + "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Test that when a required flag is present, it is not suggested anymore + output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--requiredFlag", "1", "") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + // Reset the flag for the next command + requiredFlag.Changed = false + + expected = strings.Join([]string{ + "--requiredPersistent", + "-p", + "realArg", + ":4", + "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Test that when a persistent required flag is present, it is not suggested anymore + output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--requiredPersistent", "1", "") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + // Reset the flag for the next command + requiredPersistent.Changed = false + + expected = strings.Join([]string{ + "childCmd", + "help", + "--requiredFlag", + "-r", + "realArg", + ":4", + "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Test that when all required flags are present, normal completion is done + output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--requiredFlag", "1", "--requiredPersistent", "1", "") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + // Reset the flags for the next command + requiredFlag.Changed = false + requiredPersistent.Changed = false + + expected = strings.Join([]string{ + "realArg", + ":4", + "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } +} + +func TestFlagFileExtFilterCompletionInGo(t *testing.T) { + rootCmd := &Command{ + Use: "root", + Run: emptyRun, + } + + // No extensions. Should be ignored. + rootCmd.Flags().StringP("file", "f", "", "file flag") + rootCmd.MarkFlagFilename("file") + + // Single extension + rootCmd.Flags().StringP("log", "l", "", "log flag") + rootCmd.MarkFlagFilename("log", "log") + + // Multiple extensions + rootCmd.Flags().StringP("yaml", "y", "", "yaml flag") + rootCmd.MarkFlagFilename("yaml", "yaml", "yml") + + // Directly using annotation + rootCmd.Flags().StringP("text", "t", "", "text flag") + rootCmd.Flags().SetAnnotation("text", BashCompFilenameExt, []string{"txt"}) + + // Test that the completion logic returns the proper info for the completion + // script to handle the file filtering + output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--file", "") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected := strings.Join([]string{ + ":0", + "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--log", "") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + "log", + ":8", + "Completion ended with directive: ShellCompDirectiveFilterFileExt", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--yaml", "") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + "yaml", "yml", + ":8", + "Completion ended with directive: ShellCompDirectiveFilterFileExt", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--yaml=") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + "yaml", "yml", + ":8", + "Completion ended with directive: ShellCompDirectiveFilterFileExt", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-y", "") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + "yaml", "yml", + ":8", + "Completion ended with directive: ShellCompDirectiveFilterFileExt", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-y=") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + "yaml", "yml", + ":8", + "Completion ended with directive: ShellCompDirectiveFilterFileExt", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--text", "") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + "txt", + ":8", + "Completion ended with directive: ShellCompDirectiveFilterFileExt", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } +} + +func TestFlagDirFilterCompletionInGo(t *testing.T) { + rootCmd := &Command{ + Use: "root", + Run: emptyRun, + } + + // Filter directories + rootCmd.Flags().StringP("dir", "d", "", "dir flag") + rootCmd.MarkFlagDirname("dir") + + // Filter directories within a directory + rootCmd.Flags().StringP("subdir", "s", "", "subdir") + rootCmd.Flags().SetAnnotation("subdir", BashCompSubdirsInDir, []string{"themes"}) + + // Multiple directory specification get ignored + rootCmd.Flags().StringP("manydir", "m", "", "manydir") + rootCmd.Flags().SetAnnotation("manydir", BashCompSubdirsInDir, []string{"themes", "colors"}) + + // Test that the completion logic returns the proper info for the completion + // script to handle the directory filtering + output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--dir", "") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected := strings.Join([]string{ + ":16", + "Completion ended with directive: ShellCompDirectiveFilterDirs", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-d", "") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + ":16", + "Completion ended with directive: ShellCompDirectiveFilterDirs", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--subdir", "") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + "themes", + ":16", + "Completion ended with directive: ShellCompDirectiveFilterDirs", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--subdir=") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + "themes", + ":16", + "Completion ended with directive: ShellCompDirectiveFilterDirs", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-s", "") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + "themes", + ":16", + "Completion ended with directive: ShellCompDirectiveFilterDirs", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-s=") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + "themes", + ":16", + "Completion ended with directive: ShellCompDirectiveFilterDirs", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--manydir", "") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + ":16", + "Completion ended with directive: ShellCompDirectiveFilterDirs", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } +} + func TestValidArgsFuncSingleCmd(t *testing.T) { rootCmd := &Command{ Use: "root", @@ -340,6 +1440,39 @@ func TestCompleteCmdInFishScript(t *testing.T) { checkOmit(t, output, ShellCompNoDescRequestCmd) } +func TestCompleteNoDesCmdInZshScript(t *testing.T) { + rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun} + child := &Command{ + Use: "child", + ValidArgsFunction: validArgsFunc, + Run: emptyRun, + } + rootCmd.AddCommand(child) + + buf := new(bytes.Buffer) + rootCmd.GenZshCompletionNoDesc(buf) + output := buf.String() + + check(t, output, ShellCompNoDescRequestCmd) +} + +func TestCompleteCmdInZshScript(t *testing.T) { + rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun} + child := &Command{ + Use: "child", + ValidArgsFunction: validArgsFunc, + Run: emptyRun, + } + rootCmd.AddCommand(child) + + buf := new(bytes.Buffer) + rootCmd.GenZshCompletion(buf) + output := buf.String() + + check(t, output, ShellCompRequestCmd) + checkOmit(t, output, ShellCompNoDescRequestCmd) +} + func TestFlagCompletionInGo(t *testing.T) { rootCmd := &Command{ Use: "root", @@ -630,6 +1763,107 @@ func TestFlagCompletionInGoWithDesc(t *testing.T) { } } +func TestValidArgsNotValidArgsFunc(t *testing.T) { + rootCmd := &Command{ + Use: "root", + ValidArgs: []string{"one", "two"}, + ValidArgsFunction: func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { + return []string{"three", "four"}, ShellCompDirectiveNoFileComp + }, + Run: emptyRun, + } + + // Test that if both ValidArgs and ValidArgsFunction are present + // only ValidArgs is considered + output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected := strings.Join([]string{ + "one", + "two", + ":4", + "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Check completing with a prefix + output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "t") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + "two", + ":4", + "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } +} + +func TestArgAliasesCompletionInGo(t *testing.T) { + rootCmd := &Command{ + Use: "root", + Args: OnlyValidArgs, + ValidArgs: []string{"one", "two", "three"}, + ArgAliases: []string{"un", "deux", "trois"}, + Run: emptyRun, + } + + // Test that argaliases are not completed when there are validargs that match + output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected := strings.Join([]string{ + "one", + "two", + "three", + ":4", + "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Test that argaliases are not completed when there are validargs that match using a prefix + output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "t") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + "two", + "three", + ":4", + "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Test that argaliases are completed when there are no validargs that match + output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "tr") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + "trois", + ":4", + "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } +} + func TestCompleteHelp(t *testing.T) { rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun} child1Cmd := &Command{ @@ -658,8 +1892,8 @@ func TestCompleteHelp(t *testing.T) { "child1", "child2", "help", - ":0", - "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") + ":4", + "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") if output != expected { t.Errorf("expected: %q, got: %q", expected, output) diff --git a/fish_completions.go b/fish_completions.go index c83609c8..52b89c6e 100644 --- a/fish_completions.go +++ b/fish_completions.go @@ -37,6 +37,12 @@ function __%[1]s_perform_completion end __%[1]s_debug "emptyArg: $emptyArg" + if not type -q "$args[1]" + # This can happen when "complete --do-complete %[1]s" is called when running this script. + __%[1]s_debug "Cannot find $args[1]. No completions." + return + end + set requestComp "$args[1] %[2]s $args[2..-1] $emptyArg" __%[1]s_debug "Calling $requestComp" @@ -71,7 +77,8 @@ function __%[1]s_prepare_completions # Check if the command-line is already provided. This is useful for testing. if not set --query __%[1]s_comp_commandLine - set __%[1]s_comp_commandLine (commandline) + # Use the -c flag to allow for completion in the middle of the line + set __%[1]s_comp_commandLine (commandline -c) end __%[1]s_debug "commandLine is: $__%[1]s_comp_commandLine" @@ -83,7 +90,7 @@ function __%[1]s_prepare_completions __%[1]s_debug "No completion, probably due to a failure" # Might as well do file completion, in case it helps set --global __%[1]s_comp_do_file_comp 1 - return 0 + return 1 end set directive (string sub --start 2 $results[-1]) @@ -92,20 +99,35 @@ function __%[1]s_prepare_completions __%[1]s_debug "Completions are: $__%[1]s_comp_results" __%[1]s_debug "Directive is: $directive" + set shellCompDirectiveError %[3]d + set shellCompDirectiveNoSpace %[4]d + set shellCompDirectiveNoFileComp %[5]d + set shellCompDirectiveFilterFileExt %[6]d + set shellCompDirectiveFilterDirs %[7]d + if test -z "$directive" set directive 0 end - set compErr (math (math --scale 0 $directive / %[3]d) %% 2) + set compErr (math (math --scale 0 $directive / $shellCompDirectiveError) %% 2) if test $compErr -eq 1 __%[1]s_debug "Received error directive: aborting." # Might as well do file completion, in case it helps set --global __%[1]s_comp_do_file_comp 1 - return 0 + return 1 end - set nospace (math (math --scale 0 $directive / %[4]d) %% 2) - set nofiles (math (math --scale 0 $directive / %[5]d) %% 2) + set filefilter (math (math --scale 0 $directive / $shellCompDirectiveFilterFileExt) %% 2) + set dirfilter (math (math --scale 0 $directive / $shellCompDirectiveFilterDirs) %% 2) + if test $filefilter -eq 1; or test $dirfilter -eq 1 + __%[1]s_debug "File extension filtering or directory filtering not supported" + # Do full file completion instead + set --global __%[1]s_comp_do_file_comp 1 + return 1 + end + + set nospace (math (math --scale 0 $directive / $shellCompDirectiveNoSpace) %% 2) + set nofiles (math (math --scale 0 $directive / $shellCompDirectiveNoFileComp) %% 2) __%[1]s_debug "nospace: $nospace, nofiles: $nofiles" @@ -132,9 +154,13 @@ function __%[1]s_prepare_completions return (not set --query __%[1]s_comp_do_file_comp) end -# Remove any pre-existing completions for the program since we will be handling all of them -# TODO this cleanup is not sufficient. Fish completions are only loaded once the user triggers -# them, so the below deletion will not work as it is run too early. What else can we do? +# Since Fish completions are only loaded once the user triggers them, we trigger them ourselves +# so we can properly delete any completions provided by another script. +# The space after the the program name is essential to trigger completion for the program +# and not completion of the program name itself. +complete --do-complete "%[1]s " &> /dev/null + +# Remove any pre-existing completions for the program since we will be handling all of them. complete -c %[1]s -e # The order in which the below two lines are defined is very important so that __%[1]s_prepare_completions @@ -149,7 +175,9 @@ complete -c %[1]s -n 'set --query __%[1]s_comp_do_file_comp' # It provides the program's completion choices. complete -c %[1]s -n '__%[1]s_prepare_completions' -f -a '$__%[1]s_comp_results' -`, name, compCmd, ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp)) +`, name, compCmd, + ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp, + ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs)) } // GenFishCompletion generates fish completion file and writes to the passed writer. diff --git a/fish_completions.md b/fish_completions.md index 6bfe5f88..19b2ed12 100644 --- a/fish_completions.md +++ b/fish_completions.md @@ -1,7 +1,4 @@ -## Generating Fish Completions for your own cobra.Command +## Generating Fish Completions For Your cobra.Command -Cobra supports native Fish completions generated from the root `cobra.Command`. You can use the `command.GenFishCompletion()` or `command.GenFishCompletionFile()` functions. You must provide these functions with a parameter indicating if the completions should be annotated with a description; Cobra will provide the description automatically based on usage information. You can choose to make this option configurable by your users. +Please refer to [Shell Completions](shell_completions.md) for details. -### Limitations - -* Custom completions implemented using the `ValidArgsFunction` and `RegisterFlagCompletionFunc()` are supported automatically but the ones implemented in Bash scripting are not. diff --git a/powershell_completions.md b/powershell_completions.md index afed8024..55f154a6 100644 --- a/powershell_completions.md +++ b/powershell_completions.md @@ -2,6 +2,8 @@ Cobra can generate PowerShell completion scripts. Users need PowerShell version 5.0 or above, which comes with Windows 10 and can be downloaded separately for Windows 7 or 8.1. They can then write the completions to a file and source this file from their PowerShell profile, which is referenced by the `$Profile` environment variable. See `Get-Help about_Profiles` for more info about PowerShell profiles. +*Note*: PowerShell completions have not (yet?) been aligned to Cobra's generic shell completion support. This implies the PowerShell completions are not as rich as for other shells (see [What's not yet supported](#whats-not-yet-supported)), and may behave slightly differently. They are still very useful for PowerShell users. + # What's supported - Completion for subcommands using their `.Short` description diff --git a/shell_completions.go b/shell_completions.go index ba0af9cb..d99bf91e 100644 --- a/shell_completions.go +++ b/shell_completions.go @@ -4,82 +4,81 @@ import ( "github.com/spf13/pflag" ) -// MarkFlagRequired adds the BashCompOneRequiredFlag annotation to the named flag if it exists, +// MarkFlagRequired instructs the various shell completion implementations to +// prioritize the named flag when performing completion, // and causes your command to report an error if invoked without the flag. func (c *Command) MarkFlagRequired(name string) error { return MarkFlagRequired(c.Flags(), name) } -// MarkPersistentFlagRequired adds the BashCompOneRequiredFlag annotation to the named persistent flag if it exists, +// MarkPersistentFlagRequired instructs the various shell completion implementations to +// prioritize the named persistent flag when performing completion, // and causes your command to report an error if invoked without the flag. func (c *Command) MarkPersistentFlagRequired(name string) error { return MarkFlagRequired(c.PersistentFlags(), name) } -// MarkFlagRequired adds the BashCompOneRequiredFlag annotation to the named flag if it exists, +// MarkFlagRequired instructs the various shell completion implementations to +// prioritize the named flag when performing completion, // and causes your command to report an error if invoked without the flag. func MarkFlagRequired(flags *pflag.FlagSet, name string) error { return flags.SetAnnotation(name, BashCompOneRequiredFlag, []string{"true"}) } -// MarkFlagFilename adds the BashCompFilenameExt annotation to the named flag, if it exists. -// Generated bash autocompletion will select filenames for the flag, limiting to named extensions if provided. +// MarkFlagFilename instructs the various shell completion implementations to +// limit completions for the named flag to the specified file extensions. func (c *Command) MarkFlagFilename(name string, extensions ...string) error { return MarkFlagFilename(c.Flags(), name, extensions...) } // MarkFlagCustom adds the BashCompCustom annotation to the named flag, if it exists. -// Generated bash autocompletion will call the bash function f for the flag. +// The bash completion script will call the bash function f for the flag. +// +// This will only work for bash completion. +// It is recommended to instead use c.RegisterFlagCompletionFunc(...) which allows +// to register a Go function which will work across all shells. func (c *Command) MarkFlagCustom(name string, f string) error { return MarkFlagCustom(c.Flags(), name, f) } // MarkPersistentFlagFilename instructs the various shell completion -// implementations to limit completions for this persistent flag to the -// specified extensions (patterns). -// -// Shell Completion compatibility matrix: bash, zsh +// implementations to limit completions for the named persistent flag to the +// specified file extensions. func (c *Command) MarkPersistentFlagFilename(name string, extensions ...string) error { return MarkFlagFilename(c.PersistentFlags(), name, extensions...) } // MarkFlagFilename instructs the various shell completion implementations to -// limit completions for this flag to the specified extensions (patterns). -// -// Shell Completion compatibility matrix: bash, zsh +// limit completions for the named flag to the specified file extensions. func MarkFlagFilename(flags *pflag.FlagSet, name string, extensions ...string) error { return flags.SetAnnotation(name, BashCompFilenameExt, extensions) } -// MarkFlagCustom instructs the various shell completion implementations to -// limit completions for this flag to the specified extensions (patterns). +// MarkFlagCustom adds the BashCompCustom annotation to the named flag, if it exists. +// The bash completion script will call the bash function f for the flag. // -// Shell Completion compatibility matrix: bash, zsh +// This will only work for bash completion. +// It is recommended to instead use c.RegisterFlagCompletionFunc(...) which allows +// to register a Go function which will work across all shells. func MarkFlagCustom(flags *pflag.FlagSet, name string, f string) error { return flags.SetAnnotation(name, BashCompCustom, []string{f}) } // MarkFlagDirname instructs the various shell completion implementations to -// complete only directories with this named flag. -// -// Shell Completion compatibility matrix: zsh +// limit completions for the named flag to directory names. func (c *Command) MarkFlagDirname(name string) error { return MarkFlagDirname(c.Flags(), name) } // MarkPersistentFlagDirname instructs the various shell completion -// implementations to complete only directories with this persistent named flag. -// -// Shell Completion compatibility matrix: zsh +// implementations to limit completions for the named persistent flag to +// directory names. func (c *Command) MarkPersistentFlagDirname(name string) error { return MarkFlagDirname(c.PersistentFlags(), name) } // MarkFlagDirname instructs the various shell completion implementations to -// complete only directories with this specified flag. -// -// Shell Completion compatibility matrix: zsh +// limit completions for the named flag to directory names. func MarkFlagDirname(flags *pflag.FlagSet, name string) error { - zshPattern := "-(/)" - return flags.SetAnnotation(name, zshCompDirname, []string{zshPattern}) + return flags.SetAnnotation(name, BashCompSubdirsInDir, []string{}) } diff --git a/shell_completions.md b/shell_completions.md new file mode 100644 index 00000000..e0a256dd --- /dev/null +++ b/shell_completions.md @@ -0,0 +1,429 @@ +# Generating shell completions + +Cobra can generate shell completions for multiple shells. +The currently supported shells are: +- Bash +- Zsh +- Fish +- PowerShell + +If you are using the generator you can create a completion command by running + +```bash +cobra add completion +``` +and then modifying the generated `cmd/completion.go` file to look something like this +(writing the shell script to stdout allows the most flexible use): + +```go +var completionCmd = &cobra.Command{ + Use: "completion [bash|zsh|fish|powershell]", + Short: "Generate completion script", + Long: `To load completions: + +Bash: + +$ source <(yourprogram completion bash) + +# To load completions for each session, execute once: +Linux: + $ yourprogram completion bash > /etc/bash_completion.d/yourprogram +MacOS: + $ yourprogram completion bash > /usr/local/etc/bash_completion.d/yourprogram + +Zsh: + +$ source <(yourprogram completion zsh) + +# To load completions for each session, execute once: +$ yourprogram completion zsh > "${fpath[1]}/_yourprogram" + +Fish: + +$ yourprogram completion fish | source + +# To load completions for each session, execute once: +$ yourprogram completion fish > ~/.config/fish/completions/yourprogram.fish +`, + DisableFlagsInUseLine: true, + ValidArgs: []string{"bash", "zsh", "fish", "powershell"}, + Args: cobra.ExactValidArgs(1), + Run: func(cmd *cobra.Command, args []string) { + switch args[0] { + case "bash": + cmd.Root().GenBashCompletion(os.Stdout) + case "zsh": + cmd.Root().GenZshCompletion(os.Stdout) + case "fish": + cmd.Root().GenFishCompletion(os.Stdout, true) + case "powershell": + cmd.Root().GenPowerShellCompletion(os.Stdout) + } + }, +} +``` + +**Note:** The cobra generator may include messages printed to stdout for example if the config file is loaded, this will break the auto complete script so must be removed. + +# Customizing completions + +The generated completion scripts will automatically handle completing commands and flags. However, you can make your completions much more powerful by providing information to complete your program's nouns and flag values. + +## Completion of nouns + +### Static completion of nouns + +Cobra allows you to provide a pre-defined list of completion choices for your nouns using the `ValidArgs` field. +For example, if you want `kubectl get [tab][tab]` to show a list of valid "nouns" you have to set them. +Some simplified code from `kubectl get` looks like: + +```go +validArgs []string = { "pod", "node", "service", "replicationcontroller" } + +cmd := &cobra.Command{ + Use: "get [(-o|--output=)json|yaml|template|...] (RESOURCE [NAME] | RESOURCE/NAME ...)", + Short: "Display one or many resources", + Long: get_long, + Example: get_example, + Run: func(cmd *cobra.Command, args []string) { + err := RunGet(f, out, cmd, args) + util.CheckErr(err) + }, + ValidArgs: validArgs, +} +``` + +Notice we put the `ValidArgs` field on the `get` sub-command. Doing so will give results like: + +```bash +$ kubectl get [tab][tab] +node pod replicationcontroller service +``` + +#### Aliases for nouns + +If your nouns have aliases, you can define them alongside `ValidArgs` using `ArgAliases`: + +```go +argAliases []string = { "pods", "nodes", "services", "svc", "replicationcontrollers", "rc" } + +cmd := &cobra.Command{ + ... + ValidArgs: validArgs, + ArgAliases: argAliases +} +``` + +The aliases are not shown to the user on tab completion, but they are accepted as valid nouns by +the completion algorithm if entered manually, e.g. in: + +```bash +$ kubectl get rc [tab][tab] +backend frontend database +``` + +Note that without declaring `rc` as an alias, the completion algorithm would not know to show the list of +replication controllers following `rc`. + +### Dynamic completion of nouns + +In some cases it is not possible to provide a list of completions in advance. Instead, the list of completions must be determined at execution-time. In a similar fashion as for static completions, you can use the `ValidArgsFunction` field to provide a Go function that Cobra will execute when it needs the list of completion choices for the nouns of a command. Note that either `ValidArgs` or `ValidArgsFunction` can be used for a single cobra command, but not both. +Simplified code from `helm status` looks like: + +```go +cmd := &cobra.Command{ + Use: "status RELEASE_NAME", + Short: "Display the status of the named release", + Long: status_long, + RunE: func(cmd *cobra.Command, args []string) { + RunGet(args[0]) + }, + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) != 0 { + return nil, cobra.ShellCompDirectiveNoFileComp + } + return getReleasesFromCluster(toComplete), cobra.ShellCompDirectiveNoFileComp + }, +} +``` +Where `getReleasesFromCluster()` is a Go function that obtains the list of current Helm releases running on the Kubernetes cluster. +Notice we put the `ValidArgsFunction` on the `status` sub-command. Let's assume the Helm releases on the cluster are: `harbor`, `notary`, `rook` and `thanos` then this dynamic completion will give results like: + +```bash +$ helm status [tab][tab] +harbor notary rook thanos +``` +You may have noticed the use of `cobra.ShellCompDirective`. These directives are bit fields allowing to control some shell completion behaviors for your particular completion. You can combine them with the bit-or operator such as `cobra.ShellCompDirectiveNoSpace | cobra.ShellCompDirectiveNoFileComp` +```go +// Indicates that the shell will perform its default behavior after completions +// have been provided (this implies none of the other directives). +ShellCompDirectiveDefault + +// Indicates an error occurred and completions should be ignored. +ShellCompDirectiveError + +// Indicates that the shell should not add a space after the completion, +// even if there is a single completion provided. +ShellCompDirectiveNoSpace + +// Indicates that the shell should not provide file completion even when +// no completion is provided. +ShellCompDirectiveNoFileComp + +// Indicates that the returned completions should be used as file extension filters. +// For example, to complete only files of the form *.json or *.yaml: +// return []string{"yaml", "json"}, ShellCompDirectiveFilterFileExt +// For flags, using MarkFlagFilename() and MarkPersistentFlagFilename() +// is a shortcut to using this directive explicitly. +// +ShellCompDirectiveFilterFileExt + +// Indicates that only directory names should be provided in file completion. +// For example: +// return nil, ShellCompDirectiveFilterDirs +// For flags, using MarkFlagDirname() is a shortcut to using this directive explicitly. +// +// To request directory names within another directory, the returned completions +// should specify a single directory name within which to search. For example, +// to complete directories within "themes/": +// return []string{"themes"}, ShellCompDirectiveFilterDirs +// +ShellCompDirectiveFilterDirs +``` + +***Note***: When using the `ValidArgsFunction`, Cobra will call your registered function after having parsed all flags and arguments provided in the command-line. You therefore don't need to do this parsing yourself. For example, when a user calls `helm status --namespace my-rook-ns [tab][tab]`, Cobra will call your registered `ValidArgsFunction` after having parsed the `--namespace` flag, as it would have done when calling the `RunE` function. + +#### Debugging + +Cobra achieves dynamic completion through the use of a hidden command called by the completion script. To debug your Go completion code, you can call this hidden command directly: +```bash +$ helm __complete status har +harbor +:4 +Completion ended with directive: ShellCompDirectiveNoFileComp # This is on stderr +``` +***Important:*** If the noun to complete is empty (when the user has not yet typed any letters of that noun), you must pass an empty parameter to the `__complete` command: +```bash +$ helm __complete status "" +harbor +notary +rook +thanos +:4 +Completion ended with directive: ShellCompDirectiveNoFileComp # This is on stderr +``` +Calling the `__complete` command directly allows you to run the Go debugger to troubleshoot your code. You can also add printouts to your code; Cobra provides the following functions to use for printouts in Go completion code: +```go +// Prints to the completion script debug file (if BASH_COMP_DEBUG_FILE +// is set to a file path) and optionally prints to stderr. +cobra.CompDebug(msg string, printToStdErr bool) { +cobra.CompDebugln(msg string, printToStdErr bool) + +// Prints to the completion script debug file (if BASH_COMP_DEBUG_FILE +// is set to a file path) and to stderr. +cobra.CompError(msg string) +cobra.CompErrorln(msg string) +``` +***Important:*** You should **not** leave traces that print directly to stdout in your completion code as they will be interpreted as completion choices by the completion script. Instead, use the cobra-provided debugging traces functions mentioned above. + +## Completions for flags + +### Mark flags as required + +Most of the time completions will only show sub-commands. But if a flag is required to make a sub-command work, you probably want it to show up when the user types [tab][tab]. You can mark a flag as 'Required' like so: + +```go +cmd.MarkFlagRequired("pod") +cmd.MarkFlagRequired("container") +``` + +and you'll get something like + +```bash +$ kubectl exec [tab][tab] +-c --container= -p --pod= +``` + +### Specify dynamic flag completion + +As for nouns, Cobra provides a way of defining dynamic completion of flags. To provide a Go function that Cobra will execute when it needs the list of completion choices for a flag, you must register the function using the `command.RegisterFlagCompletionFunc()` function. + +```go +flagName := "output" +cmd.RegisterFlagCompletionFunc(flagName, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return []string{"json", "table", "yaml"}, cobra.ShellCompDirectiveDefault +}) +``` +Notice that calling `RegisterFlagCompletionFunc()` is done through the `command` with which the flag is associated. In our example this dynamic completion will give results like so: + +```bash +$ helm status --output [tab][tab] +json table yaml +``` + +#### Debugging + +You can also easily debug your Go completion code for flags: +```bash +$ helm __complete status --output "" +json +table +yaml +:4 +Completion ended with directive: ShellCompDirectiveNoFileComp # This is on stderr +``` +***Important:*** You should **not** leave traces that print to stdout in your completion code as they will be interpreted as completion choices by the completion script. Instead, use the cobra-provided debugging traces functions mentioned further above. + +### Specify valid filename extensions for flags that take a filename + +To limit completions of flag values to file names with certain extensions you can either use the different `MarkFlagFilename()` functions or a combination of `RegisterFlagCompletionFunc()` and `ShellCompDirectiveFilterFileExt`, like so: +```go +flagName := "output" +cmd.MarkFlagFilename(flagName, "yaml", "json") +``` +or +```go +flagName := "output" +cmd.RegisterFlagCompletionFunc(flagName, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return []string{"yaml", "json"}, ShellCompDirectiveFilterFileExt}) +``` + +### Limit flag completions to directory names + +To limit completions of flag values to directory names you can either use the `MarkFlagDirname()` functions or a combination of `RegisterFlagCompletionFunc()` and `ShellCompDirectiveFilterDirs`, like so: +```go +flagName := "output" +cmd.MarkFlagDirname(flagName) +``` +or +```go +flagName := "output" +cmd.RegisterFlagCompletionFunc(flagName, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return nil, cobra.ShellCompDirectiveFilterDirs +}) +``` +To limit completions of flag values to directory names *within another directory* you can use a combination of `RegisterFlagCompletionFunc()` and `ShellCompDirectiveFilterDirs` like so: +```go +flagName := "output" +cmd.RegisterFlagCompletionFunc(flagName, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return []string{"themes"}, cobra.ShellCompDirectiveFilterDirs +}) +``` +### Descriptions for completions + +Both `zsh` and `fish` allow for descriptions to annotate completion choices. For commands and flags, Cobra will provide the descriptions automatically, based on usage information. For example, using zsh: +``` +$ helm s[tab] +search -- search for a keyword in charts +show -- show information of a chart +status -- displays the status of the named release +``` +while using fish: +``` +$ helm s[tab] +search (search for a keyword in charts) show (show information of a chart) status (displays the status of the named release) +``` + +Cobra allows you to add annotations to your own completions. Simply add the annotation text after each completion, following a `\t` separator. This technique applies to completions returned by `ValidArgs`, `ValidArgsFunction` and `RegisterFlagCompletionFunc()`. For example: +```go +ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return []string{"harbor\tAn image registry", "thanos\tLong-term metrics"}, cobra.ShellCompDirectiveNoFileComp +}} +``` +or +```go +ValidArgs: []string{"bash\tCompletions for bash", "zsh\tCompletions for zsh"} +``` +## Bash completions + +### Dependencies + +The bash completion script generated by Cobra requires the `bash_completion` package. You should update the help text of your completion command to show how to install the `bash_completion` package ([Kubectl docs](https://kubernetes.io/docs/tasks/tools/install-kubectl/#enabling-shell-autocompletion)) + +### Aliases + +You can also configure `bash` aliases for your program and they will also support completions. + +```bash +alias aliasname=origcommand +complete -o default -F __start_origcommand aliasname + +# and now when you run `aliasname` completion will make +# suggestions as it did for `origcommand`. + +$ aliasname +completion firstcommand secondcommand +``` +### Bash legacy dynamic completions + +For backwards-compatibility, Cobra still supports its bash legacy dynamic completion solution. +Please refer to [Bash Completions](bash_completions.md) for details. + +## Zsh completions + +Cobra supports native Zsh completion generated from the root `cobra.Command`. +The generated completion script should be put somewhere in your `$fpath` and be named +`_`. + +Zsh supports descriptions for completions. Cobra will provide the description automatically, +based on usage information. Cobra provides a way to completely disable such descriptions by +using `GenZshCompletionNoDesc()` or `GenZshCompletionFileNoDesc()`. You can choose to make +this a configurable option to your users. +``` +# With descriptions +$ helm s[tab] +search -- search for a keyword in charts +show -- show information of a chart +status -- displays the status of the named release + +# Without descriptions +$ helm s[tab] +search show status +``` +*Note*: Because of backwards-compatibility requirements, we were forced to have a different API to disable completion descriptions between `Zsh` and `Fish`. + +### Limitations + +* Custom completions implemented in Bash scripting (legacy) are not supported and will be ignored for `zsh` (including the use of the `BashCompCustom` flag annotation). + * You should instead use `ValidArgsFunction` and `RegisterFlagCompletionFunc()` which are portable to the different shells (`bash`, `zsh`, `fish`). +* The function `MarkFlagCustom()` is not supported and will be ignored for `zsh`. + * You should instead use `RegisterFlagCompletionFunc()`. + +### Zsh completions standardization + +Cobra 1.1 standardized its zsh completion support to align it with its other shell completions. Although the API was kept backwards-compatible, some small changes in behavior were introduced. +Please refer to [Zsh Completions](zsh_completions.md) for details. + +## Fish completions + +Cobra supports native Fish completions generated from the root `cobra.Command`. You can use the `command.GenFishCompletion()` or `command.GenFishCompletionFile()` functions. You must provide these functions with a parameter indicating if the completions should be annotated with a description; Cobra will provide the description automatically based on usage information. You can choose to make this option configurable by your users. +``` +# With descriptions +$ helm s[tab] +search (search for a keyword in charts) show (show information of a chart) status (displays the status of the named release) + +# Without descriptions +$ helm s[tab] +search show status +``` +*Note*: Because of backwards-compatibility requirements, we were forced to have a different API to disable completion descriptions between `Zsh` and `Fish`. + +### Limitations + +* Custom completions implemented in Bash scripting (legacy) are not supported and will be ignored for `fish` (including the use of the `BashCompCustom` flag annotation). + * You should instead use `ValidArgsFunction` and `RegisterFlagCompletionFunc()` which are portable to the different shells (`bash`, `zsh`, `fish`). +* The function `MarkFlagCustom()` is not supported and will be ignored for `fish`. + * You should instead use `RegisterFlagCompletionFunc()`. +* The following flag completion annotations are not supported and will be ignored for `fish`: + * `BashCompFilenameExt` (filtering by file extension) + * `BashCompSubdirsInDir` (filtering by directory) +* The functions corresponding to the above annotations are consequently not supported and will be ignored for `fish`: + * `MarkFlagFilename()` and `MarkPersistentFlagFilename()` (filtering by file extension) + * `MarkFlagDirname()` and `MarkPersistentFlagDirname()` (filtering by directory) +* Similarly, the following completion directives are not supported and will be ignored for `fish`: + * `ShellCompDirectiveFilterFileExt` (filtering by file extension) + * `ShellCompDirectiveFilterDirs` (filtering by directory) + +## PowerShell completions + +Please refer to [PowerShell Completions](powershell_completions.md) for details. diff --git a/zsh_completions.go b/zsh_completions.go index 12755482..c25ce680 100644 --- a/zsh_completions.go +++ b/zsh_completions.go @@ -1,336 +1,235 @@ package cobra import ( - "encoding/json" + "bytes" "fmt" "io" "os" - "sort" - "strings" - "text/template" - - "github.com/spf13/pflag" ) -const ( - zshCompArgumentAnnotation = "cobra_annotations_zsh_completion_argument_annotation" - zshCompArgumentFilenameComp = "cobra_annotations_zsh_completion_argument_file_completion" - zshCompArgumentWordComp = "cobra_annotations_zsh_completion_argument_word_completion" - zshCompDirname = "cobra_annotations_zsh_dirname" -) - -var ( - zshCompFuncMap = template.FuncMap{ - "genZshFuncName": zshCompGenFuncName, - "extractFlags": zshCompExtractFlag, - "genFlagEntryForZshArguments": zshCompGenFlagEntryForArguments, - "extractArgsCompletions": zshCompExtractArgumentCompletionHintsForRendering, - } - zshCompletionText = ` -{{/* should accept Command (that contains subcommands) as parameter */}} -{{define "argumentsC" -}} -{{ $cmdPath := genZshFuncName .}} -function {{$cmdPath}} { - local -a commands - - _arguments -C \{{- range extractFlags .}} - {{genFlagEntryForZshArguments .}} \{{- end}} - "1: :->cmnds" \ - "*::arg:->args" - - case $state in - cmnds) - commands=({{range .Commands}}{{if not .Hidden}} - "{{.Name}}:{{.Short}}"{{end}}{{end}} - ) - _describe "command" commands - ;; - esac - - case "$words[1]" in {{- range .Commands}}{{if not .Hidden}} - {{.Name}}) - {{$cmdPath}}_{{.Name}} - ;;{{end}}{{end}} - esac -} -{{range .Commands}}{{if not .Hidden}} -{{template "selectCmdTemplate" .}} -{{- end}}{{end}} -{{- end}} - -{{/* should accept Command without subcommands as parameter */}} -{{define "arguments" -}} -function {{genZshFuncName .}} { -{{" _arguments"}}{{range extractFlags .}} \ - {{genFlagEntryForZshArguments . -}} -{{end}}{{range extractArgsCompletions .}} \ - {{.}}{{end}} -} -{{end}} - -{{/* dispatcher for commands with or without subcommands */}} -{{define "selectCmdTemplate" -}} -{{if .Hidden}}{{/* ignore hidden*/}}{{else -}} -{{if .Commands}}{{template "argumentsC" .}}{{else}}{{template "arguments" .}}{{end}} -{{- end}} -{{- end}} - -{{/* template entry point */}} -{{define "Main" -}} -#compdef _{{.Name}} {{.Name}} - -{{template "selectCmdTemplate" .}} -{{end}} -` -) - -// zshCompArgsAnnotation is used to encode/decode zsh completion for -// arguments to/from Command.Annotations. -type zshCompArgsAnnotation map[int]zshCompArgHint - -type zshCompArgHint struct { - // Indicates the type of the completion to use. One of: - // zshCompArgumentFilenameComp or zshCompArgumentWordComp - Tipe string `json:"type"` - - // A value for the type above (globs for file completion or words) - Options []string `json:"options"` -} - -// GenZshCompletionFile generates zsh completion file. +// GenZshCompletionFile generates zsh completion file including descriptions. func (c *Command) GenZshCompletionFile(filename string) error { + return c.genZshCompletionFile(filename, true) +} + +// GenZshCompletion generates zsh completion file including descriptions +// and writes it to the passed writer. +func (c *Command) GenZshCompletion(w io.Writer) error { + return c.genZshCompletion(w, true) +} + +// GenZshCompletionFileNoDesc generates zsh completion file without descriptions. +func (c *Command) GenZshCompletionFileNoDesc(filename string) error { + return c.genZshCompletionFile(filename, false) +} + +// GenZshCompletionNoDesc generates zsh completion file without descriptions +// and writes it to the passed writer. +func (c *Command) GenZshCompletionNoDesc(w io.Writer) error { + return c.genZshCompletion(w, false) +} + +// MarkZshCompPositionalArgumentFile only worked for zsh and its behavior was +// not consistent with Bash completion. It has therefore been disabled. +// Instead, when no other completion is specified, file completion is done by +// default for every argument. One can disable file completion on a per-argument +// basis by using ValidArgsFunction and ShellCompDirectiveNoFileComp. +// To achieve file extension filtering, one can use ValidArgsFunction and +// ShellCompDirectiveFilterFileExt. +// +// Deprecated +func (c *Command) MarkZshCompPositionalArgumentFile(argPosition int, patterns ...string) error { + return nil +} + +// MarkZshCompPositionalArgumentWords only worked for zsh. It has therefore +// been disabled. +// To achieve the same behavior across all shells, one can use +// ValidArgs (for the first argument only) or ValidArgsFunction for +// any argument (can include the first one also). +// +// Deprecated +func (c *Command) MarkZshCompPositionalArgumentWords(argPosition int, words ...string) error { + return nil +} + +func (c *Command) genZshCompletionFile(filename string, includeDesc bool) error { outFile, err := os.Create(filename) if err != nil { return err } defer outFile.Close() - return c.GenZshCompletion(outFile) + return c.genZshCompletion(outFile, includeDesc) } -// GenZshCompletion generates a zsh completion file and writes to the passed -// writer. The completion always run on the root command regardless of the -// command it was called from. -func (c *Command) GenZshCompletion(w io.Writer) error { - tmpl, err := template.New("Main").Funcs(zshCompFuncMap).Parse(zshCompletionText) - if err != nil { - return fmt.Errorf("error creating zsh completion template: %v", err) - } - return tmpl.Execute(w, c.Root()) +func (c *Command) genZshCompletion(w io.Writer, includeDesc bool) error { + buf := new(bytes.Buffer) + genZshComp(buf, c.Name(), includeDesc) + _, err := buf.WriteTo(w) + return err } -// MarkZshCompPositionalArgumentFile marks the specified argument (first -// argument is 1) as completed by file selection. patterns (e.g. "*.txt") are -// optional - if not provided the completion will search for all files. -func (c *Command) MarkZshCompPositionalArgumentFile(argPosition int, patterns ...string) error { - if argPosition < 1 { - return fmt.Errorf("Invalid argument position (%d)", argPosition) +func genZshComp(buf *bytes.Buffer, name string, includeDesc bool) { + compCmd := ShellCompRequestCmd + if !includeDesc { + compCmd = ShellCompNoDescRequestCmd } - annotation, err := c.zshCompGetArgsAnnotations() - if err != nil { - return err - } - if c.zshcompArgsAnnotationnIsDuplicatePosition(annotation, argPosition) { - return fmt.Errorf("Duplicate annotation for positional argument at index %d", argPosition) - } - annotation[argPosition] = zshCompArgHint{ - Tipe: zshCompArgumentFilenameComp, - Options: patterns, - } - return c.zshCompSetArgsAnnotations(annotation) + buf.WriteString(fmt.Sprintf(`#compdef _%[1]s %[1]s + +# zsh completion for %-36[1]s -*- shell-script -*- + +__%[1]s_debug() +{ + local file="$BASH_COMP_DEBUG_FILE" + if [[ -n ${file} ]]; then + echo "$*" >> "${file}" + fi } -// MarkZshCompPositionalArgumentWords marks the specified positional argument -// (first argument is 1) as completed by the provided words. At east one word -// must be provided, spaces within words will be offered completion with -// "word\ word". -func (c *Command) MarkZshCompPositionalArgumentWords(argPosition int, words ...string) error { - if argPosition < 1 { - return fmt.Errorf("Invalid argument position (%d)", argPosition) - } - if len(words) == 0 { - return fmt.Errorf("Trying to set empty word list for positional argument %d", argPosition) - } - annotation, err := c.zshCompGetArgsAnnotations() - if err != nil { - return err - } - if c.zshcompArgsAnnotationnIsDuplicatePosition(annotation, argPosition) { - return fmt.Errorf("Duplicate annotation for positional argument at index %d", argPosition) - } - annotation[argPosition] = zshCompArgHint{ - Tipe: zshCompArgumentWordComp, - Options: words, - } - return c.zshCompSetArgsAnnotations(annotation) +_%[1]s() +{ + local shellCompDirectiveError=%[3]d + local shellCompDirectiveNoSpace=%[4]d + local shellCompDirectiveNoFileComp=%[5]d + local shellCompDirectiveFilterFileExt=%[6]d + local shellCompDirectiveFilterDirs=%[7]d + + local lastParam lastChar flagPrefix requestComp out directive compCount comp lastComp + local -a completions + + __%[1]s_debug "\n========= starting completion logic ==========" + __%[1]s_debug "CURRENT: ${CURRENT}, words[*]: ${words[*]}" + + # The user could have moved the cursor backwards on the command-line. + # We need to trigger completion from the $CURRENT location, so we need + # to truncate the command-line ($words) up to the $CURRENT location. + # (We cannot use $CURSOR as its value does not work when a command is an alias.) + words=("${=words[1,CURRENT]}") + __%[1]s_debug "Truncated words[*]: ${words[*]}," + + lastParam=${words[-1]} + lastChar=${lastParam[-1]} + __%[1]s_debug "lastParam: ${lastParam}, lastChar: ${lastChar}" + + # For zsh, when completing a flag with an = (e.g., %[1]s -n=) + # completions must be prefixed with the flag + setopt local_options BASH_REMATCH + if [[ "${lastParam}" =~ '-.*=' ]]; then + # We are dealing with a flag with an = + flagPrefix="-P ${BASH_REMATCH}" + fi + + # Prepare the command to obtain completions + requestComp="${words[1]} %[2]s ${words[2,-1]}" + if [ "${lastChar}" = "" ]; then + # If the last parameter is complete (there is a space following it) + # We add an extra empty parameter so we can indicate this to the go completion code. + __%[1]s_debug "Adding extra empty parameter" + requestComp="${requestComp} \"\"" + fi + + __%[1]s_debug "About to call: eval ${requestComp}" + + # Use eval to handle any environment variables and such + out=$(eval ${requestComp} 2>/dev/null) + __%[1]s_debug "completion output: ${out}" + + # Extract the directive integer following a : from the last line + local lastLine + while IFS='\n' read -r line; do + lastLine=${line} + done < <(printf "%%s\n" "${out[@]}") + __%[1]s_debug "last line: ${lastLine}" + + if [ "${lastLine[1]}" = : ]; then + directive=${lastLine[2,-1]} + # Remove the directive including the : and the newline + local suffix + (( suffix=${#lastLine}+2)) + out=${out[1,-$suffix]} + else + # There is no directive specified. Leave $out as is. + __%[1]s_debug "No directive found. Setting do default" + directive=0 + fi + + __%[1]s_debug "directive: ${directive}" + __%[1]s_debug "completions: ${out}" + __%[1]s_debug "flagPrefix: ${flagPrefix}" + + if [ $((directive & shellCompDirectiveError)) -ne 0 ]; then + __%[1]s_debug "Completion received error. Ignoring completions." + return + fi + + compCount=0 + while IFS='\n' read -r comp; do + if [ -n "$comp" ]; then + # If requested, completions are returned with a description. + # The description is preceded by a TAB character. + # For zsh's _describe, we need to use a : instead of a TAB. + # We first need to escape any : as part of the completion itself. + comp=${comp//:/\\:} + + local tab=$(printf '\t') + comp=${comp//$tab/:} + + ((compCount++)) + __%[1]s_debug "Adding completion: ${comp}" + completions+=${comp} + lastComp=$comp + fi + done < <(printf "%%s\n" "${out[@]}") + + if [ $((directive & shellCompDirectiveFilterFileExt)) -ne 0 ]; then + # File extension filtering + local filteringCmd + filteringCmd='_files' + for filter in ${completions[@]}; do + if [ ${filter[1]} != '*' ]; then + # zsh requires a glob pattern to do file filtering + filter="\*.$filter" + fi + filteringCmd+=" -g $filter" + done + filteringCmd+=" ${flagPrefix}" + + __%[1]s_debug "File filtering command: $filteringCmd" + _arguments '*:filename:'"$filteringCmd" + elif [ $((directive & shellCompDirectiveFilterDirs)) -ne 0 ]; then + # File completion for directories only + local subDir + subdir="${completions[1]}" + if [ -n "$subdir" ]; then + __%[1]s_debug "Listing directories in $subdir" + pushd "${subdir}" >/dev/null 2>&1 + else + __%[1]s_debug "Listing directories in ." + fi + + _arguments '*:dirname:_files -/'" ${flagPrefix}" + if [ -n "$subdir" ]; then + popd >/dev/null 2>&1 + fi + elif [ $((directive & shellCompDirectiveNoSpace)) -ne 0 ] && [ ${compCount} -eq 1 ]; then + __%[1]s_debug "Activating nospace." + # We can use compadd here as there is no description when + # there is only one completion. + compadd -S '' "${lastComp}" + elif [ ${compCount} -eq 0 ]; then + if [ $((directive & shellCompDirectiveNoFileComp)) -ne 0 ]; then + __%[1]s_debug "deactivating file completion" + else + # Perform file completion + __%[1]s_debug "activating file completion" + _arguments '*:filename:_files'" ${flagPrefix}" + fi + else + _describe "completions" completions $(echo $flagPrefix) + fi } - -func zshCompExtractArgumentCompletionHintsForRendering(c *Command) ([]string, error) { - var result []string - annotation, err := c.zshCompGetArgsAnnotations() - if err != nil { - return nil, err - } - for k, v := range annotation { - s, err := zshCompRenderZshCompArgHint(k, v) - if err != nil { - return nil, err - } - result = append(result, s) - } - if len(c.ValidArgs) > 0 { - if _, positionOneExists := annotation[1]; !positionOneExists { - s, err := zshCompRenderZshCompArgHint(1, zshCompArgHint{ - Tipe: zshCompArgumentWordComp, - Options: c.ValidArgs, - }) - if err != nil { - return nil, err - } - result = append(result, s) - } - } - sort.Strings(result) - return result, nil -} - -func zshCompRenderZshCompArgHint(i int, z zshCompArgHint) (string, error) { - switch t := z.Tipe; t { - case zshCompArgumentFilenameComp: - var globs []string - for _, g := range z.Options { - globs = append(globs, fmt.Sprintf(`-g "%s"`, g)) - } - return fmt.Sprintf(`'%d: :_files %s'`, i, strings.Join(globs, " ")), nil - case zshCompArgumentWordComp: - var words []string - for _, w := range z.Options { - words = append(words, fmt.Sprintf("%q", w)) - } - return fmt.Sprintf(`'%d: :(%s)'`, i, strings.Join(words, " ")), nil - default: - return "", fmt.Errorf("Invalid zsh argument completion annotation: %s", t) - } -} - -func (c *Command) zshcompArgsAnnotationnIsDuplicatePosition(annotation zshCompArgsAnnotation, position int) bool { - _, dup := annotation[position] - return dup -} - -func (c *Command) zshCompGetArgsAnnotations() (zshCompArgsAnnotation, error) { - annotation := make(zshCompArgsAnnotation) - annotationString, ok := c.Annotations[zshCompArgumentAnnotation] - if !ok { - return annotation, nil - } - err := json.Unmarshal([]byte(annotationString), &annotation) - if err != nil { - return annotation, fmt.Errorf("Error unmarshaling zsh argument annotation: %v", err) - } - return annotation, nil -} - -func (c *Command) zshCompSetArgsAnnotations(annotation zshCompArgsAnnotation) error { - jsn, err := json.Marshal(annotation) - if err != nil { - return fmt.Errorf("Error marshaling zsh argument annotation: %v", err) - } - if c.Annotations == nil { - c.Annotations = make(map[string]string) - } - c.Annotations[zshCompArgumentAnnotation] = string(jsn) - return nil -} - -func zshCompGenFuncName(c *Command) string { - if c.HasParent() { - return zshCompGenFuncName(c.Parent()) + "_" + c.Name() - } - return "_" + c.Name() -} - -func zshCompExtractFlag(c *Command) []*pflag.Flag { - var flags []*pflag.Flag - c.LocalFlags().VisitAll(func(f *pflag.Flag) { - if !f.Hidden { - flags = append(flags, f) - } - }) - c.InheritedFlags().VisitAll(func(f *pflag.Flag) { - if !f.Hidden { - flags = append(flags, f) - } - }) - return flags -} - -// zshCompGenFlagEntryForArguments returns an entry that matches _arguments -// zsh-completion parameters. It's too complicated to generate in a template. -func zshCompGenFlagEntryForArguments(f *pflag.Flag) string { - if f.Name == "" || f.Shorthand == "" { - return zshCompGenFlagEntryForSingleOptionFlag(f) - } - return zshCompGenFlagEntryForMultiOptionFlag(f) -} - -func zshCompGenFlagEntryForSingleOptionFlag(f *pflag.Flag) string { - var option, multiMark, extras string - - if zshCompFlagCouldBeSpecifiedMoreThenOnce(f) { - multiMark = "*" - } - - option = "--" + f.Name - if option == "--" { - option = "-" + f.Shorthand - } - extras = zshCompGenFlagEntryExtras(f) - - return fmt.Sprintf(`'%s%s[%s]%s'`, multiMark, option, zshCompQuoteFlagDescription(f.Usage), extras) -} - -func zshCompGenFlagEntryForMultiOptionFlag(f *pflag.Flag) string { - var options, parenMultiMark, curlyMultiMark, extras string - - if zshCompFlagCouldBeSpecifiedMoreThenOnce(f) { - parenMultiMark = "*" - curlyMultiMark = "\\*" - } - - options = fmt.Sprintf(`'(%s-%s %s--%s)'{%s-%s,%s--%s}`, - parenMultiMark, f.Shorthand, parenMultiMark, f.Name, curlyMultiMark, f.Shorthand, curlyMultiMark, f.Name) - extras = zshCompGenFlagEntryExtras(f) - - return fmt.Sprintf(`%s'[%s]%s'`, options, zshCompQuoteFlagDescription(f.Usage), extras) -} - -func zshCompGenFlagEntryExtras(f *pflag.Flag) string { - if f.NoOptDefVal != "" { - return "" - } - - extras := ":" // allow options for flag (even without assistance) - for key, values := range f.Annotations { - switch key { - case zshCompDirname: - extras = fmt.Sprintf(":filename:_files -g %q", values[0]) - case BashCompFilenameExt: - extras = ":filename:_files" - for _, pattern := range values { - extras = extras + fmt.Sprintf(` -g "%s"`, pattern) - } - } - } - - return extras -} - -func zshCompFlagCouldBeSpecifiedMoreThenOnce(f *pflag.Flag) bool { - return strings.Contains(f.Value.Type(), "Slice") || - strings.Contains(f.Value.Type(), "Array") -} - -func zshCompQuoteFlagDescription(s string) string { - return strings.Replace(s, "'", `'\''`, -1) +`, name, compCmd, + ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp, + ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs)) } diff --git a/zsh_completions.md b/zsh_completions.md index df9c2eac..f432dd86 100644 --- a/zsh_completions.md +++ b/zsh_completions.md @@ -1,39 +1,47 @@ -## Generating Zsh Completion for your cobra.Command +## Generating Zsh Completion For Your cobra.Command -Cobra supports native Zsh completion generated from the root `cobra.Command`. -The generated completion script should be put somewhere in your `$fpath` named -`_`. +Please refer to [Shell Completions](shell_completions.md) for details. -### What's Supported +## Zsh completions standardization -* Completion for all non-hidden subcommands using their `.Short` description. -* Completion for all non-hidden flags using the following rules: - * Filename completion works by marking the flag with `cmd.MarkFlagFilename...` - family of commands. - * The requirement for argument to the flag is decided by the `.NoOptDefVal` - flag value - if it's empty then completion will expect an argument. - * Flags of one of the various `*Array` and `*Slice` types supports multiple - specifications (with or without argument depending on the specific type). -* Completion of positional arguments using the following rules: - * Argument position for all options below starts at `1`. If argument position - `0` is requested it will raise an error. - * Use `command.MarkZshCompPositionalArgumentFile` to complete filenames. Glob - patterns (e.g. `"*.log"`) are optional - if not specified it will offer to - complete all file types. - * Use `command.MarkZshCompPositionalArgumentWords` to offer specific words for - completion. At least one word is required. - * It's possible to specify completion for some arguments and leave some - unspecified (e.g. offer words for second argument but nothing for first - argument). This will cause no completion for first argument but words - completion for second argument. - * If no argument completion was specified for 1st argument (but optionally was - specified for 2nd) and the command has `ValidArgs` it will be used as - completion options for 1st argument. - * Argument completions only offered for commands with no subcommands. +Cobra 1.1 standardized its zsh completion support to align it with its other shell completions. Although the API was kept backwards-compatible, some small changes in behavior were introduced. -### What's not yet Supported +### Deprecation summary -* Custom completion scripts are not supported yet (We should probably create zsh - specific one, doesn't make sense to re-use the bash one as the functions will - be different). -* Whatever other feature you're looking for and doesn't exist :) +See further below for more details on these deprecations. + +* `cmd.MarkZshCompPositionalArgumentFile(pos, []string{})` is no longer needed. It is therefore **deprecated** and silently ignored. +* `cmd.MarkZshCompPositionalArgumentFile(pos, glob[])` is **deprecated** and silently ignored. + * Instead use `ValidArgsFunction` with `ShellCompDirectiveFilterFileExt`. +* `cmd.MarkZshCompPositionalArgumentWords()` is **deprecated** and silently ignored. + * Instead use `ValidArgsFunction`. + +### Behavioral changes + +**Noun completion** +|Old behavior|New behavior| +|---|---| +|No file completion by default (opposite of bash)|File completion by default; use `ValidArgsFunction` with `ShellCompDirectiveNoFileComp` to turn off file completion on a per-argument basis| +`cmd.MarkZshCompPositionalArgumentFile(pos, []string{})` used to turn on file completion on a per-argument position basis|File completion for all arguments by default; `cmd.MarkZshCompPositionalArgumentFile()` is **deprecated** and silently ignored| +|`cmd.MarkZshCompPositionalArgumentFile(pos, glob[])` used to turn on file completion **with glob filtering** on a per-argument position basis (zsh-specific)|`cmd.MarkZshCompPositionalArgumentFile()` is **deprecated** and silently ignored; use `ValidArgsFunction` with `ShellCompDirectiveFilterFileExt` for file **extension** filtering (not full glob filtering)| +|`cmd.MarkZshCompPositionalArgumentWords(pos, words[])` used to provide completion choices on a per-argument position basis (zsh-specific)|`cmd.MarkZshCompPositionalArgumentWords()` is **deprecated** and silently ignored; use `ValidArgsFunction` to achieve the same behavior| + +**Flag-value completion** + +|Old behavior|New behavior| +|---|---| +|No file completion by default (opposite of bash)|File completion by default; use `RegisterFlagCompletionFunc()` with `ShellCompDirectiveNoFileComp` to turn off file completion| +|`cmd.MarkFlagFilename(flag, []string{})` and similar used to turn on file completion|File completion by default; `cmd.MarkFlagFilename(flag, []string{})` no longer needed in this context and silently ignored| +|`cmd.MarkFlagFilename(flag, glob[])` used to turn on file completion **with glob filtering** (syntax of `[]string{"*.yaml", "*.yml"}` incompatible with bash)|Will continue to work, however, support for bash syntax is added and should be used instead so as to work for all shells (`[]string{"yaml", "yml"}`)| +|`cmd.MarkFlagDirname(flag)` only completes directories (zsh-specific)|Has been added for all shells| +|Completion of a flag name does not repeat, unless flag is of type `*Array` or `*Slice` (not supported by bash)|Retained for `zsh` and added to `fish`| +|Completion of a flag name does not provide the `=` form (unlike bash)|Retained for `zsh` and added to `fish`| + +**Improvements** + +* Custom completion support (`ValidArgsFunction` and `RegisterFlagCompletionFunc()`) +* File completion by default if no other completions found +* Handling of required flags +* File extension filtering no longer mutually exclusive with bash usage +* Completion of directory names *within* another directory +* Support for `=` form of flags diff --git a/zsh_completions_test.go b/zsh_completions_test.go deleted file mode 100644 index e53fa886..00000000 --- a/zsh_completions_test.go +++ /dev/null @@ -1,475 +0,0 @@ -package cobra - -import ( - "bytes" - "regexp" - "strings" - "testing" -) - -func TestGenZshCompletion(t *testing.T) { - var debug bool - var option string - - tcs := []struct { - name string - root *Command - expectedExpressions []string - invocationArgs []string - skip string - }{ - { - name: "simple command", - root: func() *Command { - r := &Command{ - Use: "mycommand", - Long: "My Command long description", - Run: emptyRun, - } - r.Flags().BoolVar(&debug, "debug", debug, "description") - return r - }(), - expectedExpressions: []string{ - `(?s)function _mycommand {\s+_arguments \\\s+'--debug\[description\]'.*--help.*}`, - "#compdef _mycommand mycommand", - }, - }, - { - name: "flags with both long and short flags", - root: func() *Command { - r := &Command{ - Use: "testcmd", - Long: "long description", - Run: emptyRun, - } - r.Flags().BoolVarP(&debug, "debug", "d", debug, "debug description") - return r - }(), - expectedExpressions: []string{ - `'\(-d --debug\)'{-d,--debug}'\[debug description\]'`, - }, - }, - { - name: "command with subcommands and flags with values", - root: func() *Command { - r := &Command{ - Use: "rootcmd", - Long: "Long rootcmd description", - } - d := &Command{ - Use: "subcmd1", - Short: "Subcmd1 short description", - Run: emptyRun, - } - e := &Command{ - Use: "subcmd2", - Long: "Subcmd2 short description", - Run: emptyRun, - } - r.PersistentFlags().BoolVar(&debug, "debug", debug, "description") - d.Flags().StringVarP(&option, "option", "o", option, "option description") - r.AddCommand(d, e) - return r - }(), - expectedExpressions: []string{ - `commands=\(\n\s+"help:.*\n\s+"subcmd1:.*\n\s+"subcmd2:.*\n\s+\)`, - `_arguments \\\n.*'--debug\[description]'`, - `_arguments -C \\\n.*'--debug\[description]'`, - `function _rootcmd_subcmd1 {`, - `function _rootcmd_subcmd1 {`, - `_arguments \\\n.*'\(-o --option\)'{-o,--option}'\[option description]:' \\\n`, - }, - }, - { - name: "filename completion with and without globs", - root: func() *Command { - var file string - r := &Command{ - Use: "mycmd", - Short: "my command short description", - Run: emptyRun, - } - r.Flags().StringVarP(&file, "config", "c", file, "config file") - r.MarkFlagFilename("config") - r.Flags().String("output", "", "output file") - r.MarkFlagFilename("output", "*.log", "*.txt") - return r - }(), - expectedExpressions: []string{ - `\n +'\(-c --config\)'{-c,--config}'\[config file]:filename:_files'`, - `:_files -g "\*.log" -g "\*.txt"`, - }, - }, - { - name: "repeated variables both with and without value", - root: func() *Command { - r := genTestCommand("mycmd", true) - _ = r.Flags().BoolSliceP("debug", "d", []bool{}, "debug usage") - _ = r.Flags().StringArray("option", []string{}, "options") - return r - }(), - expectedExpressions: []string{ - `'\*--option\[options]`, - `'\(\*-d \*--debug\)'{\\\*-d,\\\*--debug}`, - }, - }, - { - name: "generated flags --help and --version should be created even when not executing root cmd", - root: func() *Command { - r := &Command{ - Use: "mycmd", - Short: "mycmd short description", - Version: "myversion", - } - s := genTestCommand("sub1", true) - r.AddCommand(s) - return s - }(), - expectedExpressions: []string{ - "--version", - "--help", - }, - invocationArgs: []string{ - "sub1", - }, - skip: "--version and --help are currently not generated when not running on root command", - }, - { - name: "zsh generation should run on root command", - root: func() *Command { - r := genTestCommand("root", false) - s := genTestCommand("sub1", true) - r.AddCommand(s) - return s - }(), - expectedExpressions: []string{ - "function _root {", - }, - }, - { - name: "flag description with single quote (') shouldn't break quotes in completion file", - root: func() *Command { - r := genTestCommand("root", true) - r.Flags().Bool("private", false, "Don't show public info") - return r - }(), - expectedExpressions: []string{ - `--private\[Don'\\''t show public info]`, - }, - }, - { - name: "argument completion for file with and without patterns", - root: func() *Command { - r := genTestCommand("root", true) - r.MarkZshCompPositionalArgumentFile(1, "*.log") - r.MarkZshCompPositionalArgumentFile(2) - return r - }(), - expectedExpressions: []string{ - `'1: :_files -g "\*.log"' \\\n\s+'2: :_files`, - }, - }, - { - name: "argument zsh completion for words", - root: func() *Command { - r := genTestCommand("root", true) - r.MarkZshCompPositionalArgumentWords(1, "word1", "word2") - return r - }(), - expectedExpressions: []string{ - `'1: :\("word1" "word2"\)`, - }, - }, - { - name: "argument completion for words with spaces", - root: func() *Command { - r := genTestCommand("root", true) - r.MarkZshCompPositionalArgumentWords(1, "single", "multiple words") - return r - }(), - expectedExpressions: []string{ - `'1: :\("single" "multiple words"\)'`, - }, - }, - { - name: "argument completion when command has ValidArgs and no annotation for argument completion", - root: func() *Command { - r := genTestCommand("root", true) - r.ValidArgs = []string{"word1", "word2"} - return r - }(), - expectedExpressions: []string{ - `'1: :\("word1" "word2"\)'`, - }, - }, - { - name: "argument completion when command has ValidArgs and no annotation for argument at argPosition 1", - root: func() *Command { - r := genTestCommand("root", true) - r.ValidArgs = []string{"word1", "word2"} - r.MarkZshCompPositionalArgumentFile(2) - return r - }(), - expectedExpressions: []string{ - `'1: :\("word1" "word2"\)' \\`, - }, - }, - { - name: "directory completion for flag", - root: func() *Command { - r := genTestCommand("root", true) - r.Flags().String("test", "", "test") - r.PersistentFlags().String("ptest", "", "ptest") - r.MarkFlagDirname("test") - r.MarkPersistentFlagDirname("ptest") - return r - }(), - expectedExpressions: []string{ - `--test\[test]:filename:_files -g "-\(/\)"`, - `--ptest\[ptest]:filename:_files -g "-\(/\)"`, - }, - }, - } - - for _, tc := range tcs { - t.Run(tc.name, func(t *testing.T) { - if tc.skip != "" { - t.Skip(tc.skip) - } - tc.root.Root().SetArgs(tc.invocationArgs) - tc.root.Execute() - buf := new(bytes.Buffer) - if err := tc.root.GenZshCompletion(buf); err != nil { - t.Error(err) - } - output := buf.Bytes() - - for _, expr := range tc.expectedExpressions { - rgx, err := regexp.Compile(expr) - if err != nil { - t.Errorf("error compiling expression (%s): %v", expr, err) - } - if !rgx.Match(output) { - t.Errorf("expected completion (%s) to match '%s'", buf.String(), expr) - } - } - }) - } -} - -func TestGenZshCompletionHidden(t *testing.T) { - tcs := []struct { - name string - root *Command - expectedExpressions []string - }{ - { - name: "hidden commands", - root: func() *Command { - r := &Command{ - Use: "main", - Short: "main short description", - } - s1 := &Command{ - Use: "sub1", - Hidden: true, - Run: emptyRun, - } - s2 := &Command{ - Use: "sub2", - Short: "short sub2 description", - Run: emptyRun, - } - r.AddCommand(s1, s2) - - return r - }(), - expectedExpressions: []string{ - "sub1", - }, - }, - { - name: "hidden flags", - root: func() *Command { - var hidden string - r := &Command{ - Use: "root", - Short: "root short description", - Run: emptyRun, - } - r.Flags().StringVarP(&hidden, "hidden", "H", hidden, "hidden usage") - if err := r.Flags().MarkHidden("hidden"); err != nil { - t.Errorf("Error setting flag hidden: %v\n", err) - } - return r - }(), - expectedExpressions: []string{ - "--hidden", - }, - }, - } - - for _, tc := range tcs { - t.Run(tc.name, func(t *testing.T) { - tc.root.Execute() - buf := new(bytes.Buffer) - if err := tc.root.GenZshCompletion(buf); err != nil { - t.Error(err) - } - output := buf.String() - - for _, expr := range tc.expectedExpressions { - if strings.Contains(output, expr) { - t.Errorf("Expected completion (%s) not to contain '%s' but it does", output, expr) - } - } - }) - } -} - -func TestMarkZshCompPositionalArgumentFile(t *testing.T) { - t.Run("Doesn't allow overwriting existing positional argument", func(t *testing.T) { - c := &Command{} - if err := c.MarkZshCompPositionalArgumentFile(1, "*.log"); err != nil { - t.Errorf("Received error when we shouldn't have: %v\n", err) - } - if err := c.MarkZshCompPositionalArgumentFile(1); err == nil { - t.Error("Didn't receive an error when trying to overwrite argument position") - } - }) - - t.Run("Refuses to accept argPosition less then 1", func(t *testing.T) { - c := &Command{} - err := c.MarkZshCompPositionalArgumentFile(0, "*") - if err == nil { - t.Fatal("Error was not thrown when indicating argument position 0") - } - if !strings.Contains(err.Error(), "position") { - t.Errorf("expected error message '%s' to contain 'position'", err.Error()) - } - }) -} - -func TestMarkZshCompPositionalArgumentWords(t *testing.T) { - t.Run("Doesn't allow overwriting existing positional argument", func(t *testing.T) { - c := &Command{} - if err := c.MarkZshCompPositionalArgumentFile(1, "*.log"); err != nil { - t.Errorf("Received error when we shouldn't have: %v\n", err) - } - if err := c.MarkZshCompPositionalArgumentWords(1, "hello"); err == nil { - t.Error("Didn't receive an error when trying to overwrite argument position") - } - }) - - t.Run("Doesn't allow calling without words", func(t *testing.T) { - c := &Command{} - if err := c.MarkZshCompPositionalArgumentWords(0); err == nil { - t.Error("Should not allow saving empty word list for annotation") - } - }) - - t.Run("Refuses to accept argPosition less then 1", func(t *testing.T) { - c := &Command{} - err := c.MarkZshCompPositionalArgumentWords(0, "word") - if err == nil { - t.Fatal("Should not allow setting argument position less then 1") - } - if !strings.Contains(err.Error(), "position") { - t.Errorf("Expected error '%s' to contain 'position' but didn't", err.Error()) - } - }) -} - -func BenchmarkMediumSizeConstruct(b *testing.B) { - root := constructLargeCommandHierarchy() - // if err := root.GenZshCompletionFile("_mycmd"); err != nil { - // b.Error(err) - // } - - for i := 0; i < b.N; i++ { - buf := new(bytes.Buffer) - err := root.GenZshCompletion(buf) - if err != nil { - b.Error(err) - } - } -} - -func TestExtractFlags(t *testing.T) { - var debug, cmdc, cmdd bool - c := &Command{ - Use: "cmdC", - Long: "Command C", - } - c.PersistentFlags().BoolVarP(&debug, "debug", "d", debug, "debug mode") - c.Flags().BoolVar(&cmdc, "cmd-c", cmdc, "Command C") - d := &Command{ - Use: "CmdD", - Long: "Command D", - } - d.Flags().BoolVar(&cmdd, "cmd-d", cmdd, "Command D") - c.AddCommand(d) - - resC := zshCompExtractFlag(c) - resD := zshCompExtractFlag(d) - - if len(resC) != 2 { - t.Errorf("expected Command C to return 2 flags, got %d", len(resC)) - } - if len(resD) != 2 { - t.Errorf("expected Command D to return 2 flags, got %d", len(resD)) - } -} - -func constructLargeCommandHierarchy() *Command { - var config, st1, st2 string - var long, debug bool - var in1, in2 int - var verbose []bool - - r := genTestCommand("mycmd", false) - r.PersistentFlags().StringVarP(&config, "config", "c", config, "config usage") - if err := r.MarkPersistentFlagFilename("config", "*"); err != nil { - panic(err) - } - s1 := genTestCommand("sub1", true) - s1.Flags().BoolVar(&long, "long", long, "long description") - s1.Flags().BoolSliceVar(&verbose, "verbose", verbose, "verbose description") - s1.Flags().StringArray("option", []string{}, "various options") - s2 := genTestCommand("sub2", true) - s2.PersistentFlags().BoolVar(&debug, "debug", debug, "debug description") - s3 := genTestCommand("sub3", true) - s3.Hidden = true - s1_1 := genTestCommand("sub1sub1", true) - s1_1.Flags().StringVar(&st1, "st1", st1, "st1 description") - s1_1.Flags().StringVar(&st2, "st2", st2, "st2 description") - s1_2 := genTestCommand("sub1sub2", true) - s1_3 := genTestCommand("sub1sub3", true) - s1_3.Flags().IntVar(&in1, "int1", in1, "int1 description") - s1_3.Flags().IntVar(&in2, "int2", in2, "int2 description") - s1_3.Flags().StringArrayP("option", "O", []string{}, "more options") - s2_1 := genTestCommand("sub2sub1", true) - s2_2 := genTestCommand("sub2sub2", true) - s2_3 := genTestCommand("sub2sub3", true) - s2_4 := genTestCommand("sub2sub4", true) - s2_5 := genTestCommand("sub2sub5", true) - - s1.AddCommand(s1_1, s1_2, s1_3) - s2.AddCommand(s2_1, s2_2, s2_3, s2_4, s2_5) - r.AddCommand(s1, s2, s3) - r.Execute() - return r -} - -func genTestCommand(name string, withRun bool) *Command { - r := &Command{ - Use: name, - Short: name + " short description", - Long: "Long description for " + name, - } - if withRun { - r.Run = emptyRun - } - - return r -} From 207dc47664b44b0a50993a2d81d053fd9ad44ae3 Mon Sep 17 00:00:00 2001 From: Sean Prince <7532924+seanprince@users.noreply.github.com> Date: Fri, 10 Jul 2020 13:32:03 +0100 Subject: [PATCH 44/56] Close #1152 by upgrading yaml.v2 to v2.3.0 (#1153) --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index dea1030b..9f584d7c 100644 --- a/go.mod +++ b/go.mod @@ -8,5 +8,5 @@ require ( github.com/mitchellh/go-homedir v1.1.0 github.com/spf13/pflag v1.0.3 github.com/spf13/viper v1.4.0 - gopkg.in/yaml.v2 v2.2.2 + gopkg.in/yaml.v2 v2.3.0 ) diff --git a/go.sum b/go.sum index 3aaa2ac0..88df09f1 100644 --- a/go.sum +++ b/go.sum @@ -146,4 +146,6 @@ gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bl gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= From 5d5290759ae9d4595d9563f4f952103199207d0f Mon Sep 17 00:00:00 2001 From: Diana Flach Date: Fri, 10 Jul 2020 22:06:59 +0200 Subject: [PATCH 45/56] Update README.md (#1154) I found this through a comment on an issue and thought this might save someone some time looking through the code. fixes #1148 --- README.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/README.md b/README.md index 1484240d..683eba42 100644 --- a/README.md +++ b/README.md @@ -312,6 +312,37 @@ var versionCmd = &cobra.Command{ } ``` +### Returning and handling errors + +If you wish to return an error to the caller of a command, `RunE` can be used. + +```go +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +func init() { + rootCmd.AddCommand(tryCmd) +} + +var tryCmd = &cobra.Command{ + Use: "try", + Short: "Try and possibly fail at something", + RunE: func(cmd *cobra.Command, args []string) error { + err := someFunc() + if err := nil { + return err + } + }, +} +``` + +The error can then be caught at the execute function call. + ## Working with Flags Flags provide modifiers to control how the action command operates. From 675ae5f5a98cc705a6d54f4c487ab81230604137 Mon Sep 17 00:00:00 2001 From: Marc Khouzam Date: Fri, 10 Jul 2020 16:12:46 -0400 Subject: [PATCH 46/56] Fish does not accept - or : in vars (#1122) Fixes #1121. This is for programs that may contain a : or - in their name. Signed-off-by: Marc Khouzam --- custom_completions_test.go | 33 ------------------ fish_completions.go | 32 +++++++++++------- fish_completions_test.go | 69 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 88 insertions(+), 46 deletions(-) create mode 100644 fish_completions_test.go diff --git a/custom_completions_test.go b/custom_completions_test.go index 66b3e638..276b8a77 100644 --- a/custom_completions_test.go +++ b/custom_completions_test.go @@ -1407,39 +1407,6 @@ func TestCompleteCmdInBashScript(t *testing.T) { check(t, output, ShellCompNoDescRequestCmd) } -func TestCompleteNoDesCmdInFishScript(t *testing.T) { - rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun} - child := &Command{ - Use: "child", - ValidArgsFunction: validArgsFunc, - Run: emptyRun, - } - rootCmd.AddCommand(child) - - buf := new(bytes.Buffer) - rootCmd.GenFishCompletion(buf, false) - output := buf.String() - - check(t, output, ShellCompNoDescRequestCmd) -} - -func TestCompleteCmdInFishScript(t *testing.T) { - rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun} - child := &Command{ - Use: "child", - ValidArgsFunction: validArgsFunc, - Run: emptyRun, - } - rootCmd.AddCommand(child) - - buf := new(bytes.Buffer) - rootCmd.GenFishCompletion(buf, true) - output := buf.String() - - check(t, output, ShellCompRequestCmd) - checkOmit(t, output, ShellCompNoDescRequestCmd) -} - func TestCompleteNoDesCmdInZshScript(t *testing.T) { rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun} child := &Command{ diff --git a/fish_completions.go b/fish_completions.go index 52b89c6e..66a6357c 100644 --- a/fish_completions.go +++ b/fish_completions.go @@ -5,9 +5,15 @@ import ( "fmt" "io" "os" + "strings" ) func genFishComp(buf *bytes.Buffer, name string, includeDesc bool) { + // Variables should not contain a '-' or ':' character + nameForVar := name + nameForVar = strings.Replace(nameForVar, "-", "_", -1) + nameForVar = strings.Replace(nameForVar, ":", "_", -1) + compCmd := ShellCompRequestCmd if !includeDesc { compCmd = ShellCompNoDescRequestCmd @@ -38,12 +44,12 @@ function __%[1]s_perform_completion __%[1]s_debug "emptyArg: $emptyArg" if not type -q "$args[1]" - # This can happen when "complete --do-complete %[1]s" is called when running this script. + # This can happen when "complete --do-complete %[2]s" is called when running this script. __%[1]s_debug "Cannot find $args[1]. No completions." return end - set requestComp "$args[1] %[2]s $args[2..-1] $emptyArg" + set requestComp "$args[1] %[3]s $args[2..-1] $emptyArg" __%[1]s_debug "Calling $requestComp" set results (eval $requestComp 2> /dev/null) @@ -99,11 +105,11 @@ function __%[1]s_prepare_completions __%[1]s_debug "Completions are: $__%[1]s_comp_results" __%[1]s_debug "Directive is: $directive" - set shellCompDirectiveError %[3]d - set shellCompDirectiveNoSpace %[4]d - set shellCompDirectiveNoFileComp %[5]d - set shellCompDirectiveFilterFileExt %[6]d - set shellCompDirectiveFilterDirs %[7]d + set shellCompDirectiveError %[4]d + set shellCompDirectiveNoSpace %[5]d + set shellCompDirectiveNoFileComp %[6]d + set shellCompDirectiveFilterFileExt %[7]d + set shellCompDirectiveFilterDirs %[8]d if test -z "$directive" set directive 0 @@ -158,24 +164,24 @@ end # so we can properly delete any completions provided by another script. # The space after the the program name is essential to trigger completion for the program # and not completion of the program name itself. -complete --do-complete "%[1]s " &> /dev/null +complete --do-complete "%[2]s " &> /dev/null # Remove any pre-existing completions for the program since we will be handling all of them. -complete -c %[1]s -e +complete -c %[2]s -e # The order in which the below two lines are defined is very important so that __%[1]s_prepare_completions # is called first. It is __%[1]s_prepare_completions that sets up the __%[1]s_comp_do_file_comp variable. # # This completion will be run second as complete commands are added FILO. # It triggers file completion choices when __%[1]s_comp_do_file_comp is set. -complete -c %[1]s -n 'set --query __%[1]s_comp_do_file_comp' +complete -c %[2]s -n 'set --query __%[1]s_comp_do_file_comp' # This completion will be run first as complete commands are added FILO. -# The call to __%[1]s_prepare_completions will setup both __%[1]s_comp_results abd __%[1]s_comp_do_file_comp. +# The call to __%[1]s_prepare_completions will setup both __%[1]s_comp_results and __%[1]s_comp_do_file_comp. # It provides the program's completion choices. -complete -c %[1]s -n '__%[1]s_prepare_completions' -f -a '$__%[1]s_comp_results' +complete -c %[2]s -n '__%[1]s_prepare_completions' -f -a '$__%[1]s_comp_results' -`, name, compCmd, +`, nameForVar, name, compCmd, ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp, ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs)) } diff --git a/fish_completions_test.go b/fish_completions_test.go new file mode 100644 index 00000000..532b6055 --- /dev/null +++ b/fish_completions_test.go @@ -0,0 +1,69 @@ +package cobra + +import ( + "bytes" + "testing" +) + +func TestCompleteNoDesCmdInFishScript(t *testing.T) { + rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun} + child := &Command{ + Use: "child", + ValidArgsFunction: validArgsFunc, + Run: emptyRun, + } + rootCmd.AddCommand(child) + + buf := new(bytes.Buffer) + rootCmd.GenFishCompletion(buf, false) + output := buf.String() + + check(t, output, ShellCompNoDescRequestCmd) +} + +func TestCompleteCmdInFishScript(t *testing.T) { + rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun} + child := &Command{ + Use: "child", + ValidArgsFunction: validArgsFunc, + Run: emptyRun, + } + rootCmd.AddCommand(child) + + buf := new(bytes.Buffer) + rootCmd.GenFishCompletion(buf, true) + output := buf.String() + + check(t, output, ShellCompRequestCmd) + checkOmit(t, output, ShellCompNoDescRequestCmd) +} + +func TestProgWithDash(t *testing.T) { + rootCmd := &Command{Use: "root-dash", Args: NoArgs, Run: emptyRun} + buf := new(bytes.Buffer) + rootCmd.GenFishCompletion(buf, false) + output := buf.String() + + // Functions name should have replace the '-' + check(t, output, "__root_dash_perform_completion") + checkOmit(t, output, "__root-dash_perform_completion") + + // The command name should not have replaced the '-' + check(t, output, "-c root-dash") + checkOmit(t, output, "-c root_dash") +} + +func TestProgWithColon(t *testing.T) { + rootCmd := &Command{Use: "root:colon", Args: NoArgs, Run: emptyRun} + buf := new(bytes.Buffer) + rootCmd.GenFishCompletion(buf, false) + output := buf.String() + + // Functions name should have replace the ':' + check(t, output, "__root_colon_perform_completion") + checkOmit(t, output, "__root:colon_perform_completion") + + // The command name should not have replaced the ':' + check(t, output, "-c root:colon") + checkOmit(t, output, "-c root_colon") +} From 884edc58ad08083e6c9a505041695aa2c3ca2d7a Mon Sep 17 00:00:00 2001 From: umarcor <38422348+umarcor@users.noreply.github.com> Date: Mon, 13 Jul 2020 19:55:00 +0200 Subject: [PATCH 47/56] update viper and pflag (#1012) --- go.mod | 4 +- go.sum | 200 +++++++++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 183 insertions(+), 21 deletions(-) diff --git a/go.mod b/go.mod index 9f584d7c..18ff7804 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/cpuguy83/go-md2man/v2 v2.0.0 github.com/inconshreveable/mousetrap v1.0.0 github.com/mitchellh/go-homedir v1.1.0 - github.com/spf13/pflag v1.0.3 - github.com/spf13/viper v1.4.0 + github.com/spf13/pflag v1.0.5 + github.com/spf13/viper v1.7.0 gopkg.in/yaml.v2 v2.3.0 ) diff --git a/go.sum b/go.sum index 88df09f1..e8b69c60 100644 --- a/go.sum +++ b/go.sum @@ -1,28 +1,48 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= -github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= @@ -32,19 +52,55 @@ github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zV github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= @@ -55,20 +111,34 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= -github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= @@ -79,11 +149,18 @@ github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= @@ -92,60 +169,145 @@ github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU= -github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM= +github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= -github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0 h1:HyfiK1WMnHj5FXFXatD+Qs1A/xC2Run6RzeW1SyHxpc= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= From b95db644ed1c0e183a90d8509ef2f9a5d51cc29b Mon Sep 17 00:00:00 2001 From: Tam Mach Date: Wed, 15 Jul 2020 13:12:39 +1000 Subject: [PATCH 48/56] Add golangci-lint in project using cobra (#1150) --- projects_using_cobra.md | 1 + 1 file changed, 1 insertion(+) diff --git a/projects_using_cobra.md b/projects_using_cobra.md index de450338..33a2bf0e 100644 --- a/projects_using_cobra.md +++ b/projects_using_cobra.md @@ -8,6 +8,7 @@ - [Gardener](https://github.com/gardener/gardenctl) - [Giant Swarm's gsctl](https://github.com/giantswarm/gsctl) - [Github CLI](https://github.com/cli/cli) +- [Golangci-lint](https://golangci-lint.run) - [GopherJS](http://www.gopherjs.org/) - [Helm](https://helm.sh) - [Hugo](https://gohugo.io) From 19e41cf081df9bf9dde5cffa0090e718c3fdc8af Mon Sep 17 00:00:00 2001 From: Dmitry Shurupov Date: Sat, 18 Jul 2020 04:48:45 +0700 Subject: [PATCH 49/56] Adding werf to projects using cobra (#1163) werf is a CI/CD & GitOps tool. Cobra is used in [werf's CLI](https://github.com/werf/werf/blob/master/cmd/werf/main.go). --- projects_using_cobra.md | 1 + 1 file changed, 1 insertion(+) diff --git a/projects_using_cobra.md b/projects_using_cobra.md index 33a2bf0e..0300a812 100644 --- a/projects_using_cobra.md +++ b/projects_using_cobra.md @@ -25,3 +25,4 @@ - [Prototool](https://github.com/uber/prototool) - [Rclone](https://rclone.org/) - [Skaffold](https://skaffold.dev/) +- [Werf](https://werf.io/) From c6fe2d4df8106986f3013bb70f1ffb062faadd6a Mon Sep 17 00:00:00 2001 From: Marc Khouzam Date: Sun, 19 Jul 2020 18:02:46 -0400 Subject: [PATCH 50/56] Improve zsh completion documentation (#1169) Signed-off-by: Marc Khouzam --- shell_completions.md | 9 +++++++-- zsh_completions.md | 1 + 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/shell_completions.md b/shell_completions.md index e0a256dd..d8416ab1 100644 --- a/shell_completions.md +++ b/shell_completions.md @@ -33,11 +33,16 @@ MacOS: Zsh: -$ source <(yourprogram completion zsh) +# If shell completion is not already enabled in your environment you will need +# to enable it. You can execute the following once: + +$ echo "autoload -U compinit; compinit" >> ~/.zshrc # To load completions for each session, execute once: $ yourprogram completion zsh > "${fpath[1]}/_yourprogram" +# You will need to start a new shell for this setup to take effect. + Fish: $ yourprogram completion fish | source @@ -363,7 +368,7 @@ Please refer to [Bash Completions](bash_completions.md) for details. Cobra supports native Zsh completion generated from the root `cobra.Command`. The generated completion script should be put somewhere in your `$fpath` and be named -`_`. +`_`. You will need to start a new shell for the completions to become available. Zsh supports descriptions for completions. Cobra will provide the description automatically, based on usage information. Cobra provides a way to completely disable such descriptions by diff --git a/zsh_completions.md b/zsh_completions.md index f432dd86..7cff6178 100644 --- a/zsh_completions.md +++ b/zsh_completions.md @@ -22,6 +22,7 @@ See further below for more details on these deprecations. |Old behavior|New behavior| |---|---| |No file completion by default (opposite of bash)|File completion by default; use `ValidArgsFunction` with `ShellCompDirectiveNoFileComp` to turn off file completion on a per-argument basis| +|Completion of flag names without the `-` prefix having been typed|Flag names are only completed if the user has typed the first `-`| `cmd.MarkZshCompPositionalArgumentFile(pos, []string{})` used to turn on file completion on a per-argument position basis|File completion for all arguments by default; `cmd.MarkZshCompPositionalArgumentFile()` is **deprecated** and silently ignored| |`cmd.MarkZshCompPositionalArgumentFile(pos, glob[])` used to turn on file completion **with glob filtering** on a per-argument position basis (zsh-specific)|`cmd.MarkZshCompPositionalArgumentFile()` is **deprecated** and silently ignored; use `ValidArgsFunction` with `ShellCompDirectiveFilterFileExt` for file **extension** filtering (not full glob filtering)| |`cmd.MarkZshCompPositionalArgumentWords(pos, words[])` used to provide completion choices on a per-argument position basis (zsh-specific)|`cmd.MarkZshCompPositionalArgumentWords()` is **deprecated** and silently ignored; use `ValidArgsFunction` to achieve the same behavior| From 0e27f22f6ef7d1b667aa5a105c48f5fe60332dfd Mon Sep 17 00:00:00 2001 From: Warren Fernandes Date: Fri, 31 Jul 2020 09:53:44 -0600 Subject: [PATCH 51/56] Add slack badge to point to cobra slack channel (#1181) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 683eba42..ac5a0db7 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ name a few. [This list](./projects_using_cobra.md) contains a more extensive lis [![Build Status](https://travis-ci.org/spf13/cobra.svg "Travis CI status")](https://travis-ci.org/spf13/cobra) [![GoDoc](https://godoc.org/github.com/spf13/cobra?status.svg)](https://godoc.org/github.com/spf13/cobra) [![Go Report Card](https://goreportcard.com/badge/github.com/spf13/cobra)](https://goreportcard.com/report/github.com/spf13/cobra) +[![Slack](https://img.shields.io/badge/Slack-cobra-brightgreen)](https://gophers.slack.com/archives/CD3LP1199) # Table of Contents From 96dc55577faf76629d3416fda5bd3a4e72e9db77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erdal=20TA=C5=9EKESEN?= Date: Wed, 5 Aug 2020 18:36:41 +0300 Subject: [PATCH 52/56] Update projects_using_cobra.md (#1147) Adds git-bump, gh-labels, and random to list of projects --- projects_using_cobra.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/projects_using_cobra.md b/projects_using_cobra.md index 0300a812..0397c30e 100644 --- a/projects_using_cobra.md +++ b/projects_using_cobra.md @@ -7,7 +7,9 @@ - [Etcd](https://etcd.io/) - [Gardener](https://github.com/gardener/gardenctl) - [Giant Swarm's gsctl](https://github.com/giantswarm/gsctl) +- [Git Bump](https://github.com/erdaltsksn/git-bump) - [Github CLI](https://github.com/cli/cli) +- [GitHub Labeler](https://github.com/erdaltsksn/gh-label) - [Golangci-lint](https://golangci-lint.run) - [GopherJS](http://www.gopherjs.org/) - [Helm](https://helm.sh) @@ -23,6 +25,7 @@ - [Pouch](https://github.com/alibaba/pouch) - [ProjectAtomic (enterprise)](http://www.projectatomic.io/) - [Prototool](https://github.com/uber/prototool) +- [Random](https://github.com/erdaltsksn/random) - [Rclone](https://rclone.org/) - [Skaffold](https://skaffold.dev/) - [Werf](https://werf.io/) From a738b60e5251bd384782aa2d9911537ca097ba07 Mon Sep 17 00:00:00 2001 From: Warren Fernandes Date: Mon, 10 Aug 2020 22:14:21 -0600 Subject: [PATCH 53/56] Add CONTRIBUTING.md (#1183) --- CONTRIBUTING.md | 50 +++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 12 +----------- 2 files changed, 51 insertions(+), 11 deletions(-) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..6f356e6a --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,50 @@ +# Contributing to Cobra + +Thank you so much for contributing to Cobra. We appreciate your time and help. +Here are some guidelines to help you get started. + +## Code of Conduct + +Be kind and respectful to the members of the community. Take time to educate +others who are seeking help. Harassment of any kind will not be tolerated. + +## Questions + +If you have questions regarding Cobra, feel free to ask it in the community +[#cobra Slack channel][cobra-slack] + +## Filing a bug or feature + +1. Before filing an issue, please check the existing issues to see if a + similar one was already opened. If there is one already opened, feel free + to comment on it. +1. If you believe you've found a bug, please provide detailed steps of + reproduction, the version of Cobra and anything else you believe will be + useful to help troubleshoot it (e.g. OS environment, environment variables, + etc...). Also state the current behavior vs. the expected behavior. +1. If you'd like to see a feature or an enhancement please open an issue with + a clear title and description of what the feature is and why it would be + beneficial to the project and its users. + +## Submitting changes + +1. CLA: Upon submitting a Pull Request (PR), contributors will be prompted to + sign a CLA. Please sign the CLA :slightly_smiling_face: +1. Tests: If you are submitting code, please ensure you have adequate tests + for the feature. Tests can be run via `go test ./...` or `make test`. +1. Since this is golang project, ensure the new code is properly formatted to + ensure code consistency. Run `make all`. + +### Quick steps to contribute + +1. Fork the project. +1. Download your fork to your PC (`git clone https://github.com/your_username/cobra && cd cobra`) +1. Create your feature branch (`git checkout -b my-new-feature`) +1. Make changes and run tests (`make test`) +1. Add them to staging (`git add .`) +1. Commit your changes (`git commit -m 'Add some feature'`) +1. Push to the branch (`git push origin my-new-feature`) +1. Create new pull request + + +[cobra-slack]: https://gophers.slack.com/archives/CD3LP1199 diff --git a/README.md b/README.md index ac5a0db7..9637c936 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ name a few. [This list](./projects_using_cobra.md) contains a more extensive lis * [Suggestions when "unknown command" happens](#suggestions-when-unknown-command-happens) * [Generating documentation for your command](#generating-documentation-for-your-command) * [Generating shell completions](#generating-shell-completions) -- [Contributing](#contributing) +- [Contributing](CONTRIBUTING.md) - [License](#license) # Overview @@ -755,16 +755,6 @@ Cobra can generate documentation based on subcommands, flags, etc. Read more abo Cobra can generate a shell-completion file for the following shells: Bash, Zsh, Fish, Powershell. If you add more information to your commands, these completions can be amazingly powerful and flexible. Read more about it in [Shell Completions](shell_completions.md). -# Contributing - -1. Fork it -2. Download your fork to your PC (`git clone https://github.com/your_username/cobra && cd cobra`) -3. Create your feature branch (`git checkout -b my-new-feature`) -4. Make changes and add them (`git add .`) -5. Commit your changes (`git commit -m 'Add some feature'`) -6. Push to the branch (`git push origin my-new-feature`) -7. Create new pull request - # License Cobra is released under the Apache 2.0 license. See [LICENSE.txt](https://github.com/spf13/cobra/blob/master/LICENSE.txt) From a0b86e58f8f908b6f14150a3c1c79e3fd5696808 Mon Sep 17 00:00:00 2001 From: Felix Yan Date: Tue, 11 Aug 2020 12:17:29 +0800 Subject: [PATCH 54/56] Correct a typo in doc/util.go (#1184) --- doc/util.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/util.go b/doc/util.go index 8d3dbece..bffde94d 100644 --- a/doc/util.go +++ b/doc/util.go @@ -20,7 +20,7 @@ import ( ) // Test to see if we have a reason to print See Also information in docs -// Basically this is a test for a parent commend or a subcommand which is +// Basically this is a test for a parent command or a subcommand which is // both not deprecated and not the autogenerated help command. func hasSeeAlso(cmd *cobra.Command) bool { if cmd.HasParent() { From 5cdf8e26ba7046dd743463f60102ab52602c6428 Mon Sep 17 00:00:00 2001 From: Michael Samoylov Date: Tue, 11 Aug 2020 15:39:51 +0300 Subject: [PATCH 55/56] Fix typo (#1187) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9637c936..53d174c7 100644 --- a/README.md +++ b/README.md @@ -334,10 +334,10 @@ var tryCmd = &cobra.Command{ Use: "try", Short: "Try and possibly fail at something", RunE: func(cmd *cobra.Command, args []string) error { - err := someFunc() - if err := nil { + if err := someFunc(); err != nil { return err } + return nil }, } ``` From 81e0311edd0bf6b4ff2c563005a646f6b286d059 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Mur=C3=A9?= Date: Sat, 15 Aug 2020 16:44:17 +0200 Subject: [PATCH 56/56] modules: add a secondary go.mod to segregate CLI dependencies (#1139) --- cobra/go.mod | 11 ++ cobra/go.sum | 310 +++++++++++++++++++++++++++++++++++++++++++++++++++ go.mod | 2 - go.sum | 299 +------------------------------------------------ 4 files changed, 322 insertions(+), 300 deletions(-) create mode 100644 cobra/go.mod create mode 100644 cobra/go.sum diff --git a/cobra/go.mod b/cobra/go.mod new file mode 100644 index 00000000..c53413db --- /dev/null +++ b/cobra/go.mod @@ -0,0 +1,11 @@ +module github.com/spf13/cobra/cobra + +go 1.12 + +require ( + github.com/mitchellh/go-homedir v1.1.0 + github.com/spf13/cobra v0.0.0 + github.com/spf13/viper v1.7.0 +) + +replace github.com/spf13/cobra => ../ diff --git a/cobra/go.sum b/cobra/go.sum new file mode 100644 index 00000000..a8893425 --- /dev/null +++ b/cobra/go.sum @@ -0,0 +1,310 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM= +github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0 h1:HyfiK1WMnHj5FXFXatD+Qs1A/xC2Run6RzeW1SyHxpc= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= diff --git a/go.mod b/go.mod index 18ff7804..0e15356d 100644 --- a/go.mod +++ b/go.mod @@ -5,8 +5,6 @@ go 1.12 require ( github.com/cpuguy83/go-md2man/v2 v2.0.0 github.com/inconshreveable/mousetrap v1.0.0 - github.com/mitchellh/go-homedir v1.1.0 github.com/spf13/pflag v1.0.5 - github.com/spf13/viper v1.7.0 gopkg.in/yaml.v2 v2.3.0 ) diff --git a/go.sum b/go.sum index e8b69c60..251f2f34 100644 --- a/go.sum +++ b/go.sum @@ -1,313 +1,16 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= -github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= -github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= -github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= -github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= -github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= -github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= -github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= -github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= -github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= -github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= -github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= -github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= -github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= -github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= -github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM= -github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= -github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= -go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0 h1:HyfiK1WMnHj5FXFXatD+Qs1A/xC2Run6RzeW1SyHxpc= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= -gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=