From d2d81d9a96e23f0255397222bb0b4e3165e492dc Mon Sep 17 00:00:00 2001 From: Daisuke Taniwaki Date: Tue, 27 Nov 2018 22:31:06 +0900 Subject: [PATCH 001/211] Fix too many underscore for __custom_func (#794) * Fix too many underscore for __custom_func * Fix typo of too many leading underscores in docs --- bash_completions.go | 2 +- bash_completions.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bash_completions.go b/bash_completions.go index 63274d96..30e74271 100644 --- a/bash_completions.go +++ b/bash_completions.go @@ -134,7 +134,7 @@ __%[1]s_handle_reply() __%[1]s_custom_func else # otherwise fall back to unqualified for compatibility - declare -F ___custom_func >/dev/null && __custom_func + declare -F __custom_func >/dev/null && __custom_func fi fi diff --git a/bash_completions.md b/bash_completions.md index 3bd32e0c..4ac61ee1 100644 --- a/bash_completions.md +++ b/bash_completions.md @@ -109,7 +109,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! +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' From 7547e83b2d85fd1893c7d76916f67689d761fecb Mon Sep 17 00:00:00 2001 From: Michael Date: Wed, 9 Jan 2019 00:34:09 +0000 Subject: [PATCH 002/211] Run tests against go 1.11 and drop 1.9 (#737) * Update the Travis and CircleCI Go versions * Adapt to new gofmt formatting The formatting of gofmt changed slightly in go 1.11. The release notes recommend to use a specific binary of gofmt. See https://golang.org/doc/go1.11#gofmt This commit adapts to the new formatting applied by gofmt and changes the configs for travis and circleci to run gofmt only with go 1.11. --- .circleci/config.yml | 77 ++++++++++++++++++++++++---------------- .travis.yml | 7 ++-- bash_completions_test.go | 2 +- 3 files changed, 51 insertions(+), 35 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 066df225..6d248bcd 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,3 +1,49 @@ +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 tool vet . 2>&1 | grep -vE 'ExampleCommand|bash_completions.*Fprint'); + fi + +jobs: + go-current: + docker: + - image: circleci/golang:1.11 + 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.10 + 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: @@ -5,34 +51,3 @@ workflows: - go-current - go-previous - go-latest -base: &base - working_directory: /go/src/github.com/spf13/cobra - steps: - - checkout - - 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 - diff -u <(echo -n) <(gofmt -d -s .) - if [ -z $NOVET ]; then - diff -u <(echo -n) <(go tool vet . 2>&1 | grep -vE 'ExampleCommand|bash_completions.*Fprint'); - fi -version: 2 -jobs: - go-current: - docker: - - image: circleci/golang:1.10.0 - <<: *base - go-previous: - docker: - - image: circleci/golang:1.9.4 - <<: *base - go-latest: - docker: - - image: circleci/golang:latest - <<: *base diff --git a/.travis.yml b/.travis.yml index 5afcb209..5f157433 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,9 +2,11 @@ language: go matrix: include: - - go: 1.9.4 - - go: 1.10.0 + - go: 1.10.x + - go: 1.11.x - go: tip + - go: 1.11.x + script: diff -u <(echo -n) <(gofmt -d -s .) allow_failures: - go: tip @@ -15,7 +17,6 @@ before_install: script: - PATH=$PATH:$PWD/bin go test -v ./... - go build - - diff -u <(echo -n) <(gofmt -d -s .) - if [ -z $NOVET ]; then diff -u <(echo -n) <(go tool vet . 2>&1 | grep -vE 'ExampleCommand|bash_completions.*Fprint'); fi diff --git a/bash_completions_test.go b/bash_completions_test.go index 94b965da..dd0fa52b 100644 --- a/bash_completions_test.go +++ b/bash_completions_test.go @@ -71,7 +71,7 @@ func TestBashCompletions(t *testing.T) { ArgAliases: []string{"pods", "nodes", "services", "replicationcontrollers", "po", "no", "svc", "rc"}, ValidArgs: []string{"pod", "node", "service", "replicationcontroller"}, BashCompletionFunction: bashCompletionFunc, - Run: emptyRun, + Run: emptyRun, } rootCmd.Flags().IntP("introot", "i", -1, "help message for flag introot") rootCmd.MarkFlagRequired("introot") From ba1052d4cbce7aac421a96de820558f75199ccbc Mon Sep 17 00:00:00 2001 From: Daisuke Taniwaki Date: Mon, 11 Mar 2019 21:55:09 +0900 Subject: [PATCH 003/211] Fix two word flags (#807) --- bash_completions.go | 7 ++++++- bash_completions_test.go | 10 ++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/bash_completions.go b/bash_completions.go index 30e74271..c3c1e501 100644 --- a/bash_completions.go +++ b/bash_completions.go @@ -199,7 +199,8 @@ __%[1]s_handle_flag() fi # skip the argument to a two word flag - if __%[1]s_contains_word "${words[c]}" "${two_word_flags[@]}"; then + if [[ ${words[c]} != *"="* ]] && __%[1]s_contains_word "${words[c]}" "${two_word_flags[@]}"; then + __%[1]s_debug "${FUNCNAME[0]}: found a flag ${words[c]}, skip the next argument" c=$((c+1)) # if we are looking for a flags value, don't show commands if [[ $c -eq $cword ]]; then @@ -379,6 +380,10 @@ func writeFlag(buf *bytes.Buffer, flag *pflag.Flag, cmd *Command) { } format += "\")\n" buf.WriteString(fmt.Sprintf(format, name)) + if len(flag.NoOptDefVal) == 0 { + format = " two_word_flags+=(\"--%s\")\n" + buf.WriteString(fmt.Sprintf(format, name)) + } writeFlagHandler(buf, "--"+name, flag.Annotations, cmd) } diff --git a/bash_completions_test.go b/bash_completions_test.go index dd0fa52b..eefa3de0 100644 --- a/bash_completions_test.go +++ b/bash_completions_test.go @@ -95,6 +95,10 @@ func TestBashCompletions(t *testing.T) { rootCmd.Flags().String("theme", "", "theme to use (located in /themes/THEMENAME/)") rootCmd.Flags().SetAnnotation("theme", BashCompSubdirsInDir, []string{"themes"}) + // For two word flags check + rootCmd.Flags().StringP("two", "t", "", "this is two word flags") + rootCmd.Flags().BoolP("two-w-default", "T", false, "this is not two word flags") + echoCmd := &Command{ Use: "echo [string to echo]", Aliases: []string{"say"}, @@ -183,6 +187,12 @@ func TestBashCompletions(t *testing.T) { // check for subdirs_in_dir flags in a subcommand checkRegex(t, output, fmt.Sprintf(`_root_echo\(\)\n{[^}]*flags_completion\+=\("__%s_handle_subdirs_in_dir_flag config"\)`, rootCmd.Name())) + // check two word flags + check(t, output, `two_word_flags+=("--two")`) + check(t, output, `two_word_flags+=("-t")`) + checkOmit(t, output, `two_word_flags+=("--two-w-default")`) + checkOmit(t, output, `two_word_flags+=("-T")`) + checkOmit(t, output, deprecatedCmd.Name()) // If available, run shellcheck against the script. From 5755ecf10233c1acfd87d417bc6605a30702c0a8 Mon Sep 17 00:00:00 2001 From: umarcor <38422348+umarcor@users.noreply.github.com> Date: Wed, 20 Mar 2019 22:21:26 +0100 Subject: [PATCH 004/211] [TrivialPatches] Typos in README.md, fix and update CI, update projects list... (#840) * update Example in README.md (#769) * specify the color as the required arg (#777) * command: fix typo in docstring of InheritedFlags (#779) * add istio to the list of projects built with Cobra (#786) * remove redundant 'else' (#806) * add mattermost-server as a project built with Cobra (#824) * update README.md (#826) Fix the comment: consistent with others * add uber/prototool as a project built with Cobra (#831) * fix(ci): use go vet, update to Go 1.12, update shellcheck to v0.4.6 (#832) * add go.mod and go.sum (#833) * chore(travis): move 'diff' job to separate stage in Travis (#839) * chore(travis): use language configuration list instead of explicit entries in matrix.include (#839) * chore(travis): update shellcheck-docker to v0.6.0 (#839) * update(README.md): separate projects by commas, instead of using a list * chore: update viper to v1.3.2 and go-md2man to v1.0.10 * fix: convert CRLF to LF when comparing files * use kyoh86/richgo to provide colored test outputs --- .circleci/config.yml | 6 ++--- .travis.yml | 27 ++++++++++++++------- README.md | 47 +++++++++++++++++++----------------- cobra/cmd/golden_test.go | 7 +++++- command.go | 6 ++--- go.mod | 13 ++++++++++ go.sum | 51 ++++++++++++++++++++++++++++++++++++++++ 7 files changed, 118 insertions(+), 39 deletions(-) create mode 100644 go.mod create mode 100644 go.sum diff --git a/.circleci/config.yml b/.circleci/config.yml index 6d248bcd..81944643 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -15,13 +15,13 @@ references: PATH=$PATH:$PWD/bin go test -v ./... go build if [ -z $NOVET ]; then - diff -u <(echo -n) <(go tool vet . 2>&1 | grep -vE 'ExampleCommand|bash_completions.*Fprint'); + diff -u <(echo -n) <(go vet . 2>&1 | grep -vE 'ExampleCommand|bash_completions.*Fprint'); fi jobs: go-current: docker: - - image: circleci/golang:1.11 + - image: circleci/golang:1.12 working_directory: *workspace steps: - checkout @@ -31,7 +31,7 @@ jobs: command: diff -u <(echo -n) <(gofmt -d -s .) go-previous: docker: - - image: circleci/golang:1.10 + - image: circleci/golang:1.11 working_directory: *workspace steps: - checkout diff --git a/.travis.yml b/.travis.yml index 5f157433..38b85f49 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,22 +1,31 @@ language: go +stages: + - diff + - test + +go: + - 1.10.x + - 1.11.x + - 1.12.x + - tip + matrix: - include: - - go: 1.10.x - - go: 1.11.x - - go: tip - - go: 1.11.x - script: diff -u <(echo -n) <(gofmt -d -s .) allow_failures: - go: tip + include: + - stage: diff + 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.4.3/shellcheck + - 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 script: - - PATH=$PATH:$PWD/bin go test -v ./... + - PATH=$PATH:$PWD/bin richgo test -v ./... - go build - if [ -z $NOVET ]; then - diff -u <(echo -n) <(go tool vet . 2>&1 | grep -vE 'ExampleCommand|bash_completions.*Fprint'); + diff -u <(echo -n) <(go vet . 2>&1 | grep -vE 'ExampleCommand|bash_completions.*Fprint'); fi diff --git a/README.md b/README.md index 5e73ffee..ff16e3f6 100644 --- a/README.md +++ b/README.md @@ -2,25 +2,28 @@ 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 including: - -* [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) +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), +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) @@ -389,7 +392,7 @@ The following validators are built in: - `MinimumNArgs(int)` - the command will report an error if there are not at least N positional args. - `MaximumNArgs(int)` - the command will report an error if there are more than N positional args. - `ExactArgs(int)` - the command will report an error if there are not exactly N positional args. -- `ExactValidArgs(int)` = the command will report and error if there are not exactly N positional args OR if there are any positional args that are not in the `ValidArgs` field of `Command` +- `ExactValidArgs(int)` - the command will report an error if there are not exactly N positional args OR if there are any positional args that are not in the `ValidArgs` field of `Command` - `RangeArgs(min, max)` - the command will report an error if the number of args is not between the minimum and maximum number of expected args. An example of setting the custom validator: @@ -399,7 +402,7 @@ var cmd = &cobra.Command{ Short: "hello", Args: func(cmd *cobra.Command, args []string) error { if len(args) < 1 { - return errors.New("requires at least one arg") + return errors.New("requires a color argument") } if myapp.IsValidColor(args[0]) { return nil @@ -459,7 +462,7 @@ Echo works a lot like print, except it has a child command.`, } var cmdTimes = &cobra.Command{ - Use: "times [# times] [string to echo]", + Use: "times [string to echo]", Short: "Echo anything to the screen more times", Long: `echo things multiple times back to the user by providing a count and a string.`, diff --git a/cobra/cmd/golden_test.go b/cobra/cmd/golden_test.go index 59a5a1c9..9010caa1 100644 --- a/cobra/cmd/golden_test.go +++ b/cobra/cmd/golden_test.go @@ -17,6 +17,11 @@ func init() { initCmd.SetOutput(new(bytes.Buffer)) } +// ensureLF converts any \r\n to \n +func ensureLF(content []byte) []byte { + return bytes.Replace(content, []byte("\r\n"), []byte("\n"), -1) +} + // compareFiles compares the content of files with pathA and pathB. // If contents are equal, it returns nil. // If not, it returns which files are not equal @@ -30,7 +35,7 @@ func compareFiles(pathA, pathB string) error { if err != nil { return err } - if !bytes.Equal(contentA, contentB) { + if !bytes.Equal(ensureLF(contentA), ensureLF(contentB)) { output := new(bytes.Buffer) output.WriteString(fmt.Sprintf("%q and %q are not equal!\n\n", pathA, pathB)) diff --git a/command.go b/command.go index 34d1bf36..b257f91b 100644 --- a/command.go +++ b/command.go @@ -817,13 +817,11 @@ func (c *Command) ExecuteC() (cmd *Command, err error) { // overriding c.InitDefaultHelpCmd() - var args []string + args := c.args // Workaround FAIL with "go test -v" or "cobra.test -test.v", see #155 if c.args == nil && filepath.Base(os.Args[0]) != "cobra.test" { args = os.Args[1:] - } else { - args = c.args } var flags []string @@ -1335,7 +1333,7 @@ func (c *Command) LocalFlags() *flag.FlagSet { return c.lflags } -// InheritedFlags returns all flags which were inherited from parents commands. +// InheritedFlags returns all flags which were inherited from parent commands. func (c *Command) InheritedFlags() *flag.FlagSet { c.mergePersistentFlags() diff --git a/go.mod b/go.mod new file mode 100644 index 00000000..9a9eb65a --- /dev/null +++ b/go.mod @@ -0,0 +1,13 @@ +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 + gopkg.in/yaml.v2 v2.2.2 +) diff --git a/go.sum b/go.sum new file mode 100644 index 00000000..9761f4d0 --- /dev/null +++ b/go.sum @@ -0,0 +1,51 @@ +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +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/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/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/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/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +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/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +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/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +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 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/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/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= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +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/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= From 67fc4837d267bc9bfd6e47f77783fcc3dffc68de Mon Sep 17 00:00:00 2001 From: Willi Eggeling Date: Thu, 21 Mar 2019 01:05:52 +0100 Subject: [PATCH 005/211] added variable to allow configuration of mousetrap message duration (#809) new variable MousetrapDisplayDuration allows to modify the default display duration of 5s, or to completely disable the timeout and wait for the user to press the return key. --- cobra.go | 7 +++++++ command_win.go | 8 +++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/cobra.go b/cobra.go index 7010fd15..6505c070 100644 --- a/cobra.go +++ b/cobra.go @@ -23,6 +23,7 @@ import ( "strconv" "strings" "text/template" + "time" "unicode" ) @@ -56,6 +57,12 @@ var MousetrapHelpText string = `This is a command line tool. You need to open cmd.exe and run it from there. ` +// MousetrapDisplayDuration controls how long the MousetrapHelpText message is displayed on Windows +// if the CLI is started from explorer.exe. Set to 0 to wait for the return key to be pressed. +// To disable the mousetrap, just set MousetrapHelpText to blank string (""). +// Works only on Microsoft Windows. +var MousetrapDisplayDuration time.Duration = 5 * time.Second + // AddTemplateFunc adds a template function that's available to Usage and Help // template generation. func AddTemplateFunc(name string, tmplFunc interface{}) { diff --git a/command_win.go b/command_win.go index edec728e..8768b173 100644 --- a/command_win.go +++ b/command_win.go @@ -3,6 +3,7 @@ package cobra import ( + "fmt" "os" "time" @@ -14,7 +15,12 @@ var preExecHookFn = preExecHook func preExecHook(c *Command) { if MousetrapHelpText != "" && mousetrap.StartedByExplorer() { c.Print(MousetrapHelpText) - time.Sleep(5 * time.Second) + if MousetrapDisplayDuration > 0 { + time.Sleep(MousetrapDisplayDuration) + } else { + c.Println("Press return to continue...") + fmt.Scanln() + } os.Exit(1) } } From 7e2436b79de609152fc3bcf7fc945b7b134ea825 Mon Sep 17 00:00:00 2001 From: Haim Ashkenazi Date: Sat, 24 Feb 2018 18:53:13 +0200 Subject: [PATCH 006/211] First try at better zsh completions: A very basic POC. Need to refactor to generate completion structure before passing to the template to avoid repeated computations. What works: * Real zsh completion (not built on bash) * Basic flags (with long flag and optional shorthand) * Basic filename completion indication (not with file extensions though) What's missing: * File extensions to filename completions * Positional args * Do we require handling only short flags? --- Gopkg.lock | 132 +++++++++++++++++++++++++ Gopkg.toml | 54 +++++++++++ zsh_completions.go | 207 ++++++++++++++++++++++------------------ zsh_completions_test.go | 171 +++++++++++++++++++++++---------- zsh_template.tmpl | 56 +++++++++++ 5 files changed, 477 insertions(+), 143 deletions(-) create mode 100644 Gopkg.lock create mode 100644 Gopkg.toml create mode 100644 zsh_template.tmpl diff --git a/Gopkg.lock b/Gopkg.lock new file mode 100644 index 00000000..fde98ef9 --- /dev/null +++ b/Gopkg.lock @@ -0,0 +1,132 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + name = "github.com/cpuguy83/go-md2man" + packages = ["md2man"] + revision = "20f5889cbdc3c73dbd2862796665e7c465ade7d1" + version = "v1.0.8" + +[[projects]] + name = "github.com/fsnotify/fsnotify" + packages = ["."] + revision = "c2828203cd70a50dcccfb2761f8b1f8ceef9a8e9" + version = "v1.4.7" + +[[projects]] + branch = "master" + name = "github.com/hashicorp/hcl" + packages = [ + ".", + "hcl/ast", + "hcl/parser", + "hcl/printer", + "hcl/scanner", + "hcl/strconv", + "hcl/token", + "json/parser", + "json/scanner", + "json/token" + ] + revision = "23c074d0eceb2b8a5bfdbb271ab780cde70f05a8" + +[[projects]] + name = "github.com/inconshreveable/mousetrap" + packages = ["."] + revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75" + version = "v1.0" + +[[projects]] + name = "github.com/magiconair/properties" + packages = ["."] + revision = "c3beff4c2358b44d0493c7dda585e7db7ff28ae6" + version = "v1.7.6" + +[[projects]] + branch = "master" + name = "github.com/mitchellh/go-homedir" + packages = ["."] + revision = "b8bc1bf767474819792c23f32d8286a45736f1c6" + +[[projects]] + branch = "master" + name = "github.com/mitchellh/mapstructure" + packages = ["."] + revision = "a4e142e9c047c904fa2f1e144d9a84e6133024bc" + +[[projects]] + name = "github.com/pelletier/go-toml" + packages = ["."] + revision = "acdc4509485b587f5e675510c4f2c63e90ff68a8" + version = "v1.1.0" + +[[projects]] + name = "github.com/russross/blackfriday" + packages = ["."] + revision = "4048872b16cc0fc2c5fd9eacf0ed2c2fedaa0c8c" + version = "v1.5" + +[[projects]] + name = "github.com/spf13/afero" + packages = [ + ".", + "mem" + ] + revision = "bb8f1927f2a9d3ab41c9340aa034f6b803f4359c" + version = "v1.0.2" + +[[projects]] + name = "github.com/spf13/cast" + packages = ["."] + revision = "8965335b8c7107321228e3e3702cab9832751bac" + version = "v1.2.0" + +[[projects]] + branch = "master" + name = "github.com/spf13/jwalterweatherman" + packages = ["."] + revision = "7c0cea34c8ece3fbeb2b27ab9b59511d360fb394" + +[[projects]] + branch = "master" + name = "github.com/spf13/pflag" + packages = ["."] + revision = "6a877ebacf28c5fc79846f4fcd380a5d9872b997" + +[[projects]] + branch = "master" + name = "github.com/spf13/viper" + packages = ["."] + revision = "aafc9e6bc7b7bb53ddaa75a5ef49a17d6e654be5" + +[[projects]] + branch = "master" + name = "golang.org/x/sys" + packages = ["unix"] + revision = "37707fdb30a5b38865cfb95e5aab41707daec7fd" + +[[projects]] + branch = "master" + name = "golang.org/x/text" + packages = [ + "internal/gen", + "internal/triegen", + "internal/ucd", + "transform", + "unicode/cldr", + "unicode/norm" + ] + revision = "4e4a3210bb54bb31f6ab2cdca2edcc0b50c420c1" + +[[projects]] + branch = "v2" + name = "gopkg.in/yaml.v2" + packages = ["."] + revision = "d670f9405373e636a5a2765eea47fac0c9bc91a4" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + inputs-digest = "d7aecc8ee6a8b343ebf8c2539348464f2566a18dfc511cd374b2cd76bebb1c6d" + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml new file mode 100644 index 00000000..3bf16541 --- /dev/null +++ b/Gopkg.toml @@ -0,0 +1,54 @@ +# Gopkg.toml example +# +# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md +# for detailed Gopkg.toml documentation. +# +# required = ["github.com/user/thing/cmd/thing"] +# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] +# +# [[constraint]] +# name = "github.com/user/project" +# version = "1.0.0" +# +# [[constraint]] +# name = "github.com/user/project2" +# branch = "dev" +# source = "github.com/myfork/project2" +# +# [[override]] +# name = "github.com/x/y" +# version = "2.4.0" +# +# [prune] +# non-go = false +# go-tests = true +# unused-packages = true + + +[[constraint]] + name = "github.com/cpuguy83/go-md2man" + version = "1.0.8" + +[[constraint]] + name = "github.com/inconshreveable/mousetrap" + version = "1.0.0" + +[[constraint]] + branch = "master" + name = "github.com/mitchellh/go-homedir" + +[[constraint]] + name = "github.com/spf13/pflag" + branch = "master" + +[[constraint]] + name = "github.com/spf13/viper" + branch = "master" + +[[constraint]] + branch = "v2" + name = "gopkg.in/yaml.v2" + +[prune] + go-tests = true + unused-packages = true diff --git a/zsh_completions.go b/zsh_completions.go index 889c22e2..9bdec654 100644 --- a/zsh_completions.go +++ b/zsh_completions.go @@ -1,11 +1,82 @@ package cobra import ( - "bytes" "fmt" "io" "os" "strings" + "text/template" + + "github.com/spf13/pflag" +) + +var ( + funcMap = template.FuncMap{ + "constructPath": constructPath, + "subCmdList": subCmdList, + "extractFlags": extractFlags, + "simpleFlag": simpleFlag, + } + zshCompletionText = ` +{{/* for pflag.Flag (specifically annotations) */}} +{{define "flagAnnotations" -}} +{{with index .Annotations "cobra_annotation_bash_completion_filename_extensions"}}:filename:_files{{end}} +{{- end}} + +{{/* for pflag.Flag with short and long options */}} +{{define "complexFlag" -}} +"(-{{.Shorthand}} --{{.Name}})"{-{{.Shorthand}},--{{.Name}}}"[{{.Usage}}]{{template "flagAnnotations" .}}" +{{- end}} + +{{/* for pflag.Flag with either short or long options */}} +{{define "simpleFlag" -}} +"{{with .Name}}--{{.}}{{else}}-{{.Shorthand}}{{end}}[{{.Usage}}]{{template "flagAnnotations" .}}" +{{- end}} + +{{/* should accept Command (that contains subcommands) as parameter */}} +{{define "argumentsC" -}} +function {{constructPath .}} { + local line + + _arguments -C \ +{{range extractFlags . -}} +{{" "}}{{if simpleFlag .}}{{template "simpleFlag" .}}{{else}}{{template "complexFlag" .}}{{end}} \ +{{end}} "1: :({{subCmdList .}})" \ + "*::arg:->args" + + case $line[1] in {{- range .Commands}} + {{.Use}}) + {{constructPath .}} + ;; +{{end}} esac +} +{{range .Commands}} +{{template "selectCmdTemplate" .}} +{{- end}} +{{- end}} + +{{/* should accept Command without subcommands as parameter */}} +{{define "arguments" -}} +function {{constructPath .}} { +{{with extractFlags . -}} +{{ " _arguments" -}} +{{range .}} \ + {{if simpleFlag .}}{{template "simpleFlag" .}}{{else}}{{template "complexFlag" .}}{{end -}} +{{end}} +{{end -}} +} +{{- end}} + +{{define "selectCmdTemplate" -}} +{{if .Commands}}{{template "argumentsC" .}}{{else}}{{template "arguments" .}}{{end}} +{{- end}} + +{{define "Main" -}} +#compdef _{{.Use}} {{.Use}} + +{{template "selectCmdTemplate" .}} +{{end}} +` ) // GenZshCompletionFile generates zsh completion file. @@ -21,106 +92,56 @@ func (c *Command) GenZshCompletionFile(filename string) error { // GenZshCompletion generates a zsh completion file and writes to the passed writer. func (c *Command) GenZshCompletion(w io.Writer) error { - buf := new(bytes.Buffer) - - writeHeader(buf, c) - maxDepth := maxDepth(c) - writeLevelMapping(buf, maxDepth) - writeLevelCases(buf, maxDepth, c) - - _, err := buf.WriteTo(w) - return err -} - -func writeHeader(w io.Writer, cmd *Command) { - fmt.Fprintf(w, "#compdef %s\n\n", cmd.Name()) -} - -func maxDepth(c *Command) int { - if len(c.Commands()) == 0 { - return 0 + tmpl, err := template.New("Main").Funcs(funcMap).Parse(zshCompletionText) + if err != nil { + return fmt.Errorf("error creating zsh completion template: %v", err) } - maxDepthSub := 0 - for _, s := range c.Commands() { - subDepth := maxDepth(s) - if subDepth > maxDepthSub { - maxDepthSub = subDepth + return tmpl.Execute(w, c) +} + +func constructPath(c *Command) string { + var path []string + tmpCmd := c + path = append(path, tmpCmd.Use) + + for { + if !tmpCmd.HasParent() { + break } + tmpCmd = tmpCmd.Parent() + path = append(path, tmpCmd.Use) } - return 1 + maxDepthSub + + // reverse path + for left, right := 0, len(path)-1; left < right; left, right = left+1, right-1 { + path[left], path[right] = path[right], path[left] + } + + return "_" + strings.Join(path, "_") } -func writeLevelMapping(w io.Writer, numLevels int) { - fmt.Fprintln(w, `_arguments \`) - for i := 1; i <= numLevels; i++ { - fmt.Fprintf(w, ` '%d: :->level%d' \`, i, i) - fmt.Fprintln(w) +// subCmdList returns a space separated list of subcommands names +func subCmdList(c *Command) string { + var subCmds []string + + for _, cmd := range c.Commands() { + subCmds = append(subCmds, cmd.Use) } - fmt.Fprintf(w, ` '%d: :%s'`, numLevels+1, "_files") - fmt.Fprintln(w) + + return strings.Join(subCmds, " ") } -func writeLevelCases(w io.Writer, maxDepth int, root *Command) { - fmt.Fprintln(w, "case $state in") - defer fmt.Fprintln(w, "esac") - - for i := 1; i <= maxDepth; i++ { - fmt.Fprintf(w, " level%d)\n", i) - writeLevel(w, root, i) - fmt.Fprintln(w, " ;;") - } - fmt.Fprintln(w, " *)") - fmt.Fprintln(w, " _arguments '*: :_files'") - fmt.Fprintln(w, " ;;") +func extractFlags(c *Command) []*pflag.Flag { + var flags []*pflag.Flag + c.LocalFlags().VisitAll(func(f *pflag.Flag) { + flags = append(flags, f) + }) + c.InheritedFlags().VisitAll(func(f *pflag.Flag) { + flags = append(flags, f) + }) + return flags } -func writeLevel(w io.Writer, root *Command, i int) { - fmt.Fprintf(w, " case $words[%d] in\n", i) - defer fmt.Fprintln(w, " esac") - - commands := filterByLevel(root, i) - byParent := groupByParent(commands) - - for p, c := range byParent { - names := names(c) - fmt.Fprintf(w, " %s)\n", p) - fmt.Fprintf(w, " _arguments '%d: :(%s)'\n", i, strings.Join(names, " ")) - fmt.Fprintln(w, " ;;") - } - fmt.Fprintln(w, " *)") - fmt.Fprintln(w, " _arguments '*: :_files'") - fmt.Fprintln(w, " ;;") - -} - -func filterByLevel(c *Command, l int) []*Command { - cs := make([]*Command, 0) - if l == 0 { - cs = append(cs, c) - return cs - } - for _, s := range c.Commands() { - cs = append(cs, filterByLevel(s, l-1)...) - } - return cs -} - -func groupByParent(commands []*Command) map[string][]*Command { - m := make(map[string][]*Command) - for _, c := range commands { - parent := c.Parent() - if parent == nil { - continue - } - m[parent.Name()] = append(m[parent.Name()], c) - } - return m -} - -func names(commands []*Command) []string { - ns := make([]string, len(commands)) - for i, c := range commands { - ns[i] = c.Name() - } - return ns +func simpleFlag(p *pflag.Flag) bool { + return p.Name == "" || p.Shorthand == "" } diff --git a/zsh_completions_test.go b/zsh_completions_test.go index 34e69496..16056297 100644 --- a/zsh_completions_test.go +++ b/zsh_completions_test.go @@ -2,74 +2,92 @@ package cobra import ( "bytes" - "strings" + "regexp" "testing" ) -func TestZshCompletion(t *testing.T) { +func TestGenZshCompletion(t *testing.T) { + var debug bool + var option string + tcs := []struct { name string root *Command expectedExpressions []string }{ { - name: "trivial", - root: &Command{Use: "trivialapp"}, - expectedExpressions: []string{"#compdef trivial"}, - }, - { - name: "linear", + name: "simple command", root: func() *Command { - r := &Command{Use: "linear"} - - sub1 := &Command{Use: "sub1"} - r.AddCommand(sub1) - - sub2 := &Command{Use: "sub2"} - sub1.AddCommand(sub2) - - sub3 := &Command{Use: "sub3"} - sub2.AddCommand(sub3) + r := &Command{ + Use: "mycommand", + Long: "My Command long description", + } + r.Flags().BoolVar(&debug, "debug", debug, "description") return r }(), - expectedExpressions: []string{"sub1", "sub2", "sub3"}, + expectedExpressions: []string{ + `function _mycommand {\s+_arguments \\\s+"--debug\[description\]"\s+}`, + "#compdef _mycommand mycommand", + }, }, { - name: "flat", + name: "flags with both long and short flags", root: func() *Command { - r := &Command{Use: "flat"} - r.AddCommand(&Command{Use: "c1"}) - r.AddCommand(&Command{Use: "c2"}) + r := &Command{ + Use: "testcmd", + Long: "long description", + } + r.Flags().BoolVarP(&debug, "debug", "d", debug, "debug description") return r }(), - expectedExpressions: []string{"(c1 c2)"}, + expectedExpressions: []string{ + `"\(-d --debug\)"{-d,--debug}"\[debug description\]"`, + }, }, { - name: "tree", + name: "command with subcommands", root: func() *Command { - r := &Command{Use: "tree"} - - sub1 := &Command{Use: "sub1"} - r.AddCommand(sub1) - - sub11 := &Command{Use: "sub11"} - sub12 := &Command{Use: "sub12"} - - sub1.AddCommand(sub11) - sub1.AddCommand(sub12) - - sub2 := &Command{Use: "sub2"} - r.AddCommand(sub2) - - sub21 := &Command{Use: "sub21"} - sub22 := &Command{Use: "sub22"} - - sub2.AddCommand(sub21) - sub2.AddCommand(sub22) - + r := &Command{ + Use: "rootcmd", + Long: "Long rootcmd description", + } + d := &Command{ + Use: "subcmd1", + Short: "Subcmd1 short descrition", + } + e := &Command{ + Use: "subcmd2", + Long: "Subcmd2 short description", + } + r.PersistentFlags().BoolVar(&debug, "debug", debug, "description") + d.Flags().StringVarP(&option, "option", "o", option, "option description") + r.AddCommand(d, e) return r }(), - expectedExpressions: []string{"(sub11 sub12)", "(sub21 sub22)"}, + expectedExpressions: []string{ + `\\\n\s+"1: :\(subcmd1 subcmd2\)" \\\n`, + `_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", + root: func() *Command { + var file string + r := &Command{ + Use: "mycmd", + Short: "my command short description", + } + r.Flags().StringVarP(&file, "config", "c", file, "config file") + r.MarkFlagFilename("config", "ext") + return r + }(), + expectedExpressions: []string{ + `\n +"\(-c --config\)"{-c,--config}"\[config file]:filename:_files"`, + }, }, } @@ -77,13 +95,66 @@ func TestZshCompletion(t *testing.T) { t.Run(tc.name, func(t *testing.T) { buf := new(bytes.Buffer) tc.root.GenZshCompletion(buf) - output := buf.String() + output := buf.Bytes() - for _, expectedExpression := range tc.expectedExpressions { - if !strings.Contains(output, expectedExpression) { - t.Errorf("Expected completion to contain %q somewhere; got %q", expectedExpression, output) + 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("expeced completion (%s) to match '%s'", buf.String(), expr) } } }) } + +} + +func BenchmarkConstructPath(b *testing.B) { + c := &Command{ + Use: "main", + Long: "main long description which is very long", + Short: "main short description", + } + d := &Command{ + Use: "hello", + } + e := &Command{ + Use: "world", + } + c.AddCommand(d) + d.AddCommand(e) + for i := 0; i < b.N; i++ { + res := constructPath(e) + if res != "_main_hello_world" { + b.Errorf("expeced path to be '_main_hello_world', got %s", res) + } + } +} + +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 := extractFlags(c) + resD := extractFlags(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)) + } } diff --git a/zsh_template.tmpl b/zsh_template.tmpl new file mode 100644 index 00000000..43846254 --- /dev/null +++ b/zsh_template.tmpl @@ -0,0 +1,56 @@ +{{define "complexFlag"}}{{ /* for pflag.Flag with short and long options */ -}} +"(-{{.Shorthand}} --{{.Name}})"\{-{{.Shorthand}}, --{{.Name}}\}[{{.Usage}}] +{{- end}} + +{{define "simpleFlag"}}{{ /* for pflag.Flag with either short or long options */ -}} +"{{with .Name}}-{{.}}{{else}}--{{.Shorthand}}{{end}}[{{.Usage}}]" +{{- end}} + +{{define "argumentsC"}} +{{- /* should accept Command (that contains subcommands) as parameter */ -}} +function {{constructPath .}} { + local line + + _arguments -C \ +{{range extractFlags . -}} + {{if simpleFlag .}}{{template "simpleFlag" .}}{{else}}{{template "complexFlag" .}}{{end}} \ +{{end -}} + "1: :({{subCmdList .}})" \ + "*:arg:->args" + + case $line[1] in +{{range .Commands -}} + {{.Use}}) + {{constructPath .}} + ;; +{{end -}} + esac +} +{{range .Commands -}} + +{{template "selectCmdTemplate" .}} +{{end -}} +{{end}} + +{{define "arguments"}} +function {{constructPath .}} { +{{- /* should accept Command without subcommands as parameter */ -}} +{{with extractFlags . -}} + _arguments \ +{{range .}} + {{if simpleFlag .}}{{template "simpleFlag" .}}{{else}}{{template "complexFlag" .}}{{end}} \ +{{end -}} +{{ /* leave this line empty because of the last backslash */ }} +{{end -}} +} +{{end}} + +{{define "selectCmdTemplate" -}} +{{with .Commands}}{{template "argumentsC" .}}{{else}}{{template "arguments" .}}{{end}} +{{end -}} + +{{define "Main"}} +#compdef _{{.Use}} {{.Use}} + +{{template "selectCmdTemplate" .}} +{{end}} From a15d0990180cf0d99905ad41201211f8ac3cab53 Mon Sep 17 00:00:00 2001 From: Haim Ashkenazi Date: Sun, 25 Feb 2018 08:20:34 +0200 Subject: [PATCH 007/211] zsh-completion fixed reference to cmd name cmd.Use is not the command name :). Found it once I figured out that I need to execute the command in order to fully test the generated completion. --- zsh_completions.go | 16 +++++++++++----- zsh_completions_test.go | 10 ++++++++-- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/zsh_completions.go b/zsh_completions.go index 9bdec654..d1c9e07b 100644 --- a/zsh_completions.go +++ b/zsh_completions.go @@ -15,6 +15,7 @@ var ( "constructPath": constructPath, "subCmdList": subCmdList, "extractFlags": extractFlags, + "cmdName": cmdName, "simpleFlag": simpleFlag, } zshCompletionText = ` @@ -45,7 +46,7 @@ function {{constructPath .}} { "*::arg:->args" case $line[1] in {{- range .Commands}} - {{.Use}}) + {{cmdName .}}) {{constructPath .}} ;; {{end}} esac @@ -72,7 +73,7 @@ function {{constructPath .}} { {{- end}} {{define "Main" -}} -#compdef _{{.Use}} {{.Use}} +#compdef _{{cmdName .}} {{cmdName .}} {{template "selectCmdTemplate" .}} {{end}} @@ -102,14 +103,14 @@ func (c *Command) GenZshCompletion(w io.Writer) error { func constructPath(c *Command) string { var path []string tmpCmd := c - path = append(path, tmpCmd.Use) + path = append(path, tmpCmd.Name()) for { if !tmpCmd.HasParent() { break } tmpCmd = tmpCmd.Parent() - path = append(path, tmpCmd.Use) + path = append(path, tmpCmd.Name()) } // reverse path @@ -125,7 +126,7 @@ func subCmdList(c *Command) string { var subCmds []string for _, cmd := range c.Commands() { - subCmds = append(subCmds, cmd.Use) + subCmds = append(subCmds, cmd.Name()) } return strings.Join(subCmds, " ") @@ -142,6 +143,11 @@ func extractFlags(c *Command) []*pflag.Flag { return flags } +// cmdName returns the command's innvocation +func cmdName(c *Command) string { + return c.Name() +} + func simpleFlag(p *pflag.Flag) bool { return p.Name == "" || p.Shorthand == "" } diff --git a/zsh_completions_test.go b/zsh_completions_test.go index 16056297..4d29e542 100644 --- a/zsh_completions_test.go +++ b/zsh_completions_test.go @@ -21,12 +21,13 @@ func TestGenZshCompletion(t *testing.T) { r := &Command{ Use: "mycommand", Long: "My Command long description", + Run: emptyRun, } r.Flags().BoolVar(&debug, "debug", debug, "description") return r }(), expectedExpressions: []string{ - `function _mycommand {\s+_arguments \\\s+"--debug\[description\]"\s+}`, + `(?s)function _mycommand {\s+_arguments \\\s+"--debug\[description\]".*--help.*}`, "#compdef _mycommand mycommand", }, }, @@ -36,6 +37,7 @@ func TestGenZshCompletion(t *testing.T) { r := &Command{ Use: "testcmd", Long: "long description", + Run: emptyRun, } r.Flags().BoolVarP(&debug, "debug", "d", debug, "debug description") return r @@ -54,10 +56,12 @@ func TestGenZshCompletion(t *testing.T) { d := &Command{ Use: "subcmd1", Short: "Subcmd1 short descrition", + 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") @@ -65,7 +69,7 @@ func TestGenZshCompletion(t *testing.T) { return r }(), expectedExpressions: []string{ - `\\\n\s+"1: :\(subcmd1 subcmd2\)" \\\n`, + `\\\n\s+"1: :\(help subcmd1 subcmd2\)" \\\n`, `_arguments \\\n.*"--debug\[description]"`, `_arguments -C \\\n.*"--debug\[description]"`, `function _rootcmd_subcmd1 {`, @@ -80,6 +84,7 @@ func TestGenZshCompletion(t *testing.T) { r := &Command{ Use: "mycmd", Short: "my command short description", + Run: emptyRun, } r.Flags().StringVarP(&file, "config", "c", file, "config file") r.MarkFlagFilename("config", "ext") @@ -93,6 +98,7 @@ func TestGenZshCompletion(t *testing.T) { for _, tc := range tcs { t.Run(tc.name, func(t *testing.T) { + tc.root.Execute() buf := new(bytes.Buffer) tc.root.GenZshCompletion(buf) output := buf.Bytes() From f0508c8e7665e581c7880c232466fe77b7a8964b Mon Sep 17 00:00:00 2001 From: Haim Ashkenazi Date: Sun, 25 Feb 2018 14:12:58 +0200 Subject: [PATCH 008/211] zsh-completion ignores hidden commands and flags :) --- zsh_completions.go | 17 ++++++++--- zsh_completions_test.go | 68 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 4 deletions(-) diff --git a/zsh_completions.go b/zsh_completions.go index d1c9e07b..7fa0df11 100644 --- a/zsh_completions.go +++ b/zsh_completions.go @@ -45,11 +45,11 @@ function {{constructPath .}} { {{end}} "1: :({{subCmdList .}})" \ "*::arg:->args" - case $line[1] in {{- range .Commands}} + case $line[1] in {{- range .Commands}}{{if not .Hidden}} {{cmdName .}}) {{constructPath .}} ;; -{{end}} esac +{{end}}{{end}} esac } {{range .Commands}} {{template "selectCmdTemplate" .}} @@ -69,8 +69,10 @@ function {{constructPath .}} { {{- end}} {{define "selectCmdTemplate" -}} +{{if .Hidden}}{{/* ignore hidden*/}}{{else -}} {{if .Commands}}{{template "argumentsC" .}}{{else}}{{template "arguments" .}}{{end}} {{- end}} +{{- end}} {{define "Main" -}} #compdef _{{cmdName .}} {{cmdName .}} @@ -126,6 +128,9 @@ func subCmdList(c *Command) string { var subCmds []string for _, cmd := range c.Commands() { + if cmd.Hidden { + continue + } subCmds = append(subCmds, cmd.Name()) } @@ -135,10 +140,14 @@ func subCmdList(c *Command) string { func extractFlags(c *Command) []*pflag.Flag { var flags []*pflag.Flag c.LocalFlags().VisitAll(func(f *pflag.Flag) { - flags = append(flags, f) + if !f.Hidden { + flags = append(flags, f) + } }) c.InheritedFlags().VisitAll(func(f *pflag.Flag) { - flags = append(flags, f) + if !f.Hidden { + flags = append(flags, f) + } }) return flags } diff --git a/zsh_completions_test.go b/zsh_completions_test.go index 4d29e542..d523762f 100644 --- a/zsh_completions_test.go +++ b/zsh_completions_test.go @@ -3,6 +3,7 @@ package cobra import ( "bytes" "regexp" + "strings" "testing" ) @@ -114,7 +115,74 @@ func TestGenZshCompletion(t *testing.T) { } }) } +} +func TestGenZshCompletionHidden(t *testing.T) { + tcs := []struct { + name string + root *Command + expectedExpressions []string + }{ + { + name: "hidden commmands", + 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) + tc.root.GenZshCompletion(buf) + 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 BenchmarkConstructPath(b *testing.B) { From 2662787697b4d82e32836f2305918a0c22baae69 Mon Sep 17 00:00:00 2001 From: Haim Ashkenazi Date: Mon, 26 Feb 2018 22:31:06 +0200 Subject: [PATCH 009/211] zsh-completion: added support for subcommand description. Also make the template more elegant on the way... --- zsh_completions.go | 41 +++++++++++++--------- zsh_completions_test.go | 78 +++++++++++++++++++++++++++++++---------- 2 files changed, 84 insertions(+), 35 deletions(-) diff --git a/zsh_completions.go b/zsh_completions.go index 7fa0df11..9dbc9c7c 100644 --- a/zsh_completions.go +++ b/zsh_completions.go @@ -37,43 +37,50 @@ var ( {{/* should accept Command (that contains subcommands) as parameter */}} {{define "argumentsC" -}} function {{constructPath .}} { - local line + local -a commands - _arguments -C \ -{{range extractFlags . -}} -{{" "}}{{if simpleFlag .}}{{template "simpleFlag" .}}{{else}}{{template "complexFlag" .}}{{end}} \ -{{end}} "1: :({{subCmdList .}})" \ + _arguments -C \{{- range extractFlags .}} + {{if simpleFlag .}}{{template "simpleFlag" .}}{{else}}{{template "complexFlag" .}}{{end}} \{{- end}} + "1: :->cmnds" \ "*::arg:->args" - case $line[1] in {{- range .Commands}}{{if not .Hidden}} - {{cmdName .}}) - {{constructPath .}} - ;; -{{end}}{{end}} esac + case $state in + cmnds) + commands=({{range .Commands}}{{if not .Hidden}} + "{{cmdName .}}:{{.Short}}"{{end}}{{end}} + ) + _describe "command" commands + ;; + esac + + case "$words[1]" in {{- range .Commands}}{{if not .Hidden}} + {{cmdName .}}) + {{constructPath .}} + ;;{{end}}{{end}} + esac } -{{range .Commands}} +{{range .Commands}}{{if not .Hidden}} {{template "selectCmdTemplate" .}} -{{- end}} +{{- end}}{{end}} {{- end}} {{/* should accept Command without subcommands as parameter */}} {{define "arguments" -}} function {{constructPath .}} { -{{with extractFlags . -}} -{{ " _arguments" -}} -{{range .}} \ +{{" _arguments"}}{{range extractFlags .}} \ {{if simpleFlag .}}{{template "simpleFlag" .}}{{else}}{{template "complexFlag" .}}{{end -}} {{end}} -{{end -}} } -{{- 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 _{{cmdName .}} {{cmdName .}} diff --git a/zsh_completions_test.go b/zsh_completions_test.go index d523762f..c8d80c85 100644 --- a/zsh_completions_test.go +++ b/zsh_completions_test.go @@ -70,7 +70,7 @@ func TestGenZshCompletion(t *testing.T) { return r }(), expectedExpressions: []string{ - `\\\n\s+"1: :\(help subcmd1 subcmd2\)" \\\n`, + `commands=\(\n\s+"help:.*\n\s+"subcmd1:.*\n\s+"subcmd2:.*\n\s+\)`, `_arguments \\\n.*"--debug\[description]"`, `_arguments -C \\\n.*"--debug\[description]"`, `function _rootcmd_subcmd1 {`, @@ -185,24 +185,17 @@ func TestGenZshCompletionHidden(t *testing.T) { } } -func BenchmarkConstructPath(b *testing.B) { - c := &Command{ - Use: "main", - Long: "main long description which is very long", - Short: "main short description", - } - d := &Command{ - Use: "hello", - } - e := &Command{ - Use: "world", - } - c.AddCommand(d) - d.AddCommand(e) +func BenchmarkMediumSizeConstruct(b *testing.B) { + root := constructLargeCommandHeirarchy() + // if err := root.GenZshCompletionFile("_mycmd"); err != nil { + // b.Error(err) + // } + for i := 0; i < b.N; i++ { - res := constructPath(e) - if res != "_main_hello_world" { - b.Errorf("expeced path to be '_main_hello_world', got %s", res) + buf := new(bytes.Buffer) + err := root.GenZshCompletion(buf) + if err != nil { + b.Error(err) } } } @@ -232,3 +225,52 @@ func TestExtractFlags(t *testing.T) { t.Errorf("expected Command D to return 2 flags, got %d", len(resD)) } } + +func constructLargeCommandHeirarchy() *Command { + var config, st1, st2 string + var long, debug bool + var in1, in2 int + + 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 descriptin") + 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 descriptionn") + s1_3.Flags().IntVar(&in2, "int2", in2, "int2 descriptionn") + 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 e8018e8612d3a4fe2f8a40c01796b15ae705ff22 Mon Sep 17 00:00:00 2001 From: Haim Ashkenazi Date: Wed, 28 Feb 2018 12:49:53 +0200 Subject: [PATCH 010/211] zsh-completion template refactoring: - removed redundant function - improved other functions :) - better names for other functions --- zsh_completions.go | 63 +++++++++++----------------------------------- 1 file changed, 14 insertions(+), 49 deletions(-) diff --git a/zsh_completions.go b/zsh_completions.go index 9dbc9c7c..dd23c45f 100644 --- a/zsh_completions.go +++ b/zsh_completions.go @@ -4,7 +4,6 @@ import ( "fmt" "io" "os" - "strings" "text/template" "github.com/spf13/pflag" @@ -12,11 +11,9 @@ import ( var ( funcMap = template.FuncMap{ - "constructPath": constructPath, - "subCmdList": subCmdList, - "extractFlags": extractFlags, - "cmdName": cmdName, - "simpleFlag": simpleFlag, + "genZshFuncName": generateZshCompletionFuncName, + "extractFlags": extractFlags, + "simpleFlag": simpleFlag, } zshCompletionText = ` {{/* for pflag.Flag (specifically annotations) */}} @@ -36,7 +33,8 @@ var ( {{/* should accept Command (that contains subcommands) as parameter */}} {{define "argumentsC" -}} -function {{constructPath .}} { +{{ $cmdPath := genZshFuncName .}} +function {{$cmdPath}} { local -a commands _arguments -C \{{- range extractFlags .}} @@ -47,15 +45,15 @@ function {{constructPath .}} { case $state in cmnds) commands=({{range .Commands}}{{if not .Hidden}} - "{{cmdName .}}:{{.Short}}"{{end}}{{end}} + "{{.Name}}:{{.Short}}"{{end}}{{end}} ) _describe "command" commands ;; esac case "$words[1]" in {{- range .Commands}}{{if not .Hidden}} - {{cmdName .}}) - {{constructPath .}} + {{.Name}}) + {{$cmdPath}}_{{.Name}} ;;{{end}}{{end}} esac } @@ -66,7 +64,7 @@ function {{constructPath .}} { {{/* should accept Command without subcommands as parameter */}} {{define "arguments" -}} -function {{constructPath .}} { +function {{genZshFuncName .}} { {{" _arguments"}}{{range extractFlags .}} \ {{if simpleFlag .}}{{template "simpleFlag" .}}{{else}}{{template "complexFlag" .}}{{end -}} {{end}} @@ -82,7 +80,7 @@ function {{constructPath .}} { {{/* template entry point */}} {{define "Main" -}} -#compdef _{{cmdName .}} {{cmdName .}} +#compdef _{{.Name}} {{.Name}} {{template "selectCmdTemplate" .}} {{end}} @@ -109,39 +107,11 @@ func (c *Command) GenZshCompletion(w io.Writer) error { return tmpl.Execute(w, c) } -func constructPath(c *Command) string { - var path []string - tmpCmd := c - path = append(path, tmpCmd.Name()) - - for { - if !tmpCmd.HasParent() { - break - } - tmpCmd = tmpCmd.Parent() - path = append(path, tmpCmd.Name()) +func generateZshCompletionFuncName(c *Command) string { + if c.HasParent() { + return generateZshCompletionFuncName(c.Parent()) + "_" + c.Name() } - - // reverse path - for left, right := 0, len(path)-1; left < right; left, right = left+1, right-1 { - path[left], path[right] = path[right], path[left] - } - - return "_" + strings.Join(path, "_") -} - -// subCmdList returns a space separated list of subcommands names -func subCmdList(c *Command) string { - var subCmds []string - - for _, cmd := range c.Commands() { - if cmd.Hidden { - continue - } - subCmds = append(subCmds, cmd.Name()) - } - - return strings.Join(subCmds, " ") + return "_" + c.Name() } func extractFlags(c *Command) []*pflag.Flag { @@ -159,11 +129,6 @@ func extractFlags(c *Command) []*pflag.Flag { return flags } -// cmdName returns the command's innvocation -func cmdName(c *Command) string { - return c.Name() -} - func simpleFlag(p *pflag.Flag) bool { return p.Name == "" || p.Shorthand == "" } From ec4b8c974c81c2077fb0f8dcd6cfe0f627b228f9 Mon Sep 17 00:00:00 2001 From: Haim Ashkenazi Date: Wed, 28 Feb 2018 17:04:17 +0200 Subject: [PATCH 011/211] zsh-completions: revised flags completion rendering + new features: - If the flags are not bool the completion expects argument. - You don't have to specify file extensions for file completion to work. - Allow multiple occurrences of flag if type is stringArray. Need to verify that these assumption are correct :) --- zsh_completions.go | 79 ++++++++++++++++++++++++++++++----------- zsh_completions_test.go | 31 +++++++++++++--- 2 files changed, 85 insertions(+), 25 deletions(-) diff --git a/zsh_completions.go b/zsh_completions.go index dd23c45f..1d73e272 100644 --- a/zsh_completions.go +++ b/zsh_completions.go @@ -4,6 +4,7 @@ import ( "fmt" "io" "os" + "strings" "text/template" "github.com/spf13/pflag" @@ -11,26 +12,11 @@ import ( var ( funcMap = template.FuncMap{ - "genZshFuncName": generateZshCompletionFuncName, - "extractFlags": extractFlags, - "simpleFlag": simpleFlag, + "genZshFuncName": generateZshCompletionFuncName, + "extractFlags": extractFlags, + "genFlagEntryForZshArguments": genFlagEntryForZshArguments, } zshCompletionText = ` -{{/* for pflag.Flag (specifically annotations) */}} -{{define "flagAnnotations" -}} -{{with index .Annotations "cobra_annotation_bash_completion_filename_extensions"}}:filename:_files{{end}} -{{- end}} - -{{/* for pflag.Flag with short and long options */}} -{{define "complexFlag" -}} -"(-{{.Shorthand}} --{{.Name}})"{-{{.Shorthand}},--{{.Name}}}"[{{.Usage}}]{{template "flagAnnotations" .}}" -{{- end}} - -{{/* for pflag.Flag with either short or long options */}} -{{define "simpleFlag" -}} -"{{with .Name}}--{{.}}{{else}}-{{.Shorthand}}{{end}}[{{.Usage}}]{{template "flagAnnotations" .}}" -{{- end}} - {{/* should accept Command (that contains subcommands) as parameter */}} {{define "argumentsC" -}} {{ $cmdPath := genZshFuncName .}} @@ -38,7 +24,7 @@ function {{$cmdPath}} { local -a commands _arguments -C \{{- range extractFlags .}} - {{if simpleFlag .}}{{template "simpleFlag" .}}{{else}}{{template "complexFlag" .}}{{end}} \{{- end}} + {{genFlagEntryForZshArguments .}} \{{- end}} "1: :->cmnds" \ "*::arg:->args" @@ -66,7 +52,7 @@ function {{$cmdPath}} { {{define "arguments" -}} function {{genZshFuncName .}} { {{" _arguments"}}{{range extractFlags .}} \ - {{if simpleFlag .}}{{template "simpleFlag" .}}{{else}}{{template "complexFlag" .}}{{end -}} + {{genFlagEntryForZshArguments . -}} {{end}} } {{end}} @@ -132,3 +118,56 @@ func extractFlags(c *Command) []*pflag.Flag { func simpleFlag(p *pflag.Flag) bool { return p.Name == "" || p.Shorthand == "" } + +// genFlagEntryForZshArguments returns an entry that matches _arguments +// zsh-completion parameters. It's too complicated to generate in a template. +func genFlagEntryForZshArguments(f *pflag.Flag) string { + if f.Name == "" || f.Shorthand == "" { + return genFlagEntryForSingleOptionFlag(f) + } + return genFlagEntryForMultiOptionFlag(f) +} + +func genFlagEntryForSingleOptionFlag(f *pflag.Flag) string { + var option, multiMark, extras string + + if f.Value.Type() == "stringArray" { + multiMark = "*" + } + + option = "--" + f.Name + if option == "--" { + option = "-" + f.Shorthand + } + extras = genZshFlagEntryExtras(f) + + return fmt.Sprintf(`"%s%s[%s]%s"`, multiMark, option, f.Usage, extras) +} + +func genFlagEntryForMultiOptionFlag(f *pflag.Flag) string { + var options, parenMultiMark, curlyMultiMark, extras string + + if f.Value.Type() == "stringArray" { + 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 = genZshFlagEntryExtras(f) + + return fmt.Sprintf(`%s"[%s]%s"`, options, f.Usage, extras) +} + +func genZshFlagEntryExtras(f *pflag.Flag) string { + var extras string + + _, pathSpecified := f.Annotations[BashCompFilenameExt] + if pathSpecified { + extras = ":filename:_files" + } else if !strings.HasPrefix(f.Value.Type(), "bool") { + extras = ":" // allow option variable without assisting + } + + return extras +} diff --git a/zsh_completions_test.go b/zsh_completions_test.go index c8d80c85..048c5e51 100644 --- a/zsh_completions_test.go +++ b/zsh_completions_test.go @@ -48,7 +48,7 @@ func TestGenZshCompletion(t *testing.T) { }, }, { - name: "command with subcommands", + name: "command with subcommands and flags with values", root: func() *Command { r := &Command{ Use: "rootcmd", @@ -75,7 +75,7 @@ func TestGenZshCompletion(t *testing.T) { `_arguments -C \\\n.*"--debug\[description]"`, `function _rootcmd_subcmd1 {`, `function _rootcmd_subcmd1 {`, - `_arguments \\\n.*"\(-o --option\)"{-o,--option}"\[option description]" \\\n`, + `_arguments \\\n.*"\(-o --option\)"{-o,--option}"\[option description]:" \\\n`, }, }, { @@ -88,20 +88,35 @@ func TestGenZshCompletion(t *testing.T) { Run: emptyRun, } r.Flags().StringVarP(&file, "config", "c", file, "config file") - r.MarkFlagFilename("config", "ext") + r.MarkFlagFilename("config") return r }(), expectedExpressions: []string{ `\n +"\(-c --config\)"{-c,--config}"\[config file]:filename:_files"`, }, }, + { + name: "repeated variables both with and without value", + root: func() *Command { + r := genTestCommand("mycmd", true) + _ = r.Flags().StringArrayP("debug", "d", []string{}, "debug usage") + _ = r.Flags().StringArray("option", []string{}, "options") + return r + }(), + expectedExpressions: []string{ + `"\*--option\[options]`, + `"\(\*-d \*--debug\)"{\\\*-d,\\\*--debug}`, + }, + }, } for _, tc := range tcs { t.Run(tc.name, func(t *testing.T) { tc.root.Execute() buf := new(bytes.Buffer) - tc.root.GenZshCompletion(buf) + if err := tc.root.GenZshCompletion(buf); err != nil { + t.Error(err) + } output := buf.Bytes() for _, expr := range tc.expectedExpressions { @@ -173,7 +188,9 @@ func TestGenZshCompletionHidden(t *testing.T) { t.Run(tc.name, func(t *testing.T) { tc.root.Execute() buf := new(bytes.Buffer) - tc.root.GenZshCompletion(buf) + if err := tc.root.GenZshCompletion(buf); err != nil { + t.Error(err) + } output := buf.String() for _, expr := range tc.expectedExpressions { @@ -230,6 +247,7 @@ func constructLargeCommandHeirarchy() *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") @@ -238,6 +256,8 @@ func constructLargeCommandHeirarchy() *Command { } s1 := genTestCommand("sub1", true) s1.Flags().BoolVar(&long, "long", long, "long descriptin") + 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) @@ -249,6 +269,7 @@ func constructLargeCommandHeirarchy() *Command { s1_3 := genTestCommand("sub1sub3", true) s1_3.Flags().IntVar(&in1, "int1", in1, "int1 descriptionn") s1_3.Flags().IntVar(&in2, "int2", in2, "int2 descriptionn") + s1_3.Flags().StringArrayP("option", "O", []string{}, "more options") s2_1 := genTestCommand("sub2sub1", true) s2_2 := genTestCommand("sub2sub2", true) s2_3 := genTestCommand("sub2sub3", true) From e9ee8f044615ebe5f59ec8423e3b80e0e05cd7ab Mon Sep 17 00:00:00 2001 From: Haim Ashkenazi Date: Wed, 28 Feb 2018 21:09:10 +0200 Subject: [PATCH 012/211] zsh-completion: removed the _dep_ files. They were committed by mistake. --- Gopkg.lock | 132 ----------------------------------------------------- Gopkg.toml | 54 ---------------------- 2 files changed, 186 deletions(-) delete mode 100644 Gopkg.lock delete mode 100644 Gopkg.toml diff --git a/Gopkg.lock b/Gopkg.lock deleted file mode 100644 index fde98ef9..00000000 --- a/Gopkg.lock +++ /dev/null @@ -1,132 +0,0 @@ -# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. - - -[[projects]] - name = "github.com/cpuguy83/go-md2man" - packages = ["md2man"] - revision = "20f5889cbdc3c73dbd2862796665e7c465ade7d1" - version = "v1.0.8" - -[[projects]] - name = "github.com/fsnotify/fsnotify" - packages = ["."] - revision = "c2828203cd70a50dcccfb2761f8b1f8ceef9a8e9" - version = "v1.4.7" - -[[projects]] - branch = "master" - name = "github.com/hashicorp/hcl" - packages = [ - ".", - "hcl/ast", - "hcl/parser", - "hcl/printer", - "hcl/scanner", - "hcl/strconv", - "hcl/token", - "json/parser", - "json/scanner", - "json/token" - ] - revision = "23c074d0eceb2b8a5bfdbb271ab780cde70f05a8" - -[[projects]] - name = "github.com/inconshreveable/mousetrap" - packages = ["."] - revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75" - version = "v1.0" - -[[projects]] - name = "github.com/magiconair/properties" - packages = ["."] - revision = "c3beff4c2358b44d0493c7dda585e7db7ff28ae6" - version = "v1.7.6" - -[[projects]] - branch = "master" - name = "github.com/mitchellh/go-homedir" - packages = ["."] - revision = "b8bc1bf767474819792c23f32d8286a45736f1c6" - -[[projects]] - branch = "master" - name = "github.com/mitchellh/mapstructure" - packages = ["."] - revision = "a4e142e9c047c904fa2f1e144d9a84e6133024bc" - -[[projects]] - name = "github.com/pelletier/go-toml" - packages = ["."] - revision = "acdc4509485b587f5e675510c4f2c63e90ff68a8" - version = "v1.1.0" - -[[projects]] - name = "github.com/russross/blackfriday" - packages = ["."] - revision = "4048872b16cc0fc2c5fd9eacf0ed2c2fedaa0c8c" - version = "v1.5" - -[[projects]] - name = "github.com/spf13/afero" - packages = [ - ".", - "mem" - ] - revision = "bb8f1927f2a9d3ab41c9340aa034f6b803f4359c" - version = "v1.0.2" - -[[projects]] - name = "github.com/spf13/cast" - packages = ["."] - revision = "8965335b8c7107321228e3e3702cab9832751bac" - version = "v1.2.0" - -[[projects]] - branch = "master" - name = "github.com/spf13/jwalterweatherman" - packages = ["."] - revision = "7c0cea34c8ece3fbeb2b27ab9b59511d360fb394" - -[[projects]] - branch = "master" - name = "github.com/spf13/pflag" - packages = ["."] - revision = "6a877ebacf28c5fc79846f4fcd380a5d9872b997" - -[[projects]] - branch = "master" - name = "github.com/spf13/viper" - packages = ["."] - revision = "aafc9e6bc7b7bb53ddaa75a5ef49a17d6e654be5" - -[[projects]] - branch = "master" - name = "golang.org/x/sys" - packages = ["unix"] - revision = "37707fdb30a5b38865cfb95e5aab41707daec7fd" - -[[projects]] - branch = "master" - name = "golang.org/x/text" - packages = [ - "internal/gen", - "internal/triegen", - "internal/ucd", - "transform", - "unicode/cldr", - "unicode/norm" - ] - revision = "4e4a3210bb54bb31f6ab2cdca2edcc0b50c420c1" - -[[projects]] - branch = "v2" - name = "gopkg.in/yaml.v2" - packages = ["."] - revision = "d670f9405373e636a5a2765eea47fac0c9bc91a4" - -[solve-meta] - analyzer-name = "dep" - analyzer-version = 1 - inputs-digest = "d7aecc8ee6a8b343ebf8c2539348464f2566a18dfc511cd374b2cd76bebb1c6d" - solver-name = "gps-cdcl" - solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml deleted file mode 100644 index 3bf16541..00000000 --- a/Gopkg.toml +++ /dev/null @@ -1,54 +0,0 @@ -# Gopkg.toml example -# -# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md -# for detailed Gopkg.toml documentation. -# -# required = ["github.com/user/thing/cmd/thing"] -# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] -# -# [[constraint]] -# name = "github.com/user/project" -# version = "1.0.0" -# -# [[constraint]] -# name = "github.com/user/project2" -# branch = "dev" -# source = "github.com/myfork/project2" -# -# [[override]] -# name = "github.com/x/y" -# version = "2.4.0" -# -# [prune] -# non-go = false -# go-tests = true -# unused-packages = true - - -[[constraint]] - name = "github.com/cpuguy83/go-md2man" - version = "1.0.8" - -[[constraint]] - name = "github.com/inconshreveable/mousetrap" - version = "1.0.0" - -[[constraint]] - branch = "master" - name = "github.com/mitchellh/go-homedir" - -[[constraint]] - name = "github.com/spf13/pflag" - branch = "master" - -[[constraint]] - name = "github.com/spf13/viper" - branch = "master" - -[[constraint]] - branch = "v2" - name = "gopkg.in/yaml.v2" - -[prune] - go-tests = true - unused-packages = true From df12a0a24975e4c9f278e62927ca08d000b9be71 Mon Sep 17 00:00:00 2001 From: Haim Ashkenazi Date: Wed, 28 Feb 2018 21:52:35 +0200 Subject: [PATCH 013/211] zsh-completion: two fixes for identifying flag usage: Fixed after input from @eparis: - Decide on option parameter by checking NoOptDefVal - Slices also could be specified multiple times. --- zsh_completions.go | 11 ++++++++--- zsh_completions_test.go | 19 ++++++++++++++++++- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/zsh_completions.go b/zsh_completions.go index 1d73e272..1c1c8885 100644 --- a/zsh_completions.go +++ b/zsh_completions.go @@ -131,7 +131,7 @@ func genFlagEntryForZshArguments(f *pflag.Flag) string { func genFlagEntryForSingleOptionFlag(f *pflag.Flag) string { var option, multiMark, extras string - if f.Value.Type() == "stringArray" { + if flagCouldBeSpecifiedMoreThenOnce(f) { multiMark = "*" } @@ -147,7 +147,7 @@ func genFlagEntryForSingleOptionFlag(f *pflag.Flag) string { func genFlagEntryForMultiOptionFlag(f *pflag.Flag) string { var options, parenMultiMark, curlyMultiMark, extras string - if f.Value.Type() == "stringArray" { + if flagCouldBeSpecifiedMoreThenOnce(f) { parenMultiMark = "*" curlyMultiMark = "\\*" } @@ -165,9 +165,14 @@ func genZshFlagEntryExtras(f *pflag.Flag) string { _, pathSpecified := f.Annotations[BashCompFilenameExt] if pathSpecified { extras = ":filename:_files" - } else if !strings.HasPrefix(f.Value.Type(), "bool") { + } else if f.NoOptDefVal == "" { extras = ":" // allow option variable without assisting } return extras } + +func flagCouldBeSpecifiedMoreThenOnce(f *pflag.Flag) bool { + return strings.Contains(f.Value.Type(), "Slice") || + strings.Contains(f.Value.Type(), "Array") +} diff --git a/zsh_completions_test.go b/zsh_completions_test.go index 048c5e51..ba2a3bfb 100644 --- a/zsh_completions_test.go +++ b/zsh_completions_test.go @@ -15,6 +15,7 @@ func TestGenZshCompletion(t *testing.T) { name string root *Command expectedExpressions []string + skip string }{ { name: "simple command", @@ -99,7 +100,7 @@ func TestGenZshCompletion(t *testing.T) { name: "repeated variables both with and without value", root: func() *Command { r := genTestCommand("mycmd", true) - _ = r.Flags().StringArrayP("debug", "d", []string{}, "debug usage") + _ = r.Flags().BoolSliceP("debug", "d", []bool{}, "debug usage") _ = r.Flags().StringArray("option", []string{}, "options") return r }(), @@ -108,6 +109,18 @@ func TestGenZshCompletion(t *testing.T) { `"\(\*-d \*--debug\)"{\\\*-d,\\\*--debug}`, }, }, + { + name: "boolSlice should not accept arguments", + root: func() *Command { + r := genTestCommand("mycmd", true) + r.Flags().BoolSlice("verbose", []bool{}, "verbosity level") + return r + }(), + expectedExpressions: []string{ + `"\*--verbose\[verbosity level]"`, + }, + skip: "BoolSlice behaves strangely both with NoOptDefVal and type (identifies as bool)", + }, } for _, tc := range tcs { @@ -119,6 +132,10 @@ func TestGenZshCompletion(t *testing.T) { } output := buf.Bytes() + if tc.skip != "" { + t.Skip("Skipping:", tc.skip) + } + for _, expr := range tc.expectedExpressions { rgx, err := regexp.Compile(expr) if err != nil { From 461a39d5b9353091df9cd9852334113d2d9f22b9 Mon Sep 17 00:00:00 2001 From: Haim Ashkenazi Date: Thu, 1 Mar 2018 08:02:33 +0200 Subject: [PATCH 014/211] zsh-completion: removed forgotten function. --- zsh_completions.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/zsh_completions.go b/zsh_completions.go index 1c1c8885..41703468 100644 --- a/zsh_completions.go +++ b/zsh_completions.go @@ -115,10 +115,6 @@ func extractFlags(c *Command) []*pflag.Flag { return flags } -func simpleFlag(p *pflag.Flag) bool { - return p.Name == "" || p.Shorthand == "" -} - // genFlagEntryForZshArguments returns an entry that matches _arguments // zsh-completion parameters. It's too complicated to generate in a template. func genFlagEntryForZshArguments(f *pflag.Flag) string { From dd577bdf3103152e76d48bdb168460f239bdfa29 Mon Sep 17 00:00:00 2001 From: Haim Ashkenazi Date: Thu, 1 Mar 2018 08:50:20 +0200 Subject: [PATCH 015/211] zsh-completion: added zsh-completion documentation. --- README.md | 6 ++++++ zsh_completion.md | 26 ++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 zsh_completion.md diff --git a/README.md b/README.md index ff16e3f6..8a9ace4c 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,7 @@ etc. * [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) - [Contributing](#contributing) - [License](#license) @@ -719,6 +720,11 @@ Cobra can generate documentation based on subcommands, flags, etc. in the follow 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). + # Contributing 1. Fork it diff --git a/zsh_completion.md b/zsh_completion.md new file mode 100644 index 00000000..bfedcf19 --- /dev/null +++ b/zsh_completion.md @@ -0,0 +1,26 @@ +## 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 +`_`. + +### What's Supported + +* 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. However, it will ignore specific extensions requested by + this command (see about what's not supported yet below). + * 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 `*Arrary` and `*Slice` types supports multiple + specifications (with or without argument depending on the specific type). + +### What's not yet Supported + +* Positional argument completion are not supported yet. +* Filename completion ignores extension specification. +* 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 :) From bda855a1a0bf7a7c2d9402a6250d53615dacd294 Mon Sep 17 00:00:00 2001 From: Haim Ashkenazi Date: Thu, 1 Mar 2018 08:53:25 +0200 Subject: [PATCH 016/211] zsh-completions: fixed zsh completion markdown file name. --- zsh_completion.md => zsh_completions.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename zsh_completion.md => zsh_completions.md (100%) diff --git a/zsh_completion.md b/zsh_completions.md similarity index 100% rename from zsh_completion.md rename to zsh_completions.md From 50f385938e098a69ec93b3490ff68442ec3d6524 Mon Sep 17 00:00:00 2001 From: Haim Ashkenazi Date: Fri, 2 Mar 2018 08:42:52 +0200 Subject: [PATCH 017/211] zsh-completion: added support for filename globbing. --- zsh_completions.go | 11 +++++++---- zsh_completions.md | 4 +--- zsh_completions_test.go | 23 +++++++++++++---------- 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/zsh_completions.go b/zsh_completions.go index 41703468..2e0d3e38 100644 --- a/zsh_completions.go +++ b/zsh_completions.go @@ -137,7 +137,7 @@ func genFlagEntryForSingleOptionFlag(f *pflag.Flag) string { } extras = genZshFlagEntryExtras(f) - return fmt.Sprintf(`"%s%s[%s]%s"`, multiMark, option, f.Usage, extras) + return fmt.Sprintf(`'%s%s[%s]%s'`, multiMark, option, f.Usage, extras) } func genFlagEntryForMultiOptionFlag(f *pflag.Flag) string { @@ -148,19 +148,22 @@ func genFlagEntryForMultiOptionFlag(f *pflag.Flag) string { curlyMultiMark = "\\*" } - options = fmt.Sprintf(`"(%s-%s %s--%s)"{%s-%s,%s--%s}`, + options = fmt.Sprintf(`'(%s-%s %s--%s)'{%s-%s,%s--%s}`, parenMultiMark, f.Shorthand, parenMultiMark, f.Name, curlyMultiMark, f.Shorthand, curlyMultiMark, f.Name) extras = genZshFlagEntryExtras(f) - return fmt.Sprintf(`%s"[%s]%s"`, options, f.Usage, extras) + return fmt.Sprintf(`%s'[%s]%s'`, options, f.Usage, extras) } func genZshFlagEntryExtras(f *pflag.Flag) string { var extras string - _, pathSpecified := f.Annotations[BashCompFilenameExt] + globs, pathSpecified := f.Annotations[BashCompFilenameExt] if pathSpecified { extras = ":filename:_files" + for _, g := range globs { + extras = extras + fmt.Sprintf(` -g "%s"`, g) + } } else if f.NoOptDefVal == "" { extras = ":" // allow option variable without assisting } diff --git a/zsh_completions.md b/zsh_completions.md index bfedcf19..c218179a 100644 --- a/zsh_completions.md +++ b/zsh_completions.md @@ -9,8 +9,7 @@ The generated completion script should be put somewhere in your `$fpath` named * 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. However, it will ignore specific extensions requested by - this command (see about what's not supported yet below). + 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 `*Arrary` and `*Slice` types supports multiple @@ -19,7 +18,6 @@ The generated completion script should be put somewhere in your `$fpath` named ### What's not yet Supported * Positional argument completion are not supported yet. -* Filename completion ignores extension specification. * 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). diff --git a/zsh_completions_test.go b/zsh_completions_test.go index ba2a3bfb..c5199ed3 100644 --- a/zsh_completions_test.go +++ b/zsh_completions_test.go @@ -29,7 +29,7 @@ func TestGenZshCompletion(t *testing.T) { return r }(), expectedExpressions: []string{ - `(?s)function _mycommand {\s+_arguments \\\s+"--debug\[description\]".*--help.*}`, + `(?s)function _mycommand {\s+_arguments \\\s+'--debug\[description\]'.*--help.*}`, "#compdef _mycommand mycommand", }, }, @@ -45,7 +45,7 @@ func TestGenZshCompletion(t *testing.T) { return r }(), expectedExpressions: []string{ - `"\(-d --debug\)"{-d,--debug}"\[debug description\]"`, + `'\(-d --debug\)'{-d,--debug}'\[debug description\]'`, }, }, { @@ -72,15 +72,15 @@ func TestGenZshCompletion(t *testing.T) { }(), expectedExpressions: []string{ `commands=\(\n\s+"help:.*\n\s+"subcmd1:.*\n\s+"subcmd2:.*\n\s+\)`, - `_arguments \\\n.*"--debug\[description]"`, - `_arguments -C \\\n.*"--debug\[description]"`, + `_arguments \\\n.*'--debug\[description]'`, + `_arguments -C \\\n.*'--debug\[description]'`, `function _rootcmd_subcmd1 {`, `function _rootcmd_subcmd1 {`, - `_arguments \\\n.*"\(-o --option\)"{-o,--option}"\[option description]:" \\\n`, + `_arguments \\\n.*'\(-o --option\)'{-o,--option}'\[option description]:' \\\n`, }, }, { - name: "filename completion", + name: "filename completion with and without globs", root: func() *Command { var file string r := &Command{ @@ -90,10 +90,13 @@ func TestGenZshCompletion(t *testing.T) { } 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"`, + `\n +'\(-c --config\)'{-c,--config}'\[config file]:filename:_files'`, + `:_files -g "\*.log" -g "\*.txt"`, }, }, { @@ -105,8 +108,8 @@ func TestGenZshCompletion(t *testing.T) { return r }(), expectedExpressions: []string{ - `"\*--option\[options]`, - `"\(\*-d \*--debug\)"{\\\*-d,\\\*--debug}`, + `'\*--option\[options]`, + `'\(\*-d \*--debug\)'{\\\*-d,\\\*--debug}`, }, }, { @@ -117,7 +120,7 @@ func TestGenZshCompletion(t *testing.T) { return r }(), expectedExpressions: []string{ - `"\*--verbose\[verbosity level]"`, + `'\*--verbose\[verbosity level]'`, }, skip: "BoolSlice behaves strangely both with NoOptDefVal and type (identifies as bool)", }, From 0d9a33d2da4a2006f3d2293b4a2f0b436228ee54 Mon Sep 17 00:00:00 2001 From: Haim Ashkenazi Date: Sat, 3 Mar 2018 14:50:58 +0200 Subject: [PATCH 018/211] zsh-completion: remove temporary file Yet another temporary file that found itself in the repo :( --- zsh_template.tmpl | 56 ----------------------------------------------- 1 file changed, 56 deletions(-) delete mode 100644 zsh_template.tmpl diff --git a/zsh_template.tmpl b/zsh_template.tmpl deleted file mode 100644 index 43846254..00000000 --- a/zsh_template.tmpl +++ /dev/null @@ -1,56 +0,0 @@ -{{define "complexFlag"}}{{ /* for pflag.Flag with short and long options */ -}} -"(-{{.Shorthand}} --{{.Name}})"\{-{{.Shorthand}}, --{{.Name}}\}[{{.Usage}}] -{{- end}} - -{{define "simpleFlag"}}{{ /* for pflag.Flag with either short or long options */ -}} -"{{with .Name}}-{{.}}{{else}}--{{.Shorthand}}{{end}}[{{.Usage}}]" -{{- end}} - -{{define "argumentsC"}} -{{- /* should accept Command (that contains subcommands) as parameter */ -}} -function {{constructPath .}} { - local line - - _arguments -C \ -{{range extractFlags . -}} - {{if simpleFlag .}}{{template "simpleFlag" .}}{{else}}{{template "complexFlag" .}}{{end}} \ -{{end -}} - "1: :({{subCmdList .}})" \ - "*:arg:->args" - - case $line[1] in -{{range .Commands -}} - {{.Use}}) - {{constructPath .}} - ;; -{{end -}} - esac -} -{{range .Commands -}} - -{{template "selectCmdTemplate" .}} -{{end -}} -{{end}} - -{{define "arguments"}} -function {{constructPath .}} { -{{- /* should accept Command without subcommands as parameter */ -}} -{{with extractFlags . -}} - _arguments \ -{{range .}} - {{if simpleFlag .}}{{template "simpleFlag" .}}{{else}}{{template "complexFlag" .}}{{end}} \ -{{end -}} -{{ /* leave this line empty because of the last backslash */ }} -{{end -}} -} -{{end}} - -{{define "selectCmdTemplate" -}} -{{with .Commands}}{{template "argumentsC" .}}{{else}}{{template "arguments" .}}{{end}} -{{end -}} - -{{define "Main"}} -#compdef _{{.Use}} {{.Use}} - -{{template "selectCmdTemplate" .}} -{{end}} From 91e80cc4a4b48b031b65a5cd4024ed5b113c131f Mon Sep 17 00:00:00 2001 From: Haim Ashkenazi Date: Sat, 3 Mar 2018 20:34:00 +0200 Subject: [PATCH 019/211] zsh-completion: remove bad test I thought there was a bug in the boolSlice definition but it seems It was my mistake in identifying what's going on. Also removed the provisioning to skip tests (doesn't seem to be needed anymore). --- zsh_completions_test.go | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/zsh_completions_test.go b/zsh_completions_test.go index c5199ed3..c4e1a95f 100644 --- a/zsh_completions_test.go +++ b/zsh_completions_test.go @@ -15,7 +15,6 @@ func TestGenZshCompletion(t *testing.T) { name string root *Command expectedExpressions []string - skip string }{ { name: "simple command", @@ -112,18 +111,6 @@ func TestGenZshCompletion(t *testing.T) { `'\(\*-d \*--debug\)'{\\\*-d,\\\*--debug}`, }, }, - { - name: "boolSlice should not accept arguments", - root: func() *Command { - r := genTestCommand("mycmd", true) - r.Flags().BoolSlice("verbose", []bool{}, "verbosity level") - return r - }(), - expectedExpressions: []string{ - `'\*--verbose\[verbosity level]'`, - }, - skip: "BoolSlice behaves strangely both with NoOptDefVal and type (identifies as bool)", - }, } for _, tc := range tcs { @@ -135,10 +122,6 @@ func TestGenZshCompletion(t *testing.T) { } output := buf.Bytes() - if tc.skip != "" { - t.Skip("Skipping:", tc.skip) - } - for _, expr := range tc.expectedExpressions { rgx, err := regexp.Compile(expr) if err != nil { From 7ce08e227e8c9db9b243dfef1e792af31d19cdd0 Mon Sep 17 00:00:00 2001 From: Haim Ashkenazi Date: Sun, 4 Mar 2018 23:56:31 +0200 Subject: [PATCH 020/211] zsh-completion: completion should always parse the root command! It was running on the command it was invoked from which caused some additional helpers (--help, --version) not to be generated. --- zsh_completions.go | 6 ++++-- zsh_completions_test.go | 17 +++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/zsh_completions.go b/zsh_completions.go index 2e0d3e38..4705a905 100644 --- a/zsh_completions.go +++ b/zsh_completions.go @@ -84,13 +84,15 @@ func (c *Command) GenZshCompletionFile(filename string) error { return c.GenZshCompletion(outFile) } -// GenZshCompletion generates a zsh completion file and writes to the passed writer. +// 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(funcMap).Parse(zshCompletionText) if err != nil { return fmt.Errorf("error creating zsh completion template: %v", err) } - return tmpl.Execute(w, c) + return tmpl.Execute(w, c.Root()) } func generateZshCompletionFuncName(c *Command) string { diff --git a/zsh_completions_test.go b/zsh_completions_test.go index c4e1a95f..66b6e692 100644 --- a/zsh_completions_test.go +++ b/zsh_completions_test.go @@ -111,6 +111,23 @@ func TestGenZshCompletion(t *testing.T) { `'\(\*-d \*--debug\)'{\\\*-d,\\\*--debug}`, }, }, + { + name: "command should run on the root command so --version and --help will be generated", + 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", + }, + }, } for _, tc := range tcs { From 7b62c7df786ae4a385717290f9f5b8f2c4edc6b9 Mon Sep 17 00:00:00 2001 From: Haim Ashkenazi Date: Mon, 5 Mar 2018 00:46:00 +0200 Subject: [PATCH 021/211] zsh-completion: --version and --help still doesn't work correctly When invoking from subcommand. Modified the test to prove. --- zsh_completions_test.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/zsh_completions_test.go b/zsh_completions_test.go index 66b6e692..b1321c9b 100644 --- a/zsh_completions_test.go +++ b/zsh_completions_test.go @@ -15,6 +15,8 @@ func TestGenZshCompletion(t *testing.T) { name string root *Command expectedExpressions []string + invocationArgs []string + skip string }{ { name: "simple command", @@ -112,7 +114,7 @@ func TestGenZshCompletion(t *testing.T) { }, }, { - name: "command should run on the root command so --version and --help will be generated", + name: "generated flags --help and --version should be created even when not executing root cmd", root: func() *Command { r := &Command{ Use: "mycmd", @@ -127,11 +129,19 @@ func TestGenZshCompletion(t *testing.T) { "--version", "--help", }, + invocationArgs: []string{ + "sub1", + }, + skip: "--version and --help are currently not generated when not running on root command", }, } 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 { From 66a98807d4036a0b21797ad8434b97a9ce0ae49d Mon Sep 17 00:00:00 2001 From: Haim Ashkenazi Date: Mon, 5 Mar 2018 01:09:55 +0200 Subject: [PATCH 022/211] zsh-completion: test to verify that we're always running on root cmd. --- zsh_completions_test.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/zsh_completions_test.go b/zsh_completions_test.go index b1321c9b..22a66d6d 100644 --- a/zsh_completions_test.go +++ b/zsh_completions_test.go @@ -134,6 +134,18 @@ func TestGenZshCompletion(t *testing.T) { }, skip: "--version and --help are currently not generated when not running on root command", }, + { + name: "zsh generation should run on root commannd", + root: func() *Command { + r := genTestCommand("root", false) + s := genTestCommand("sub1", true) + r.AddCommand(s) + return s + }(), + expectedExpressions: []string{ + "function _root {", + }, + }, } for _, tc := range tcs { From 8822449c0fb9575f1d2453e80cd3e55ab8fc2844 Mon Sep 17 00:00:00 2001 From: Haim Ashkenazi Date: Sat, 17 Mar 2018 20:55:27 +0200 Subject: [PATCH 023/211] zsh-completion: added escapinng of single quotes in flag description. --- zsh_completions.go | 8 ++++++-- zsh_completions_test.go | 11 +++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/zsh_completions.go b/zsh_completions.go index 4705a905..59f55312 100644 --- a/zsh_completions.go +++ b/zsh_completions.go @@ -139,7 +139,7 @@ func genFlagEntryForSingleOptionFlag(f *pflag.Flag) string { } extras = genZshFlagEntryExtras(f) - return fmt.Sprintf(`'%s%s[%s]%s'`, multiMark, option, f.Usage, extras) + return fmt.Sprintf(`'%s%s[%s]%s'`, multiMark, option, quoteDescription(f.Usage), extras) } func genFlagEntryForMultiOptionFlag(f *pflag.Flag) string { @@ -154,7 +154,7 @@ func genFlagEntryForMultiOptionFlag(f *pflag.Flag) string { parenMultiMark, f.Shorthand, parenMultiMark, f.Name, curlyMultiMark, f.Shorthand, curlyMultiMark, f.Name) extras = genZshFlagEntryExtras(f) - return fmt.Sprintf(`%s'[%s]%s'`, options, f.Usage, extras) + return fmt.Sprintf(`%s'[%s]%s'`, options, quoteDescription(f.Usage), extras) } func genZshFlagEntryExtras(f *pflag.Flag) string { @@ -177,3 +177,7 @@ func flagCouldBeSpecifiedMoreThenOnce(f *pflag.Flag) bool { return strings.Contains(f.Value.Type(), "Slice") || strings.Contains(f.Value.Type(), "Array") } + +func quoteDescription(s string) string { + return strings.Replace(s, "'", `'\''`, -1) +} diff --git a/zsh_completions_test.go b/zsh_completions_test.go index 22a66d6d..6788797a 100644 --- a/zsh_completions_test.go +++ b/zsh_completions_test.go @@ -146,6 +146,17 @@ func TestGenZshCompletion(t *testing.T) { "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]`, + }, + }, } for _, tc := range tcs { From d262154093af3bc0c8d63a8dbadaa13e7807360a Mon Sep 17 00:00:00 2001 From: Haim Ashkenazi Date: Thu, 22 Mar 2018 19:58:25 +0200 Subject: [PATCH 024/211] zsh-completion: tidy up function and variable names There are many files in the package, renamed all zsh-completion related names to convey that. --- zsh_completions.go | 46 ++++++++++++++++++++--------------------- zsh_completions_test.go | 4 ++-- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/zsh_completions.go b/zsh_completions.go index 59f55312..490eb021 100644 --- a/zsh_completions.go +++ b/zsh_completions.go @@ -11,10 +11,10 @@ import ( ) var ( - funcMap = template.FuncMap{ - "genZshFuncName": generateZshCompletionFuncName, - "extractFlags": extractFlags, - "genFlagEntryForZshArguments": genFlagEntryForZshArguments, + zshCompFuncMap = template.FuncMap{ + "genZshFuncName": zshCompGenFuncName, + "extractFlags": zshCompExtractFlag, + "genFlagEntryForZshArguments": zshCompGenFlagEntryForArguments, } zshCompletionText = ` {{/* should accept Command (that contains subcommands) as parameter */}} @@ -88,21 +88,21 @@ func (c *Command) GenZshCompletionFile(filename string) error { // 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(funcMap).Parse(zshCompletionText) + 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 generateZshCompletionFuncName(c *Command) string { +func zshCompGenFuncName(c *Command) string { if c.HasParent() { - return generateZshCompletionFuncName(c.Parent()) + "_" + c.Name() + return zshCompGenFuncName(c.Parent()) + "_" + c.Name() } return "_" + c.Name() } -func extractFlags(c *Command) []*pflag.Flag { +func zshCompExtractFlag(c *Command) []*pflag.Flag { var flags []*pflag.Flag c.LocalFlags().VisitAll(func(f *pflag.Flag) { if !f.Hidden { @@ -117,19 +117,19 @@ func extractFlags(c *Command) []*pflag.Flag { return flags } -// genFlagEntryForZshArguments returns an entry that matches _arguments +// zshCompGenFlagEntryForArguments returns an entry that matches _arguments // zsh-completion parameters. It's too complicated to generate in a template. -func genFlagEntryForZshArguments(f *pflag.Flag) string { +func zshCompGenFlagEntryForArguments(f *pflag.Flag) string { if f.Name == "" || f.Shorthand == "" { - return genFlagEntryForSingleOptionFlag(f) + return zshCompGenFlagEntryForSingleOptionFlag(f) } - return genFlagEntryForMultiOptionFlag(f) + return zshCompGenFlagEntryForMultiOptionFlag(f) } -func genFlagEntryForSingleOptionFlag(f *pflag.Flag) string { +func zshCompGenFlagEntryForSingleOptionFlag(f *pflag.Flag) string { var option, multiMark, extras string - if flagCouldBeSpecifiedMoreThenOnce(f) { + if zshCompFlagCouldBeSpecifiedMoreThenOnce(f) { multiMark = "*" } @@ -137,27 +137,27 @@ func genFlagEntryForSingleOptionFlag(f *pflag.Flag) string { if option == "--" { option = "-" + f.Shorthand } - extras = genZshFlagEntryExtras(f) + extras = zshCompGenFlagEntryExtras(f) - return fmt.Sprintf(`'%s%s[%s]%s'`, multiMark, option, quoteDescription(f.Usage), extras) + return fmt.Sprintf(`'%s%s[%s]%s'`, multiMark, option, zshCompQuoteFlagDescription(f.Usage), extras) } -func genFlagEntryForMultiOptionFlag(f *pflag.Flag) string { +func zshCompGenFlagEntryForMultiOptionFlag(f *pflag.Flag) string { var options, parenMultiMark, curlyMultiMark, extras string - if flagCouldBeSpecifiedMoreThenOnce(f) { + 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 = genZshFlagEntryExtras(f) + extras = zshCompGenFlagEntryExtras(f) - return fmt.Sprintf(`%s'[%s]%s'`, options, quoteDescription(f.Usage), extras) + return fmt.Sprintf(`%s'[%s]%s'`, options, zshCompQuoteFlagDescription(f.Usage), extras) } -func genZshFlagEntryExtras(f *pflag.Flag) string { +func zshCompGenFlagEntryExtras(f *pflag.Flag) string { var extras string globs, pathSpecified := f.Annotations[BashCompFilenameExt] @@ -173,11 +173,11 @@ func genZshFlagEntryExtras(f *pflag.Flag) string { return extras } -func flagCouldBeSpecifiedMoreThenOnce(f *pflag.Flag) bool { +func zshCompFlagCouldBeSpecifiedMoreThenOnce(f *pflag.Flag) bool { return strings.Contains(f.Value.Type(), "Slice") || strings.Contains(f.Value.Type(), "Array") } -func quoteDescription(s string) string { +func zshCompQuoteFlagDescription(s string) string { return strings.Replace(s, "'", `'\''`, -1) } diff --git a/zsh_completions_test.go b/zsh_completions_test.go index 6788797a..4ef2e2f4 100644 --- a/zsh_completions_test.go +++ b/zsh_completions_test.go @@ -285,8 +285,8 @@ func TestExtractFlags(t *testing.T) { d.Flags().BoolVar(&cmdd, "cmd-d", cmdd, "Command D") c.AddCommand(d) - resC := extractFlags(c) - resD := extractFlags(d) + resC := zshCompExtractFlag(c) + resD := zshCompExtractFlag(d) if len(resC) != 2 { t.Errorf("expected Command C to return 2 flags, got %d", len(resC)) From edbb6712e2cba085bb278feeb1b88ae88079ecdf Mon Sep 17 00:00:00 2001 From: Haim Ashkenazi Date: Fri, 23 Mar 2018 13:09:56 +0300 Subject: [PATCH 025/211] zsh-completions: implemented argument completion. --- zsh_completions.go | 149 +++++++++++++++++++++++++++++++++++++++- zsh_completions.md | 17 ++++- zsh_completions_test.go | 128 +++++++++++++++++++++++++++++++--- 3 files changed, 283 insertions(+), 11 deletions(-) diff --git a/zsh_completions.go b/zsh_completions.go index 490eb021..68bb5c6e 100644 --- a/zsh_completions.go +++ b/zsh_completions.go @@ -1,20 +1,29 @@ package cobra import ( + "encoding/json" "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" +) + var ( zshCompFuncMap = template.FuncMap{ "genZshFuncName": zshCompGenFuncName, "extractFlags": zshCompExtractFlag, "genFlagEntryForZshArguments": zshCompGenFlagEntryForArguments, + "extractArgsCompletions": zshCompExtractArgumentCompletionHintsForRendering, } zshCompletionText = ` {{/* should accept Command (that contains subcommands) as parameter */}} @@ -53,7 +62,8 @@ function {{$cmdPath}} { function {{genZshFuncName .}} { {{" _arguments"}}{{range extractFlags .}} \ {{genFlagEntryForZshArguments . -}} -{{end}} +{{end}}{{range extractArgsCompletions .}} \ + {{.}}{{end}} } {{end}} @@ -73,6 +83,19 @@ function {{genZshFuncName .}} { ` ) +// 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. func (c *Command) GenZshCompletionFile(filename string) error { outFile, err := os.Create(filename) @@ -95,6 +118,130 @@ func (c *Command) GenZshCompletion(w io.Writer) error { return tmpl.Execute(w, c.Root()) } +// 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) + } + 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) +} + +// 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) +} + +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() diff --git a/zsh_completions.md b/zsh_completions.md index c218179a..95242d34 100644 --- a/zsh_completions.md +++ b/zsh_completions.md @@ -14,10 +14,25 @@ The generated completion script should be put somewhere in your `$fpath` named flag value - if it's empty then completion will expect an argument. * Flags of one of the various `*Arrary` 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. ### What's not yet Supported -* Positional argument completion are not supported yet. * 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). diff --git a/zsh_completions_test.go b/zsh_completions_test.go index 4ef2e2f4..976cbfc2 100644 --- a/zsh_completions_test.go +++ b/zsh_completions_test.go @@ -58,7 +58,7 @@ func TestGenZshCompletion(t *testing.T) { } d := &Command{ Use: "subcmd1", - Short: "Subcmd1 short descrition", + Short: "Subcmd1 short description", Run: emptyRun, } e := &Command{ @@ -135,7 +135,7 @@ func TestGenZshCompletion(t *testing.T) { skip: "--version and --help are currently not generated when not running on root command", }, { - name: "zsh generation should run on root commannd", + name: "zsh generation should run on root command", root: func() *Command { r := genTestCommand("root", false) s := genTestCommand("sub1", true) @@ -157,6 +157,63 @@ func TestGenZshCompletion(t *testing.T) { `--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"\)' \\`, + }, + }, } for _, tc := range tcs { @@ -178,7 +235,7 @@ func TestGenZshCompletion(t *testing.T) { t.Errorf("error compiling expression (%s): %v", expr, err) } if !rgx.Match(output) { - t.Errorf("expeced completion (%s) to match '%s'", buf.String(), expr) + t.Errorf("expected completion (%s) to match '%s'", buf.String(), expr) } } }) @@ -192,7 +249,7 @@ func TestGenZshCompletionHidden(t *testing.T) { expectedExpressions []string }{ { - name: "hidden commmands", + name: "hidden commands", root: func() *Command { r := &Command{ Use: "main", @@ -255,8 +312,61 @@ func TestGenZshCompletionHidden(t *testing.T) { } } +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 := constructLargeCommandHeirarchy() + root := constructLargeCommandHierarchy() // if err := root.GenZshCompletionFile("_mycmd"); err != nil { // b.Error(err) // } @@ -296,7 +406,7 @@ func TestExtractFlags(t *testing.T) { } } -func constructLargeCommandHeirarchy() *Command { +func constructLargeCommandHierarchy() *Command { var config, st1, st2 string var long, debug bool var in1, in2 int @@ -308,7 +418,7 @@ func constructLargeCommandHeirarchy() *Command { panic(err) } s1 := genTestCommand("sub1", true) - s1.Flags().BoolVar(&long, "long", long, "long descriptin") + 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) @@ -320,8 +430,8 @@ func constructLargeCommandHeirarchy() *Command { 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 descriptionn") - s1_3.Flags().IntVar(&in2, "int2", in2, "int2 descriptionn") + 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) From 601d83077b12a12f839d20881d9e5d3075aaad1b Mon Sep 17 00:00:00 2001 From: Haim Ashkenazi Date: Fri, 23 Mar 2018 13:57:53 +0300 Subject: [PATCH 026/211] typo in zsh-completions.md --- zsh_completions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zsh_completions.md b/zsh_completions.md index 95242d34..df9c2eac 100644 --- a/zsh_completions.md +++ b/zsh_completions.md @@ -12,7 +12,7 @@ The generated completion script should be put somewhere in your `$fpath` named 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 `*Arrary` and `*Slice` types supports multiple + * 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 From e2c45ac9eb0cdb823b89c1e868d698dc74822797 Mon Sep 17 00:00:00 2001 From: Haim Ashkenazi Date: Sun, 3 Jun 2018 22:08:30 +0300 Subject: [PATCH 027/211] Started working on Unified API for the various shell completions: - Moved some general function to a more generic shell_completions file. - Added functions to mark flag as directory completion. - Started making the global functions docs more generic (not bash specific) and added compatibility matrix. --- bash_completions.go | 48 ----------------------- shell_completions.go | 85 +++++++++++++++++++++++++++++++++++++++++ zsh_completions.go | 22 +++++++---- zsh_completions_test.go | 15 ++++++++ 4 files changed, 114 insertions(+), 56 deletions(-) create mode 100644 shell_completions.go diff --git a/bash_completions.go b/bash_completions.go index c3c1e501..57bb8e1b 100644 --- a/bash_completions.go +++ b/bash_completions.go @@ -545,51 +545,3 @@ func (c *Command) GenBashCompletionFile(filename string) error { return c.GenBashCompletion(outFile) } - -// MarkFlagRequired adds the BashCompOneRequiredFlag annotation to the named flag if it exists, -// 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, -// 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, -// 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. -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. -func (c *Command) MarkFlagCustom(name string, f string) error { - return MarkFlagCustom(c.Flags(), name, f) -} - -// MarkPersistentFlagFilename adds the BashCompFilenameExt annotation to the named persistent flag, if it exists. -// Generated bash autocompletion will select filenames for the flag, limiting to named extensions if provided. -func (c *Command) MarkPersistentFlagFilename(name string, extensions ...string) error { - return MarkFlagFilename(c.PersistentFlags(), name, extensions...) -} - -// MarkFlagFilename adds the BashCompFilenameExt annotation to the named flag in the flag set, if it exists. -// Generated bash autocompletion will select filenames for the flag, limiting to named extensions if provided. -func MarkFlagFilename(flags *pflag.FlagSet, name string, extensions ...string) error { - return flags.SetAnnotation(name, BashCompFilenameExt, extensions) -} - -// MarkFlagCustom adds the BashCompCustom annotation to the named flag in the flag set, if it exists. -// Generated bash autocompletion will call the bash function f for the flag. -func MarkFlagCustom(flags *pflag.FlagSet, name string, f string) error { - return flags.SetAnnotation(name, BashCompCustom, []string{f}) -} diff --git a/shell_completions.go b/shell_completions.go new file mode 100644 index 00000000..ba0af9cb --- /dev/null +++ b/shell_completions.go @@ -0,0 +1,85 @@ +package cobra + +import ( + "github.com/spf13/pflag" +) + +// MarkFlagRequired adds the BashCompOneRequiredFlag annotation to the named flag if it exists, +// 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, +// 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, +// 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. +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. +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 +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 +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). +// +// Shell Completion compatibility matrix: bash, zsh +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 +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 +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 +func MarkFlagDirname(flags *pflag.FlagSet, name string) error { + zshPattern := "-(/)" + return flags.SetAnnotation(name, zshCompDirname, []string{zshPattern}) +} diff --git a/zsh_completions.go b/zsh_completions.go index 68bb5c6e..12755482 100644 --- a/zsh_completions.go +++ b/zsh_completions.go @@ -16,6 +16,7 @@ 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 ( @@ -305,16 +306,21 @@ func zshCompGenFlagEntryForMultiOptionFlag(f *pflag.Flag) string { } func zshCompGenFlagEntryExtras(f *pflag.Flag) string { - var extras string + if f.NoOptDefVal != "" { + return "" + } - globs, pathSpecified := f.Annotations[BashCompFilenameExt] - if pathSpecified { - extras = ":filename:_files" - for _, g := range globs { - extras = extras + fmt.Sprintf(` -g "%s"`, g) + 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) + } } - } else if f.NoOptDefVal == "" { - extras = ":" // allow option variable without assisting } return extras diff --git a/zsh_completions_test.go b/zsh_completions_test.go index 976cbfc2..e53fa886 100644 --- a/zsh_completions_test.go +++ b/zsh_completions_test.go @@ -214,6 +214,21 @@ func TestGenZshCompletion(t *testing.T) { `'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 { From 21ccc7b307f4a6453e393a0ce1fe40eedbeffa2d Mon Sep 17 00:00:00 2001 From: Jan Kuehle Date: Mon, 17 Dec 2018 23:01:34 +0000 Subject: [PATCH 028/211] Add basic PowerShell completions --- powershell_completions.go | 100 +++++++++++++++++++++++++++ powershell_completions_test.go | 122 +++++++++++++++++++++++++++++++++ 2 files changed, 222 insertions(+) create mode 100644 powershell_completions.go create mode 100644 powershell_completions_test.go diff --git a/powershell_completions.go b/powershell_completions.go new file mode 100644 index 00000000..756c61b9 --- /dev/null +++ b/powershell_completions.go @@ -0,0 +1,100 @@ +// PowerShell completions are based on the amazing work from clap: +// https://github.com/clap-rs/clap/blob/3294d18efe5f264d12c9035f404c7d189d4824e1/src/completions/powershell.rs +// +// The generated scripts require PowerShell v5.0+ (which comes Windows 10, but +// can be downloaded separately for windows 7 or 8.1). + +package cobra + +import ( + "bytes" + "fmt" + "io" + "os" + "strings" + + "github.com/spf13/pflag" +) + +var powerShellCompletionTemplate = `using namespace System.Management.Automation +using namespace System.Management.Automation.Language +Register-ArgumentCompleter -Native -CommandName '%s' -ScriptBlock { + param($wordToComplete, $commandAst, $cursorPosition) + $commandElements = $commandAst.CommandElements + $command = @( + '%s' + for ($i = 1; $i -lt $commandElements.Count; $i++) { + $element = $commandElements[$i] + if ($element -isnot [StringConstantExpressionAst] -or + $element.StringConstantType -ne [StringConstantType]::BareWord -or + $element.Value.StartsWith('-')) { + break + } + $element.Value + } + ) -join ';' + $completions = @(switch ($command) {%s + }) + $completions.Where{ $_.CompletionText -like "$wordToComplete*" } | + Sort-Object -Property ListItemText +}` + +func generatePowerShellSubcommandCases(out io.Writer, cmd *Command, previousCommandName string) { + var cmdName string + if previousCommandName == "" { + cmdName = cmd.Name() + } else { + cmdName = fmt.Sprintf("%s;%s", previousCommandName, cmd.Name()) + } + + fmt.Fprintf(out, "\n '%s' {", cmdName) + + cmd.Flags().VisitAll(func(flag *pflag.Flag) { + if nonCompletableFlag(flag) { + return + } + usage := escapeStringForPowerShell(flag.Usage) + if len(flag.Shorthand) > 0 { + fmt.Fprintf(out, "\n [CompletionResult]::new('-%s', '%s', [CompletionResultType]::ParameterName, '%s')", flag.Shorthand, flag.Shorthand, usage) + } + fmt.Fprintf(out, "\n [CompletionResult]::new('--%s', '%s', [CompletionResultType]::ParameterName, '%s')", flag.Name, flag.Name, usage) + }) + + for _, subCmd := range cmd.Commands() { + usage := escapeStringForPowerShell(subCmd.Short) + fmt.Fprintf(out, "\n [CompletionResult]::new('%s', '%s', [CompletionResultType]::ParameterValue, '%s')", subCmd.Name(), subCmd.Name(), usage) + } + + fmt.Fprint(out, "\n break\n }") + + for _, subCmd := range cmd.Commands() { + generatePowerShellSubcommandCases(out, subCmd, cmdName) + } +} + +func escapeStringForPowerShell(s string) string { + return strings.Replace(s, "'", "''", -1) +} + +// GenPowerShellCompletion generates PowerShell completion file and writes to the passed writer. +func (c *Command) GenPowerShellCompletion(w io.Writer) error { + buf := new(bytes.Buffer) + + var subCommandCases bytes.Buffer + generatePowerShellSubcommandCases(&subCommandCases, c, "") + fmt.Fprintf(buf, powerShellCompletionTemplate, c.Name(), c.Name(), subCommandCases.String()) + + _, err := buf.WriteTo(w) + return err +} + +// GenPowerShellCompletionFile generates PowerShell completion file. +func (c *Command) GenPowerShellCompletionFile(filename string) error { + outFile, err := os.Create(filename) + if err != nil { + return err + } + defer outFile.Close() + + return c.GenPowerShellCompletion(outFile) +} diff --git a/powershell_completions_test.go b/powershell_completions_test.go new file mode 100644 index 00000000..29b609de --- /dev/null +++ b/powershell_completions_test.go @@ -0,0 +1,122 @@ +package cobra + +import ( + "bytes" + "strings" + "testing" +) + +func TestPowerShellCompletion(t *testing.T) { + tcs := []struct { + name string + root *Command + expectedExpressions []string + }{ + { + name: "trivial", + root: &Command{Use: "trivialapp"}, + expectedExpressions: []string{ + "Register-ArgumentCompleter -Native -CommandName 'trivialapp' -ScriptBlock", + "$command = @(\n 'trivialapp'\n", + }, + }, + { + name: "tree", + root: func() *Command { + r := &Command{Use: "tree"} + + sub1 := &Command{Use: "sub1"} + r.AddCommand(sub1) + + sub11 := &Command{Use: "sub11"} + sub12 := &Command{Use: "sub12"} + + sub1.AddCommand(sub11) + sub1.AddCommand(sub12) + + sub2 := &Command{Use: "sub2"} + r.AddCommand(sub2) + + sub21 := &Command{Use: "sub21"} + sub22 := &Command{Use: "sub22"} + + sub2.AddCommand(sub21) + sub2.AddCommand(sub22) + + return r + }(), + expectedExpressions: []string{ + "'tree'", + "[CompletionResult]::new('sub1', 'sub1', [CompletionResultType]::ParameterValue, '')", + "[CompletionResult]::new('sub2', 'sub2', [CompletionResultType]::ParameterValue, '')", + "'tree;sub1'", + "[CompletionResult]::new('sub11', 'sub11', [CompletionResultType]::ParameterValue, '')", + "[CompletionResult]::new('sub12', 'sub12', [CompletionResultType]::ParameterValue, '')", + "'tree;sub1;sub11'", + "'tree;sub1;sub12'", + "'tree;sub2'", + "[CompletionResult]::new('sub21', 'sub21', [CompletionResultType]::ParameterValue, '')", + "[CompletionResult]::new('sub22', 'sub22', [CompletionResultType]::ParameterValue, '')", + "'tree;sub2;sub21'", + "'tree;sub2;sub22'", + }, + }, + { + name: "flags", + root: func() *Command { + r := &Command{Use: "flags"} + r.Flags().StringP("flag1", "a", "", "") + r.Flags().String("flag2", "", "") + + sub1 := &Command{Use: "sub1"} + sub1.Flags().StringP("flag3", "c", "", "") + r.AddCommand(sub1) + + return r + }(), + expectedExpressions: []string{ + "'flags'", + "[CompletionResult]::new('-a', 'a', [CompletionResultType]::ParameterName, '')", + "[CompletionResult]::new('--flag1', 'flag1', [CompletionResultType]::ParameterName, '')", + "[CompletionResult]::new('--flag2', 'flag2', [CompletionResultType]::ParameterName, '')", + "[CompletionResult]::new('sub1', 'sub1', [CompletionResultType]::ParameterValue, '')", + "'flags;sub1'", + "[CompletionResult]::new('-c', 'c', [CompletionResultType]::ParameterName, '')", + "[CompletionResult]::new('--flag3', 'flag3', [CompletionResultType]::ParameterName, '')", + }, + }, + { + name: "usage", + root: func() *Command { + r := &Command{Use: "usage"} + r.Flags().String("flag", "", "this describes the usage of the 'flag' flag") + + sub1 := &Command{ + Use: "sub1", + Short: "short describes 'sub1'", + } + r.AddCommand(sub1) + + return r + }(), + expectedExpressions: []string{ + "[CompletionResult]::new('--flag', 'flag', [CompletionResultType]::ParameterName, 'this describes the usage of the ''flag'' flag')", + "[CompletionResult]::new('sub1', 'sub1', [CompletionResultType]::ParameterValue, 'short describes ''sub1''')", + }, + }, + } + + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + buf := new(bytes.Buffer) + tc.root.GenPowerShellCompletion(buf) + output := buf.String() + + for _, expectedExpression := range tc.expectedExpressions { + if !strings.Contains(output, expectedExpression) { + t.Errorf("Expected completion to contain %q somewhere; got %q", expectedExpression, output) + } + } + }) + } +} From d658160bddb021fa7747c7920de931d8ef8e68c3 Mon Sep 17 00:00:00 2001 From: Jan Kuehle Date: Mon, 17 Dec 2018 23:20:45 +0000 Subject: [PATCH 029/211] Add markdown file explaining support for PowerShell --- powershell_completions.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 powershell_completions.md diff --git a/powershell_completions.md b/powershell_completions.md new file mode 100644 index 00000000..afed8024 --- /dev/null +++ b/powershell_completions.md @@ -0,0 +1,14 @@ +# Generating PowerShell Completions For Your Own cobra.Command + +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. + +# What's supported + +- Completion for subcommands using their `.Short` description +- Completion for non-hidden flags using their `.Name` and `.Shorthand` + +# What's not yet supported + +- Command aliases +- Required, filename or custom flags (they will work like normal flags) +- Custom completion scripts From 80ea2901b62e9663b6104d03362f5042b57836b7 Mon Sep 17 00:00:00 2001 From: jharshman Date: Tue, 29 Jan 2019 00:46:54 -0800 Subject: [PATCH 030/211] vgo-support - re-working code generator --- cobra/cmd/init.go | 94 ++++++++++++++++++++++++++++++-------------- cobra/cmd/project.go | 7 ++++ cobra/cmd/root.go | 3 +- cobra/tpl/main.go | 17 ++++++++ 4 files changed, 90 insertions(+), 31 deletions(-) create mode 100644 cobra/tpl/main.go diff --git a/cobra/cmd/init.go b/cobra/cmd/init.go index d65e6c8c..f59be065 100644 --- a/cobra/cmd/init.go +++ b/cobra/cmd/init.go @@ -15,19 +15,24 @@ package cmd import ( "fmt" + "github.com/spf13/cobra/cobra/tpl" "os" "path" "path/filepath" + "text/template" "github.com/spf13/cobra" "github.com/spf13/viper" ) -var initCmd = &cobra.Command{ - Use: "init [name]", - Aliases: []string{"initialize", "initialise", "create"}, - Short: "Initialize a Cobra Application", - Long: `Initialize (cobra init) will create a new application, with a license +var ( + pkgName string + + initCmd = &cobra.Command{ + Use: "init [name]", + Aliases: []string{"initialize", "initialise", "create"}, + Short: "Initialize a Cobra Application", + 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; @@ -39,37 +44,66 @@ and the appropriate structure for a Cobra-based CLI application. Init will not use an existing directory with contents.`, - Run: func(cmd *cobra.Command, args []string) { - wd, err := os.Getwd() - if err != nil { - er(err) - } + Run: func(cmd *cobra.Command, args []string) { - var project *Project - if len(args) == 0 { - project = NewProjectFromPath(wd) - } else if len(args) == 1 { - arg := args[0] - if arg[0] == '.' { - arg = filepath.Join(wd, arg) + wd, err := os.Getwd() + if err != nil { + er(err) } - if filepath.IsAbs(arg) { - project = NewProjectFromPath(arg) - } else { - project = NewProject(arg) + + project := &Project{ + AbsolutePath: wd, + PkgName: pkgName, + Legal: getLicense(), + Copyright: copyrightLine(), } - } else { - er("please provide only one argument") - } - initializeProject(project) + // open file for writing + f, err := os.Create(fmt.Sprintf("%s/main.go", project.AbsolutePath)) + if err != nil { + er(err) + } + defer f.Close() - fmt.Fprintln(cmd.OutOrStdout(), `Your Cobra application is ready at -`+project.AbsPath()+` + t := template.Must(template.New("init").Parse(string(tpl.MainTemplate()))) + err = t.Execute(f, project) + if err != nil { + er(err) + } + /* + wd, err := os.Getwd() + if err != nil { + er(err) + } -Give it a try by going there and running `+"`go run main.go`."+` -Add commands to it by running `+"`cobra add [cmdname]`.") - }, + var project *Project + if len(args) == 0 { + project = NewProjectFromPath(wd) + } else if len(args) == 1 { + arg := args[0] + if arg[0] == '.' { + arg = filepath.Join(wd, arg) + } + if filepath.IsAbs(arg) { + project = NewProjectFromPath(arg) + } else { + project = NewProject(arg) + } + } else { + er("please provide only one argument") + } + + initializeProject(project) + */ + + fmt.Printf("Your Cobra applicaton is ready at\n%s\n", project.AbsolutePath) + }, + } +) + +func init() { + initCmd.Flags().StringVar(&pkgName, "pkg-name", "", "fully qualified pkg name") + initCmd.MarkFlagRequired("pkg-name") } func initializeProject(project *Project) { diff --git a/cobra/cmd/project.go b/cobra/cmd/project.go index 7ddb8258..138a3802 100644 --- a/cobra/cmd/project.go +++ b/cobra/cmd/project.go @@ -9,6 +9,13 @@ import ( // Project contains name, license and paths to projects. type Project struct { + // v2 + PkgName string + Copyright string + AbsolutePath string + Legal License + + // v1 absPath string cmdPath string srcPath string diff --git a/cobra/cmd/root.go b/cobra/cmd/root.go index 19568f98..624c717c 100644 --- a/cobra/cmd/root.go +++ b/cobra/cmd/root.go @@ -23,7 +23,8 @@ import ( var ( // Used for flags. - cfgFile, userLicense string + cfgFile string + userLicense string rootCmd = &cobra.Command{ Use: "cobra", diff --git a/cobra/tpl/main.go b/cobra/tpl/main.go new file mode 100644 index 00000000..af19ac80 --- /dev/null +++ b/cobra/tpl/main.go @@ -0,0 +1,17 @@ +package tpl + +func MainTemplate() []byte { + return []byte(` +/* +{{ .Copyright }} +{{if .Legal.Header}}{{ .Legal.Header }}{{end}} +*/ +package main + +import "{{ .PkgName }}/cmd" + +func main() { + cmd.Execute() +} +`) +} From c356c6491b84c9d61ca6558ab5a3d2efbe0a751f Mon Sep 17 00:00:00 2001 From: jharshman Date: Tue, 29 Jan 2019 00:48:25 -0800 Subject: [PATCH 031/211] add .idea/* to gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 1b8c7c26..3b053c59 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,5 @@ tags *.exe cobra.test + +.idea/* From 26d210e2cd929a8d5f57a08cd0391eae58c4c687 Mon Sep 17 00:00:00 2001 From: jharshman Date: Tue, 29 Jan 2019 01:07:11 -0800 Subject: [PATCH 032/211] vgo - fixing up the root template --- cobra/cmd/init.go | 28 +++++++++++--- cobra/tpl/main.go | 96 ++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 118 insertions(+), 6 deletions(-) diff --git a/cobra/cmd/init.go b/cobra/cmd/init.go index f59be065..306a45a9 100644 --- a/cobra/cmd/init.go +++ b/cobra/cmd/init.go @@ -56,20 +56,38 @@ Init will not use an existing directory with contents.`, PkgName: pkgName, Legal: getLicense(), Copyright: copyrightLine(), + // viper + // appname } - // open file for writing - f, err := os.Create(fmt.Sprintf("%s/main.go", project.AbsolutePath)) + // create main.go + mainFile, err := os.Create(fmt.Sprintf("%s/main.go", project.AbsolutePath)) if err != nil { er(err) } - defer f.Close() + defer mainFile.Close() - t := template.Must(template.New("init").Parse(string(tpl.MainTemplate()))) - err = t.Execute(f, project) + mainTemplate := template.Must(template.New("main").Parse(string(tpl.MainTemplate()))) + err = mainTemplate.Execute(mainFile, project) if err != nil { er(err) } + + // create cmd/root.go + rootFile, err := os.Create(fmt.Sprintf("%s/cmd/root.go", project.AbsolutePath)) + if err != nil { + er(err) + } + defer rootFile.Close() + + rootTemplate := template.Must(template.New("root").Parse(string(tpl.RootTemplate()))) + err = rootTemplate.Execute(rootFile, project) + if err != nil { + er(err) + } + + createLicenseFile(project.Legal, project.AbsolutePath) + /* wd, err := os.Getwd() if err != nil { diff --git a/cobra/tpl/main.go b/cobra/tpl/main.go index af19ac80..ee2d528b 100644 --- a/cobra/tpl/main.go +++ b/cobra/tpl/main.go @@ -4,7 +4,7 @@ func MainTemplate() []byte { return []byte(` /* {{ .Copyright }} -{{if .Legal.Header}}{{ .Legal.Header }}{{end}} +{{ if .Legal.Header }}{{ .Legal.Header }}{{ end }} */ package main @@ -15,3 +15,97 @@ func main() { } `) } + +func RootTemplate() []byte { + return []byte(` +/* +{{ .Copyright }} +{{ if .Legal.Header }}{{ .Legal.Header }}{{ end }} +*/ +package cmd + +import ( + "fmt" + "os" + "github.com/spf13/cobra" +{{ if .Viper }} + homedir "github.com/mitchellh/go-homedir" + "github.com/spf13/viper" +{{ end }} +) + +{{ if .Viper }} +var cfgFile string +{{ 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 +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) { }, +} + +// 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) + } +} + +func init() { +{{- if .Viper }} + 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. +{{ if .Viper }} + 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)") +{{ 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") +} + +{{ 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) + } + + // Search config in home directory with name ".{{ .appName }}" (without extension). + viper.AddConfigPath(home) + viper.SetConfigName(".{{ .appName }}") + } + + 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()) + } +} +{{ end }} +`) +} From 17dc9f81420b263d94ea687b9503ce7fedbdece8 Mon Sep 17 00:00:00 2001 From: jharshman Date: Tue, 29 Jan 2019 01:19:08 -0800 Subject: [PATCH 033/211] fixing up templates more --- cobra/cmd/init.go | 4 +-- cobra/cmd/project.go | 2 ++ cobra/tpl/main.go | 86 ++++++++++++++++++++++---------------------- 3 files changed, 47 insertions(+), 45 deletions(-) diff --git a/cobra/cmd/init.go b/cobra/cmd/init.go index 306a45a9..02e303f7 100644 --- a/cobra/cmd/init.go +++ b/cobra/cmd/init.go @@ -56,8 +56,8 @@ Init will not use an existing directory with contents.`, PkgName: pkgName, Legal: getLicense(), Copyright: copyrightLine(), - // viper - // appname + Viper: viper.GetBool("useViper"), + AppName: path.Base(pkgName), } // create main.go diff --git a/cobra/cmd/project.go b/cobra/cmd/project.go index 138a3802..3f55732e 100644 --- a/cobra/cmd/project.go +++ b/cobra/cmd/project.go @@ -14,6 +14,8 @@ type Project struct { Copyright string AbsolutePath string Legal License + Viper bool + AppName string // v1 absPath string diff --git a/cobra/tpl/main.go b/cobra/tpl/main.go index ee2d528b..435bf6cf 100644 --- a/cobra/tpl/main.go +++ b/cobra/tpl/main.go @@ -11,7 +11,7 @@ package main import "{{ .PkgName }}/cmd" func main() { - cmd.Execute() + cmd.Execute() } `) } @@ -25,12 +25,12 @@ func RootTemplate() []byte { package cmd import ( - "fmt" - "os" + "fmt" + "os" "github.com/spf13/cobra" {{ if .Viper }} - homedir "github.com/mitchellh/go-homedir" - "github.com/spf13/viper" + homedir "github.com/mitchellh/go-homedir" + "github.com/spf13/viper" {{ end }} ) @@ -40,71 +40,71 @@ var cfgFile string // 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 }} // 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 }} `) From 69420a9ffa803f0f0990be1fb8ea9ec69fd5fb69 Mon Sep 17 00:00:00 2001 From: jharshman Date: Tue, 29 Jan 2019 19:11:26 -0800 Subject: [PATCH 034/211] vgo - create directory --- cobra/cmd/init.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cobra/cmd/init.go b/cobra/cmd/init.go index 02e303f7..c334121a 100644 --- a/cobra/cmd/init.go +++ b/cobra/cmd/init.go @@ -74,6 +74,9 @@ Init will not use an existing directory with contents.`, } // create cmd/root.go + if _, err = os.Stat(fmt.Sprintf("%s/cmd", project.AbsolutePath)); os.IsNotExist(err) { + os.Mkdir("cmd", 0751) + } rootFile, err := os.Create(fmt.Sprintf("%s/cmd/root.go", project.AbsolutePath)) if err != nil { er(err) From abab9aa52a698bb6fcb945b10968695ec52379aa Mon Sep 17 00:00:00 2001 From: jharshman Date: Tue, 29 Jan 2019 19:34:11 -0800 Subject: [PATCH 035/211] vgo - add Create method to Project struct --- cobra/cmd/init.go | 62 +++----------------------------------------- cobra/cmd/project.go | 39 ++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 59 deletions(-) diff --git a/cobra/cmd/init.go b/cobra/cmd/init.go index c334121a..2e5bc4e9 100644 --- a/cobra/cmd/init.go +++ b/cobra/cmd/init.go @@ -15,14 +15,11 @@ package cmd import ( "fmt" - "github.com/spf13/cobra/cobra/tpl" + "github.com/spf13/cobra" + "github.com/spf13/viper" "os" "path" "path/filepath" - "text/template" - - "github.com/spf13/cobra" - "github.com/spf13/viper" ) var ( @@ -60,62 +57,9 @@ Init will not use an existing directory with contents.`, AppName: path.Base(pkgName), } - // create main.go - mainFile, err := os.Create(fmt.Sprintf("%s/main.go", project.AbsolutePath)) - if err != nil { + if err := project.Create(); err != nil { er(err) } - defer mainFile.Close() - - mainTemplate := template.Must(template.New("main").Parse(string(tpl.MainTemplate()))) - err = mainTemplate.Execute(mainFile, project) - if err != nil { - er(err) - } - - // create cmd/root.go - if _, err = os.Stat(fmt.Sprintf("%s/cmd", project.AbsolutePath)); os.IsNotExist(err) { - os.Mkdir("cmd", 0751) - } - rootFile, err := os.Create(fmt.Sprintf("%s/cmd/root.go", project.AbsolutePath)) - if err != nil { - er(err) - } - defer rootFile.Close() - - rootTemplate := template.Must(template.New("root").Parse(string(tpl.RootTemplate()))) - err = rootTemplate.Execute(rootFile, project) - if err != nil { - er(err) - } - - createLicenseFile(project.Legal, project.AbsolutePath) - - /* - wd, err := os.Getwd() - if err != nil { - er(err) - } - - var project *Project - if len(args) == 0 { - project = NewProjectFromPath(wd) - } else if len(args) == 1 { - arg := args[0] - if arg[0] == '.' { - arg = filepath.Join(wd, arg) - } - if filepath.IsAbs(arg) { - project = NewProjectFromPath(arg) - } else { - project = NewProject(arg) - } - } else { - er("please provide only one argument") - } - - initializeProject(project) - */ fmt.Printf("Your Cobra applicaton is ready at\n%s\n", project.AbsolutePath) }, diff --git a/cobra/cmd/project.go b/cobra/cmd/project.go index 3f55732e..cf66e83e 100644 --- a/cobra/cmd/project.go +++ b/cobra/cmd/project.go @@ -1,10 +1,13 @@ package cmd import ( + "fmt" + "github.com/spf13/cobra/cobra/tpl" "os" "path/filepath" "runtime" "strings" + "text/template" ) // Project contains name, license and paths to projects. @@ -25,6 +28,42 @@ type Project struct { name string } +func (p *Project) Create() error { + + // create main.go + mainFile, err := os.Create(fmt.Sprintf("%s/main.go", p.AbsolutePath)) + if err != nil { + return err + } + defer mainFile.Close() + + mainTemplate := template.Must(template.New("main").Parse(string(tpl.MainTemplate()))) + err = mainTemplate.Execute(mainFile, p) + if err != nil { + return err + } + + // create cmd/root.go + if _, err = os.Stat(fmt.Sprintf("%s/cmd", p.AbsolutePath)); os.IsNotExist(err) { + os.Mkdir("cmd", 0751) + } + rootFile, err := os.Create(fmt.Sprintf("%s/cmd/root.go", p.AbsolutePath)) + if err != nil { + return err + } + defer rootFile.Close() + + rootTemplate := template.Must(template.New("root").Parse(string(tpl.RootTemplate()))) + err = rootTemplate.Execute(rootFile, p) + if err != nil { + return err + } + + // create license + createLicenseFile(p.Legal, p.AbsolutePath) + return nil +} + // NewProject returns Project with specified project name. func NewProject(projectName string) *Project { if projectName == "" { From 5b1685faaa2b08aec83e0ea49d00e0a49735a773 Mon Sep 17 00:00:00 2001 From: jharshman Date: Tue, 29 Jan 2019 20:25:38 -0800 Subject: [PATCH 036/211] vgo - generate license --- cobra/cmd/project.go | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/cobra/cmd/project.go b/cobra/cmd/project.go index cf66e83e..ab349830 100644 --- a/cobra/cmd/project.go +++ b/cobra/cmd/project.go @@ -60,10 +60,39 @@ func (p *Project) Create() error { } // create license - createLicenseFile(p.Legal, p.AbsolutePath) - return nil + return createLicenseFile(p.Legal, p.AbsolutePath) } +func (p *Project) createLicenseFile() error { + data := map[string]interface{}{ + "copyright": copyrightLine(), + } + licenseFile, err := os.Create(fmt.Sprintf("%s/LICENSE", p.AbsolutePath)) + if err != nil { + return err + } + + licenseTemplate := template.Must(template.New("license").Parse(p.Legal.Text)) + return licenseTemplate.Execute(licenseFile, data) +} + +//func createLicenseFile(license License, path string) { +// data := make(map[string]interface{}) +// data["copyright"] = copyrightLine() +// +// // Generate license template from text and data. +// text, err := executeTemplate(license.Text, data) +// if err != nil { +// er(err) +// } +// +// // Write license text to LICENSE file. +// err = writeStringToFile(filepath.Join(path, "LICENSE"), text) +// if err != nil { +// er(err) +// } +//} + // NewProject returns Project with specified project name. func NewProject(projectName string) *Project { if projectName == "" { From 91dbcb7ffee662057bde52378f2a79381b4aef6b Mon Sep 17 00:00:00 2001 From: jharshman Date: Tue, 29 Jan 2019 20:26:08 -0800 Subject: [PATCH 037/211] remove commented code --- cobra/cmd/project.go | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/cobra/cmd/project.go b/cobra/cmd/project.go index ab349830..1b97c3d6 100644 --- a/cobra/cmd/project.go +++ b/cobra/cmd/project.go @@ -76,23 +76,6 @@ func (p *Project) createLicenseFile() error { return licenseTemplate.Execute(licenseFile, data) } -//func createLicenseFile(license License, path string) { -// data := make(map[string]interface{}) -// data["copyright"] = copyrightLine() -// -// // Generate license template from text and data. -// text, err := executeTemplate(license.Text, data) -// if err != nil { -// er(err) -// } -// -// // Write license text to LICENSE file. -// err = writeStringToFile(filepath.Join(path, "LICENSE"), text) -// if err != nil { -// er(err) -// } -//} - // NewProject returns Project with specified project name. func NewProject(projectName string) *Project { if projectName == "" { From 44c2d482f6512000718de39a3bbabb5aec856d56 Mon Sep 17 00:00:00 2001 From: jharshman Date: Tue, 29 Jan 2019 20:27:46 -0800 Subject: [PATCH 038/211] fix calling to createLicenseFile --- cobra/cmd/project.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cobra/cmd/project.go b/cobra/cmd/project.go index 1b97c3d6..f3bc9eb8 100644 --- a/cobra/cmd/project.go +++ b/cobra/cmd/project.go @@ -60,7 +60,7 @@ func (p *Project) Create() error { } // create license - return createLicenseFile(p.Legal, p.AbsolutePath) + return p. createLicenseFile() } func (p *Project) createLicenseFile() error { From 73b5215dc72c245abe81cc72b7c1adfc1ebfab70 Mon Sep 17 00:00:00 2001 From: jharshman Date: Tue, 29 Jan 2019 20:28:47 -0800 Subject: [PATCH 039/211] vgo - fix format --- cobra/cmd/project.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cobra/cmd/project.go b/cobra/cmd/project.go index f3bc9eb8..47c63abf 100644 --- a/cobra/cmd/project.go +++ b/cobra/cmd/project.go @@ -60,7 +60,7 @@ func (p *Project) Create() error { } // create license - return p. createLicenseFile() + return p.createLicenseFile() } func (p *Project) createLicenseFile() error { From 4c22a20fd45b379d3e9b9223dd76445e269199e4 Mon Sep 17 00:00:00 2001 From: jharshman Date: Tue, 29 Jan 2019 20:35:27 -0800 Subject: [PATCH 040/211] vgo - remove unused methods --- cobra/cmd/init.go | 164 +--------------------------------------------- 1 file changed, 1 insertion(+), 163 deletions(-) diff --git a/cobra/cmd/init.go b/cobra/cmd/init.go index 2e5bc4e9..b5802e96 100644 --- a/cobra/cmd/init.go +++ b/cobra/cmd/init.go @@ -19,7 +19,6 @@ import ( "github.com/spf13/viper" "os" "path" - "path/filepath" ) var ( @@ -69,165 +68,4 @@ Init will not use an existing directory with contents.`, func init() { initCmd.Flags().StringVar(&pkgName, "pkg-name", "", "fully qualified pkg name") initCmd.MarkFlagRequired("pkg-name") -} - -func initializeProject(project *Project) { - if !exists(project.AbsPath()) { // If path doesn't yet exist, create it - err := os.MkdirAll(project.AbsPath(), os.ModePerm) - if err != nil { - er(err) - } - } else if !isEmpty(project.AbsPath()) { // If path exists and is not empty don't use it - er("Cobra will not create a new project in a non empty directory: " + project.AbsPath()) - } - - // We have a directory and it's empty. Time to initialize it. - createLicenseFile(project.License(), project.AbsPath()) - createMainFile(project) - createRootCmdFile(project) -} - -func createLicenseFile(license License, path string) { - data := make(map[string]interface{}) - data["copyright"] = copyrightLine() - - // Generate license template from text and data. - text, err := executeTemplate(license.Text, data) - if err != nil { - er(err) - } - - // Write license text to LICENSE file. - err = writeStringToFile(filepath.Join(path, "LICENSE"), text) - if err != nil { - er(err) - } -} - -func createMainFile(project *Project) { - mainTemplate := `{{ comment .copyright }} -{{if .license}}{{ comment .license }}{{end}} - -package main - -import "{{ .importpath }}" - -func main() { - cmd.Execute() -} -` - data := make(map[string]interface{}) - data["copyright"] = copyrightLine() - data["license"] = project.License().Header - data["importpath"] = path.Join(project.Name(), filepath.Base(project.CmdPath())) - - mainScript, err := executeTemplate(mainTemplate, data) - if err != nil { - er(err) - } - - err = writeStringToFile(filepath.Join(project.AbsPath(), "main.go"), mainScript) - if err != nil { - er(err) - } -} - -func createRootCmdFile(project *Project) { - template := `{{comment .copyright}} -{{if .license}}{{comment .license}}{{end}} - -package cmd - -import ( - "fmt" - "os" -{{if .viper}} - homedir "github.com/mitchellh/go-homedir"{{end}} - "github.com/spf13/cobra"{{if .viper}} - "github.com/spf13/viper"{{end}} -){{if .viper}} - -var cfgFile string{{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 -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) { }, -} - -// 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) - } -} - -func init() { {{- if .viper}} - 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.{{ if .viper }} - 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)"){{ 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") -}{{ 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) - } - - // Search config in home directory with name ".{{ .appName }}" (without extension). - viper.AddConfigPath(home) - viper.SetConfigName(".{{ .appName }}") - } - - 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()) - } -}{{ end }} -` - - data := make(map[string]interface{}) - data["copyright"] = copyrightLine() - data["viper"] = viper.GetBool("useViper") - data["license"] = project.License().Header - data["appName"] = path.Base(project.Name()) - - rootCmdScript, err := executeTemplate(template, data) - if err != nil { - er(err) - } - - err = writeStringToFile(filepath.Join(project.CmdPath(), "root.go"), rootCmdScript) - if err != nil { - er(err) - } - -} +} \ No newline at end of file From c3b51f3a2e0ea821c0cb8ed8e5850e98394641c5 Mon Sep 17 00:00:00 2001 From: jharshman Date: Tue, 29 Jan 2019 23:41:41 -0800 Subject: [PATCH 041/211] simplify test --- cobra/cmd/add_test.go | 12 ++---------- cobra/cmd/init.go | 2 +- cobra/cmd/init_test.go | 23 ++++++++++++++++++++++- 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/cobra/cmd/add_test.go b/cobra/cmd/add_test.go index b920e2b9..94497084 100644 --- a/cobra/cmd/add_test.go +++ b/cobra/cmd/add_test.go @@ -1,15 +1,6 @@ package cmd -import ( - "errors" - "io/ioutil" - "os" - "path/filepath" - "testing" - - "github.com/spf13/viper" -) - +/* // TestGoldenAddCmd initializes the project "github.com/spf13/testproject" // in GOPATH, adds "test" command // and compares the content of all files in cmd directory of testproject @@ -107,3 +98,4 @@ func TestValidateCmdName(t *testing.T) { } } } +*/ diff --git a/cobra/cmd/init.go b/cobra/cmd/init.go index b5802e96..25377558 100644 --- a/cobra/cmd/init.go +++ b/cobra/cmd/init.go @@ -68,4 +68,4 @@ Init will not use an existing directory with contents.`, func init() { initCmd.Flags().StringVar(&pkgName, "pkg-name", "", "fully qualified pkg name") initCmd.MarkFlagRequired("pkg-name") -} \ No newline at end of file +} diff --git a/cobra/cmd/init_test.go b/cobra/cmd/init_test.go index 40eb4038..f61a139f 100644 --- a/cobra/cmd/init_test.go +++ b/cobra/cmd/init_test.go @@ -10,11 +10,32 @@ import ( "github.com/spf13/viper" ) +func TestGoldenInitCmd(t *testing.T) { + wd, _ := os.Getwd() + project := &Project{ + AbsolutePath: wd, + PkgName: "github.com/spf13/testproject", + Legal: getLicense(), + Viper: true, + AppName: "testproject", + } + + err := project.Create() + if err != nil { + t.Fatal(err) + } + + //expectedFiles := []string{"LICENSE", "main.go", "cmd/root.go"} + //for _, f := range expectedFiles { + // // read each file and compare with corresponding golden file + //} +} + // TestGoldenInitCmd initializes the project "github.com/spf13/testproject" // in GOPATH and compares the content of files in initialized project with // appropriate golden files ("testdata/*.golden"). // Use -update to update existing golden files. -func TestGoldenInitCmd(t *testing.T) { +func TTestGoldenInitCmd(t *testing.T) { projectName := "github.com/spf13/testproject" project := NewProject(projectName) defer os.RemoveAll(project.AbsPath()) From 04af6aed80d3a451ea8de88881be0239804d5f42 Mon Sep 17 00:00:00 2001 From: jharshman Date: Wed, 30 Jan 2019 00:02:51 -0800 Subject: [PATCH 042/211] vgo - add todo --- cobra/cmd/init.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cobra/cmd/init.go b/cobra/cmd/init.go index 25377558..a6abd1c3 100644 --- a/cobra/cmd/init.go +++ b/cobra/cmd/init.go @@ -47,6 +47,10 @@ Init will not use an existing directory with contents.`, er(err) } + // todo: + // if . use current directory + // else create named directory and set wd to that + project := &Project{ AbsolutePath: wd, PkgName: pkgName, From e993d53002aa6dca9d85ed6d8633a6984c3eafb1 Mon Sep 17 00:00:00 2001 From: jharshman Date: Wed, 30 Jan 2019 00:20:25 -0800 Subject: [PATCH 043/211] vgo - take named directory or current wd --- cobra/cmd/init.go | 8 +++++--- cobra/cmd/project.go | 10 +++++++++- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/cobra/cmd/init.go b/cobra/cmd/init.go index a6abd1c3..63397d11 100644 --- a/cobra/cmd/init.go +++ b/cobra/cmd/init.go @@ -47,9 +47,11 @@ Init will not use an existing directory with contents.`, er(err) } - // todo: - // if . use current directory - // else create named directory and set wd to that + if len(args) > 0 { + if args[0] != "." { + wd = fmt.Sprintf("%s/%s", wd, args[0]) + } + } project := &Project{ AbsolutePath: wd, diff --git a/cobra/cmd/project.go b/cobra/cmd/project.go index 47c63abf..34dea56f 100644 --- a/cobra/cmd/project.go +++ b/cobra/cmd/project.go @@ -30,6 +30,14 @@ type Project struct { func (p *Project) Create() error { + // check if AbsolutePath exists + if _, err := os.Stat(p.AbsolutePath); os.IsNotExist(err) { + // create directory + if err := os.Mkdir(p.AbsolutePath, 0754); err != nil { + return err + } + } + // create main.go mainFile, err := os.Create(fmt.Sprintf("%s/main.go", p.AbsolutePath)) if err != nil { @@ -45,7 +53,7 @@ func (p *Project) Create() error { // create cmd/root.go if _, err = os.Stat(fmt.Sprintf("%s/cmd", p.AbsolutePath)); os.IsNotExist(err) { - os.Mkdir("cmd", 0751) + os.Mkdir(fmt.Sprintf("%s/cmd", p.AbsolutePath), 0751) } rootFile, err := os.Create(fmt.Sprintf("%s/cmd/root.go", p.AbsolutePath)) if err != nil { From 642c3c7a0edbb0242be3f380808df5474e377291 Mon Sep 17 00:00:00 2001 From: jharshman Date: Wed, 30 Jan 2019 00:33:51 -0800 Subject: [PATCH 044/211] vgo - compare generated files against golden files --- cobra/cmd/init_test.go | 86 ++++-------------------------------------- 1 file changed, 8 insertions(+), 78 deletions(-) diff --git a/cobra/cmd/init_test.go b/cobra/cmd/init_test.go index f61a139f..f96b6575 100644 --- a/cobra/cmd/init_test.go +++ b/cobra/cmd/init_test.go @@ -1,19 +1,16 @@ package cmd import ( - "errors" - "io/ioutil" + "fmt" "os" "path/filepath" "testing" - - "github.com/spf13/viper" ) func TestGoldenInitCmd(t *testing.T) { wd, _ := os.Getwd() project := &Project{ - AbsolutePath: wd, + AbsolutePath: fmt.Sprintf("%s/testproject", wd), PkgName: "github.com/spf13/testproject", Legal: getLicense(), Viper: true, @@ -25,80 +22,13 @@ func TestGoldenInitCmd(t *testing.T) { t.Fatal(err) } - //expectedFiles := []string{"LICENSE", "main.go", "cmd/root.go"} - //for _, f := range expectedFiles { - // // read each file and compare with corresponding golden file - //} -} - -// TestGoldenInitCmd initializes the project "github.com/spf13/testproject" -// in GOPATH and compares the content of files in initialized project with -// appropriate golden files ("testdata/*.golden"). -// Use -update to update existing golden files. -func TTestGoldenInitCmd(t *testing.T) { - projectName := "github.com/spf13/testproject" - project := NewProject(projectName) - defer os.RemoveAll(project.AbsPath()) - - viper.Set("author", "NAME HERE ") - viper.Set("license", "apache") - viper.Set("year", 2017) - defer viper.Set("author", nil) - defer viper.Set("license", nil) - defer viper.Set("year", nil) - - os.Args = []string{"cobra", "init", projectName} - if err := rootCmd.Execute(); err != nil { - t.Fatal("Error by execution:", err) - } - - expectedFiles := []string{".", "cmd", "LICENSE", "main.go", "cmd/root.go"} - gotFiles := []string{} - - // Check project file hierarchy and compare the content of every single file - // with appropriate golden file. - err := filepath.Walk(project.AbsPath(), func(path string, info os.FileInfo, err error) error { + 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 { - return err + t.Fatal(err) } - - // Make path relative to project.AbsPath(). - // E.g. path = "/home/user/go/src/github.com/spf13/testproject/cmd/root.go" - // then it returns just "cmd/root.go". - relPath, err := filepath.Rel(project.AbsPath(), path) - if err != nil { - return err - } - relPath = filepath.ToSlash(relPath) - gotFiles = append(gotFiles, relPath) - goldenPath := filepath.Join("testdata", filepath.Base(path)+".golden") - - switch relPath { - // Known directories. - case ".", "cmd": - return nil - // Known files. - case "LICENSE", "main.go", "cmd/root.go": - if *update { - got, err := ioutil.ReadFile(path) - if err != nil { - return err - } - if err := ioutil.WriteFile(goldenPath, got, 0644); err != nil { - t.Fatal("Error while updating file:", err) - } - } - return compareFiles(path, goldenPath) - } - // Unknown file. - return errors.New("unknown file: " + path) - }) - if err != nil { - t.Fatal(err) - } - - // Check if some files lack. - if err := checkLackFiles(expectedFiles, gotFiles); err != nil { - t.Fatal(err) } } From 50665e99933b63952bde8df17bfe2113ab6dbe44 Mon Sep 17 00:00:00 2001 From: jharshman Date: Wed, 30 Jan 2019 00:46:53 -0800 Subject: [PATCH 045/211] vgo - update golden templates --- cobra/cmd/init_test.go | 1 + cobra/cmd/testdata/main.go.golden | 29 ++++---- cobra/cmd/testdata/root.go.golden | 118 ++++++++++++++++-------------- cobra/tpl/main.go | 6 +- 4 files changed, 81 insertions(+), 73 deletions(-) diff --git a/cobra/cmd/init_test.go b/cobra/cmd/init_test.go index f96b6575..77145fcb 100644 --- a/cobra/cmd/init_test.go +++ b/cobra/cmd/init_test.go @@ -13,6 +13,7 @@ func TestGoldenInitCmd(t *testing.T) { AbsolutePath: fmt.Sprintf("%s/testproject", wd), PkgName: "github.com/spf13/testproject", Legal: getLicense(), + Copyright: copyrightLine(), Viper: true, AppName: "testproject", } diff --git a/cobra/cmd/testdata/main.go.golden b/cobra/cmd/testdata/main.go.golden index cdbe38d7..4ad570c5 100644 --- a/cobra/cmd/testdata/main.go.golden +++ b/cobra/cmd/testdata/main.go.golden @@ -1,21 +1,22 @@ -// Copyright © 2017 NAME HERE -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +/* +Copyright © 2019 NAME HERE +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ 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 d74f4cd4..d3b889ba 100644 --- a/cobra/cmd/testdata/root.go.golden +++ b/cobra/cmd/testdata/root.go.golden @@ -1,89 +1,97 @@ -// Copyright © 2017 NAME HERE -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +/* +Copyright © 2019 NAME HERE +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ package cmd import ( - "fmt" - "os" + "fmt" + "os" + "github.com/spf13/cobra" + + homedir "github.com/mitchellh/go-homedir" + "github.com/spf13/viper" - homedir "github.com/mitchellh/go-homedir" - "github.com/spf13/cobra" - "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. - rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.testproject.yaml)") + // Here you will define your flags and configuration settings. + // Cobra supports persistent flags, which, if defined here, + // will be global for your application. - // 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") + 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") } + // 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 435bf6cf..b62d0265 100644 --- a/cobra/tpl/main.go +++ b/cobra/tpl/main.go @@ -1,8 +1,7 @@ package tpl func MainTemplate() []byte { - return []byte(` -/* + return []byte(`/* {{ .Copyright }} {{ if .Legal.Header }}{{ .Legal.Header }}{{ end }} */ @@ -17,8 +16,7 @@ func main() { } func RootTemplate() []byte { - return []byte(` -/* + return []byte(`/* {{ .Copyright }} {{ if .Legal.Header }}{{ .Legal.Header }}{{ end }} */ From 3741457400aa1a40bac77971975dc4d99360764e Mon Sep 17 00:00:00 2001 From: jharshman Date: Wed, 30 Jan 2019 00:58:29 -0800 Subject: [PATCH 046/211] add CommandTemplate --- cobra/cmd/project.go | 2 ++ cobra/tpl/main.go | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/cobra/cmd/project.go b/cobra/cmd/project.go index 34dea56f..789a2490 100644 --- a/cobra/cmd/project.go +++ b/cobra/cmd/project.go @@ -19,6 +19,8 @@ type Project struct { Legal License Viper bool AppName string + CmdName string + CmdParent string // v1 absPath string diff --git a/cobra/tpl/main.go b/cobra/tpl/main.go index b62d0265..1b8ff10a 100644 --- a/cobra/tpl/main.go +++ b/cobra/tpl/main.go @@ -107,3 +107,47 @@ func initConfig() { {{ end }} `) } + +func AddCommandTemplate() []byte { + return []byte(`/* +{{ .Copyright }} +{{ if .Legal.Header }}{{ .Legal.Header }}{{ end }} +*/ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +// {{ .CmdName }}Cmd represents the {{ .CmdName }} command +var {{ .CmdName }}Cmd = &cobra.Command{ + Use: "{{ .CmdName }}", + Short: "A brief description of your command", + Long: ` + "`" + `A longer description that spans multiple lines and likely contains examples +and usage of using your command. 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.` + "`" + `, + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("{{ .CmdName }} called") + }, +} + +func init() { + {{ .CmdParent }}.AddCommand({{ .CmdName }}Cmd) + + // Here you will define your flags and configuration settings. + + // Cobra supports Persistent Flags which will work for this command + // and all subcommands, e.g.: + // {{ .CmdName }}Cmd.PersistentFlags().String("foo", "", "A help for foo") + + // Cobra supports local flags which will only run when this command + // is called directly, e.g.: + // {{ .CmdName }}Cmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +} +`) +} \ No newline at end of file From c7ac101cf82f9a5660532cdd00fb5e908b359c29 Mon Sep 17 00:00:00 2001 From: jharshman Date: Wed, 30 Jan 2019 01:24:26 -0800 Subject: [PATCH 047/211] vgo - fixing up the add op to work with vgo --- cobra/cmd/add.go | 129 +++++++++++++++---------------------------- cobra/cmd/project.go | 12 +++- cobra/tpl/main.go | 2 +- 3 files changed, 54 insertions(+), 89 deletions(-) diff --git a/cobra/cmd/add.go b/cobra/cmd/add.go index fb22096a..ca19cb71 100644 --- a/cobra/cmd/add.go +++ b/cobra/cmd/add.go @@ -16,24 +16,21 @@ package cmd import ( "fmt" "os" - "path/filepath" + "path" "unicode" "github.com/spf13/cobra" ) -func init() { - addCmd.Flags().StringVarP(&packageName, "package", "t", "", "target package name (e.g. github.com/spf13/hugo)") - addCmd.Flags().StringVarP(&parentName, "parent", "p", "rootCmd", "variable name of parent command for this command") -} +var ( + packageName string + parentName string -var packageName, parentName string - -var addCmd = &cobra.Command{ - Use: "add [command name]", - Aliases: []string{"command"}, - Short: "Add a command to a Cobra Application", - Long: `Add (cobra add) will create a new command, with a license and + addCmd = &cobra.Command{ + Use: "add [command name]", + Aliases: []string{"command"}, + Short: "Add a command to a Cobra Application", + Long: `Add (cobra add) will create a new command, with a license and the appropriate structure for a Cobra-based CLI application, and register it to its parent (default rootCmd). @@ -42,28 +39,47 @@ with an initial uppercase letter. Example: cobra add server -> resulting in a new cmd/server.go`, - Run: func(cmd *cobra.Command, args []string) { - if len(args) < 1 { - er("add needs a name for the command") - } + Run: func(cmd *cobra.Command, args []string) { + if len(args) < 1 { + er("add needs a name for the command") + } + + commandName := validateCmdName(args[0]) + + if packageName == "" { + // derive packageName + } - var project *Project - if packageName != "" { - project = NewProject(packageName) - } else { wd, err := os.Getwd() if err != nil { er(err) } - project = NewProjectFromPath(wd) - } - cmdName := validateCmdName(args[0]) - cmdPath := filepath.Join(project.CmdPath(), cmdName+".go") - createCmdFile(project.License(), cmdPath, cmdName) + command := &Command{ + CmdName: commandName, + CmdParent: parentName, + Project: &Project{ + AbsolutePath: fmt.Sprintf("%s/cmd", wd), + AppName: path.Base(packageName), + PkgName: packageName, + Legal: getLicense(), + Copyright: copyrightLine(), + }, + } - fmt.Fprintln(cmd.OutOrStdout(), cmdName, "created at", cmdPath) - }, + err = command.Create() + if err != nil { + er(err) + } + + fmt.Printf("%s created at %s", command.CmdName, command.Project.AbsolutePath) + }, + } +) + +func init() { + addCmd.Flags().StringVarP(&packageName, "package", "t", "", "target package name (e.g. github.com/spf13/hugo)") + addCmd.Flags().StringVarP(&parentName, "parent", "p", "rootCmd", "variable name of parent command for this command") } // validateCmdName returns source without any dashes and underscore. @@ -118,62 +134,3 @@ func validateCmdName(source string) string { } return output } - -func createCmdFile(license License, path, cmdName string) { - template := `{{comment .copyright}} -{{if .license}}{{comment .license}}{{end}} - -package {{.cmdPackage}} - -import ( - "fmt" - - "github.com/spf13/cobra" -) - -// {{.cmdName}}Cmd represents the {{.cmdName}} command -var {{.cmdName}}Cmd = &cobra.Command{ - Use: "{{.cmdName}}", - Short: "A brief description of your command", - Long: ` + "`" + `A longer description that spans multiple lines and likely contains examples -and usage of using your command. 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.` + "`" + `, - Run: func(cmd *cobra.Command, args []string) { - fmt.Println("{{.cmdName}} called") - }, -} - -func init() { - {{.parentName}}.AddCommand({{.cmdName}}Cmd) - - // Here you will define your flags and configuration settings. - - // Cobra supports Persistent Flags which will work for this command - // and all subcommands, e.g.: - // {{.cmdName}}Cmd.PersistentFlags().String("foo", "", "A help for foo") - - // Cobra supports local flags which will only run when this command - // is called directly, e.g.: - // {{.cmdName}}Cmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") -} -` - - data := make(map[string]interface{}) - data["copyright"] = copyrightLine() - data["license"] = license.Header - data["cmdPackage"] = filepath.Base(filepath.Dir(path)) // last dir of path - data["parentName"] = parentName - data["cmdName"] = cmdName - - cmdScript, err := executeTemplate(template, data) - if err != nil { - er(err) - } - err = writeStringToFile(path, cmdScript) - if err != nil { - er(err) - } -} diff --git a/cobra/cmd/project.go b/cobra/cmd/project.go index 789a2490..fe9ea319 100644 --- a/cobra/cmd/project.go +++ b/cobra/cmd/project.go @@ -19,8 +19,6 @@ type Project struct { Legal License Viper bool AppName string - CmdName string - CmdParent string // v1 absPath string @@ -30,6 +28,12 @@ type Project struct { name string } +type Command struct { + CmdName string + CmdParent string + *Project +} + func (p *Project) Create() error { // check if AbsolutePath exists @@ -86,6 +90,10 @@ func (p *Project) createLicenseFile() error { return licenseTemplate.Execute(licenseFile, data) } +func (c *Command) Create() error { + return nil +} + // NewProject returns Project with specified project name. func NewProject(projectName string) *Project { if projectName == "" { diff --git a/cobra/tpl/main.go b/cobra/tpl/main.go index 1b8ff10a..ba4d7cc5 100644 --- a/cobra/tpl/main.go +++ b/cobra/tpl/main.go @@ -150,4 +150,4 @@ func init() { // {{ .CmdName }}Cmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") } `) -} \ No newline at end of file +} From 732e4db0d43f782e8b3b576c433db2b811171386 Mon Sep 17 00:00:00 2001 From: jharshman Date: Wed, 30 Jan 2019 01:33:04 -0800 Subject: [PATCH 048/211] vgo - trim some uneeded data from struct --- cobra/cmd/add.go | 9 ++++----- cobra/cmd/project.go | 12 +++++++++++- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/cobra/cmd/add.go b/cobra/cmd/add.go index ca19cb71..806a7cc5 100644 --- a/cobra/cmd/add.go +++ b/cobra/cmd/add.go @@ -16,7 +16,6 @@ package cmd import ( "fmt" "os" - "path" "unicode" "github.com/spf13/cobra" @@ -60,10 +59,10 @@ Example: cobra add server -> resulting in a new cmd/server.go`, CmdParent: parentName, Project: &Project{ AbsolutePath: fmt.Sprintf("%s/cmd", wd), - AppName: path.Base(packageName), - PkgName: packageName, - Legal: getLicense(), - Copyright: copyrightLine(), + //AppName: path.Base(packageName), + //PkgName: packageName, + Legal: getLicense(), + Copyright: copyrightLine(), }, } diff --git a/cobra/cmd/project.go b/cobra/cmd/project.go index fe9ea319..9b56e849 100644 --- a/cobra/cmd/project.go +++ b/cobra/cmd/project.go @@ -91,7 +91,17 @@ func (p *Project) createLicenseFile() error { } func (c *Command) Create() error { - return nil + cmdFile, err := os.Create(fmt.Sprintf("%s/cmd/%s.go", c.Project.AbsolutePath, c.CmdName)) + if err != nil { + return err + } + defer cmdFile.Close() + + commandTemplate := template.Must(template.New("sub").Parse(string(tpl.AddCommandTemplate()))) + err = commandTemplate.Execute(cmdFile, c.Project.AbsolutePath) + if err != nil { + return err + } } // NewProject returns Project with specified project name. From b8ad19ad0d8befeb56daba8874bc17552c4eb405 Mon Sep 17 00:00:00 2001 From: jharshman Date: Wed, 30 Jan 2019 01:34:31 -0800 Subject: [PATCH 049/211] reorder some operations --- cobra/cmd/add.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/cobra/cmd/add.go b/cobra/cmd/add.go index 806a7cc5..599fc604 100644 --- a/cobra/cmd/add.go +++ b/cobra/cmd/add.go @@ -43,17 +43,12 @@ Example: cobra add server -> resulting in a new cmd/server.go`, er("add needs a name for the command") } - commandName := validateCmdName(args[0]) - - if packageName == "" { - // derive packageName - } - wd, err := os.Getwd() if err != nil { er(err) } + commandName := validateCmdName(args[0]) command := &Command{ CmdName: commandName, CmdParent: parentName, From 221bae39865086cc5d0cfbd0c0c4d9853552a9d5 Mon Sep 17 00:00:00 2001 From: jharshman Date: Wed, 30 Jan 2019 01:37:10 -0800 Subject: [PATCH 050/211] depricate package name flag --- cobra/cmd/add.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cobra/cmd/add.go b/cobra/cmd/add.go index 599fc604..4fb46329 100644 --- a/cobra/cmd/add.go +++ b/cobra/cmd/add.go @@ -74,6 +74,7 @@ Example: cobra add server -> resulting in a new cmd/server.go`, func init() { addCmd.Flags().StringVarP(&packageName, "package", "t", "", "target package name (e.g. github.com/spf13/hugo)") addCmd.Flags().StringVarP(&parentName, "parent", "p", "rootCmd", "variable name of parent command for this command") + addCmd.Flags().MarkDeprecated("package", "this operation has been removed.") } // validateCmdName returns source without any dashes and underscore. From 3c42f846c2b95e5a47f3793932a9f98f0e3cc49f Mon Sep 17 00:00:00 2001 From: jharshman Date: Wed, 30 Jan 2019 01:38:24 -0800 Subject: [PATCH 051/211] fix duplicated dir --- cobra/cmd/project.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cobra/cmd/project.go b/cobra/cmd/project.go index 9b56e849..3f7a4c8f 100644 --- a/cobra/cmd/project.go +++ b/cobra/cmd/project.go @@ -91,7 +91,7 @@ func (p *Project) createLicenseFile() error { } func (c *Command) Create() error { - cmdFile, err := os.Create(fmt.Sprintf("%s/cmd/%s.go", c.Project.AbsolutePath, c.CmdName)) + cmdFile, err := os.Create(fmt.Sprintf("%s/%s.go", c.Project.AbsolutePath, c.CmdName)) if err != nil { return err } From 2fea75b02e2e18fab31f754b0f014944b3d9e42d Mon Sep 17 00:00:00 2001 From: jharshman Date: Wed, 30 Jan 2019 01:44:39 -0800 Subject: [PATCH 052/211] vgo - add command working --- cobra/cmd/project.go | 3 ++- cobra/tpl/main.go | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/cobra/cmd/project.go b/cobra/cmd/project.go index 3f7a4c8f..9f8dba90 100644 --- a/cobra/cmd/project.go +++ b/cobra/cmd/project.go @@ -98,10 +98,11 @@ func (c *Command) Create() error { defer cmdFile.Close() commandTemplate := template.Must(template.New("sub").Parse(string(tpl.AddCommandTemplate()))) - err = commandTemplate.Execute(cmdFile, c.Project.AbsolutePath) + err = commandTemplate.Execute(cmdFile, c) if err != nil { return err } + return nil } // NewProject returns Project with specified project name. diff --git a/cobra/tpl/main.go b/cobra/tpl/main.go index ba4d7cc5..71f1f450 100644 --- a/cobra/tpl/main.go +++ b/cobra/tpl/main.go @@ -110,8 +110,8 @@ func initConfig() { func AddCommandTemplate() []byte { return []byte(`/* -{{ .Copyright }} -{{ if .Legal.Header }}{{ .Legal.Header }}{{ end }} +{{ .Project.Copyright }} +{{ if .Project.Legal.Header }}{{ .Project.Legal.Header }}{{ end }} */ package cmd From 0bb1506d255f827d36e044d13fa2b5470bca4009 Mon Sep 17 00:00:00 2001 From: jharshman Date: Wed, 30 Jan 2019 01:45:15 -0800 Subject: [PATCH 053/211] remove commented field in struct --- cobra/cmd/add.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/cobra/cmd/add.go b/cobra/cmd/add.go index 4fb46329..5b0d9e11 100644 --- a/cobra/cmd/add.go +++ b/cobra/cmd/add.go @@ -54,8 +54,6 @@ Example: cobra add server -> resulting in a new cmd/server.go`, CmdParent: parentName, Project: &Project{ AbsolutePath: fmt.Sprintf("%s/cmd", wd), - //AppName: path.Base(packageName), - //PkgName: packageName, Legal: getLicense(), Copyright: copyrightLine(), }, From 303a3e5160b0d2d72ca96ed9eff9e016fe54b2f9 Mon Sep 17 00:00:00 2001 From: jharshman Date: Wed, 30 Jan 2019 01:49:57 -0800 Subject: [PATCH 054/211] vgo - strip out unused methods --- cobra/cmd/project.go | 197 +------------------------------------- cobra/cmd/project_test.go | 23 +---- 2 files changed, 6 insertions(+), 214 deletions(-) diff --git a/cobra/cmd/project.go b/cobra/cmd/project.go index 9f8dba90..852623f6 100644 --- a/cobra/cmd/project.go +++ b/cobra/cmd/project.go @@ -4,9 +4,6 @@ import ( "fmt" "github.com/spf13/cobra/cobra/tpl" "os" - "path/filepath" - "runtime" - "strings" "text/template" ) @@ -20,12 +17,11 @@ type Project struct { Viper bool AppName string - // v1 - absPath string - cmdPath string - srcPath string - license License - name string + //absPath string + //cmdPath string + //srcPath string + //license License + //name string } type Command struct { @@ -104,186 +100,3 @@ func (c *Command) Create() error { } return nil } - -// NewProject returns Project with specified project name. -func NewProject(projectName string) *Project { - if projectName == "" { - er("can't create project with blank name") - } - - p := new(Project) - p.name = projectName - - // 1. Find already created protect. - p.absPath = findPackage(projectName) - - // 2. If there are no created project with this path, and user is in GOPATH, - // then use GOPATH/src/projectName. - if p.absPath == "" { - wd, err := os.Getwd() - if err != nil { - er(err) - } - for _, srcPath := range srcPaths { - goPath := filepath.Dir(srcPath) - if filepathHasPrefix(wd, goPath) { - p.absPath = filepath.Join(srcPath, projectName) - break - } - } - } - - // 3. If user is not in GOPATH, then use (first GOPATH)/src/projectName. - if p.absPath == "" { - p.absPath = filepath.Join(srcPaths[0], projectName) - } - - return p -} - -// findPackage returns full path to existing go package in GOPATHs. -func findPackage(packageName string) string { - if packageName == "" { - return "" - } - - for _, srcPath := range srcPaths { - packagePath := filepath.Join(srcPath, packageName) - if exists(packagePath) { - return packagePath - } - } - - return "" -} - -// NewProjectFromPath returns Project with specified absolute path to -// package. -func NewProjectFromPath(absPath string) *Project { - if absPath == "" { - er("can't create project: absPath can't be blank") - } - if !filepath.IsAbs(absPath) { - er("can't create project: absPath is not absolute") - } - - // If absPath is symlink, use its destination. - fi, err := os.Lstat(absPath) - if err != nil { - er("can't read path info: " + err.Error()) - } - if fi.Mode()&os.ModeSymlink != 0 { - path, err := os.Readlink(absPath) - if err != nil { - er("can't read the destination of symlink: " + err.Error()) - } - absPath = path - } - - p := new(Project) - p.absPath = strings.TrimSuffix(absPath, findCmdDir(absPath)) - p.name = filepath.ToSlash(trimSrcPath(p.absPath, p.SrcPath())) - return p -} - -// trimSrcPath trims at the beginning of absPath the srcPath. -func trimSrcPath(absPath, srcPath string) string { - relPath, err := filepath.Rel(srcPath, absPath) - if err != nil { - er(err) - } - return relPath -} - -// License returns the License object of project. -func (p *Project) License() License { - if p.license.Text == "" && p.license.Name != "None" { - p.license = getLicense() - } - return p.license -} - -// Name returns the name of project, e.g. "github.com/spf13/cobra" -func (p Project) Name() string { - return p.name -} - -// CmdPath returns absolute path to directory, where all commands are located. -func (p *Project) CmdPath() string { - if p.absPath == "" { - return "" - } - if p.cmdPath == "" { - p.cmdPath = filepath.Join(p.absPath, findCmdDir(p.absPath)) - } - return p.cmdPath -} - -// findCmdDir checks if base of absPath is cmd dir and returns it or -// looks for existing cmd dir in absPath. -func findCmdDir(absPath string) string { - if !exists(absPath) || isEmpty(absPath) { - return "cmd" - } - - if isCmdDir(absPath) { - return filepath.Base(absPath) - } - - files, _ := filepath.Glob(filepath.Join(absPath, "c*")) - for _, file := range files { - if isCmdDir(file) { - return filepath.Base(file) - } - } - - return "cmd" -} - -// isCmdDir checks if base of name is one of cmdDir. -func isCmdDir(name string) bool { - name = filepath.Base(name) - for _, cmdDir := range []string{"cmd", "cmds", "command", "commands"} { - if name == cmdDir { - return true - } - } - return false -} - -// AbsPath returns absolute path of project. -func (p Project) AbsPath() string { - return p.absPath -} - -// SrcPath returns absolute path to $GOPATH/src where project is located. -func (p *Project) SrcPath() string { - if p.srcPath != "" { - return p.srcPath - } - if p.absPath == "" { - p.srcPath = srcPaths[0] - return p.srcPath - } - - for _, srcPath := range srcPaths { - if filepathHasPrefix(p.absPath, srcPath) { - p.srcPath = srcPath - break - } - } - - return p.srcPath -} - -func filepathHasPrefix(path string, prefix string) bool { - if len(path) <= len(prefix) { - return false - } - if runtime.GOOS == "windows" { - // Paths in windows are case-insensitive. - return strings.EqualFold(path[0:len(prefix)], prefix) - } - return path[0:len(prefix)] == prefix - -} diff --git a/cobra/cmd/project_test.go b/cobra/cmd/project_test.go index 037f7c55..ed5b054a 100644 --- a/cobra/cmd/project_test.go +++ b/cobra/cmd/project_test.go @@ -1,24 +1,3 @@ package cmd -import ( - "testing" -) - -func TestFindExistingPackage(t *testing.T) { - path := findPackage("github.com/spf13/cobra") - if path == "" { - t.Fatal("findPackage didn't find the existing package") - } - if !hasGoPathPrefix(path) { - t.Fatalf("%q is not in GOPATH, but must be", path) - } -} - -func hasGoPathPrefix(path string) bool { - for _, srcPath := range srcPaths { - if filepathHasPrefix(path, srcPath) { - return true - } - } - return false -} +/* todo: write tests */ From 11aa612384e0e18662f319528bde7b68ffca43ee Mon Sep 17 00:00:00 2001 From: jharshman Date: Wed, 30 Jan 2019 19:47:38 -0800 Subject: [PATCH 055/211] test add --- cobra/cmd/add.go | 8 +-- cobra/cmd/add_test.go | 87 ++++++++----------------------- cobra/cmd/project.go | 2 +- cobra/cmd/testdata/test.go.golden | 27 +++++----- cobra/tpl/main.go | 2 +- 5 files changed, 41 insertions(+), 85 deletions(-) diff --git a/cobra/cmd/add.go b/cobra/cmd/add.go index 5b0d9e11..2d240b8a 100644 --- a/cobra/cmd/add.go +++ b/cobra/cmd/add.go @@ -53,9 +53,9 @@ Example: cobra add server -> resulting in a new cmd/server.go`, CmdName: commandName, CmdParent: parentName, Project: &Project{ - AbsolutePath: fmt.Sprintf("%s/cmd", wd), - Legal: getLicense(), - Copyright: copyrightLine(), + AbsolutePath: wd, + Legal: getLicense(), + Copyright: copyrightLine(), }, } @@ -64,7 +64,7 @@ Example: cobra add server -> resulting in a new cmd/server.go`, er(err) } - fmt.Printf("%s created at %s", command.CmdName, command.Project.AbsolutePath) + fmt.Printf("%s created at %s", command.CmdName, command.AbsolutePath) }, } ) diff --git a/cobra/cmd/add_test.go b/cobra/cmd/add_test.go index 94497084..d9ae0f6d 100644 --- a/cobra/cmd/add_test.go +++ b/cobra/cmd/add_test.go @@ -1,76 +1,32 @@ package cmd -/* -// TestGoldenAddCmd initializes the project "github.com/spf13/testproject" -// in GOPATH, adds "test" command -// and compares the content of all files in cmd directory of testproject -// with appropriate golden files. -// Use -update to update existing golden files. +import ( + "fmt" + "os" + "testing" +) + func TestGoldenAddCmd(t *testing.T) { - projectName := "github.com/spf13/testproject" - project := NewProject(projectName) - defer os.RemoveAll(project.AbsPath()) - viper.Set("author", "NAME HERE ") - viper.Set("license", "apache") - viper.Set("year", 2017) - defer viper.Set("author", nil) - defer viper.Set("license", nil) - defer viper.Set("year", nil) + wd, _ := os.Getwd() + command := &Command{ + CmdName: "test", + CmdParent: parentName, + Project: &Project{ + AbsolutePath: fmt.Sprintf("%s/testproject", wd), + Legal: getLicense(), + Copyright: copyrightLine(), + }, + } - // Initialize the project first. - initializeProject(project) - - // Then add the "test" command. - cmdName := "test" - cmdPath := filepath.Join(project.CmdPath(), cmdName+".go") - createCmdFile(project.License(), cmdPath, cmdName) - - expectedFiles := []string{".", "root.go", "test.go"} - gotFiles := []string{} - - // Check project file hierarchy and compare the content of every single file - // with appropriate golden file. - err := filepath.Walk(project.CmdPath(), func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - - // Make path relative to project.CmdPath(). - // E.g. path = "/home/user/go/src/github.com/spf13/testproject/cmd/root.go" - // then it returns just "root.go". - relPath, err := filepath.Rel(project.CmdPath(), path) - if err != nil { - return err - } - relPath = filepath.ToSlash(relPath) - gotFiles = append(gotFiles, relPath) - goldenPath := filepath.Join("testdata", filepath.Base(path)+".golden") - - switch relPath { - // Known directories. - case ".": - return nil - // Known files. - case "root.go", "test.go": - if *update { - got, err := ioutil.ReadFile(path) - if err != nil { - return err - } - ioutil.WriteFile(goldenPath, got, 0644) - } - return compareFiles(path, goldenPath) - } - // Unknown file. - return errors.New("unknown file: " + path) - }) - if err != nil { + if err := command.Create(); err != nil { t.Fatal(err) } - // Check if some files lack. - if err := checkLackFiles(expectedFiles, gotFiles); err != nil { + generatedFile := fmt.Sprintf("%s/cmd/%s.go", command.AbsolutePath, command.CmdName) + goldenFile := fmt.Sprintf("testdata/%s.go.golden", command.CmdName) + err := compareFiles(generatedFile, goldenFile) + if err != nil { t.Fatal(err) } } @@ -98,4 +54,3 @@ func TestValidateCmdName(t *testing.T) { } } } -*/ diff --git a/cobra/cmd/project.go b/cobra/cmd/project.go index 852623f6..167c55ff 100644 --- a/cobra/cmd/project.go +++ b/cobra/cmd/project.go @@ -87,7 +87,7 @@ func (p *Project) createLicenseFile() error { } func (c *Command) Create() error { - cmdFile, err := os.Create(fmt.Sprintf("%s/%s.go", c.Project.AbsolutePath, c.CmdName)) + cmdFile, err := os.Create(fmt.Sprintf("%s/cmd/%s.go", c.AbsolutePath, c.CmdName)) if err != nil { return err } diff --git a/cobra/cmd/testdata/test.go.golden b/cobra/cmd/testdata/test.go.golden index ed644275..fb8e0fa9 100644 --- a/cobra/cmd/testdata/test.go.golden +++ b/cobra/cmd/testdata/test.go.golden @@ -1,17 +1,18 @@ -// Copyright © 2017 NAME HERE -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +/* +Copyright © 2019 NAME HERE +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ package cmd import ( diff --git a/cobra/tpl/main.go b/cobra/tpl/main.go index 71f1f450..5e5a0fae 100644 --- a/cobra/tpl/main.go +++ b/cobra/tpl/main.go @@ -111,7 +111,7 @@ func initConfig() { func AddCommandTemplate() []byte { return []byte(`/* {{ .Project.Copyright }} -{{ if .Project.Legal.Header }}{{ .Project.Legal.Header }}{{ end }} +{{ if .Legal.Header }}{{ .Legal.Header }}{{ end }} */ package cmd From 984374f5b622efc94388e2872915c2d97404adba Mon Sep 17 00:00:00 2001 From: jharshman Date: Wed, 30 Jan 2019 20:09:57 -0800 Subject: [PATCH 056/211] clean up testproject files after test executes --- cobra/cmd/add_test.go | 13 +++++++++++++ cobra/cmd/init_test.go | 7 +++++++ 2 files changed, 20 insertions(+) diff --git a/cobra/cmd/add_test.go b/cobra/cmd/add_test.go index d9ae0f6d..0de1d221 100644 --- a/cobra/cmd/add_test.go +++ b/cobra/cmd/add_test.go @@ -16,9 +16,22 @@ func TestGoldenAddCmd(t *testing.T) { AbsolutePath: fmt.Sprintf("%s/testproject", wd), Legal: getLicense(), Copyright: copyrightLine(), + + // required to init + AppName: "testproject", + PkgName: "github.com/spf13/testproject", + Viper: true, }, } + // 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 77145fcb..9540b2d3 100644 --- a/cobra/cmd/init_test.go +++ b/cobra/cmd/init_test.go @@ -8,6 +8,7 @@ import ( ) func TestGoldenInitCmd(t *testing.T) { + wd, _ := os.Getwd() project := &Project{ AbsolutePath: fmt.Sprintf("%s/testproject", wd), @@ -23,6 +24,12 @@ func TestGoldenInitCmd(t *testing.T) { 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) From 2411ac592a8a714d324edd1abc880a55c17a9de0 Mon Sep 17 00:00:00 2001 From: Joshua Harshman Date: Tue, 7 May 2019 11:22:21 -0600 Subject: [PATCH 057/211] remove unused struct fields --- cobra/cmd/project.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/cobra/cmd/project.go b/cobra/cmd/project.go index 167c55ff..dd2f7ea2 100644 --- a/cobra/cmd/project.go +++ b/cobra/cmd/project.go @@ -16,12 +16,6 @@ type Project struct { Legal License Viper bool AppName string - - //absPath string - //cmdPath string - //srcPath string - //license License - //name string } type Command struct { From 9eb9f5c66b643a25cb4c468e3a585c4d3a31c689 Mon Sep 17 00:00:00 2001 From: ialidzhikov Date: Sun, 14 Apr 2019 21:35:42 +0300 Subject: [PATCH 058/211] Add gardenctl to projects build using Cobra Signed-off-by: ialidzhikov --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 8a9ace4c..bce0fa7a 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ Many of the most widely used Go projects are built using Cobra, such as: [Istio](https://istio.io), [Prototool](https://github.com/uber/prototool), [mattermost-server](https://github.com/mattermost/mattermost-server), +[Gardener](https://github.com/gardener/gardenctl), etc. [![Build Status](https://travis-ci.org/spf13/cobra.svg "Travis CI status")](https://travis-ci.org/spf13/cobra) From 5f23f55c8183489b4e881ff0dc99ac6a746baa0c Mon Sep 17 00:00:00 2001 From: Go Frendi Gunawan Date: Tue, 4 Jun 2019 11:25:58 +0700 Subject: [PATCH 059/211] Update README.md To avoid confusion, it is better to use `localCmd` instead of `rootCmd` --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bce0fa7a..60c5a425 100644 --- a/README.md +++ b/README.md @@ -338,7 +338,7 @@ rootCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "verbose out A flag can also be assigned locally which will only apply to that specific command. ```go -rootCmd.Flags().StringVarP(&Source, "source", "s", "", "Source directory to read from") +localCmd.Flags().StringVarP(&Source, "source", "s", "", "Source directory to read from") ``` ### Local Flag on Parent Commands From 4a716d101b39c61535dbb10aa765dfee95b41085 Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Mon, 11 Feb 2019 07:10:59 +0100 Subject: [PATCH 060/211] Extending redirection to stdout, stderr, stdin --- command.go | 69 +++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 61 insertions(+), 8 deletions(-) diff --git a/command.go b/command.go index b257f91b..a00367d3 100644 --- a/command.go +++ b/command.go @@ -177,8 +177,6 @@ type Command struct { // that we can use on every pflag set and children commands globNormFunc func(f *flag.FlagSet, name string) flag.NormalizedName - // output is an output writer defined by user. - output io.Writer // usageFunc is usage func defined by user. usageFunc func(*Command) error // usageTemplate is usage template defined by user. @@ -195,6 +193,13 @@ type Command struct { helpCommand *Command // versionTemplate is the version template defined by user. versionTemplate string + + // inReader is a reader defined by the user that replaces stdin + inReader io.Reader + // outWriter is a writer defined by the user that replaces stdout + outWriter io.Writer + // errWriter is a writer defined by the user that replaces stderr + errWriter io.Writer } // SetArgs sets arguments for the command. It is set to os.Args[1:] by default, if desired, can be overridden @@ -206,7 +211,25 @@ func (c *Command) SetArgs(a []string) { // SetOutput sets the destination for usage and error messages. // If output is nil, os.Stderr is used. func (c *Command) SetOutput(output io.Writer) { - c.output = output + c.outWriter = output +} + +// SetOut sets the destination for usage messages. +// If newOut is nil, os.Stdout is used. +func (c *Command) SetOut(newOut io.Writer) { + c.outWriter = newOut +} + +// SetErr sets the destination for error messages. +// If newErr is nil, os.Stderr is used. +func (c *Command) SetErr(newErr io.Writer) { + c.errWriter = newErr +} + +// SetOut sets the source for input data +// If newIn is nil, os.Stdin is used. +func (c *Command) SetIn(newIn io.Reader) { + c.inReader = newIn } // SetUsageFunc sets usage function. Usage can be defined by application. @@ -267,9 +290,19 @@ func (c *Command) OutOrStderr() io.Writer { return c.getOut(os.Stderr) } +// ErrOrStderr returns output to stderr +func (c *Command) ErrOrStderr() io.Writer { + return c.getErr(os.Stderr) +} + +// ErrOrStderr returns output to stderr +func (c *Command) InOrStdin() io.Reader { + return c.getIn(os.Stdin) +} + func (c *Command) getOut(def io.Writer) io.Writer { - if c.output != nil { - return c.output + if c.outWriter != nil { + return c.outWriter } if c.HasParent() { return c.parent.getOut(def) @@ -277,6 +310,26 @@ func (c *Command) getOut(def io.Writer) io.Writer { return def } +func (c *Command) getErr(def io.Writer) io.Writer { + if c.errWriter != nil { + return c.errWriter + } + if c.HasParent() { + return c.parent.getErr(def) + } + return def +} + +func (c *Command) getIn(def io.Reader) io.Reader { + if c.inReader != nil { + return c.inReader + } + if c.HasParent() { + return c.parent.getIn(def) + } + return def +} + // UsageFunc returns either the function set by SetUsageFunc for this command // or a parent, or it returns a default usage function. func (c *Command) UsageFunc() (f func(*Command) error) { @@ -331,11 +384,11 @@ func (c *Command) Help() error { // UsageString return usage string. func (c *Command) UsageString() string { - tmpOutput := c.output + tmpOutput := c.outWriter bb := new(bytes.Buffer) - c.SetOutput(bb) + c.outWriter = bb c.Usage() - c.output = tmpOutput + c.outWriter = tmpOutput return bb.String() } From 0ea93dd60d8a44862790c8751955bdf65be0c4ce Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Mon, 11 Feb 2019 08:22:54 +0100 Subject: [PATCH 061/211] Fixed linter issues --- command.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/command.go b/command.go index a00367d3..58cc8732 100644 --- a/command.go +++ b/command.go @@ -217,12 +217,12 @@ func (c *Command) SetOutput(output io.Writer) { // SetOut sets the destination for usage messages. // If newOut is nil, os.Stdout is used. func (c *Command) SetOut(newOut io.Writer) { - c.outWriter = newOut + c.outWriter = newOut } // SetErr sets the destination for error messages. // If newErr is nil, os.Stderr is used. -func (c *Command) SetErr(newErr io.Writer) { +func (c *Command) SetErr(newErr io.Writer) { c.errWriter = newErr } From 618bc00f8084be91329cbceb8094a7e5190a6c29 Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Mon, 11 Feb 2019 16:06:55 +0100 Subject: [PATCH 062/211] Allow for explicit output to err/stderr --- command.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/command.go b/command.go index 58cc8732..3e97687a 100644 --- a/command.go +++ b/command.go @@ -1121,6 +1121,21 @@ func (c *Command) Printf(format string, i ...interface{}) { c.Print(fmt.Sprintf(format, i...)) } +// PrintErr is a convenience method to Print to the defined Err output, fallback to Stderr if not set. +func (c *Command) PrintErr(i ...interface{}) { + fmt.Fprint(c.ErrOrStderr(), i...) +} + +// PrintErrln is a convenience method to Println to the defined Err output, fallback to Stderr if not set. +func (c *Command) PrintErrln(i ...interface{}) { + c.Print(fmt.Sprintln(i...)) +} + +// PrintErrf is a convenience method to Printf to the defined Err output, fallback to Stderr if not set. +func (c *Command) PrintErrf(format string, i ...interface{}) { + c.Print(fmt.Sprintf(format, i...)) +} + // CommandPath returns the full path to this command. func (c *Command) CommandPath() string { if c.HasParent() { From cb27ce11fba1a33e42233dea2658805c62a17e74 Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Wed, 13 Feb 2019 06:32:16 +0100 Subject: [PATCH 063/211] Deprecate and maintain backwards compatibility --- command.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/command.go b/command.go index 3e97687a..6dbb3e8c 100644 --- a/command.go +++ b/command.go @@ -210,8 +210,10 @@ func (c *Command) SetArgs(a []string) { // SetOutput sets the destination for usage and error messages. // If output is nil, os.Stderr is used. +// Deprecated: Use SetOut and/or SetErr instead func (c *Command) SetOutput(output io.Writer) { c.outWriter = output + c.errWriter = output } // SetOut sets the destination for usage messages. From e35034f0daec26bfcbfd2b6fb258b359a611b6e7 Mon Sep 17 00:00:00 2001 From: Alessio Treglia Date: Tue, 30 Apr 2019 18:41:56 +0100 Subject: [PATCH 064/211] Add tests --- command_test.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/command_test.go b/command_test.go index 6e483a3e..4f1d3690 100644 --- a/command_test.go +++ b/command_test.go @@ -1381,6 +1381,30 @@ func TestSetOutput(t *testing.T) { } } +func TestSetOut(t *testing.T) { + c := &Command{} + c.SetOut(nil) + if out := c.OutOrStdout(); out != os.Stdout { + t.Errorf("Expected setting output to nil to revert back to stdout") + } +} + +func TestSetErr(t *testing.T) { + c := &Command{} + c.SetErr(nil) + if out := c.ErrOrStderr(); out != os.Stderr { + t.Errorf("Expected setting error to nil to revert back to stderr") + } +} + +func TestSetIn(t *testing.T) { + c := &Command{} + c.SetIn(nil) + if out := c.InOrStdin(); out != os.Stdin { + t.Errorf("Expected setting input to nil to revert back to stdin") + } +} + func TestFlagErrorFunc(t *testing.T) { c := &Command{Use: "c", Run: emptyRun} From b6357260812dd09c49dc7d55042f0c795b419f6b Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Wed, 15 May 2019 18:49:16 +0200 Subject: [PATCH 065/211] considering stderr in UsageString --- command.go | 11 ++++++++++- command_test.go | 16 ++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/command.go b/command.go index 6dbb3e8c..c7e89830 100644 --- a/command.go +++ b/command.go @@ -384,13 +384,22 @@ func (c *Command) Help() error { return nil } -// UsageString return usage string. +// UsageString returns usage string. func (c *Command) UsageString() string { + // Storing normal writers tmpOutput := c.outWriter + tmpErr := c.errWriter + bb := new(bytes.Buffer) c.outWriter = bb + c.errWriter = bb + c.Usage() + + // Setting things back to normal c.outWriter = tmpOutput + c.errWriter = tmpErr + return bb.String() } diff --git a/command_test.go b/command_test.go index 4f1d3690..258f20a2 100644 --- a/command_test.go +++ b/command_test.go @@ -1405,6 +1405,22 @@ func TestSetIn(t *testing.T) { } } +func TestUsageStringRedirected(t *testing.T) { + c := &Command{} + + c.usageFunc = func(cmd *Command) error { + cmd.Print("[stdout1]") + cmd.PrintErr("[stderr2]") + cmd.Print("[stdout3]") + return nil; + } + + expected := "[stdout1][stderr2][stdout3]" + if got := c.UsageString(); got != expected { + t.Errorf("Expected usage string to consider both stdout and stderr") + } +} + func TestFlagErrorFunc(t *testing.T) { c := &Command{Use: "c", Run: emptyRun} From f2b07da1e2c38d5f12845a4f607e2e1018cbb1f5 Mon Sep 17 00:00:00 2001 From: Juan Leni Date: Wed, 15 May 2019 18:53:39 +0200 Subject: [PATCH 066/211] fixing linter issues --- command_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/command_test.go b/command_test.go index 258f20a2..2fa2003c 100644 --- a/command_test.go +++ b/command_test.go @@ -1412,11 +1412,11 @@ func TestUsageStringRedirected(t *testing.T) { cmd.Print("[stdout1]") cmd.PrintErr("[stderr2]") cmd.Print("[stdout3]") - return nil; + return nil } expected := "[stdout1][stderr2][stdout3]" - if got := c.UsageString(); got != expected { + if got := c.UsageString(); got != expected { t.Errorf("Expected usage string to consider both stdout and stderr") } } From 21f39ca07ee7bf0c29e1bbdb2ab35df70c64b4b2 Mon Sep 17 00:00:00 2001 From: rsteube Date: Thu, 11 Jul 2019 20:18:47 +0200 Subject: [PATCH 067/211] bash: fix shellcheck errors (#889) https://github.com/koalaman/shellcheck/wiki/SC2207 https://github.com/koalaman/shellcheck/wiki/SC2164 --- bash_completions.go | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/bash_completions.go b/bash_completions.go index 57bb8e1b..03ddda27 100644 --- a/bash_completions.go +++ b/bash_completions.go @@ -72,7 +72,9 @@ __%[1]s_handle_reply() else allflags=("${flags[*]} ${two_word_flags[*]}") fi - COMPREPLY=( $(compgen -W "${allflags[*]}" -- "$cur") ) + while IFS='' read -r c; do + COMPREPLY+=("$c") + done < <(compgen -W "${allflags[*]}" -- "$cur") if [[ $(type -t compopt) = "builtin" ]]; then [[ "${COMPREPLY[0]}" == *= ]] || compopt +o nospace fi @@ -122,10 +124,14 @@ __%[1]s_handle_reply() if [[ ${#must_have_one_flag[@]} -ne 0 ]]; then completions+=("${must_have_one_flag[@]}") fi - COMPREPLY=( $(compgen -W "${completions[*]}" -- "$cur") ) + while IFS='' read -r c; do + COMPREPLY+=("$c") + done < <(compgen -W "${completions[*]}" -- "$cur") if [[ ${#COMPREPLY[@]} -eq 0 && ${#noun_aliases[@]} -gt 0 && ${#must_have_one_noun[@]} -ne 0 ]]; then - COMPREPLY=( $(compgen -W "${noun_aliases[*]}" -- "$cur") ) + while IFS='' read -r c; do + COMPREPLY+=("$c") + done < <(compgen -W "${noun_aliases[*]}" -- "$cur") fi if [[ ${#COMPREPLY[@]} -eq 0 ]]; then @@ -160,7 +166,7 @@ __%[1]s_handle_filename_extension_flag() __%[1]s_handle_subdirs_in_dir_flag() { local dir="$1" - pushd "${dir}" >/dev/null 2>&1 && _filedir -d && popd >/dev/null 2>&1 + pushd "${dir}" >/dev/null 2>&1 && _filedir -d && popd >/dev/null 2>&1 || return } __%[1]s_handle_flag() From 9a31ddff0ea7ee4045c71a113ff135917752383b Mon Sep 17 00:00:00 2001 From: "Carol A. Scott" Date: Fri, 12 Jul 2019 08:55:10 -0700 Subject: [PATCH 068/211] Add Linkerd to list of projects using Cobra (#892) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 60c5a425..1c4d6bc5 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ Many of the most widely used Go projects are built using Cobra, such as: [Prototool](https://github.com/uber/prototool), [mattermost-server](https://github.com/mattermost/mattermost-server), [Gardener](https://github.com/gardener/gardenctl), +[Linkerd](https://linkerd.io/), etc. [![Build Status](https://travis-ci.org/spf13/cobra.svg "Travis CI status")](https://travis-ci.org/spf13/cobra) From 2d7544ebdeb5f926805c57af8dccc6c359de28d4 Mon Sep 17 00:00:00 2001 From: umarcor <38422348+umarcor@users.noreply.github.com> Date: Mon, 15 Jul 2019 17:44:15 +0200 Subject: [PATCH 069/211] fix missing newline in cmd/add (#905) --- cobra/cmd/add.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cobra/cmd/add.go b/cobra/cmd/add.go index 2d240b8a..6645a755 100644 --- a/cobra/cmd/add.go +++ b/cobra/cmd/add.go @@ -64,7 +64,7 @@ Example: cobra add server -> resulting in a new cmd/server.go`, er(err) } - fmt.Printf("%s created at %s", command.CmdName, command.AbsolutePath) + fmt.Printf("%s created at %s\n", command.CmdName, command.AbsolutePath) }, } ) From 1c9c46d5c1cc2aaebdd1898c0680e85e8a44b36d Mon Sep 17 00:00:00 2001 From: Joshua Harshman Date: Wed, 24 Jul 2019 10:10:51 -0600 Subject: [PATCH 070/211] Update Generator Docs to reflect changes brought in #817 (#904) --- cobra/README.md | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/cobra/README.md b/cobra/README.md index 6054f95c..c360fdac 100644 --- a/cobra/README.md +++ b/cobra/README.md @@ -16,11 +16,23 @@ for you. It is a very powerful application that will populate your program with the right structure so you can immediately enjoy all the benefits of Cobra. It will also automatically apply the license you specify to your application. -Cobra init is pretty smart. You can provide it a full path, or simply a path -similar to what is expected in the import. +Cobra init is pretty smart. You can either run it in your current application directory +or you can specify a relative path to an existing project. If the directory does not exist, it will be created for you. + +Updates to the Cobra generator have now decoupled it from the GOPATH. +As such `--pkg-name` is required. + +**Note:** init will no longer fail on non-empty directories. ``` -cobra init github.com/spf13/newApp +mkdir -p newApp && cd newApp +cobra init --pkg-name github.com/spf13/newApp +``` + +or + +``` +cobra init --pkg-name github.com/spf13/newApp path/to/newApp ``` ### cobra add From af29f95b816b2d9ac6ed548f991dc3acca113280 Mon Sep 17 00:00:00 2001 From: Bruce Downs Date: Mon, 29 Jul 2019 23:32:32 -0700 Subject: [PATCH 071/211] Add ignore of cobra posix binary and all of intellij generated files --- .gitignore | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 3b053c59..b2b848e7 100644 --- a/.gitignore +++ b/.gitignore @@ -32,7 +32,8 @@ Session.vim tags *.exe - +cobra cobra.test -.idea/* +.idea/ +*.iml From 955267993934d057ed4c6e9785a446c2dbf012da Mon Sep 17 00:00:00 2001 From: Bruce Downs Date: Mon, 29 Jul 2019 23:34:42 -0700 Subject: [PATCH 072/211] Add idiomatic handling of go error in distinct main func --- cobra/cmd/root.go | 4 ++-- cobra/main.go | 14 ++++++++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/cobra/cmd/root.go b/cobra/cmd/root.go index 624c717c..97f404bb 100644 --- a/cobra/cmd/root.go +++ b/cobra/cmd/root.go @@ -36,8 +36,8 @@ to quickly create a Cobra application.`, ) // Execute executes the root command. -func Execute() { - rootCmd.Execute() +func Execute() error { + return rootCmd.Execute() } func init() { diff --git a/cobra/main.go b/cobra/main.go index c3a9d9cb..8add69a8 100644 --- a/cobra/main.go +++ b/cobra/main.go @@ -13,8 +13,18 @@ package main -import "github.com/spf13/cobra/cobra/cmd" +import ( + "os" + + "github.com/spf13/cobra/cobra/cmd" +) func main() { - cmd.Execute() + if err := runMain(); err != nil { + os.Exit(1) + } +} + +func runMain() error { + return cmd.Execute() } From 9334a46bd6b3887f3561d705440038ec93b7f62e Mon Sep 17 00:00:00 2001 From: Bruce Downs Date: Mon, 29 Jul 2019 23:36:50 -0700 Subject: [PATCH 073/211] Return an error in the case of unrunnable subcommand * credit to @chriswhelix for initial commit --- command.go | 16 ++++++++++++++-- command_test.go | 4 ++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/command.go b/command.go index c7e89830..05fd9f34 100644 --- a/command.go +++ b/command.go @@ -17,6 +17,7 @@ package cobra import ( "bytes" + "errors" "fmt" "io" "os" @@ -27,6 +28,9 @@ import ( flag "github.com/spf13/pflag" ) +// NotRunnable defines subcommand error +var NotRunnable = errors.New("subcommand is required") + // FParseErrWhitelist configures Flag parse errors to be ignored type FParseErrWhitelist flag.ParseErrorsWhitelist @@ -369,7 +373,7 @@ func (c *Command) HelpFunc() func(*Command, []string) { } return func(c *Command, a []string) { c.mergePersistentFlags() - err := tmpl(c.OutOrStdout(), c.HelpTemplate(), c) + err := tmpl(c.OutOrStderr(), c.HelpTemplate(), c) if err != nil { c.Println(err) } @@ -786,7 +790,7 @@ func (c *Command) execute(a []string) (err error) { } if !c.Runnable() { - return flag.ErrHelp + return NotRunnable } c.preRun() @@ -920,6 +924,14 @@ 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 == NotRunnable { + 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 2fa2003c..e7961c8f 100644 --- a/command_test.go +++ b/command_test.go @@ -836,8 +836,8 @@ func TestHelpExecutedOnNonRunnableChild(t *testing.T) { rootCmd.AddCommand(childCmd) output, err := executeCommand(rootCmd, "child") - if err != nil { - t.Errorf("Unexpected error: %v", err) + if err != NotRunnable { + t.Errorf("Expected error") } checkStringContains(t, output, childCmd.Long) From 51f06c7dd1e73470976107fc6931b21143b83676 Mon Sep 17 00:00:00 2001 From: Bruce Downs Date: Tue, 30 Jul 2019 15:26:11 -0700 Subject: [PATCH 074/211] Correct all complaints from golint * i.e. * go get golang.org/x/lint/golint * go list ./... | xargs golint --- args.go | 1 + cobra.go | 4 ++-- cobra/cmd/project.go | 7 +++++-- cobra/tpl/main.go | 3 +++ command.go | 13 +++++++------ command_test.go | 2 +- 6 files changed, 19 insertions(+), 11 deletions(-) diff --git a/args.go b/args.go index c4d820b8..e5a34c16 100644 --- a/args.go +++ b/args.go @@ -4,6 +4,7 @@ import ( "fmt" ) +// PositionalArgs defines positional arguments callback type PositionalArgs func(cmd *Command, args []string) error // Legacy arg validation has the following behaviour: diff --git a/cobra.go b/cobra.go index 6505c070..d01becc8 100644 --- a/cobra.go +++ b/cobra.go @@ -52,7 +52,7 @@ var EnableCommandSorting = true // if the CLI is started from explorer.exe. // To disable the mousetrap, just set this variable to blank string (""). // Works only on Microsoft Windows. -var MousetrapHelpText string = `This is a command line tool. +var MousetrapHelpText = `This is a command line tool. You need to open cmd.exe and run it from there. ` @@ -61,7 +61,7 @@ You need to open cmd.exe and run it from there. // if the CLI is started from explorer.exe. Set to 0 to wait for the return key to be pressed. // To disable the mousetrap, just set MousetrapHelpText to blank string (""). // Works only on Microsoft Windows. -var MousetrapDisplayDuration time.Duration = 5 * time.Second +var MousetrapDisplayDuration = 5 * time.Second // AddTemplateFunc adds a template function that's available to Usage and Help // template generation. diff --git a/cobra/cmd/project.go b/cobra/cmd/project.go index dd2f7ea2..a71cae31 100644 --- a/cobra/cmd/project.go +++ b/cobra/cmd/project.go @@ -2,9 +2,10 @@ package cmd import ( "fmt" - "github.com/spf13/cobra/cobra/tpl" "os" "text/template" + + "github.com/spf13/cobra/cobra/tpl" ) // Project contains name, license and paths to projects. @@ -18,14 +19,15 @@ type Project struct { AppName string } +// Command structure type Command struct { CmdName string CmdParent string *Project } +// Create project receiver func (p *Project) Create() error { - // check if AbsolutePath exists if _, err := os.Stat(p.AbsolutePath); os.IsNotExist(err) { // create directory @@ -80,6 +82,7 @@ func (p *Project) createLicenseFile() error { return licenseTemplate.Execute(licenseFile, data) } +// Create command receiver func (c *Command) Create() error { cmdFile, err := os.Create(fmt.Sprintf("%s/cmd/%s.go", c.AbsolutePath, c.CmdName)) if err != nil { diff --git a/cobra/tpl/main.go b/cobra/tpl/main.go index 5e5a0fae..f6fe8977 100644 --- a/cobra/tpl/main.go +++ b/cobra/tpl/main.go @@ -1,5 +1,6 @@ package tpl +// MainTemplate defines main template string func MainTemplate() []byte { return []byte(`/* {{ .Copyright }} @@ -15,6 +16,7 @@ func main() { `) } +// RootTemplate defines root template string func RootTemplate() []byte { return []byte(`/* {{ .Copyright }} @@ -108,6 +110,7 @@ func initConfig() { `) } +// AddCommandTemplate defines add command template string func AddCommandTemplate() []byte { return []byte(`/* {{ .Project.Copyright }} diff --git a/command.go b/command.go index 05fd9f34..ca6dc139 100644 --- a/command.go +++ b/command.go @@ -28,8 +28,8 @@ import ( flag "github.com/spf13/pflag" ) -// NotRunnable defines subcommand error -var NotRunnable = errors.New("subcommand is required") +// ErrSubCommandRequired defines subcommand error +var ErrSubCommandRequired = errors.New("subcommand is required") // FParseErrWhitelist configures Flag parse errors to be ignored type FParseErrWhitelist flag.ParseErrorsWhitelist @@ -232,7 +232,7 @@ func (c *Command) SetErr(newErr io.Writer) { c.errWriter = newErr } -// SetOut sets the source for input data +// SetIn sets the source for input data // If newIn is nil, os.Stdin is used. func (c *Command) SetIn(newIn io.Reader) { c.inReader = newIn @@ -301,7 +301,7 @@ func (c *Command) ErrOrStderr() io.Writer { return c.getErr(os.Stderr) } -// ErrOrStderr returns output to stderr +// InOrStdin returns output to stderr func (c *Command) InOrStdin() io.Reader { return c.getIn(os.Stdin) } @@ -790,7 +790,7 @@ func (c *Command) execute(a []string) (err error) { } if !c.Runnable() { - return NotRunnable + return ErrSubCommandRequired } c.preRun() @@ -927,7 +927,7 @@ func (c *Command) ExecuteC() (cmd *Command, err error) { // 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 == NotRunnable { + if err == ErrSubCommandRequired { cmd.HelpFunc()(cmd, args) return cmd, err } @@ -947,6 +947,7 @@ func (c *Command) ExecuteC() (cmd *Command, err error) { return cmd, err } +// ValidateArgs validates arguments func (c *Command) ValidateArgs(args []string) error { if c.Args == nil { return nil diff --git a/command_test.go b/command_test.go index e7961c8f..b26bd4ab 100644 --- a/command_test.go +++ b/command_test.go @@ -836,7 +836,7 @@ func TestHelpExecutedOnNonRunnableChild(t *testing.T) { rootCmd.AddCommand(childCmd) output, err := executeCommand(rootCmd, "child") - if err != NotRunnable { + if err != ErrSubCommandRequired { t.Errorf("Expected error") } From d85196337cc9d017cd24c1b0416eb1494e2aef26 Mon Sep 17 00:00:00 2001 From: Bruce Downs Date: Tue, 30 Jul 2019 15:32:58 -0700 Subject: [PATCH 075/211] Correct all complaints from goimports * i.e. * go get golang.org/x/tools/cmd/goimports * goimports -w *.go * goimports -w cobra/ --- cobra/cmd/init.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cobra/cmd/init.go b/cobra/cmd/init.go index 63397d11..dcf5ada4 100644 --- a/cobra/cmd/init.go +++ b/cobra/cmd/init.go @@ -15,10 +15,11 @@ package cmd import ( "fmt" - "github.com/spf13/cobra" - "github.com/spf13/viper" "os" "path" + + "github.com/spf13/cobra" + "github.com/spf13/viper" ) var ( From 993cc5372a05240dfd59e3ba952748b36b2cd117 Mon Sep 17 00:00:00 2001 From: Bruce Downs Date: Thu, 1 Aug 2019 09:47:14 -0700 Subject: [PATCH 076/211] Adjustments per PR review feedback from @bogem --- args.go | 1 - cobra/cmd/project.go | 3 --- cobra/main.go | 6 +----- cobra/tpl/main.go | 3 --- command.go | 2 -- 5 files changed, 1 insertion(+), 14 deletions(-) diff --git a/args.go b/args.go index e5a34c16..c4d820b8 100644 --- a/args.go +++ b/args.go @@ -4,7 +4,6 @@ import ( "fmt" ) -// PositionalArgs defines positional arguments callback type PositionalArgs func(cmd *Command, args []string) error // Legacy arg validation has the following behaviour: diff --git a/cobra/cmd/project.go b/cobra/cmd/project.go index a71cae31..a53893cc 100644 --- a/cobra/cmd/project.go +++ b/cobra/cmd/project.go @@ -19,14 +19,12 @@ type Project struct { AppName string } -// Command structure type Command struct { CmdName string CmdParent string *Project } -// Create project receiver func (p *Project) Create() error { // check if AbsolutePath exists if _, err := os.Stat(p.AbsolutePath); os.IsNotExist(err) { @@ -82,7 +80,6 @@ func (p *Project) createLicenseFile() error { return licenseTemplate.Execute(licenseFile, data) } -// Create command receiver func (c *Command) Create() error { cmdFile, err := os.Create(fmt.Sprintf("%s/cmd/%s.go", c.AbsolutePath, c.CmdName)) if err != nil { diff --git a/cobra/main.go b/cobra/main.go index 8add69a8..eeaf9824 100644 --- a/cobra/main.go +++ b/cobra/main.go @@ -20,11 +20,7 @@ import ( ) func main() { - if err := runMain(); err != nil { + if err := cmd.Execute(); err != nil { os.Exit(1) } } - -func runMain() error { - return cmd.Execute() -} diff --git a/cobra/tpl/main.go b/cobra/tpl/main.go index f6fe8977..5e5a0fae 100644 --- a/cobra/tpl/main.go +++ b/cobra/tpl/main.go @@ -1,6 +1,5 @@ package tpl -// MainTemplate defines main template string func MainTemplate() []byte { return []byte(`/* {{ .Copyright }} @@ -16,7 +15,6 @@ func main() { `) } -// RootTemplate defines root template string func RootTemplate() []byte { return []byte(`/* {{ .Copyright }} @@ -110,7 +108,6 @@ func initConfig() { `) } -// AddCommandTemplate defines add command template string func AddCommandTemplate() []byte { return []byte(`/* {{ .Project.Copyright }} diff --git a/command.go b/command.go index ca6dc139..42e500de 100644 --- a/command.go +++ b/command.go @@ -28,7 +28,6 @@ import ( flag "github.com/spf13/pflag" ) -// ErrSubCommandRequired defines subcommand error var ErrSubCommandRequired = errors.New("subcommand is required") // FParseErrWhitelist configures Flag parse errors to be ignored @@ -947,7 +946,6 @@ func (c *Command) ExecuteC() (cmd *Command, err error) { return cmd, err } -// ValidateArgs validates arguments func (c *Command) ValidateArgs(args []string) error { if c.Args == nil { return nil From b80588d523ec50c7fee20218426cf2ff70920f06 Mon Sep 17 00:00:00 2001 From: Gregoire Mahe <31134503+gregoiremahe@users.noreply.github.com> Date: Mon, 5 Aug 2019 17:56:17 +0200 Subject: [PATCH 077/211] fix undefined cfgFile in documentation (#924) --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 1c4d6bc5..649e2e2f 100644 --- a/README.md +++ b/README.md @@ -218,6 +218,8 @@ import ( "github.com/spf13/viper" ) +var cfgFile string + func init() { cobra.OnInitialize(initConfig) rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.cobra.yaml)") 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 078/211] 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 079/211] 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 080/211] 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 081/211] 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 082/211] 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 083/211] 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 084/211] 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 085/211] 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 086/211] 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 087/211] 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 088/211] 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 089/211] 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 090/211] 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 091/211] 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 092/211] 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 093/211] 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 094/211] 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 095/211] 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 096/211] 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 097/211] 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 098/211] 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 099/211] 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 100/211] 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 101/211] 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 102/211] 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 103/211] 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 104/211] 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 105/211] 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 106/211] 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 107/211] 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 108/211] 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 109/211] 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 110/211] 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 111/211] 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 112/211] 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 113/211] 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 114/211] 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 115/211] 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 116/211] 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 117/211] 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 118/211] 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 119/211] 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 120/211] 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 121/211] 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 122/211] 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 123/211] 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 124/211] 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 125/211] 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 126/211] 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 127/211] 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 128/211] 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 129/211] 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 130/211] 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 131/211] 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 132/211] 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 133/211] 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= From 8cfa4b4acf63ab83334178f96ac694d3ea24e231 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Mur=C3=A9?= Date: Tue, 18 Aug 2020 22:14:09 +0200 Subject: [PATCH 134/211] Add documentation for Use (#1188) --- command.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/command.go b/command.go index 5f1caccc..27d39d54 100644 --- a/command.go +++ b/command.go @@ -37,6 +37,14 @@ type FParseErrWhitelist flag.ParseErrorsWhitelist // definition to ensure usability. type Command struct { // Use is the one-line usage message. + // Recommended syntax is as follow: + // [ ] identifies an optional argument. Arguments that are not enclosed in brackets are required. + // ... indicates that you can specify multiple values for the previous argument. + // | indicates mutually exclusive information. You can use the argument to the left of the separator or the + // argument to the right of the separator. You cannot use both arguments in a single use of the command. + // { } delimits a set of mutually exclusive arguments when one of the arguments is required. If the arguments are + // optional, they are enclosed in brackets ([ ]). + // Example: add [-F file | -D dir]... [-f format] profile Use string // Aliases is an array of aliases that can be used instead of the first word in Use. From 9ed1d713d619c428c8a8adcd13667d6b3f54b247 Mon Sep 17 00:00:00 2001 From: Joshua Harshman Date: Sun, 23 Aug 2020 11:45:41 -0600 Subject: [PATCH 135/211] bugfix/cli: Temporary fix for go get on cobra cli (#1200) PR #1139 introduced a complexity that will have to be taken into account as we figure out our release pipeline. This fix pins to cobrav1.0.0 as a temporary workaround. fixes: #1191 --- cobra/go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cobra/go.mod b/cobra/go.mod index c53413db..ae1e15e6 100644 --- a/cobra/go.mod +++ b/cobra/go.mod @@ -4,7 +4,7 @@ go 1.12 require ( github.com/mitchellh/go-homedir v1.1.0 - github.com/spf13/cobra v0.0.0 + github.com/spf13/cobra v1.0.0 github.com/spf13/viper v1.7.0 ) From 02a0d2fbc9e61d26f8e5979749f6030964a55a3e Mon Sep 17 00:00:00 2001 From: Marc Lopez Rubio Date: Wed, 26 Aug 2020 18:18:51 +0300 Subject: [PATCH 136/211] doc: GenMarkdown skip Synopsis on empty long cmd (#1207) This patch modifies the GenMarkdownCustom to skip writing a Synopsis header with the `cmd.Short` duplicated both before and after the header. Instead, it only writes the `### Synopsis` header and the paragraph when a `cmd.Long` has some kind of content in it. Adds `TestGenMdDocWithNoLongOrSynopsis` as the test case. Signed-off-by: Marc Lopez --- doc/cmd_test.go | 7 ++++++- doc/md_docs.go | 14 +++++--------- doc/md_docs_test.go | 14 ++++++++++++++ 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/doc/cmd_test.go b/doc/cmd_test.go index d29c577d..0917d602 100644 --- a/doc/cmd_test.go +++ b/doc/cmd_test.go @@ -27,7 +27,7 @@ func init() { printCmd.Flags().BoolP("boolthree", "b", true, "help message for flag boolthree") echoCmd.AddCommand(timesCmd, echoSubCmd, deprecatedCmd) - rootCmd.AddCommand(printCmd, echoCmd) + rootCmd.AddCommand(printCmd, echoCmd, dummyCmd) } var rootCmd = &cobra.Command{ @@ -73,6 +73,11 @@ var printCmd = &cobra.Command{ Long: `an absolutely utterly useless command for testing.`, } +var dummyCmd = &cobra.Command{ + Use: "dummy [action]", + Short: "Performs a dummy action", +} + func checkStringContains(t *testing.T, got, expected string) { if !strings.Contains(got, expected) { t.Errorf("Expected to contain: \n %v\nGot:\n %v\n", expected, got) diff --git a/doc/md_docs.go b/doc/md_docs.go index d76f6d5e..5181af8d 100644 --- a/doc/md_docs.go +++ b/doc/md_docs.go @@ -58,16 +58,12 @@ func GenMarkdownCustom(cmd *cobra.Command, w io.Writer, linkHandler func(string) buf := new(bytes.Buffer) name := cmd.CommandPath() - short := cmd.Short - long := cmd.Long - if len(long) == 0 { - long = short - } - buf.WriteString("## " + name + "\n\n") - buf.WriteString(short + "\n\n") - buf.WriteString("### Synopsis\n\n") - buf.WriteString(long + "\n\n") + buf.WriteString(cmd.Short + "\n\n") + if len(cmd.Long) > 0 { + buf.WriteString("### Synopsis\n\n") + buf.WriteString(cmd.Long + "\n\n") + } if cmd.Runnable() { buf.WriteString(fmt.Sprintf("```\n%s\n```\n\n", cmd.UseLine())) diff --git a/doc/md_docs_test.go b/doc/md_docs_test.go index c060f32f..f3251679 100644 --- a/doc/md_docs_test.go +++ b/doc/md_docs_test.go @@ -28,6 +28,20 @@ func TestGenMdDoc(t *testing.T) { checkStringContains(t, output, "Options inherited from parent commands") } +func TestGenMdDocWithNoLongOrSynopsis(t *testing.T) { + // We generate on subcommand so we have both subcommands and parents. + buf := new(bytes.Buffer) + if err := GenMarkdown(dummyCmd, buf); err != nil { + t.Fatal(err) + } + output := buf.String() + + checkStringContains(t, output, dummyCmd.Example) + checkStringContains(t, output, dummyCmd.Short) + checkStringContains(t, output, "Options inherited from parent commands") + checkStringOmits(t, output, "### Synopsis") +} + func TestGenMdNoHiddenParents(t *testing.T) { // We generate on subcommand so we have both subcommands and parents. for _, name := range []string{"rootflag", "strtwo"} { From 50258f15eb8a64e95e5d9ec571f17e550e37fa69 Mon Sep 17 00:00:00 2001 From: Luap99 <45212748+Luap99@users.noreply.github.com> Date: Wed, 9 Sep 2020 17:34:51 +0200 Subject: [PATCH 137/211] Complete subcommands when TraverseChildren is set (#1171) * Complete subcommands when TraverseChildren is true in custom completion The current custom completion logic does not complete subcommands when a local flag is set. This is good unless TraverseChildren is set to true where local flags can be set on parent commands. This commit allows subcommands to be completed if TraverseChildren is set to true on the root cmd. Closes #1170 Signed-off-by: Paul Holzinger * Complete subcommands when TraverseChildren is true in bash completion The current bash completion logic does not complete subcommands when a local flag is set. There is also a bug where subcommands are sometimes still getting completed. see: #1172 If TraverseChildren is true we should allow subcommands to be completed even if a local flag is set. Closes #1172 Signed-off-by: Paul Holzinger --- bash_completions.go | 12 +++++--- bash_completions_test.go | 25 ++++++++++++++++ custom_completions.go | 30 +++++++++++++------ custom_completions_test.go | 60 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 114 insertions(+), 13 deletions(-) diff --git a/bash_completions.go b/bash_completions.go index ab428ccb..846636d7 100644 --- a/bash_completions.go +++ b/bash_completions.go @@ -495,12 +495,14 @@ func writeFlag(buf *bytes.Buffer, flag *pflag.Flag, cmd *Command) { func writeLocalNonPersistentFlag(buf *bytes.Buffer, flag *pflag.Flag) { name := flag.Name - format := " local_nonpersistent_flags+=(\"--%s" + format := " local_nonpersistent_flags+=(\"--%[1]s\")\n" if len(flag.NoOptDefVal) == 0 { - format += "=" + format += " local_nonpersistent_flags+=(\"--%[1]s=\")\n" } - format += "\")\n" buf.WriteString(fmt.Sprintf(format, name)) + if len(flag.Shorthand) > 0 { + buf.WriteString(fmt.Sprintf(" local_nonpersistent_flags+=(\"-%s\")\n", flag.Shorthand)) + } } // Setup annotations for go completions for registered flags @@ -535,7 +537,9 @@ func writeFlags(buf *bytes.Buffer, cmd *Command) { if len(flag.Shorthand) > 0 { writeShortFlag(buf, flag, cmd) } - if localNonPersistentFlags.Lookup(flag.Name) != nil { + // localNonPersistentFlags are used to stop the completion of subcommands when one is set + // if TraverseChildren is true we should allow to complete subcommands + if localNonPersistentFlags.Lookup(flag.Name) != nil && !cmd.Root().TraverseChildren { writeLocalNonPersistentFlag(buf, flag) } }) diff --git a/bash_completions_test.go b/bash_completions_test.go index eefa3de0..2c182ba7 100644 --- a/bash_completions_test.go +++ b/bash_completions_test.go @@ -193,6 +193,13 @@ func TestBashCompletions(t *testing.T) { checkOmit(t, output, `two_word_flags+=("--two-w-default")`) checkOmit(t, output, `two_word_flags+=("-T")`) + // check local nonpersistent flag + check(t, output, `local_nonpersistent_flags+=("--two")`) + check(t, output, `local_nonpersistent_flags+=("--two=")`) + check(t, output, `local_nonpersistent_flags+=("-t")`) + check(t, output, `local_nonpersistent_flags+=("--two-w-default")`) + check(t, output, `local_nonpersistent_flags+=("-T")`) + checkOmit(t, output, deprecatedCmd.Name()) // If available, run shellcheck against the script. @@ -235,3 +242,21 @@ func TestBashCompletionDeprecatedFlag(t *testing.T) { t.Errorf("expected completion to not include %q flag: Got %v", flagName, output) } } + +func TestBashCompletionTraverseChildren(t *testing.T) { + c := &Command{Use: "c", Run: emptyRun, TraverseChildren: true} + + c.Flags().StringP("string-flag", "s", "", "string flag") + c.Flags().BoolP("bool-flag", "b", false, "bool flag") + + buf := new(bytes.Buffer) + c.GenBashCompletion(buf) + output := buf.String() + + // check that local nonpersistent flag are not set since we have TraverseChildren set to true + checkOmit(t, output, `local_nonpersistent_flags+=("--string-flag")`) + checkOmit(t, output, `local_nonpersistent_flags+=("--string-flag=")`) + checkOmit(t, output, `local_nonpersistent_flags+=("-s")`) + checkOmit(t, output, `local_nonpersistent_flags+=("--bool-flag")`) + checkOmit(t, output, `local_nonpersistent_flags+=("-b")`) +} diff --git a/custom_completions.go b/custom_completions.go index c25c03e4..fc852908 100644 --- a/custom_completions.go +++ b/custom_completions.go @@ -175,8 +175,16 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi toComplete := args[len(args)-1] trimmedArgs := args[:len(args)-1] + var finalCmd *Command + var finalArgs []string + var err error // Find the real command for which completion must be performed - finalCmd, finalArgs, err := c.Root().Find(trimmedArgs) + // check if we need to traverse here to parse local flags on parent commands + if c.Root().TraverseChildren { + finalCmd, finalArgs, err = c.Root().Traverse(trimmedArgs) + } else { + finalCmd, finalArgs, err = c.Root().Find(trimmedArgs) + } if err != nil { // Unable to find the real command. E.g., someInvalidCmd return c, []string{}, ShellCompDirectiveDefault, fmt.Errorf("Unable to find a command for arguments: %v", trimmedArgs) @@ -271,20 +279,24 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi 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 - } - }) + // If TraverseChildren is true on the root command we don't check for + // local flags because we can use a local flag on a parent command + if !finalCmd.Root().TraverseChildren { + // Check if there are any local, non-persistent flags on the command-line + 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 + // - there are no local, non-peristent flag on the command-line or TraverseChildren is true for _, subCmd := range finalCmd.Commands() { if subCmd.IsAvailableCommand() || subCmd == finalCmd.helpCommand { if strings.HasPrefix(subCmd.Name(), toComplete) { diff --git a/custom_completions_test.go b/custom_completions_test.go index 276b8a77..fbc6a536 100644 --- a/custom_completions_test.go +++ b/custom_completions_test.go @@ -139,6 +139,8 @@ func TestNoCmdNameCompletionInGo(t *testing.T) { Use: "root", Run: emptyRun, } + rootCmd.Flags().String("localroot", "", "local root flag") + childCmd1 := &Command{ Use: "childCmd1", Short: "First command", @@ -187,6 +189,64 @@ func TestNoCmdNameCompletionInGo(t *testing.T) { t.Errorf("expected: %q, got: %q", expected, output) } + // Test that sub-command names are completed if a local non-persistent flag is present and TraverseChildren is set to true + // set TraverseChildren to true on the root cmd + rootCmd.TraverseChildren = true + + output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--localroot", "value", "") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + // Reset TraverseChildren for next command + rootCmd.TraverseChildren = false + + expected = strings.Join([]string{ + "childCmd1", + "help", + ":4", + "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Test that sub-command names from a child cmd are completed if a local non-persistent flag is present + // and TraverseChildren is set to true on the root cmd + rootCmd.TraverseChildren = true + + output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--localroot", "value", "childCmd1", "--nonPersistent", "value", "") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + // Reset TraverseChildren for next command + rootCmd.TraverseChildren = false + // Reset the flag for the next command + nonPersistentFlag.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 we don't use Traverse when we shouldn't. + // This command should not return a completion since the command line is invalid without TraverseChildren. + output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--localroot", "value", "childCmd1", "") + 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 short flag is present output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "childCmd1", "-n", "value", "") if err != nil { From 8a63648dd905e5c96bee06e6851a894fc75aa058 Mon Sep 17 00:00:00 2001 From: Luap99 <45212748+Luap99@users.noreply.github.com> Date: Wed, 9 Sep 2020 19:27:42 +0200 Subject: [PATCH 138/211] Handle linebreaks in custom completions. (#1162) If a command/flag description contains a linebreak then the shell completion script will interpret this as new command/flag. To fix this we only use the first line from the description in the output. Signed-off-by: Paul Holzinger --- custom_completions.go | 6 ++++++ custom_completions_test.go | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/custom_completions.go b/custom_completions.go index fc852908..f9e88e08 100644 --- a/custom_completions.go +++ b/custom_completions.go @@ -132,6 +132,12 @@ func (c *Command) initCompleteCmd(args []string) { comp = strings.Split(comp, "\t")[0] } + // Make sure we only write the first line to the output. + // This is needed if a description contains a linebreak. + // Otherwise the shell scripts will interpret the other lines as new flags + // and could therefore provide a wrong completion. + comp = strings.Split(comp, "\n")[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 diff --git a/custom_completions_test.go b/custom_completions_test.go index fbc6a536..14ec5a9e 100644 --- a/custom_completions_test.go +++ b/custom_completions_test.go @@ -561,7 +561,7 @@ func TestFlagNameCompletionInGoWithDesc(t *testing.T) { } rootCmd.AddCommand(childCmd) - rootCmd.Flags().IntP("first", "f", -1, "first flag") + rootCmd.Flags().IntP("first", "f", -1, "first flag\nlonger description for flag") rootCmd.PersistentFlags().BoolP("second", "s", false, "second flag") childCmd.Flags().String("subFlag", "", "sub flag") From 6c06523c96f18046b968374723fcb4950dfd127e Mon Sep 17 00:00:00 2001 From: Umberto Baldi <34278123+umbynos@users.noreply.github.com> Date: Tue, 15 Sep 2020 16:44:32 +0200 Subject: [PATCH 139/211] add arduino-cli to projects using cobra (#1117) * add arduino-cli to projects using cobra --- projects_using_cobra.md | 1 + 1 file changed, 1 insertion(+) diff --git a/projects_using_cobra.md b/projects_using_cobra.md index 0397c30e..06cba2f5 100644 --- a/projects_using_cobra.md +++ b/projects_using_cobra.md @@ -1,5 +1,6 @@ ## Projects using Cobra +- [Arduino CLI](https://github.com/arduino/arduino-cli) - [Bleve](http://www.blevesearch.com/) - [CockroachDB](http://www.cockroachlabs.com/) - [Delve](https://github.com/derekparker/delve) From 2a8d0f327d7abf52c256b17aa998290e1e5bd011 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabr=C3=ADcio=20Jos=C3=A9=20Souza?= <1116377+fabriciojs@users.noreply.github.com> Date: Tue, 15 Sep 2020 11:45:35 -0300 Subject: [PATCH 140/211] Adding Kool to list of projects using cobra (#1224) --- projects_using_cobra.md | 1 + 1 file changed, 1 insertion(+) diff --git a/projects_using_cobra.md b/projects_using_cobra.md index 06cba2f5..5cc4e8d5 100644 --- a/projects_using_cobra.md +++ b/projects_using_cobra.md @@ -16,6 +16,7 @@ - [Helm](https://helm.sh) - [Hugo](https://gohugo.io) - [Istio](https://istio.io) +- [Kool](https://github.com/kool-dev/kool) - [Kubernetes](http://kubernetes.io/) - [Linkerd](https://linkerd.io/) - [Mattermost-server](https://github.com/mattermost/mattermost-server) From 8a39cb261478a2bedc5759889215a788eadc1d7d Mon Sep 17 00:00:00 2001 From: Arda Aytekin Date: Wed, 16 Sep 2020 17:26:27 +0200 Subject: [PATCH 141/211] Bug fix in README (#1199) Bug fixed in `README.md`. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 53d174c7..dd937e57 100644 --- a/README.md +++ b/README.md @@ -108,7 +108,7 @@ Using Cobra is easy. First, use `go get` to install the latest version of the library. This command will install the `cobra` generator executable along with the library and its dependencies: - go get -u github.com/spf13/cobra/cobra + go get -u github.com/spf13/cobra Next, include Cobra in your application: From 7f8e83d9366ab8e96959f9c3fbdf3507d4b39c3b Mon Sep 17 00:00:00 2001 From: Danny Hermes Date: Wed, 16 Sep 2020 08:27:58 -0700 Subject: [PATCH 142/211] Modifying "snake-case" to "kebab-case" for clarity. (#1196) --- cobra/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cobra/README.md b/cobra/README.md index 0f718c2b..dcaf0c5f 100644 --- a/cobra/README.md +++ b/cobra/README.md @@ -52,7 +52,7 @@ cobra add config cobra add create -p 'configCmd' ``` -*Note: Use camelCase (not snake_case/snake-case) for command names. +*Note: Use camelCase (not snake_case/kebab-case) for command names. Otherwise, you will encounter errors. For example, `cobra add add-user` is incorrect, but `cobra add addUser` is valid.* From 0bc8bfbe596beeea88b39beb0fca86b3d7d10de6 Mon Sep 17 00:00:00 2001 From: John McBride Date: Wed, 23 Sep 2020 16:26:21 -0600 Subject: [PATCH 143/211] Remove secondary go mod to prevent broken `go get` (#1233) --- cobra/go.mod | 11 -- cobra/go.sum | 310 --------------------------------------------------- go.mod | 2 + go.sum | 299 ++++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 300 insertions(+), 322 deletions(-) delete mode 100644 cobra/go.mod delete mode 100644 cobra/go.sum diff --git a/cobra/go.mod b/cobra/go.mod deleted file mode 100644 index ae1e15e6..00000000 --- a/cobra/go.mod +++ /dev/null @@ -1,11 +0,0 @@ -module github.com/spf13/cobra/cobra - -go 1.12 - -require ( - github.com/mitchellh/go-homedir v1.1.0 - github.com/spf13/cobra v1.0.0 - github.com/spf13/viper v1.7.0 -) - -replace github.com/spf13/cobra => ../ diff --git a/cobra/go.sum b/cobra/go.sum deleted file mode 100644 index a8893425..00000000 --- a/cobra/go.sum +++ /dev/null @@ -1,310 +0,0 @@ -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 0e15356d..18ff7804 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,8 @@ 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 251f2f34..e8b69c60 100644 --- a/go.sum +++ b/go.sum @@ -1,16 +1,313 @@ +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= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +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= From 40d34bca1bffe2f5e84b18d7fd94d5b3c02275a6 Mon Sep 17 00:00:00 2001 From: Alessio Treglia Date: Thu, 1 Oct 2020 17:28:00 +0200 Subject: [PATCH 144/211] Fix stderr printing functions (#894) * Fix stderr printing functions Follow-up of #822 * Errors go to stderr as per POSIX * use PrintErrf() instead of extra call to Sprintf() * Error messages should always be printed to os.Stderr. * add test case for Print* redirection Thanks: @bukowa for the patch. --- README.md | 2 +- command.go | 14 +++++++------- command_test.go | 42 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index dd937e57..b8cda7aa 100644 --- a/README.md +++ b/README.md @@ -177,7 +177,7 @@ var rootCmd = &cobra.Command{ func Execute() { if err := rootCmd.Execute(); err != nil { - fmt.Println(err) + fmt.Fprintln(os.Stderr, err) os.Exit(1) } } diff --git a/command.go b/command.go index 27d39d54..77b399e0 100644 --- a/command.go +++ b/command.go @@ -367,7 +367,7 @@ func (c *Command) UsageFunc() (f func(*Command) error) { c.mergePersistentFlags() err := tmpl(c.OutOrStderr(), c.UsageTemplate(), c) if err != nil { - c.Println(err) + c.PrintErrln(err) } return err } @@ -395,7 +395,7 @@ func (c *Command) HelpFunc() func(*Command, []string) { // See https://github.com/spf13/cobra/issues/1002 err := tmpl(c.OutOrStdout(), c.HelpTemplate(), c) if err != nil { - c.Println(err) + c.PrintErrln(err) } } } @@ -938,8 +938,8 @@ func (c *Command) ExecuteC() (cmd *Command, err error) { c = cmd } if !c.SilenceErrors { - c.Println("Error:", err.Error()) - c.Printf("Run '%v --help' for usage.\n", c.CommandPath()) + c.PrintErrln("Error:", err.Error()) + c.PrintErrf("Run '%v --help' for usage.\n", c.CommandPath()) } return c, err } @@ -967,7 +967,7 @@ func (c *Command) ExecuteC() (cmd *Command, err error) { // If root command has SilentErrors flagged, // all subcommands should respect it if !cmd.SilenceErrors && !c.SilenceErrors { - c.Println("Error:", err.Error()) + c.PrintErrln("Error:", err.Error()) } // If root command has SilentUsage flagged, @@ -1209,12 +1209,12 @@ func (c *Command) PrintErr(i ...interface{}) { // PrintErrln is a convenience method to Println to the defined Err output, fallback to Stderr if not set. func (c *Command) PrintErrln(i ...interface{}) { - c.Print(fmt.Sprintln(i...)) + c.PrintErr(fmt.Sprintln(i...)) } // PrintErrf is a convenience method to Printf to the defined Err output, fallback to Stderr if not set. func (c *Command) PrintErrf(format string, i ...interface{}) { - c.Print(fmt.Sprintf(format, i...)) + c.PrintErr(fmt.Sprintf(format, i...)) } // CommandPath returns the full path to this command. diff --git a/command_test.go b/command_test.go index 16cc41b4..3a47a81b 100644 --- a/command_test.go +++ b/command_test.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "fmt" + "io/ioutil" "os" "reflect" "strings" @@ -1636,6 +1637,47 @@ func TestUsageStringRedirected(t *testing.T) { } } +func TestCommandPrintRedirection(t *testing.T) { + errBuff, outBuff := bytes.NewBuffer(nil), bytes.NewBuffer(nil) + root := &Command{ + Run: func(cmd *Command, args []string) { + + cmd.PrintErr("PrintErr") + cmd.PrintErrln("PrintErr", "line") + cmd.PrintErrf("PrintEr%s", "r") + + cmd.Print("Print") + cmd.Println("Print", "line") + cmd.Printf("Prin%s", "t") + }, + } + + root.SetErr(errBuff) + root.SetOut(outBuff) + + if err := root.Execute(); err != nil { + t.Error(err) + } + + gotErrBytes, err := ioutil.ReadAll(errBuff) + if err != nil { + t.Error(err) + } + + gotOutBytes, err := ioutil.ReadAll(outBuff) + if err != nil { + t.Error(err) + } + + if wantErr := []byte("PrintErrPrintErr line\nPrintErr"); !bytes.Equal(gotErrBytes, wantErr) { + t.Errorf("got: '%s' want: '%s'", gotErrBytes, wantErr) + } + + if wantOut := []byte("PrintPrint line\nPrint"); !bytes.Equal(gotOutBytes, wantOut) { + t.Errorf("got: '%s' want: '%s'", gotOutBytes, wantOut) + } +} + func TestFlagErrorFunc(t *testing.T) { c := &Command{Use: "c", Run: emptyRun} From f64bfa1e08c3199f3e740b5f96a9dd07b727c368 Mon Sep 17 00:00:00 2001 From: midchildan Date: Sun, 4 Oct 2020 10:25:07 +0900 Subject: [PATCH 145/211] Fix zsh completion not working on the first time in a shell session (#1237) The zsh completion script output by cobra is a stub completion function which replaces itself with the actual completion function. This technique enables cobra to define helper functions without splitting the completion script into multiple files. However, the current implementation forgets to call the actual completion function at the end of the stub function, meaning that completion won't work the first time it's invoked in a shell session. This commit is a fix for this problem. --- zsh_completions.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/zsh_completions.go b/zsh_completions.go index c25ce680..92a70394 100644 --- a/zsh_completions.go +++ b/zsh_completions.go @@ -229,6 +229,11 @@ _%[1]s() _describe "completions" completions $(echo $flagPrefix) fi } + +# don't run the completion function when being source-ed or eval-ed +if [ "$funcstack[1]" = "_%[1]s" ]; then + _%[1]s +fi `, name, compCmd, ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp, ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs)) From b97b5ead31f7d34f764ac8666e40c214bb8e06dc Mon Sep 17 00:00:00 2001 From: Matej Vasek Date: Tue, 6 Oct 2020 05:54:06 +0200 Subject: [PATCH 146/211] fix: fish output redirection (#1247) Signed-off-by: Matej Vasek --- fish_completions.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fish_completions.go b/fish_completions.go index 66a6357c..eaae9bca 100644 --- a/fish_completions.go +++ b/fish_completions.go @@ -164,7 +164,8 @@ 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 "%[2]s " &> /dev/null +complete --do-complete "%[2]s " > /dev/null 2>&1 +# Using '> /dev/null 2>&1' since '&>' is not supported in older versions of fish. # Remove any pre-existing completions for the program since we will be handling all of them. complete -c %[2]s -e From 723d0c36fc2f1c8d4cc49180d3875534a7229820 Mon Sep 17 00:00:00 2001 From: Alessio Treglia Date: Wed, 14 Oct 2020 16:51:35 +0100 Subject: [PATCH 147/211] Add tendermint and cosmos-sdk to the list of projects using cobra (#855) * Add tendermint and cosmos-sdk to the list of projects using cobra Co-authored-by: Steve Francia --- projects_using_cobra.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/projects_using_cobra.md b/projects_using_cobra.md index 5cc4e8d5..31c27203 100644 --- a/projects_using_cobra.md +++ b/projects_using_cobra.md @@ -3,6 +3,7 @@ - [Arduino CLI](https://github.com/arduino/arduino-cli) - [Bleve](http://www.blevesearch.com/) - [CockroachDB](http://www.cockroachlabs.com/) +- [Cosmos SDK](https://github.com/cosmos/cosmos-sdk) - [Delve](https://github.com/derekparker/delve) - [Docker (distribution)](https://github.com/docker/distribution) - [Etcd](https://etcd.io/) @@ -30,4 +31,5 @@ - [Random](https://github.com/erdaltsksn/random) - [Rclone](https://rclone.org/) - [Skaffold](https://skaffold.dev/) +- [Tendermint](https://github.com/tendermint/tendermint) - [Werf](https://werf.io/) From 142dfb15a8b9122dd6543adc769cbf59b976d793 Mon Sep 17 00:00:00 2001 From: Adam Demuri Date: Wed, 14 Oct 2020 09:53:09 -0600 Subject: [PATCH 148/211] Add example for making persistent flags required (#1135) --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index b8cda7aa..3cf1b25d 100644 --- a/README.md +++ b/README.md @@ -419,6 +419,12 @@ rootCmd.Flags().StringVarP(&Region, "region", "r", "", "AWS region (required)") rootCmd.MarkFlagRequired("region") ``` +Or, for persistent flags: +```go +rootCmd.PersistentFlags().StringVarP(&Region, "region", "r", "", "AWS region (required)") +rootCmd.MarkPersistentFlagRequired("region") +``` + ## Positional and Custom Arguments Validation of positional arguments can be specified using the `Args` field From f32f4ef15ba84d01a8672ca8402be5d7695054f8 Mon Sep 17 00:00:00 2001 From: Marc Khouzam Date: Sun, 18 Oct 2020 14:50:48 -0400 Subject: [PATCH 149/211] Don't use yaml.v2 2.3.0 which has a breaking change (#1259) yaml.v2 contains a breaking change from https://github.com/go-yaml/yaml/pull/571 yaml.v2 2.2.8 does not have that change and also addresses the CVE that was behind the move to yaml.v2 2.3.0. This commit reverts to using yaml.v2 2.2.8 Signed-off-by: Marc Khouzam --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 18ff7804..57e3244d 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.5 github.com/spf13/viper v1.7.0 - gopkg.in/yaml.v2 v2.3.0 + gopkg.in/yaml.v2 v2.2.8 ) diff --git a/go.sum b/go.sum index e8b69c60..0aae7386 100644 --- a/go.sum +++ b/go.sum @@ -304,8 +304,8 @@ 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= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/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= From 86f8bfd7fef868a174e1b606783bd7f5c82ddf8f Mon Sep 17 00:00:00 2001 From: Sascha Steinbiss Date: Sun, 18 Oct 2020 20:59:26 +0200 Subject: [PATCH 150/211] fix manpage building with new go-md2man (#1255) This addresses #1049 by changing the format of the generated Markdown input. --- doc/man_docs.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/doc/man_docs.go b/doc/man_docs.go index b29a6778..b67ac1f1 100644 --- a/doc/man_docs.go +++ b/doc/man_docs.go @@ -145,9 +145,7 @@ func manPreamble(buf *bytes.Buffer, header *GenManHeader, cmd *cobra.Command, da description = cmd.Short } - buf.WriteString(fmt.Sprintf(`%% %s(%s)%s -%% %s -%% %s + buf.WriteString(fmt.Sprintf(`%% "%s" "%s" "%s" "%s" "%s" # NAME `, header.Title, header.Section, header.date, header.Source, header.Manual)) buf.WriteString(fmt.Sprintf("%s \\- %s\n\n", dashedName, cmd.Short)) From 08c51e585ca78e4b9408ae034d0cdacdd81aad41 Mon Sep 17 00:00:00 2001 From: Vincent Date: Tue, 10 Nov 2020 00:46:15 +0100 Subject: [PATCH 151/211] Add ORY Hydra & Kratos to projects_using_cobra.md (#1273) * Add ORY Hydra & Kratos to projects_using_cobra.md * fix: alphabetical order my bad! * fix: ORY to Ory I think now it should be good, sorry for the confusion! --- projects_using_cobra.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/projects_using_cobra.md b/projects_using_cobra.md index 31c27203..0d8755c9 100644 --- a/projects_using_cobra.md +++ b/projects_using_cobra.md @@ -25,6 +25,8 @@ - [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/) +- [Ory Hydra](https://github.com/ory/hydra) +- [Ory Kratos](https://github.com/ory/kratos) - [Pouch](https://github.com/alibaba/pouch) - [ProjectAtomic (enterprise)](http://www.projectatomic.io/) - [Prototool](https://github.com/uber/prototool) From 39b5a91b209824d527abd198284e1c7f3e82e526 Mon Sep 17 00:00:00 2001 From: zaataylor <40524990+zaataylor@users.noreply.github.com> Date: Fri, 4 Dec 2020 14:15:09 -0500 Subject: [PATCH 152/211] README.md Readability Improvements (#1228) I made some small changes to the README.md file to enhance its readability. --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 3cf1b25d..0c48c677 100644 --- a/README.md +++ b/README.md @@ -62,8 +62,8 @@ Cobra is built on a structure of commands, arguments & flags. **Commands** represent actions, **Args** are things and **Flags** are modifiers for those actions. -The best applications will read like sentences when used. Users will know how -to use the application because they will natively understand how to use it. +The best applications read like sentences when used, and as a result, users +intuitively know how to interact with them. The pattern to follow is `APPNAME VERB NOUN --ADJECTIVE.` @@ -268,7 +268,7 @@ func initConfig() { With the root command you need to have your main function execute it. Execute should be run on the root for clarity, though it can be called on any command. -In a Cobra app, typically the main.go file is very bare. It serves, one purpose, to initialize Cobra. +In a Cobra app, typically the main.go file is very bare. It serves one purpose: to initialize Cobra. ```go package main @@ -363,7 +363,7 @@ There are two different approaches to assign a flag. ### Persistent Flags -A flag can be 'persistent' meaning that this flag will be available to the +A flag can be 'persistent', meaning that this flag will be available to the command it's assigned to as well as every command under that command. For global flags, assign a flag as a persistent flag on the root. @@ -373,7 +373,7 @@ rootCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "verbose out ### Local Flags -A flag can also be assigned locally which will only apply to that specific command. +A flag can also be assigned locally, which will only apply to that specific command. ```go localCmd.Flags().StringVarP(&Source, "source", "s", "", "Source directory to read from") @@ -381,8 +381,8 @@ localCmd.Flags().StringVarP(&Source, "source", "s", "", "Source directory to rea ### Local Flag on Parent Commands -By default Cobra only parses local flags on the target command, any local flags on -parent commands are ignored. By enabling `Command.TraverseChildren` Cobra will +By default, Cobra only parses local flags on the target command, and any local flags on +parent commands are ignored. By enabling `Command.TraverseChildren`, Cobra will parse local flags on each command before executing the target command. ```go @@ -404,8 +404,8 @@ func init() { } ``` -In this example the persistent flag `author` is bound with `viper`. -**Note**, that the variable `author` will not be set to the value from config, +In this example, the persistent flag `author` is bound with `viper`. +**Note**: the variable `author` will not be set to the value from config, when the `--author` flag is not provided by user. More in [viper documentation](https://github.com/spf13/viper#working-with-flags). @@ -465,7 +465,7 @@ var cmd = &cobra.Command{ In the example below, we have defined three commands. Two are at the top level and one (cmdTimes) is a child of one of the top commands. In this case the root -is not executable meaning that a subcommand is required. This is accomplished +is not executable, meaning that a subcommand is required. This is accomplished by not providing a 'Run' for the 'rootCmd'. We have only defined one flag for a single command. From 7df62f7668c7fbeafd6dbb45ad0294f4ea08c0ec Mon Sep 17 00:00:00 2001 From: namusyaka Date: Sat, 5 Dec 2020 04:34:11 +0900 Subject: [PATCH 153/211] fix typos (#1274) --- command.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/command.go b/command.go index 77b399e0..2b82a553 100644 --- a/command.go +++ b/command.go @@ -964,13 +964,13 @@ func (c *Command) ExecuteC() (cmd *Command, err error) { return cmd, nil } - // If root command has SilentErrors flagged, + // If root command has SilenceErrors flagged, // all subcommands should respect it if !cmd.SilenceErrors && !c.SilenceErrors { c.PrintErrln("Error:", err.Error()) } - // If root command has SilentUsage flagged, + // If root command has SilenceUsage flagged, // all subcommands should respect it if !cmd.SilenceUsage && !c.SilenceUsage { c.Println(cmd.UsageString()) From 471c9ac367820b3239e22f9379ad0b1f5f8a84a3 Mon Sep 17 00:00:00 2001 From: Jon Love Date: Tue, 29 Dec 2020 09:39:22 -0500 Subject: [PATCH 154/211] Add the new Twitch CLI to to projects_using_cobra.md (#1301) --- projects_using_cobra.md | 1 + 1 file changed, 1 insertion(+) diff --git a/projects_using_cobra.md b/projects_using_cobra.md index 0d8755c9..d98a71e3 100644 --- a/projects_using_cobra.md +++ b/projects_using_cobra.md @@ -34,4 +34,5 @@ - [Rclone](https://rclone.org/) - [Skaffold](https://skaffold.dev/) - [Tendermint](https://github.com/tendermint/tendermint) +- [Twitch CLI](https://github.com/twitchdev/twitch-cli) - [Werf](https://werf.io/) From a4ab3fa09e3d6c4ac3fb7deece97a910957305ab Mon Sep 17 00:00:00 2001 From: Paul Holzinger <45212748+Luap99@users.noreply.github.com> Date: Tue, 29 Dec 2020 15:57:32 +0100 Subject: [PATCH 155/211] powershell completion with custom comp (#1208) The current powershell completion is not very capable. Let's port it to the go custom completion logic to have a unified experience accross all shells. Powershell supports three different completion modes - TabCompleteNext (default windows style - on each key press the next option is displayed) - Complete (works like bash) - MenuComplete (works like zsh) You set the mode with `Set-PSReadLineKeyHandler -Key Tab -Function ` To keep it backwards compatible `GenPowerShellCompletion` will not display descriptions. Use `GenPowerShellCompletionWithDesc` instead. Descriptions will only be displayed with `MenuComplete` or `Complete`. Signed-off-by: Paul Holzinger --- powershell_completions.go | 319 ++++++++++++++++++++++++++------- powershell_completions.md | 15 +- powershell_completions_test.go | 122 ------------- shell_completions.md | 58 +++++- 4 files changed, 307 insertions(+), 207 deletions(-) delete mode 100644 powershell_completions_test.go diff --git a/powershell_completions.go b/powershell_completions.go index 756c61b9..48022ea5 100644 --- a/powershell_completions.go +++ b/powershell_completions.go @@ -1,6 +1,3 @@ -// PowerShell completions are based on the amazing work from clap: -// https://github.com/clap-rs/clap/blob/3294d18efe5f264d12c9035f404c7d189d4824e1/src/completions/powershell.rs -// // The generated scripts require PowerShell v5.0+ (which comes Windows 10, but // can be downloaded separately for windows 7 or 8.1). @@ -11,90 +8,278 @@ import ( "fmt" "io" "os" - "strings" - - "github.com/spf13/pflag" ) -var powerShellCompletionTemplate = `using namespace System.Management.Automation -using namespace System.Management.Automation.Language -Register-ArgumentCompleter -Native -CommandName '%s' -ScriptBlock { - param($wordToComplete, $commandAst, $cursorPosition) - $commandElements = $commandAst.CommandElements - $command = @( - '%s' - for ($i = 1; $i -lt $commandElements.Count; $i++) { - $element = $commandElements[$i] - if ($element -isnot [StringConstantExpressionAst] -or - $element.StringConstantType -ne [StringConstantType]::BareWord -or - $element.Value.StartsWith('-')) { - break - } - $element.Value +func genPowerShellComp(buf *bytes.Buffer, name string, includeDesc bool) { + compCmd := ShellCompRequestCmd + if !includeDesc { + compCmd = ShellCompNoDescRequestCmd + } + buf.WriteString(fmt.Sprintf(`# powershell completion for %-36[1]s -*- shell-script -*- + +function __%[1]s_debug { + if ($env:BASH_COMP_DEBUG_FILE) { + "$args" | Out-File -Append -FilePath "$env:BASH_COMP_DEBUG_FILE" + } +} + +filter __%[1]s_escapeStringWithSpecialChars { +`+" $_ -replace '\\s|#|@|\\$|;|,|''|\\{|\\}|\\(|\\)|\"|`|\\||<|>|&','`$&'"+` +} + +Register-ArgumentCompleter -CommandName '%[1]s' -ScriptBlock { + param( + $WordToComplete, + $CommandAst, + $CursorPosition + ) + + # Get the current command line and convert into a string + $Command = $CommandAst.CommandElements + $Command = "$Command" + + __%[1]s_debug "" + __%[1]s_debug "========= starting completion logic ==========" + __%[1]s_debug "WordToComplete: $WordToComplete Command: $Command CursorPosition: $CursorPosition" + + # The user could have moved the cursor backwards on the command-line. + # We need to trigger completion from the $CursorPosition location, so we need + # to truncate the command-line ($Command) up to the $CursorPosition location. + # Make sure the $Command is longer then the $CursorPosition before we truncate. + # This happens because the $Command does not include the last space. + if ($Command.Length -gt $CursorPosition) { + $Command=$Command.Substring(0,$CursorPosition) + } + __%[1]s_debug "Truncated command: $Command" + + $ShellCompDirectiveError=%[3]d + $ShellCompDirectiveNoSpace=%[4]d + $ShellCompDirectiveNoFileComp=%[5]d + $ShellCompDirectiveFilterFileExt=%[6]d + $ShellCompDirectiveFilterDirs=%[7]d + + # Prepare the command to request completions for the program. + # Split the command at the first space to separate the program and arguments. + $Program,$Arguments = $Command.Split(" ",2) + $RequestComp="$Program %[2]s $Arguments" + __%[1]s_debug "RequestComp: $RequestComp" + + # we cannot use $WordToComplete because it + # has the wrong values if the cursor was moved + # so use the last argument + if ($WordToComplete -ne "" ) { + $WordToComplete = $Arguments.Split(" ")[-1] + } + __%[1]s_debug "New WordToComplete: $WordToComplete" + + + # Check for flag with equal sign + $IsEqualFlag = ($WordToComplete -Like "--*=*" ) + if ( $IsEqualFlag ) { + __%[1]s_debug "Completing equal sign flag" + # Remove the flag part + $Flag,$WordToComplete = $WordToComplete.Split("=",2) + } + + if ( $WordToComplete -eq "" -And ( -Not $IsEqualFlag )) { + # 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 "Adding extra empty parameter" +`+" # We need to use `\"`\" to pass an empty argument a \"\" or '' does not work!!!"+` +`+" $RequestComp=\"$RequestComp\" + ' `\"`\"' "+` + } + + __%[1]s_debug "Calling $RequestComp" + #call the command store the output in $out and redirect stderr and stdout to null + # $Out is an array contains each line per element + Invoke-Expression -OutVariable out "$RequestComp" 2>&1 | Out-Null + + + # get directive from last line + [int]$Directive = $Out[-1].TrimStart(':') + if ($Directive -eq "") { + # There is no directive specified + $Directive = 0 + } + __%[1]s_debug "The completion directive is: $Directive" + + # remove directive (last element) from out + $Out = $Out | Where-Object { $_ -ne $Out[-1] } + __%[1]s_debug "The completions are: $Out" + + if (($Directive -band $ShellCompDirectiveError) -ne 0 ) { + # Error code. No completion. + __%[1]s_debug "Received error from custom completion go code" + return + } + + $Longest = 0 + $Values = $Out | ForEach-Object { + #Split the output in name and description +`+" $Name, $Description = $_.Split(\"`t\",2)"+` + __%[1]s_debug "Name: $Name Description: $Description" + + # Look for the longest completion so that we can format things nicely + if ($Longest -lt $Name.Length) { + $Longest = $Name.Length } - ) -join ';' - $completions = @(switch ($command) {%s - }) - $completions.Where{ $_.CompletionText -like "$wordToComplete*" } | - Sort-Object -Property ListItemText -}` -func generatePowerShellSubcommandCases(out io.Writer, cmd *Command, previousCommandName string) { - var cmdName string - if previousCommandName == "" { - cmdName = cmd.Name() - } else { - cmdName = fmt.Sprintf("%s;%s", previousCommandName, cmd.Name()) - } + # Set the description to a one space string if there is none set. + # This is needed because the CompletionResult does not accept an empty string as argument + if (-Not $Description) { + $Description = " " + } + @{Name="$Name";Description="$Description"} + } - fmt.Fprintf(out, "\n '%s' {", cmdName) - cmd.Flags().VisitAll(func(flag *pflag.Flag) { - if nonCompletableFlag(flag) { - return - } - usage := escapeStringForPowerShell(flag.Usage) - if len(flag.Shorthand) > 0 { - fmt.Fprintf(out, "\n [CompletionResult]::new('-%s', '%s', [CompletionResultType]::ParameterName, '%s')", flag.Shorthand, flag.Shorthand, usage) - } - fmt.Fprintf(out, "\n [CompletionResult]::new('--%s', '%s', [CompletionResultType]::ParameterName, '%s')", flag.Name, flag.Name, usage) - }) + $Space = " " + if (($Directive -band $ShellCompDirectiveNoSpace) -ne 0 ) { + # remove the space here + __%[1]s_debug "ShellCompDirectiveNoSpace is called" + $Space = "" + } - for _, subCmd := range cmd.Commands() { - usage := escapeStringForPowerShell(subCmd.Short) - fmt.Fprintf(out, "\n [CompletionResult]::new('%s', '%s', [CompletionResultType]::ParameterValue, '%s')", subCmd.Name(), subCmd.Name(), usage) - } + if (($Directive -band $ShellCompDirectiveNoFileComp) -ne 0 ) { + __%[1]s_debug "ShellCompDirectiveNoFileComp is called" + + if ($Values.Length -eq 0) { + # Just print an empty string here so the + # shell does not start to complete paths. + # We cannot use CompletionResult here because + # it does not accept an empty string as argument. + "" + return + } + } - fmt.Fprint(out, "\n break\n }") + if ((($Directive -band $ShellCompDirectiveFilterFileExt) -ne 0 ) -or + (($Directive -band $ShellCompDirectiveFilterDirs) -ne 0 )) { + __%[1]s_debug "ShellCompDirectiveFilterFileExt ShellCompDirectiveFilterDirs are not supported" - for _, subCmd := range cmd.Commands() { - generatePowerShellSubcommandCases(out, subCmd, cmdName) - } + # return here to prevent the completion of the extensions + return + } + + $Values = $Values | Where-Object { + # filter the result + $_.Name -like "$WordToComplete*" + + # Join the flag back if we have a equal sign flag + if ( $IsEqualFlag ) { + __%[1]s_debug "Join the equal sign flag back to the completion value" + $_.Name = $Flag + "=" + $_.Name + } + } + + # Get the current mode + $Mode = (Get-PSReadLineKeyHandler | Where-Object {$_.Key -eq "Tab" }).Function + __%[1]s_debug "Mode: $Mode" + + $Values | ForEach-Object { + + # store temporay because switch will overwrite $_ + $comp = $_ + + # Powershell supports three different completion modes + # - TabCompleteNext (default windows style - on each key press the next option is displayed) + # - Complete (works like bash) + # - MenuComplete (works like zsh) + # You set the mode with Set-PSReadLineKeyHandler -Key Tab -Function + + # CompletionResult Arguments: + # 1) CompletionText text to be used as the auto completion result + # 2) ListItemText text to be displayed in the suggestion list + # 3) ResultType type of completion result + # 4) ToolTip text for the tooltip with details about the object + + switch ($Mode) { + + # bash like + "Complete" { + + if ($Values.Length -eq 1) { + __%[1]s_debug "Only one completion left" + + # insert space after value + [System.Management.Automation.CompletionResult]::new($($comp.Name | __%[1]s_escapeStringWithSpecialChars) + $Space, "$($comp.Name)", 'ParameterValue', "$($comp.Description)") + + } else { + # Add the proper number of spaces to align the descriptions + while($comp.Name.Length -lt $Longest) { + $comp.Name = $comp.Name + " " + } + + # Check for empty description and only add parentheses if needed + if ($($comp.Description) -eq " " ) { + $Description = "" + } else { + $Description = " ($($comp.Description))" + } + + [System.Management.Automation.CompletionResult]::new("$($comp.Name)$Description", "$($comp.Name)$Description", 'ParameterValue', "$($comp.Description)") + } + } + + # zsh like + "MenuComplete" { + # insert space after value + # MenuComplete will automatically show the ToolTip of + # the highlighted value at the bottom of the suggestions. + [System.Management.Automation.CompletionResult]::new($($comp.Name | __%[1]s_escapeStringWithSpecialChars) + $Space, "$($comp.Name)", 'ParameterValue', "$($comp.Description)") + } + + # TabCompleteNext and in case we get something unknown + Default { + # Like MenuComplete but we don't want to add a space here because + # the user need to press space anyway to get the completion. + # Description will not be shown because thats not possible with TabCompleteNext + [System.Management.Automation.CompletionResult]::new($($comp.Name | __%[1]s_escapeStringWithSpecialChars), "$($comp.Name)", 'ParameterValue', "$($comp.Description)") + } + } + + } +} +`, name, compCmd, + ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp, + ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs)) } -func escapeStringForPowerShell(s string) string { - return strings.Replace(s, "'", "''", -1) -} - -// GenPowerShellCompletion generates PowerShell completion file and writes to the passed writer. -func (c *Command) GenPowerShellCompletion(w io.Writer) error { +func (c *Command) genPowerShellCompletion(w io.Writer, includeDesc bool) error { buf := new(bytes.Buffer) - - var subCommandCases bytes.Buffer - generatePowerShellSubcommandCases(&subCommandCases, c, "") - fmt.Fprintf(buf, powerShellCompletionTemplate, c.Name(), c.Name(), subCommandCases.String()) - + genPowerShellComp(buf, c.Name(), includeDesc) _, err := buf.WriteTo(w) return err } -// GenPowerShellCompletionFile generates PowerShell completion file. -func (c *Command) GenPowerShellCompletionFile(filename string) error { +func (c *Command) genPowerShellCompletionFile(filename string, includeDesc bool) error { outFile, err := os.Create(filename) if err != nil { return err } defer outFile.Close() - return c.GenPowerShellCompletion(outFile) + return c.genPowerShellCompletion(outFile, includeDesc) +} + +// GenPowerShellCompletionFile generates powershell completion file without descriptions. +func (c *Command) GenPowerShellCompletionFile(filename string) error { + return c.genPowerShellCompletionFile(filename, false) +} + +// GenPowerShellCompletion generates powershell completion file without descriptions +// and writes it to the passed writer. +func (c *Command) GenPowerShellCompletion(w io.Writer) error { + return c.genPowerShellCompletion(w, false) +} + +// GenPowerShellCompletionFileWithDesc generates powershell completion file with descriptions. +func (c *Command) GenPowerShellCompletionFileWithDesc(filename string) error { + return c.genPowerShellCompletionFile(filename, true) +} + +// GenPowerShellCompletionWithDesc generates powershell completion file with descriptions +// and writes it to the passed writer. +func (c *Command) GenPowerShellCompletionWithDesc(w io.Writer) error { + return c.genPowerShellCompletion(w, true) } diff --git a/powershell_completions.md b/powershell_completions.md index 55f154a6..c449f1e5 100644 --- a/powershell_completions.md +++ b/powershell_completions.md @@ -1,16 +1,3 @@ # Generating PowerShell Completions For Your Own cobra.Command -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 -- Completion for non-hidden flags using their `.Name` and `.Shorthand` - -# What's not yet supported - -- Command aliases -- Required, filename or custom flags (they will work like normal flags) -- Custom completion scripts +Please refer to [Shell Completions](shell_completions.md#powershell-completions) for details. diff --git a/powershell_completions_test.go b/powershell_completions_test.go deleted file mode 100644 index 29b609de..00000000 --- a/powershell_completions_test.go +++ /dev/null @@ -1,122 +0,0 @@ -package cobra - -import ( - "bytes" - "strings" - "testing" -) - -func TestPowerShellCompletion(t *testing.T) { - tcs := []struct { - name string - root *Command - expectedExpressions []string - }{ - { - name: "trivial", - root: &Command{Use: "trivialapp"}, - expectedExpressions: []string{ - "Register-ArgumentCompleter -Native -CommandName 'trivialapp' -ScriptBlock", - "$command = @(\n 'trivialapp'\n", - }, - }, - { - name: "tree", - root: func() *Command { - r := &Command{Use: "tree"} - - sub1 := &Command{Use: "sub1"} - r.AddCommand(sub1) - - sub11 := &Command{Use: "sub11"} - sub12 := &Command{Use: "sub12"} - - sub1.AddCommand(sub11) - sub1.AddCommand(sub12) - - sub2 := &Command{Use: "sub2"} - r.AddCommand(sub2) - - sub21 := &Command{Use: "sub21"} - sub22 := &Command{Use: "sub22"} - - sub2.AddCommand(sub21) - sub2.AddCommand(sub22) - - return r - }(), - expectedExpressions: []string{ - "'tree'", - "[CompletionResult]::new('sub1', 'sub1', [CompletionResultType]::ParameterValue, '')", - "[CompletionResult]::new('sub2', 'sub2', [CompletionResultType]::ParameterValue, '')", - "'tree;sub1'", - "[CompletionResult]::new('sub11', 'sub11', [CompletionResultType]::ParameterValue, '')", - "[CompletionResult]::new('sub12', 'sub12', [CompletionResultType]::ParameterValue, '')", - "'tree;sub1;sub11'", - "'tree;sub1;sub12'", - "'tree;sub2'", - "[CompletionResult]::new('sub21', 'sub21', [CompletionResultType]::ParameterValue, '')", - "[CompletionResult]::new('sub22', 'sub22', [CompletionResultType]::ParameterValue, '')", - "'tree;sub2;sub21'", - "'tree;sub2;sub22'", - }, - }, - { - name: "flags", - root: func() *Command { - r := &Command{Use: "flags"} - r.Flags().StringP("flag1", "a", "", "") - r.Flags().String("flag2", "", "") - - sub1 := &Command{Use: "sub1"} - sub1.Flags().StringP("flag3", "c", "", "") - r.AddCommand(sub1) - - return r - }(), - expectedExpressions: []string{ - "'flags'", - "[CompletionResult]::new('-a', 'a', [CompletionResultType]::ParameterName, '')", - "[CompletionResult]::new('--flag1', 'flag1', [CompletionResultType]::ParameterName, '')", - "[CompletionResult]::new('--flag2', 'flag2', [CompletionResultType]::ParameterName, '')", - "[CompletionResult]::new('sub1', 'sub1', [CompletionResultType]::ParameterValue, '')", - "'flags;sub1'", - "[CompletionResult]::new('-c', 'c', [CompletionResultType]::ParameterName, '')", - "[CompletionResult]::new('--flag3', 'flag3', [CompletionResultType]::ParameterName, '')", - }, - }, - { - name: "usage", - root: func() *Command { - r := &Command{Use: "usage"} - r.Flags().String("flag", "", "this describes the usage of the 'flag' flag") - - sub1 := &Command{ - Use: "sub1", - Short: "short describes 'sub1'", - } - r.AddCommand(sub1) - - return r - }(), - expectedExpressions: []string{ - "[CompletionResult]::new('--flag', 'flag', [CompletionResultType]::ParameterName, 'this describes the usage of the ''flag'' flag')", - "[CompletionResult]::new('sub1', 'sub1', [CompletionResultType]::ParameterValue, 'short describes ''sub1''')", - }, - }, - } - - for _, tc := range tcs { - t.Run(tc.name, func(t *testing.T) { - buf := new(bytes.Buffer) - tc.root.GenPowerShellCompletion(buf) - output := buf.String() - - for _, expectedExpression := range tc.expectedExpressions { - if !strings.Contains(output, expectedExpression) { - t.Errorf("Expected completion to contain %q somewhere; got %q", expectedExpression, output) - } - } - }) - } -} diff --git a/shell_completions.md b/shell_completions.md index d8416ab1..511b2f3c 100644 --- a/shell_completions.md +++ b/shell_completions.md @@ -49,6 +49,14 @@ $ yourprogram completion fish | source # To load completions for each session, execute once: $ yourprogram completion fish > ~/.config/fish/completions/yourprogram.fish + +Powershell: + +PS> yourprogram completion powershell | Out-String | Invoke-Expression + +# To load completions for every new session, run: +PS> yourprogram completion powershell > yourprogram.ps1 +# and source this file from your powershell profile. `, DisableFlagsInUseLine: true, ValidArgs: []string{"bash", "zsh", "fish", "powershell"}, @@ -316,7 +324,7 @@ cmd.RegisterFlagCompletionFunc(flagName, func(cmd *cobra.Command, args []string, ``` ### 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: +`zsh`, `fish` and `powershell` 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 @@ -390,7 +398,7 @@ search show status ### 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`). + * You should instead use `ValidArgsFunction` and `RegisterFlagCompletionFunc()` which are portable to the different shells (`bash`, `zsh`, `fish`, `powershell`). * The function `MarkFlagCustom()` is not supported and will be ignored for `zsh`. * You should instead use `RegisterFlagCompletionFunc()`. @@ -416,7 +424,7 @@ search show status ### 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`). + * You should instead use `ValidArgsFunction` and `RegisterFlagCompletionFunc()` which are portable to the different shells (`bash`, `zsh`, `fish`, `powershell`). * 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`: @@ -431,4 +439,46 @@ search show status ## PowerShell completions -Please refer to [PowerShell Completions](powershell_completions.md) for details. +Cobra supports native PowerShell completions generated from the root `cobra.Command`. You can use the `command.GenPowerShellCompletion()` or `command.GenPowerShellCompletionFile()` functions. To include descriptions use `command.GenPowerShellCompletionWithDesc()` and `command.GenPowerShellCompletionFileWithDesc()`. Cobra will provide the description automatically based on usage information. You can choose to make this option configurable by your users. + +The script is designed to support all three Powershell completion modes: + +* TabCompleteNext (default windows style - on each key press the next option is displayed) +* Complete (works like bash) +* MenuComplete (works like zsh) + +You set the mode with `Set-PSReadLineKeyHandler -Key Tab -Function `. Descriptions are only displayed when using the `Complete` or `MenuComplete` mode. + +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. + +``` +# With descriptions and Mode 'Complete' +$ helm s[tab] +search (search for a keyword in charts) show (show information of a chart) status (displays the status of the named release) + +# With descriptions and Mode 'MenuComplete' The description of the current selected value will be displayed below the suggestions. +$ helm s[tab] +search show status + +search for a keyword in charts + +# Without descriptions +$ helm s[tab] +search show status +``` + +### Limitations + +* Custom completions implemented in Bash scripting (legacy) are not supported and will be ignored for `powershell` (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`, `powershell`). +* The function `MarkFlagCustom()` is not supported and will be ignored for `powershell`. + * You should instead use `RegisterFlagCompletionFunc()`. +* The following flag completion annotations are not supported and will be ignored for `powershell`: + * `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 `powershell`: + * `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 `powershell`: + * `ShellCompDirectiveFilterFileExt` (filtering by file extension) + * `ShellCompDirectiveFilterDirs` (filtering by directory) \ No newline at end of file From 4384b91fb4f13b1a969bce056c81ef9c128e534a Mon Sep 17 00:00:00 2001 From: Maxime Bury Date: Sat, 16 Jan 2021 17:41:43 -0800 Subject: [PATCH 156/211] Bump license year to 2021 in golden files (#1309) * Update main.go.golden * Update root.go.golden * Update test.go.golden --- 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 0bf75c5a..f69d34aa 100644 --- a/cobra/cmd/testdata/main.go.golden +++ b/cobra/cmd/testdata/main.go.golden @@ -1,5 +1,5 @@ /* -Copyright © 2020 NAME HERE +Copyright © 2021 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 1db829c7..260c7aa8 100644 --- a/cobra/cmd/testdata/root.go.golden +++ b/cobra/cmd/testdata/root.go.golden @@ -1,5 +1,5 @@ /* -Copyright © 2020 NAME HERE +Copyright © 2021 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 3eef95fe..584daa23 100644 --- a/cobra/cmd/testdata/test.go.golden +++ b/cobra/cmd/testdata/test.go.golden @@ -1,5 +1,5 @@ /* -Copyright © 2020 NAME HERE +Copyright © 2021 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 23a6174c7f9d0c64a48e634054bdf6886fb7fbba Mon Sep 17 00:00:00 2001 From: Joon-Ho Son <20669173+sonjoonho@users.noreply.github.com> Date: Thu, 21 Jan 2021 03:33:16 +0000 Subject: [PATCH 157/211] Add the ability to specify a filePostpender in GenMarkdownTreeCustom (#1270) --- doc/md_docs.go | 11 +++++++---- doc/md_docs_test.go | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/doc/md_docs.go b/doc/md_docs.go index 5181af8d..fafab26c 100644 --- a/doc/md_docs.go +++ b/doc/md_docs.go @@ -122,17 +122,17 @@ func GenMarkdownCustom(cmd *cobra.Command, w io.Writer, linkHandler func(string) func GenMarkdownTree(cmd *cobra.Command, dir string) error { identity := func(s string) string { return s } emptyStr := func(s string) string { return "" } - return GenMarkdownTreeCustom(cmd, dir, emptyStr, identity) + return GenMarkdownTreeCustom(cmd, dir, emptyStr, emptyStr, identity) } // GenMarkdownTreeCustom is the the same as GenMarkdownTree, but -// with custom filePrepender and linkHandler. -func GenMarkdownTreeCustom(cmd *cobra.Command, dir string, filePrepender, linkHandler func(string) string) error { +// with custom filePrepender, filePostpender, and linkHandler. +func GenMarkdownTreeCustom(cmd *cobra.Command, dir string, filePrepender, filePostpender, linkHandler func(string) string) error { for _, c := range cmd.Commands() { if !c.IsAvailableCommand() || c.IsAdditionalHelpTopicCommand() { continue } - if err := GenMarkdownTreeCustom(c, dir, filePrepender, linkHandler); err != nil { + if err := GenMarkdownTreeCustom(c, dir, filePrepender, filePostpender, linkHandler); err != nil { return err } } @@ -151,5 +151,8 @@ func GenMarkdownTreeCustom(cmd *cobra.Command, dir string, filePrepender, linkHa if err := GenMarkdownCustom(cmd, f, linkHandler); err != nil { return err } + if _, err := io.WriteString(f, filePostpender(filename)); err != nil { + return err + } return nil } diff --git a/doc/md_docs_test.go b/doc/md_docs_test.go index f3251679..29430b42 100644 --- a/doc/md_docs_test.go +++ b/doc/md_docs_test.go @@ -95,6 +95,37 @@ func TestGenMdTree(t *testing.T) { } } +func TestGenMdTreeCustom(t *testing.T) { + tmpdir, err := ioutil.TempDir("", "test-gen-md-tree") + if err != nil { + t.Fatalf("Failed to create tmpdir: %v", err) + } + defer os.RemoveAll(tmpdir) + + prepender := func(s string) string { return "Prepended" } + postpender := func(s string) string { return "Postpended" } + identity := func(s string) string { return s } + + if err := GenMarkdownTreeCustom(rootCmd, tmpdir, prepender, postpender, identity); err != nil { + t.Fatalf("GenMarkdownTree failed: %v", err) + } + + gotRoot := fileContents(t, tmpdir, "root.md") + checkStringContains(t, gotRoot, "Prepended") + checkStringContains(t, gotRoot, rootCmd.Long) + checkStringContains(t, gotRoot, "Postpended") + + gotEcho := fileContents(t, tmpdir, "root_echo.md") + checkStringContains(t, gotEcho, "Prepended") + checkStringContains(t, gotEcho, echoCmd.Long) + checkStringContains(t, gotEcho, "Postpended") + + gotEchoSub := fileContents(t, tmpdir, "root_echo_echosub.md") + checkStringContains(t, gotEchoSub, "Prepended") + checkStringContains(t, gotEchoSub, echoSubCmd.Long) + checkStringContains(t, gotEchoSub, "Postpended") +} + func BenchmarkGenMarkdownToFile(b *testing.B) { file, err := ioutil.TempFile("", "") if err != nil { @@ -110,3 +141,11 @@ func BenchmarkGenMarkdownToFile(b *testing.B) { } } } + +func fileContents(t *testing.T, dir, filename string) string { + contents, err := ioutil.ReadFile(filepath.Join(dir, filename)) + if err != nil { + t.Fatalf("Error loading file %q: %v ", filename, err) + } + return string(contents) +} From ff416ad438f79ee925809ff5e80fb796e23cb24b Mon Sep 17 00:00:00 2001 From: John McBride Date: Sat, 23 Jan 2021 16:05:55 -0700 Subject: [PATCH 158/211] Revert "Add the ability to specify a filePostpender in GenMarkdownTreeCustom (#1270)" (#1317) This reverts commit 23a6174c7f9d0c64a48e634054bdf6886fb7fbba. --- doc/md_docs.go | 11 ++++------- doc/md_docs_test.go | 39 --------------------------------------- 2 files changed, 4 insertions(+), 46 deletions(-) diff --git a/doc/md_docs.go b/doc/md_docs.go index fafab26c..5181af8d 100644 --- a/doc/md_docs.go +++ b/doc/md_docs.go @@ -122,17 +122,17 @@ func GenMarkdownCustom(cmd *cobra.Command, w io.Writer, linkHandler func(string) func GenMarkdownTree(cmd *cobra.Command, dir string) error { identity := func(s string) string { return s } emptyStr := func(s string) string { return "" } - return GenMarkdownTreeCustom(cmd, dir, emptyStr, emptyStr, identity) + return GenMarkdownTreeCustom(cmd, dir, emptyStr, identity) } // GenMarkdownTreeCustom is the the same as GenMarkdownTree, but -// with custom filePrepender, filePostpender, and linkHandler. -func GenMarkdownTreeCustom(cmd *cobra.Command, dir string, filePrepender, filePostpender, linkHandler func(string) string) error { +// with custom filePrepender and linkHandler. +func GenMarkdownTreeCustom(cmd *cobra.Command, dir string, filePrepender, linkHandler func(string) string) error { for _, c := range cmd.Commands() { if !c.IsAvailableCommand() || c.IsAdditionalHelpTopicCommand() { continue } - if err := GenMarkdownTreeCustom(c, dir, filePrepender, filePostpender, linkHandler); err != nil { + if err := GenMarkdownTreeCustom(c, dir, filePrepender, linkHandler); err != nil { return err } } @@ -151,8 +151,5 @@ func GenMarkdownTreeCustom(cmd *cobra.Command, dir string, filePrepender, filePo if err := GenMarkdownCustom(cmd, f, linkHandler); err != nil { return err } - if _, err := io.WriteString(f, filePostpender(filename)); err != nil { - return err - } return nil } diff --git a/doc/md_docs_test.go b/doc/md_docs_test.go index 29430b42..f3251679 100644 --- a/doc/md_docs_test.go +++ b/doc/md_docs_test.go @@ -95,37 +95,6 @@ func TestGenMdTree(t *testing.T) { } } -func TestGenMdTreeCustom(t *testing.T) { - tmpdir, err := ioutil.TempDir("", "test-gen-md-tree") - if err != nil { - t.Fatalf("Failed to create tmpdir: %v", err) - } - defer os.RemoveAll(tmpdir) - - prepender := func(s string) string { return "Prepended" } - postpender := func(s string) string { return "Postpended" } - identity := func(s string) string { return s } - - if err := GenMarkdownTreeCustom(rootCmd, tmpdir, prepender, postpender, identity); err != nil { - t.Fatalf("GenMarkdownTree failed: %v", err) - } - - gotRoot := fileContents(t, tmpdir, "root.md") - checkStringContains(t, gotRoot, "Prepended") - checkStringContains(t, gotRoot, rootCmd.Long) - checkStringContains(t, gotRoot, "Postpended") - - gotEcho := fileContents(t, tmpdir, "root_echo.md") - checkStringContains(t, gotEcho, "Prepended") - checkStringContains(t, gotEcho, echoCmd.Long) - checkStringContains(t, gotEcho, "Postpended") - - gotEchoSub := fileContents(t, tmpdir, "root_echo_echosub.md") - checkStringContains(t, gotEchoSub, "Prepended") - checkStringContains(t, gotEchoSub, echoSubCmd.Long) - checkStringContains(t, gotEchoSub, "Postpended") -} - func BenchmarkGenMarkdownToFile(b *testing.B) { file, err := ioutil.TempFile("", "") if err != nil { @@ -141,11 +110,3 @@ func BenchmarkGenMarkdownToFile(b *testing.B) { } } } - -func fileContents(t *testing.T, dir, filename string) string { - contents, err := ioutil.ReadFile(filepath.Join(dir, filename)) - if err != nil { - t.Fatalf("Error loading file %q: %v ", filename, err) - } - return string(contents) -} From 9df156e6d11ee27303bc69593f0f1e6de84693ec Mon Sep 17 00:00:00 2001 From: Joshua Harshman Date: Tue, 26 Jan 2021 10:55:24 -0700 Subject: [PATCH 159/211] Cobra User Contract (#1292) * Add some guiding principals to the project. Establish an understanding between user and maintainer. Set a goal for releases, security fixes and bug patches. * fix grammatical errors --- CONDUCT.md | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 CONDUCT.md diff --git a/CONDUCT.md b/CONDUCT.md new file mode 100644 index 00000000..9d16f88f --- /dev/null +++ b/CONDUCT.md @@ -0,0 +1,37 @@ +## Cobra User Contract + +### Versioning +Cobra will follow a steady release cadence. Non breaking changes will be released as minor versions quarterly. Patch bug releases are at the discretion of the maintainers. Users can expect security patch fixes to be released within relatively short order of a CVE becoming known. For more information on security patch fixes see the CVE section below. Releases will follow [Semantic Versioning](https://semver.org/). Users tracking the Master branch should expect unpredictable breaking changes as the project continues to move forward. For stability, it is highly recommended to use a release. + +### Backward Compatibility +We will maintain two major releases in a moving window. The N-1 release will only receive bug fixes and security updates and will be dropped once N+1 is released. + +### Deprecation +Deprecation of Go versions or dependent packages will only occur in major releases. To reduce the change of this taking users by surprise, any large deprecation will be preceded by an announcement in the [#cobra slack channel](https://gophers.slack.com/archives/CD3LP1199) and an Issue on Github. + +### CVE +Maintainers will make every effort to release security patches in the case of a medium to high severity CVE directly impacting the library. The speed in which these patches reach a release is up to the discretion of the maintainers. A low severity CVE may be a lower priority than a high severity one. + +### Communication +Cobra maintainers will use GitHub issues and the [#cobra slack channel](https://gophers.slack.com/archives/CD3LP1199) as the primary means of communication with the community. This is to foster open communication with all users and contributors. + +### Breaking Changes +Breaking changes are generally allowed in the master branch, as this is the branch used to develop the next release of Cobra. + +There may be times, however, when master is closed for breaking changes. This is likely to happen as we near the release of a new version. + +Breaking changes are not allowed in release branches, as these represent minor versions that have already been released. These version have consumers who expect the APIs, behaviors, etc, to remain stable during the lifetime of the patch stream for the minor release. + +Examples of breaking changes include: +- Removing or renaming exported constant, variable, type, or function. +- Updating the version of critical libraries such as `spf13/pflag`, `spf13/viper` etc... + - Some version updates may be acceptable for picking up bug fixes, but maintainers must exercise caution when reviewing. + +There may, at times, need to be exceptions where breaking changes are allowed in release branches. These are at the discretion of the project's maintainers, and must be carefully considered before merging. + +### CI Testing +Maintainers will ensure the Cobra test suite utilizes the current supported versions of Golang. + +### Disclaimer +Changes to this document and the contents therein are at the discretion of the maintainers. +None of the contents of this document are legally binding in any way to the maintainers or the users. From 1135bdecebe90d831220783e9e3d7baea8a05e39 Mon Sep 17 00:00:00 2001 From: Anthony Fok Date: Mon, 1 Feb 2021 12:44:33 -0700 Subject: [PATCH 160/211] Update gopkg.in/yaml.v2 to v2.4.0 The previous breaking change in yaml.v2 v2.3.0 has been reverted, see https://github.com/go-yaml/yaml/pull/670 --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 57e3244d..ff561440 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.5 github.com/spf13/viper v1.7.0 - gopkg.in/yaml.v2 v2.2.8 + gopkg.in/yaml.v2 v2.4.0 ) diff --git a/go.sum b/go.sum index 0aae7386..9328ee3e 100644 --- a/go.sum +++ b/go.sum @@ -304,8 +304,8 @@ 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.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 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= From 1d71ff02701cb01f7e9980754da9c95cb92252d2 Mon Sep 17 00:00:00 2001 From: Joshua Harshman Date: Mon, 1 Feb 2021 15:59:47 -0700 Subject: [PATCH 161/211] Deprecate Go < 1.14 (#1323) In accordance with our adopted best practices, the main branch and the next major release of Cobra will deprecate older and un-maintained versions of Golang. fix #1322 --- .travis.yml | 8 ++++---- go.mod | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index a9bd4e54..3255e06f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,8 +6,8 @@ stages: - build go: - - 1.12.x - - 1.13.x + - 1.14.x + - 1.15.x - tip before_install: @@ -19,10 +19,10 @@ matrix: - go: tip include: - stage: diff - go: 1.13.x + go: 1.14.x script: make fmt - stage: build - go: 1.13.x + go: 1.14.x script: make cobra_generator script: diff --git a/go.mod b/go.mod index ff561440..eb7d47fa 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/spf13/cobra -go 1.12 +go 1.14 require ( github.com/cpuguy83/go-md2man/v2 v2.0.0 From 652c755d3751109d878a9c8228c0bcb7aed0d4f5 Mon Sep 17 00:00:00 2001 From: Unai Martinez-Corral <38422348+umarcor@users.noreply.github.com> Date: Mon, 8 Feb 2021 00:08:50 +0000 Subject: [PATCH 162/211] Use golangci-lint (#1044) Use golangci-lint. Repair warnings and errors resulting from linting. --- .golangci.yml | 48 +++++++ .travis.yml | 9 +- Makefile | 18 +-- README.md | 9 +- bash_completions.go | 133 ++++++++++---------- bash_completions_test.go | 39 +++--- cobra.go | 15 +++ cobra/cmd/add.go | 13 +- cobra/cmd/add_test.go | 6 +- cobra/cmd/golden_test.go | 27 ---- cobra/cmd/helpers.go | 120 +----------------- cobra/cmd/helpers_test.go | 9 ++ cobra/cmd/init.go | 6 +- cobra/cmd/init_test.go | 2 +- cobra/cmd/licenses.go | 4 +- cobra/cmd/project.go | 3 +- cobra/cmd/root.go | 8 +- cobra/cmd/testdata/root.go.golden | 16 +-- cobra/tpl/main.go | 19 +-- cobra_test.go | 6 + command.go | 112 ++++++++--------- command_test.go | 199 ++++++++++++++---------------- custom_completions.go | 4 +- custom_completions_test.go | 46 +++---- doc/man_docs.go | 28 ++--- doc/man_docs_test.go | 8 +- doc/man_examples_test.go | 4 +- fish_completions.go | 6 +- fish_completions_test.go | 8 +- powershell_completions.go | 26 ++-- shell_completions.md | 7 +- zsh_completions.go | 4 +- 32 files changed, 437 insertions(+), 525 deletions(-) create mode 100644 .golangci.yml create mode 100644 cobra/cmd/helpers_test.go diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 00000000..0d6e6179 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,48 @@ +run: + deadline: 5m + +linters: + disable-all: true + enable: + #- bodyclose + - deadcode + #- depguard + #- dogsled + #- dupl + - errcheck + #- exhaustive + #- funlen + - gas + #- gochecknoinits + - goconst + #- gocritic + #- gocyclo + #- gofmt + - goimports + - golint + #- gomnd + #- goprintffuncname + #- gosec + #- gosimple + - govet + - ineffassign + - interfacer + #- lll + - maligned + - megacheck + #- misspell + #- nakedret + #- noctx + #- nolintlint + #- rowserrcheck + #- scopelint + #- staticcheck + - structcheck + #- stylecheck + #- typecheck + - unconvert + #- unparam + #- unused + - varcheck + #- whitespace + fast: false diff --git a/.travis.yml b/.travis.yml index 3255e06f..0821efdc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,6 @@ language: go stages: - - diff - test - build @@ -10,20 +9,20 @@ go: - 1.15.x - tip +env: GO111MODULE=on + before_install: - go get -u github.com/kyoh86/richgo - go get -u github.com/mitchellh/gox + - curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin latest matrix: allow_failures: - go: tip include: - - stage: diff - go: 1.14.x - script: make fmt - stage: build go: 1.14.x script: make cobra_generator -script: +script: - make test diff --git a/Makefile b/Makefile index e9740d1e..472c73bf 100644 --- a/Makefile +++ b/Makefile @@ -1,21 +1,29 @@ BIN="./bin" SRC=$(shell find . -name "*.go") +ifeq (, $(shell which golangci-lint)) +$(warning "could not find golangci-lint in $(PATH), run: curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh") +endif + 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 +.PHONY: fmt lint test cobra_generator install_deps clean default: all -all: fmt vet test cobra_generator +all: fmt test cobra_generator fmt: $(info ******************** checking formatting ********************) @test -z $(shell gofmt -l $(SRC)) || (gofmt -d $(SRC); exit 1) -test: install_deps vet +lint: + $(info ******************** running lint tools ********************) + golangci-lint run -v + +test: install_deps lint $(info ******************** running tests ********************) richgo test -v ./... @@ -28,9 +36,5 @@ install_deps: $(info ******************** downloading dependencies ********************) go get -v ./... -vet: - $(info ******************** vetting ********************) - go vet ./... - clean: rm -rf $(BIN) diff --git a/README.md b/README.md index 0c48c677..4a8811f3 100644 --- a/README.md +++ b/README.md @@ -234,11 +234,6 @@ 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. @@ -246,9 +241,7 @@ func initConfig() { } else { // Find home directory. home, err := homedir.Dir() - if err != nil { - er(err) - } + cobra.CheckErr(err) // Search config in home directory with name ".cobra" (without extension). viper.AddConfigPath(home) diff --git a/bash_completions.go b/bash_completions.go index 846636d7..71061479 100644 --- a/bash_completions.go +++ b/bash_completions.go @@ -19,9 +19,9 @@ const ( BashCompSubdirsInDir = "cobra_annotation_bash_completion_subdirs_in_dir" ) -func writePreamble(buf *bytes.Buffer, name string) { - buf.WriteString(fmt.Sprintf("# bash completion for %-36s -*- shell-script -*-\n", name)) - buf.WriteString(fmt.Sprintf(` +func writePreamble(buf io.StringWriter, name string) { + WriteStringAndCheck(buf, fmt.Sprintf("# bash completion for %-36s -*- shell-script -*-\n", name)) + WriteStringAndCheck(buf, fmt.Sprintf(` __%[1]s_debug() { if [[ -n ${BASH_COMP_DEBUG_FILE} ]]; then @@ -380,10 +380,10 @@ __%[1]s_handle_word() ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs)) } -func writePostscript(buf *bytes.Buffer, name string) { +func writePostscript(buf io.StringWriter, name string) { name = strings.Replace(name, ":", "__", -1) - buf.WriteString(fmt.Sprintf("__start_%s()\n", name)) - buf.WriteString(fmt.Sprintf(`{ + WriteStringAndCheck(buf, fmt.Sprintf("__start_%s()\n", name)) + WriteStringAndCheck(buf, fmt.Sprintf(`{ local cur prev words cword declare -A flaghash 2>/dev/null || : declare -A aliashash 2>/dev/null || : @@ -410,33 +410,33 @@ func writePostscript(buf *bytes.Buffer, name string) { } `, name)) - buf.WriteString(fmt.Sprintf(`if [[ $(type -t compopt) = "builtin" ]]; then + WriteStringAndCheck(buf, fmt.Sprintf(`if [[ $(type -t compopt) = "builtin" ]]; then complete -o default -F __start_%s %s else complete -o default -o nospace -F __start_%s %s fi `, name, name, name, name)) - buf.WriteString("# ex: ts=4 sw=4 et filetype=sh\n") + WriteStringAndCheck(buf, "# ex: ts=4 sw=4 et filetype=sh\n") } -func writeCommands(buf *bytes.Buffer, cmd *Command) { - buf.WriteString(" commands=()\n") +func writeCommands(buf io.StringWriter, cmd *Command) { + WriteStringAndCheck(buf, " commands=()\n") for _, c := range cmd.Commands() { if !c.IsAvailableCommand() && c != cmd.helpCommand { continue } - buf.WriteString(fmt.Sprintf(" commands+=(%q)\n", c.Name())) + WriteStringAndCheck(buf, fmt.Sprintf(" commands+=(%q)\n", c.Name())) writeCmdAliases(buf, c) } - buf.WriteString("\n") + WriteStringAndCheck(buf, "\n") } -func writeFlagHandler(buf *bytes.Buffer, name string, annotations map[string][]string, cmd *Command) { +func writeFlagHandler(buf io.StringWriter, name string, annotations map[string][]string, cmd *Command) { for key, value := range annotations { switch key { case BashCompFilenameExt: - buf.WriteString(fmt.Sprintf(" flags_with_completion+=(%q)\n", name)) + WriteStringAndCheck(buf, fmt.Sprintf(" flags_with_completion+=(%q)\n", name)) var ext string if len(value) > 0 { @@ -444,17 +444,18 @@ func writeFlagHandler(buf *bytes.Buffer, name string, annotations map[string][]s } else { ext = "_filedir" } - buf.WriteString(fmt.Sprintf(" flags_completion+=(%q)\n", ext)) + WriteStringAndCheck(buf, fmt.Sprintf(" flags_completion+=(%q)\n", ext)) case BashCompCustom: - buf.WriteString(fmt.Sprintf(" flags_with_completion+=(%q)\n", name)) + WriteStringAndCheck(buf, fmt.Sprintf(" flags_with_completion+=(%q)\n", name)) + if len(value) > 0 { handlers := strings.Join(value, "; ") - buf.WriteString(fmt.Sprintf(" flags_completion+=(%q)\n", handlers)) + WriteStringAndCheck(buf, fmt.Sprintf(" flags_completion+=(%q)\n", handlers)) } else { - buf.WriteString(" flags_completion+=(:)\n") + WriteStringAndCheck(buf, " flags_completion+=(:)\n") } case BashCompSubdirsInDir: - buf.WriteString(fmt.Sprintf(" flags_with_completion+=(%q)\n", name)) + WriteStringAndCheck(buf, fmt.Sprintf(" flags_with_completion+=(%q)\n", name)) var ext string if len(value) == 1 { @@ -462,46 +463,48 @@ func writeFlagHandler(buf *bytes.Buffer, name string, annotations map[string][]s } else { ext = "_filedir -d" } - buf.WriteString(fmt.Sprintf(" flags_completion+=(%q)\n", ext)) + WriteStringAndCheck(buf, fmt.Sprintf(" flags_completion+=(%q)\n", ext)) } } } -func writeShortFlag(buf *bytes.Buffer, flag *pflag.Flag, cmd *Command) { +const cbn = "\")\n" + +func writeShortFlag(buf io.StringWriter, flag *pflag.Flag, cmd *Command) { name := flag.Shorthand format := " " if len(flag.NoOptDefVal) == 0 { format += "two_word_" } - format += "flags+=(\"-%s\")\n" - buf.WriteString(fmt.Sprintf(format, name)) + format += "flags+=(\"-%s" + cbn + WriteStringAndCheck(buf, fmt.Sprintf(format, name)) writeFlagHandler(buf, "-"+name, flag.Annotations, cmd) } -func writeFlag(buf *bytes.Buffer, flag *pflag.Flag, cmd *Command) { +func writeFlag(buf io.StringWriter, flag *pflag.Flag, cmd *Command) { name := flag.Name format := " flags+=(\"--%s" if len(flag.NoOptDefVal) == 0 { format += "=" } - format += "\")\n" - buf.WriteString(fmt.Sprintf(format, name)) + format += cbn + WriteStringAndCheck(buf, fmt.Sprintf(format, name)) if len(flag.NoOptDefVal) == 0 { - format = " two_word_flags+=(\"--%s\")\n" - buf.WriteString(fmt.Sprintf(format, name)) + format = " two_word_flags+=(\"--%s" + cbn + WriteStringAndCheck(buf, fmt.Sprintf(format, name)) } writeFlagHandler(buf, "--"+name, flag.Annotations, cmd) } -func writeLocalNonPersistentFlag(buf *bytes.Buffer, flag *pflag.Flag) { +func writeLocalNonPersistentFlag(buf io.StringWriter, flag *pflag.Flag) { name := flag.Name - format := " local_nonpersistent_flags+=(\"--%[1]s\")\n" + format := " local_nonpersistent_flags+=(\"--%[1]s" + cbn if len(flag.NoOptDefVal) == 0 { - format += " local_nonpersistent_flags+=(\"--%[1]s=\")\n" + format += " local_nonpersistent_flags+=(\"--%[1]s=" + cbn } - buf.WriteString(fmt.Sprintf(format, name)) + WriteStringAndCheck(buf, fmt.Sprintf(format, name)) if len(flag.Shorthand) > 0 { - buf.WriteString(fmt.Sprintf(" local_nonpersistent_flags+=(\"-%s\")\n", flag.Shorthand)) + WriteStringAndCheck(buf, fmt.Sprintf(" local_nonpersistent_flags+=(\"-%s\")\n", flag.Shorthand)) } } @@ -519,9 +522,9 @@ func prepareCustomAnnotationsForFlags(cmd *Command) { } } -func writeFlags(buf *bytes.Buffer, cmd *Command) { +func writeFlags(buf io.StringWriter, cmd *Command) { prepareCustomAnnotationsForFlags(cmd) - buf.WriteString(` flags=() + WriteStringAndCheck(buf, ` flags=() two_word_flags=() local_nonpersistent_flags=() flags_with_completion=() @@ -553,11 +556,11 @@ func writeFlags(buf *bytes.Buffer, cmd *Command) { } }) - buf.WriteString("\n") + WriteStringAndCheck(buf, "\n") } -func writeRequiredFlag(buf *bytes.Buffer, cmd *Command) { - buf.WriteString(" must_have_one_flag=()\n") +func writeRequiredFlag(buf io.StringWriter, cmd *Command) { + WriteStringAndCheck(buf, " must_have_one_flag=()\n") flags := cmd.NonInheritedFlags() flags.VisitAll(func(flag *pflag.Flag) { if nonCompletableFlag(flag) { @@ -570,55 +573,55 @@ func writeRequiredFlag(buf *bytes.Buffer, cmd *Command) { if flag.Value.Type() != "bool" { format += "=" } - format += "\")\n" - buf.WriteString(fmt.Sprintf(format, flag.Name)) + format += cbn + WriteStringAndCheck(buf, fmt.Sprintf(format, flag.Name)) if len(flag.Shorthand) > 0 { - buf.WriteString(fmt.Sprintf(" must_have_one_flag+=(\"-%s\")\n", flag.Shorthand)) + WriteStringAndCheck(buf, fmt.Sprintf(" must_have_one_flag+=(\"-%s"+cbn, flag.Shorthand)) } } } }) } -func writeRequiredNouns(buf *bytes.Buffer, cmd *Command) { - buf.WriteString(" must_have_one_noun=()\n") - sort.Sort(sort.StringSlice(cmd.ValidArgs)) +func writeRequiredNouns(buf io.StringWriter, cmd *Command) { + WriteStringAndCheck(buf, " must_have_one_noun=()\n") + sort.Strings(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)) + WriteStringAndCheck(buf, fmt.Sprintf(" must_have_one_noun+=(%q)\n", value)) } if cmd.ValidArgsFunction != nil { - buf.WriteString(" has_completion_function=1\n") + WriteStringAndCheck(buf, " has_completion_function=1\n") } } -func writeCmdAliases(buf *bytes.Buffer, cmd *Command) { +func writeCmdAliases(buf io.StringWriter, cmd *Command) { if len(cmd.Aliases) == 0 { return } - sort.Sort(sort.StringSlice(cmd.Aliases)) + sort.Strings(cmd.Aliases) - buf.WriteString(fmt.Sprint(` if [[ -z "${BASH_VERSION}" || "${BASH_VERSINFO[0]}" -gt 3 ]]; then`, "\n")) + WriteStringAndCheck(buf, fmt.Sprint(` if [[ -z "${BASH_VERSION}" || "${BASH_VERSINFO[0]}" -gt 3 ]]; then`, "\n")) for _, value := range cmd.Aliases { - buf.WriteString(fmt.Sprintf(" command_aliases+=(%q)\n", value)) - buf.WriteString(fmt.Sprintf(" aliashash[%q]=%q\n", value, cmd.Name())) + WriteStringAndCheck(buf, fmt.Sprintf(" command_aliases+=(%q)\n", value)) + WriteStringAndCheck(buf, fmt.Sprintf(" aliashash[%q]=%q\n", value, cmd.Name())) } - buf.WriteString(` fi`) - buf.WriteString("\n") + WriteStringAndCheck(buf, ` fi`) + WriteStringAndCheck(buf, "\n") } -func writeArgAliases(buf *bytes.Buffer, cmd *Command) { - buf.WriteString(" noun_aliases=()\n") - sort.Sort(sort.StringSlice(cmd.ArgAliases)) +func writeArgAliases(buf io.StringWriter, cmd *Command) { + WriteStringAndCheck(buf, " noun_aliases=()\n") + sort.Strings(cmd.ArgAliases) for _, value := range cmd.ArgAliases { - buf.WriteString(fmt.Sprintf(" noun_aliases+=(%q)\n", value)) + WriteStringAndCheck(buf, fmt.Sprintf(" noun_aliases+=(%q)\n", value)) } } -func gen(buf *bytes.Buffer, cmd *Command) { +func gen(buf io.StringWriter, cmd *Command) { for _, c := range cmd.Commands() { if !c.IsAvailableCommand() && c != cmd.helpCommand { continue @@ -630,22 +633,22 @@ func gen(buf *bytes.Buffer, cmd *Command) { commandName = strings.Replace(commandName, ":", "__", -1) if cmd.Root() == cmd { - buf.WriteString(fmt.Sprintf("_%s_root_command()\n{\n", commandName)) + WriteStringAndCheck(buf, fmt.Sprintf("_%s_root_command()\n{\n", commandName)) } else { - buf.WriteString(fmt.Sprintf("_%s()\n{\n", commandName)) + WriteStringAndCheck(buf, fmt.Sprintf("_%s()\n{\n", commandName)) } - buf.WriteString(fmt.Sprintf(" last_command=%q\n", commandName)) - buf.WriteString("\n") - buf.WriteString(" command_aliases=()\n") - buf.WriteString("\n") + WriteStringAndCheck(buf, fmt.Sprintf(" last_command=%q\n", commandName)) + WriteStringAndCheck(buf, "\n") + WriteStringAndCheck(buf, " command_aliases=()\n") + WriteStringAndCheck(buf, "\n") writeCommands(buf, cmd) writeFlags(buf, cmd) writeRequiredFlag(buf, cmd) writeRequiredNouns(buf, cmd) writeArgAliases(buf, cmd) - buf.WriteString("}\n\n") + WriteStringAndCheck(buf, "}\n\n") } // GenBashCompletion generates bash completion file and writes to the passed writer. diff --git a/bash_completions_test.go b/bash_completions_test.go index 2c182ba7..21d1fe0e 100644 --- a/bash_completions_test.go +++ b/bash_completions_test.go @@ -40,10 +40,9 @@ func checkRegex(t *testing.T, found, pattern string) { } func runShellCheck(s string) error { - excluded := []string{ + cmd := exec.Command("shellcheck", "-s", "bash", "-", "-e", "SC2034", // PREFIX appears unused. Verify it or export it. - } - cmd := exec.Command("shellcheck", "-s", "bash", "-", "-e", strings.Join(excluded, ",")) + ) cmd.Stderr = os.Stderr cmd.Stdout = os.Stdout @@ -52,7 +51,9 @@ func runShellCheck(s string) error { return err } go func() { - stdin.Write([]byte(s)) + _, err := stdin.Write([]byte(s)) + CheckErr(err) + stdin.Close() }() @@ -74,26 +75,26 @@ func TestBashCompletions(t *testing.T) { Run: emptyRun, } rootCmd.Flags().IntP("introot", "i", -1, "help message for flag introot") - rootCmd.MarkFlagRequired("introot") + assertNoErr(t, rootCmd.MarkFlagRequired("introot")) // Filename. rootCmd.Flags().String("filename", "", "Enter a filename") - rootCmd.MarkFlagFilename("filename", "json", "yaml", "yml") + assertNoErr(t, rootCmd.MarkFlagFilename("filename", "json", "yaml", "yml")) // Persistent filename. rootCmd.PersistentFlags().String("persistent-filename", "", "Enter a filename") - rootCmd.MarkPersistentFlagFilename("persistent-filename") - rootCmd.MarkPersistentFlagRequired("persistent-filename") + assertNoErr(t, rootCmd.MarkPersistentFlagFilename("persistent-filename")) + assertNoErr(t, rootCmd.MarkPersistentFlagRequired("persistent-filename")) // Filename extensions. rootCmd.Flags().String("filename-ext", "", "Enter a filename (extension limited)") - rootCmd.MarkFlagFilename("filename-ext") + assertNoErr(t, rootCmd.MarkFlagFilename("filename-ext")) rootCmd.Flags().String("custom", "", "Enter a filename (extension limited)") - rootCmd.MarkFlagCustom("custom", "__complete_custom") + assertNoErr(t, rootCmd.MarkFlagCustom("custom", "__complete_custom")) // Subdirectories in a given directory. rootCmd.Flags().String("theme", "", "theme to use (located in /themes/THEMENAME/)") - rootCmd.Flags().SetAnnotation("theme", BashCompSubdirsInDir, []string{"themes"}) + assertNoErr(t, rootCmd.Flags().SetAnnotation("theme", BashCompSubdirsInDir, []string{"themes"})) // For two word flags check rootCmd.Flags().StringP("two", "t", "", "this is two word flags") @@ -109,9 +110,9 @@ func TestBashCompletions(t *testing.T) { } echoCmd.Flags().String("filename", "", "Enter a filename") - echoCmd.MarkFlagFilename("filename", "json", "yaml", "yml") + assertNoErr(t, echoCmd.MarkFlagFilename("filename", "json", "yaml", "yml")) echoCmd.Flags().String("config", "", "config to use (located in /config/PROFILE/)") - echoCmd.Flags().SetAnnotation("config", BashCompSubdirsInDir, []string{"config"}) + assertNoErr(t, echoCmd.Flags().SetAnnotation("config", BashCompSubdirsInDir, []string{"config"})) printCmd := &Command{ Use: "print [string to print]", @@ -149,7 +150,7 @@ func TestBashCompletions(t *testing.T) { rootCmd.AddCommand(echoCmd, printCmd, deprecatedCmd, colonCmd) buf := new(bytes.Buffer) - rootCmd.GenBashCompletion(buf) + assertNoErr(t, rootCmd.GenBashCompletion(buf)) output := buf.String() check(t, output, "_root") @@ -216,10 +217,10 @@ func TestBashCompletionHiddenFlag(t *testing.T) { const flagName = "hiddenFlag" c.Flags().Bool(flagName, false, "") - c.Flags().MarkHidden(flagName) + assertNoErr(t, c.Flags().MarkHidden(flagName)) buf := new(bytes.Buffer) - c.GenBashCompletion(buf) + assertNoErr(t, c.GenBashCompletion(buf)) output := buf.String() if strings.Contains(output, flagName) { @@ -232,10 +233,10 @@ func TestBashCompletionDeprecatedFlag(t *testing.T) { const flagName = "deprecated-flag" c.Flags().Bool(flagName, false, "") - c.Flags().MarkDeprecated(flagName, "use --not-deprecated instead") + assertNoErr(t, c.Flags().MarkDeprecated(flagName, "use --not-deprecated instead")) buf := new(bytes.Buffer) - c.GenBashCompletion(buf) + assertNoErr(t, c.GenBashCompletion(buf)) output := buf.String() if strings.Contains(output, flagName) { @@ -250,7 +251,7 @@ func TestBashCompletionTraverseChildren(t *testing.T) { c.Flags().BoolP("bool-flag", "b", false, "bool flag") buf := new(bytes.Buffer) - c.GenBashCompletion(buf) + assertNoErr(t, c.GenBashCompletion(buf)) output := buf.String() // check that local nonpersistent flag are not set since we have TraverseChildren set to true diff --git a/cobra.go b/cobra.go index d01becc8..d6cbfd71 100644 --- a/cobra.go +++ b/cobra.go @@ -19,6 +19,7 @@ package cobra import ( "fmt" "io" + "os" "reflect" "strconv" "strings" @@ -205,3 +206,17 @@ func stringInSlice(a string, list []string) bool { } return false } + +// CheckErr prints the msg with the prefix 'Error:' and exits with error code 1. If the msg is nil, it does nothing. +func CheckErr(msg interface{}) { + if msg != nil { + fmt.Fprintln(os.Stderr, "Error:", msg) + os.Exit(1) + } +} + +// WriteStringAndCheck writes a string into a buffer, and checks if the error is not nil. +func WriteStringAndCheck(b io.StringWriter, s string) { + _, err := b.WriteString(s) + CheckErr(err) +} diff --git a/cobra/cmd/add.go b/cobra/cmd/add.go index 6645a755..8377411e 100644 --- a/cobra/cmd/add.go +++ b/cobra/cmd/add.go @@ -40,13 +40,11 @@ Example: cobra add server -> resulting in a new cmd/server.go`, Run: func(cmd *cobra.Command, args []string) { if len(args) < 1 { - er("add needs a name for the command") + cobra.CheckErr(fmt.Errorf("add needs a name for the command")) } wd, err := os.Getwd() - if err != nil { - er(err) - } + cobra.CheckErr(err) commandName := validateCmdName(args[0]) command := &Command{ @@ -59,10 +57,7 @@ Example: cobra add server -> resulting in a new cmd/server.go`, }, } - err = command.Create() - if err != nil { - er(err) - } + cobra.CheckErr(command.Create()) fmt.Printf("%s created at %s\n", command.CmdName, command.AbsolutePath) }, @@ -72,7 +67,7 @@ Example: cobra add server -> resulting in a new cmd/server.go`, func init() { addCmd.Flags().StringVarP(&packageName, "package", "t", "", "target package name (e.g. github.com/spf13/hugo)") addCmd.Flags().StringVarP(&parentName, "parent", "p", "rootCmd", "variable name of parent command for this command") - addCmd.Flags().MarkDeprecated("package", "this operation has been removed.") + cobra.CheckErr(addCmd.Flags().MarkDeprecated("package", "this operation has been removed.")) } // validateCmdName returns source without any dashes and underscore. diff --git a/cobra/cmd/add_test.go b/cobra/cmd/add_test.go index de92fcea..0b32ca67 100644 --- a/cobra/cmd/add_test.go +++ b/cobra/cmd/add_test.go @@ -14,10 +14,8 @@ func TestGoldenAddCmd(t *testing.T) { } defer os.RemoveAll(command.AbsolutePath) - command.Project.Create() - if err := command.Create(); err != nil { - t.Fatal(err) - } + assertNoErr(t, command.Project.Create()) + assertNoErr(t, command.Create()) generatedFile := fmt.Sprintf("%s/cmd/%s.go", command.AbsolutePath, command.CmdName) goldenFile := fmt.Sprintf("testdata/%s.go.golden", command.CmdName) diff --git a/cobra/cmd/golden_test.go b/cobra/cmd/golden_test.go index e4055f33..99b92e31 100644 --- a/cobra/cmd/golden_test.go +++ b/cobra/cmd/golden_test.go @@ -3,14 +3,11 @@ package cmd import ( "bytes" "errors" - "flag" "fmt" "io/ioutil" "os/exec" ) -var update = flag.Bool("update", false, "update .golden files") - func init() { // Mute commands. addCmd.SetOut(new(bytes.Buffer)) @@ -58,27 +55,3 @@ func compareFiles(pathA, pathB string) error { } return nil } - -// checkLackFiles checks if all elements of expected are in got. -func checkLackFiles(expected, got []string) error { - lacks := make([]string, 0, len(expected)) - for _, ev := range expected { - if !stringInStringSlice(ev, got) { - lacks = append(lacks, ev) - } - } - if len(lacks) > 0 { - return fmt.Errorf("Lack %v file(s): %v", len(lacks), lacks) - } - return nil -} - -// stringInStringSlice checks if s is an element of slice. -func stringInStringSlice(s string, slice []string) bool { - for _, v := range slice { - if s == v { - return true - } - } - return false -} diff --git a/cobra/cmd/helpers.go b/cobra/cmd/helpers.go index cd94b3e3..6a8047e3 100644 --- a/cobra/cmd/helpers.go +++ b/cobra/cmd/helpers.go @@ -14,14 +14,12 @@ package cmd import ( - "bytes" - "fmt" - "io" "os" "os/exec" "path/filepath" "strings" - "text/template" + + "github.com/spf13/cobra" ) var srcPaths []string @@ -43,14 +41,12 @@ func init() { } out, err := exec.Command(goExecutable, "env", "GOPATH").Output() - if err != nil { - er(err) - } + cobra.CheckErr(err) toolchainGoPath := strings.TrimSpace(string(out)) goPaths = filepath.SplitList(toolchainGoPath) if len(goPaths) == 0 { - er("$GOPATH is not set") + cobra.CheckErr("$GOPATH is not set") } } srcPaths = make([]string, 0, len(goPaths)) @@ -58,111 +54,3 @@ func init() { srcPaths = append(srcPaths, filepath.Join(goPath, "src")) } } - -func er(msg interface{}) { - fmt.Println("Error:", msg) - os.Exit(1) -} - -// isEmpty checks if a given path is empty. -// Hidden files in path are ignored. -func isEmpty(path string) bool { - fi, err := os.Stat(path) - if err != nil { - er(err) - } - - if !fi.IsDir() { - return fi.Size() == 0 - } - - f, err := os.Open(path) - if err != nil { - er(err) - } - defer f.Close() - - names, err := f.Readdirnames(-1) - if err != nil && err != io.EOF { - er(err) - } - - for _, name := range names { - if len(name) > 0 && name[0] != '.' { - return false - } - } - return true -} - -// exists checks if a file or directory exists. -func exists(path string) bool { - if path == "" { - return false - } - _, err := os.Stat(path) - if err == nil { - return true - } - if !os.IsNotExist(err) { - er(err) - } - return false -} - -func executeTemplate(tmplStr string, data interface{}) (string, error) { - tmpl, err := template.New("").Funcs(template.FuncMap{"comment": commentifyString}).Parse(tmplStr) - if err != nil { - return "", err - } - - buf := new(bytes.Buffer) - err = tmpl.Execute(buf, data) - return buf.String(), err -} - -func writeStringToFile(path string, s string) error { - return writeToFile(path, strings.NewReader(s)) -} - -// writeToFile writes r to file with path only -// if file/directory on given path doesn't exist. -func writeToFile(path string, r io.Reader) error { - if exists(path) { - return fmt.Errorf("%v already exists", path) - } - - dir := filepath.Dir(path) - if dir != "" { - if err := os.MkdirAll(dir, 0777); err != nil { - return err - } - } - - file, err := os.Create(path) - if err != nil { - return err - } - defer file.Close() - - _, err = io.Copy(file, r) - return err -} - -// commentfyString comments every line of in. -func commentifyString(in string) string { - var newlines []string - lines := strings.Split(in, "\n") - for _, line := range lines { - if strings.HasPrefix(line, "//") { - newlines = append(newlines, line) - } else { - if line == "" { - newlines = append(newlines, "//") - } else { - newlines = append(newlines, "// "+line) - } - } - } - return strings.Join(newlines, "\n") -} diff --git a/cobra/cmd/helpers_test.go b/cobra/cmd/helpers_test.go new file mode 100644 index 00000000..c5d20026 --- /dev/null +++ b/cobra/cmd/helpers_test.go @@ -0,0 +1,9 @@ +package cmd + +import "testing" + +func assertNoErr(t *testing.T, e error) { + if e != nil { + t.Error(e) + } +} diff --git a/cobra/cmd/init.go b/cobra/cmd/init.go index 504a4785..8c0e617a 100644 --- a/cobra/cmd/init.go +++ b/cobra/cmd/init.go @@ -39,9 +39,7 @@ and the appropriate structure for a Cobra-based CLI application. Run: func(_ *cobra.Command, args []string) { projectPath, err := initializeProject(args) - if err != nil { - er(err) - } + cobra.CheckErr(err) fmt.Printf("Your Cobra application is ready at\n%s\n", projectPath) }, } @@ -49,7 +47,7 @@ and the appropriate structure for a Cobra-based CLI application. func init() { initCmd.Flags().StringVar(&pkgName, "pkg-name", "", "fully qualified pkg name") - initCmd.MarkFlagRequired("pkg-name") + cobra.CheckErr(initCmd.MarkFlagRequired("pkg-name")) } func initializeProject(args []string) (string, error) { diff --git a/cobra/cmd/init_test.go b/cobra/cmd/init_test.go index c4b3f09a..6d21ef77 100644 --- a/cobra/cmd/init_test.go +++ b/cobra/cmd/init_test.go @@ -59,7 +59,7 @@ func TestGoldenInitCmd(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - initCmd.Flags().Set("pkg-name", tt.pkgName) + assertNoErr(t, initCmd.Flags().Set("pkg-name", tt.pkgName)) viper.Set("useViper", true) projectPath, err := initializeProject(tt.args) defer func() { diff --git a/cobra/cmd/licenses.go b/cobra/cmd/licenses.go index a070134d..2b3a4243 100644 --- a/cobra/cmd/licenses.go +++ b/cobra/cmd/licenses.go @@ -16,9 +16,11 @@ package cmd import ( + "fmt" "strings" "time" + "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -92,7 +94,7 @@ func copyrightLine() string { func findLicense(name string) License { found := matchLicense(name) if found == "" { - er("unknown license: " + name) + cobra.CheckErr(fmt.Errorf("unknown license: " + name)) } return Licenses[found] } diff --git a/cobra/cmd/project.go b/cobra/cmd/project.go index ecd783d0..bd68a31d 100644 --- a/cobra/cmd/project.go +++ b/cobra/cmd/project.go @@ -5,6 +5,7 @@ import ( "os" "text/template" + "github.com/spf13/cobra" "github.com/spf13/cobra/cobra/tpl" ) @@ -49,7 +50,7 @@ func (p *Project) Create() error { // create cmd/root.go if _, err = os.Stat(fmt.Sprintf("%s/cmd", p.AbsolutePath)); os.IsNotExist(err) { - os.Mkdir(fmt.Sprintf("%s/cmd", p.AbsolutePath), 0751) + cobra.CheckErr(os.Mkdir(fmt.Sprintf("%s/cmd", p.AbsolutePath), 0751)) } rootFile, err := os.Create(fmt.Sprintf("%s/cmd/root.go", p.AbsolutePath)) if err != nil { diff --git a/cobra/cmd/root.go b/cobra/cmd/root.go index 97f404bb..f27ae7e6 100644 --- a/cobra/cmd/root.go +++ b/cobra/cmd/root.go @@ -47,8 +47,8 @@ func init() { 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")) + cobra.CheckErr(viper.BindPFlag("author", rootCmd.PersistentFlags().Lookup("author"))) + cobra.CheckErr(viper.BindPFlag("useViper", rootCmd.PersistentFlags().Lookup("viper"))) viper.SetDefault("author", "NAME HERE ") viper.SetDefault("license", "apache") @@ -63,9 +63,7 @@ func initConfig() { } else { // Find home directory. home, err := homedir.Dir() - if err != nil { - er(err) - } + cobra.CheckErr(err) // Search config in home directory with name ".cobra" (without extension). viper.AddConfigPath(home) diff --git a/cobra/cmd/testdata/root.go.golden b/cobra/cmd/testdata/root.go.golden index 260c7aa8..be040365 100644 --- a/cobra/cmd/testdata/root.go.golden +++ b/cobra/cmd/testdata/root.go.golden @@ -17,8 +17,8 @@ package cmd import ( "fmt" - "github.com/spf13/cobra" "os" + "github.com/spf13/cobra" homedir "github.com/mitchellh/go-homedir" "github.com/spf13/viper" @@ -38,16 +38,13 @@ 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) { }, + // 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) - } + cobra.CheckErr(rootCmd.Execute()) } func init() { @@ -72,10 +69,7 @@ func initConfig() { } else { // Find home directory. home, err := homedir.Dir() - if err != nil { - fmt.Println(err) - os.Exit(1) - } + cobra.CheckErr(err) // Search config in home directory with name ".testproject" (without extension). viper.AddConfigPath(home) @@ -86,6 +80,6 @@ func initConfig() { // If a config file is found, read it in. if err := viper.ReadInConfig(); err == nil { - fmt.Println("Using config file:", viper.ConfigFileUsed()) + fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed()) } } diff --git a/cobra/tpl/main.go b/cobra/tpl/main.go index 4348e561..0b9c6610 100644 --- a/cobra/tpl/main.go +++ b/cobra/tpl/main.go @@ -24,12 +24,11 @@ package cmd import ( "fmt" - "github.com/spf13/cobra" "os" + "github.com/spf13/cobra" {{ if .Viper }} homedir "github.com/mitchellh/go-homedir" - "github.com/spf13/viper" -{{ end -}} + "github.com/spf13/viper"{{ end }} ) {{ if .Viper -}} @@ -48,16 +47,13 @@ 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) { }, + // 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) - } + cobra.CheckErr(rootCmd.Execute()) } func init() { @@ -86,10 +82,7 @@ func initConfig() { } else { // Find home directory. home, err := homedir.Dir() - if err != nil { - fmt.Println(err) - os.Exit(1) - } + cobra.CheckErr(err) // Search config in home directory with name ".{{ .AppName }}" (without extension). viper.AddConfigPath(home) @@ -100,7 +93,7 @@ func initConfig() { // If a config file is found, read it in. if err := viper.ReadInConfig(); err == nil { - fmt.Println("Using config file:", viper.ConfigFileUsed()) + fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed()) } } {{- end }} diff --git a/cobra_test.go b/cobra_test.go index 0d1755bd..1219cc07 100644 --- a/cobra_test.go +++ b/cobra_test.go @@ -5,6 +5,12 @@ import ( "text/template" ) +func assertNoErr(t *testing.T, e error) { + if e != nil { + t.Error(e) + } +} + func TestAddTemplateFunctions(t *testing.T) { AddTemplateFunc("t", func() bool { return true }) AddTemplateFuncs(template.FuncMap{ diff --git a/command.go b/command.go index 2b82a553..d6732ad1 100644 --- a/command.go +++ b/command.go @@ -84,9 +84,6 @@ type Command struct { // Deprecated defines, if this command is deprecated and should print this string when used. Deprecated string - // Hidden defines, if this command is hidden and should NOT show up in the list of available commands. - Hidden bool - // Annotations are key/value pairs that can be used by applications to identify or // group commands. Annotations map[string]string @@ -126,55 +123,6 @@ type Command struct { // PersistentPostRunE: PersistentPostRun but returns an error. PersistentPostRunE func(cmd *Command, args []string) error - // SilenceErrors is an option to quiet errors down stream. - SilenceErrors bool - - // SilenceUsage is an option to silence usage when an error occurs. - SilenceUsage bool - - // DisableFlagParsing disables the flag parsing. - // If this is true all flags will be passed to the command as arguments. - DisableFlagParsing bool - - // DisableAutoGenTag defines, if gen tag ("Auto generated by spf13/cobra...") - // will be printed by generating docs for this command. - DisableAutoGenTag bool - - // DisableFlagsInUseLine will disable the addition of [flags] to the usage - // line of a command when printing help or generating docs - DisableFlagsInUseLine bool - - // DisableSuggestions disables the suggestions based on Levenshtein distance - // that go along with 'unknown command' messages. - DisableSuggestions bool - // SuggestionsMinimumDistance defines minimum levenshtein distance to display suggestions. - // Must be > 0. - SuggestionsMinimumDistance int - - // TraverseChildren parses flags on all parents before executing child command. - TraverseChildren bool - - // 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. - parent *Command - // Max lengths of commands' string lengths for use in padding. - commandsMaxUseLen int - commandsMaxCommandPathLen int - commandsMaxNameLen int - // commandsAreSorted defines, if command slice are sorted or not. - commandsAreSorted bool - // commandCalledAs is the name or alias value used to call this command. - commandCalledAs struct { - name string - called bool - } - // args is actual args parsed from flags. args []string // flagErrorBuf contains all error messages from pflag. @@ -216,6 +164,60 @@ type Command struct { outWriter io.Writer // errWriter is a writer defined by the user that replaces stderr errWriter io.Writer + + //FParseErrWhitelist flag parse errors to be ignored + FParseErrWhitelist FParseErrWhitelist + + // commandsAreSorted defines, if command slice are sorted or not. + commandsAreSorted bool + // commandCalledAs is the name or alias value used to call this command. + commandCalledAs struct { + name string + called bool + } + + ctx context.Context + + // commands is the list of commands supported by this program. + commands []*Command + // parent is a parent command for this command. + parent *Command + // Max lengths of commands' string lengths for use in padding. + commandsMaxUseLen int + commandsMaxCommandPathLen int + commandsMaxNameLen int + + // TraverseChildren parses flags on all parents before executing child command. + TraverseChildren bool + + // Hidden defines, if this command is hidden and should NOT show up in the list of available commands. + Hidden bool + + // SilenceErrors is an option to quiet errors down stream. + SilenceErrors bool + + // SilenceUsage is an option to silence usage when an error occurs. + SilenceUsage bool + + // DisableFlagParsing disables the flag parsing. + // If this is true all flags will be passed to the command as arguments. + DisableFlagParsing bool + + // DisableAutoGenTag defines, if gen tag ("Auto generated by spf13/cobra...") + // will be printed by generating docs for this command. + DisableAutoGenTag bool + + // DisableFlagsInUseLine will disable the addition of [flags] to the usage + // line of a command when printing help or generating docs + DisableFlagsInUseLine bool + + // DisableSuggestions disables the suggestions based on Levenshtein distance + // that go along with 'unknown command' messages. + DisableSuggestions bool + + // SuggestionsMinimumDistance defines minimum levenshtein distance to display suggestions. + // Must be > 0. + SuggestionsMinimumDistance int } // Context returns underlying command context. If command wasn't @@ -418,7 +420,7 @@ func (c *Command) UsageString() string { c.outWriter = bb c.errWriter = bb - c.Usage() + CheckErr(c.Usage()) // Setting things back to normal c.outWriter = tmpOutput @@ -1087,10 +1089,10 @@ Simply type ` + c.Name() + ` help [path to command] for full details.`, cmd, _, e := c.Root().Find(args) if cmd == nil || e != nil { c.Printf("Unknown help topic %#q\n", args) - c.Root().Usage() + CheckErr(c.Root().Usage()) } else { cmd.InitDefaultHelpFlag() // make possible 'help' flag to be shown - cmd.Help() + CheckErr(cmd.Help()) } }, } diff --git a/command_test.go b/command_test.go index 3a47a81b..9640fc5d 100644 --- a/command_test.go +++ b/command_test.go @@ -58,6 +58,8 @@ func checkStringOmits(t *testing.T, got, expected string) { } } +const onetwo = "one two" + func TestSingleCommand(t *testing.T) { var rootCmdArgs []string rootCmd := &Command{ @@ -78,9 +80,8 @@ func TestSingleCommand(t *testing.T) { } got := strings.Join(rootCmdArgs, " ") - expected := "one two" - if got != expected { - t.Errorf("rootCmdArgs expected: %q, got: %q", expected, got) + if got != onetwo { + t.Errorf("rootCmdArgs expected: %q, got: %q", onetwo, got) } } @@ -104,9 +105,8 @@ func TestChildCommand(t *testing.T) { } got := strings.Join(child1CmdArgs, " ") - expected := "one two" - if got != expected { - t.Errorf("child1CmdArgs expected: %q, got: %q", expected, got) + if got != onetwo { + t.Errorf("child1CmdArgs expected: %q, got: %q", onetwo, got) } } @@ -145,7 +145,7 @@ func TestSubcommandExecuteC(t *testing.T) { } if c.Name() != "child" { - t.Errorf(`invalid command returned from ExecuteC: expected "child"', got %q`, c.Name()) + t.Errorf(`invalid command returned from ExecuteC: expected "child"', got: %q`, c.Name()) } } @@ -243,9 +243,8 @@ func TestCommandAlias(t *testing.T) { } got := strings.Join(timesCmdArgs, " ") - expected := "one two" - if got != expected { - t.Errorf("timesCmdArgs expected: %v, got: %v", expected, got) + if got != onetwo { + t.Errorf("timesCmdArgs expected: %v, got: %v", onetwo, got) } } @@ -271,9 +270,8 @@ func TestEnablePrefixMatching(t *testing.T) { } got := strings.Join(aCmdArgs, " ") - expected := "one two" - if got != expected { - t.Errorf("aCmdArgs expected: %q, got: %q", expected, got) + if got != onetwo { + t.Errorf("aCmdArgs expected: %q, got: %q", onetwo, got) } EnablePrefixMatching = false @@ -307,9 +305,8 @@ func TestAliasPrefixMatching(t *testing.T) { } got := strings.Join(timesCmdArgs, " ") - expected := "one two" - if got != expected { - t.Errorf("timesCmdArgs expected: %v, got: %v", expected, got) + if got != onetwo { + t.Errorf("timesCmdArgs expected: %v, got: %v", onetwo, got) } EnablePrefixMatching = false @@ -338,9 +335,8 @@ func TestChildSameName(t *testing.T) { } got := strings.Join(fooCmdArgs, " ") - expected := "one two" - if got != expected { - t.Errorf("fooCmdArgs expected: %v, got: %v", expected, got) + if got != onetwo { + t.Errorf("fooCmdArgs expected: %v, got: %v", onetwo, got) } } @@ -368,9 +364,8 @@ func TestGrandChildSameName(t *testing.T) { } got := strings.Join(fooCmdArgs, " ") - expected := "one two" - if got != expected { - t.Errorf("fooCmdArgs expected: %v, got: %v", expected, got) + if got != onetwo { + t.Errorf("fooCmdArgs expected: %v, got: %v", onetwo, got) } } @@ -406,9 +401,8 @@ func TestFlagLong(t *testing.T) { } got := strings.Join(cArgs, " ") - expected := "one two" - if got != expected { - t.Errorf("Expected arguments: %q, got %q", expected, got) + if got != onetwo { + t.Errorf("rootCmdArgs expected: %q, got: %q", onetwo, got) } } @@ -441,9 +435,8 @@ func TestFlagShort(t *testing.T) { } got := strings.Join(cArgs, " ") - expected := "one two" - if got != expected { - t.Errorf("Expected arguments: %q, got %q", expected, got) + if got != onetwo { + t.Errorf("rootCmdArgs expected: %q, got: %q", onetwo, got) } } @@ -645,9 +638,8 @@ func TestPersistentFlagsOnSameCommand(t *testing.T) { } got := strings.Join(rootCmdArgs, " ") - expected := "one two" - if got != expected { - t.Errorf("rootCmdArgs expected: %q, got %q", expected, got) + if got != onetwo { + t.Errorf("rootCmdArgs expected: %q, got %q", onetwo, got) } if flagValue != 7 { t.Errorf("flagValue expected: %v, got %v", 7, flagValue) @@ -731,9 +723,8 @@ func TestPersistentFlagsOnChild(t *testing.T) { } got := strings.Join(childCmdArgs, " ") - expected := "one two" - if got != expected { - t.Errorf("childCmdArgs expected: %q, got %q", expected, got) + if got != onetwo { + t.Errorf("rootCmdArgs expected: %q, got: %q", onetwo, got) } if parentFlagValue != 8 { t.Errorf("parentFlagValue expected: %v, got %v", 8, parentFlagValue) @@ -746,9 +737,9 @@ func TestPersistentFlagsOnChild(t *testing.T) { func TestRequiredFlags(t *testing.T) { c := &Command{Use: "c", Run: emptyRun} c.Flags().String("foo1", "", "") - c.MarkFlagRequired("foo1") + assertNoErr(t, c.MarkFlagRequired("foo1")) c.Flags().String("foo2", "", "") - c.MarkFlagRequired("foo2") + assertNoErr(t, c.MarkFlagRequired("foo2")) c.Flags().String("bar", "", "") expected := fmt.Sprintf("required flag(s) %q, %q not set", "foo1", "foo2") @@ -764,16 +755,16 @@ func TestRequiredFlags(t *testing.T) { func TestPersistentRequiredFlags(t *testing.T) { parent := &Command{Use: "parent", Run: emptyRun} parent.PersistentFlags().String("foo1", "", "") - parent.MarkPersistentFlagRequired("foo1") + assertNoErr(t, parent.MarkPersistentFlagRequired("foo1")) parent.PersistentFlags().String("foo2", "", "") - parent.MarkPersistentFlagRequired("foo2") + assertNoErr(t, parent.MarkPersistentFlagRequired("foo2")) parent.Flags().String("foo3", "", "") child := &Command{Use: "child", Run: emptyRun} child.Flags().String("bar1", "", "") - child.MarkFlagRequired("bar1") + assertNoErr(t, child.MarkFlagRequired("bar1")) child.Flags().String("bar2", "", "") - child.MarkFlagRequired("bar2") + assertNoErr(t, child.MarkFlagRequired("bar2")) child.Flags().String("bar3", "", "") parent.AddCommand(child) @@ -793,7 +784,7 @@ func TestPersistentRequiredFlagsWithDisableFlagParsing(t *testing.T) { parent := &Command{Use: "parent", Run: emptyRun} parent.PersistentFlags().Bool("foo", false, "") flag := parent.PersistentFlags().Lookup("foo") - parent.MarkPersistentFlagRequired("foo") + assertNoErr(t, parent.MarkPersistentFlagRequired("foo")) child := &Command{Use: "child", Run: emptyRun} child.DisableFlagParsing = true @@ -1299,20 +1290,19 @@ func TestHooks(t *testing.T) { t.Errorf("Unexpected error: %v", err) } - if persPreArgs != "one two" { - t.Errorf("Expected persPreArgs %q, got %q", "one two", persPreArgs) - } - if preArgs != "one two" { - t.Errorf("Expected preArgs %q, got %q", "one two", preArgs) - } - if runArgs != "one two" { - t.Errorf("Expected runArgs %q, got %q", "one two", runArgs) - } - if postArgs != "one two" { - t.Errorf("Expected postArgs %q, got %q", "one two", postArgs) - } - if persPostArgs != "one two" { - t.Errorf("Expected persPostArgs %q, got %q", "one two", persPostArgs) + for _, v := range []struct { + name string + got string + }{ + {"persPreArgs", persPreArgs}, + {"preArgs", preArgs}, + {"runArgs", runArgs}, + {"postArgs", postArgs}, + {"persPostArgs", persPostArgs}, + } { + if v.got != onetwo { + t.Errorf("Expected %s %q, got %q", v.name, onetwo, v.got) + } } } @@ -1380,44 +1370,42 @@ func TestPersistentHooks(t *testing.T) { t.Errorf("Unexpected error: %v", err) } - // TODO: currently PersistenPreRun* defined in parent does not - // run if the matchin child subcommand has PersistenPreRun. - // If the behavior changes (https://github.com/spf13/cobra/issues/252) - // this test must be fixed. - if parentPersPreArgs != "" { - t.Errorf("Expected blank parentPersPreArgs, got %q", parentPersPreArgs) - } - if parentPreArgs != "" { - t.Errorf("Expected blank parentPreArgs, got %q", parentPreArgs) - } - if parentRunArgs != "" { - t.Errorf("Expected blank parentRunArgs, got %q", parentRunArgs) - } - if parentPostArgs != "" { - t.Errorf("Expected blank parentPostArgs, got %q", parentPostArgs) - } - // TODO: currently PersistenPostRun* defined in parent does not - // run if the matchin child subcommand has PersistenPostRun. - // If the behavior changes (https://github.com/spf13/cobra/issues/252) - // this test must be fixed. - if parentPersPostArgs != "" { - t.Errorf("Expected blank parentPersPostArgs, got %q", parentPersPostArgs) + for _, v := range []struct { + name string + got string + }{ + // TODO: currently PersistenPreRun* defined in parent does not + // run if the matchin child subcommand has PersistenPreRun. + // If the behavior changes (https://github.com/spf13/cobra/issues/252) + // this test must be fixed. + {"parentPersPreArgs", parentPersPreArgs}, + {"parentPreArgs", parentPreArgs}, + {"parentRunArgs", parentRunArgs}, + {"parentPostArgs", parentPostArgs}, + // TODO: currently PersistenPostRun* defined in parent does not + // run if the matchin child subcommand has PersistenPostRun. + // If the behavior changes (https://github.com/spf13/cobra/issues/252) + // this test must be fixed. + {"parentPersPostArgs", parentPersPostArgs}, + } { + if v.got != "" { + t.Errorf("Expected blank %s, got %q", v.name, v.got) + } } - if childPersPreArgs != "one two" { - t.Errorf("Expected childPersPreArgs %q, got %q", "one two", childPersPreArgs) - } - if childPreArgs != "one two" { - t.Errorf("Expected childPreArgs %q, got %q", "one two", childPreArgs) - } - if childRunArgs != "one two" { - t.Errorf("Expected childRunArgs %q, got %q", "one two", childRunArgs) - } - if childPostArgs != "one two" { - t.Errorf("Expected childPostArgs %q, got %q", "one two", childPostArgs) - } - if childPersPostArgs != "one two" { - t.Errorf("Expected childPersPostArgs %q, got %q", "one two", childPersPostArgs) + for _, v := range []struct { + name string + got string + }{ + {"childPersPreArgs", childPersPreArgs}, + {"childPreArgs", childPreArgs}, + {"childRunArgs", childRunArgs}, + {"childPostArgs", childPostArgs}, + {"childPersPostArgs", childPersPostArgs}, + } { + if v.got != onetwo { + t.Errorf("Expected %s %q, got %q", v.name, onetwo, v.got) + } } } @@ -1741,7 +1729,7 @@ func TestMergeCommandLineToFlags(t *testing.T) { func TestUseDeprecatedFlags(t *testing.T) { c := &Command{Use: "c", Run: emptyRun} c.Flags().BoolP("deprecated", "d", false, "deprecated flag") - c.Flags().MarkDeprecated("deprecated", "This flag is deprecated") + assertNoErr(t, c.Flags().MarkDeprecated("deprecated", "This flag is deprecated")) output, err := executeCommand(c, "c", "-d") if err != nil { @@ -1868,7 +1856,6 @@ type calledAsTestcase struct { call string want string epm bool - tc bool } func (tc *calledAsTestcase) test(t *testing.T) { @@ -1890,7 +1877,7 @@ func (tc *calledAsTestcase) test(t *testing.T) { parent.SetOut(output) parent.SetErr(output) - parent.Execute() + _ = parent.Execute() if called == nil { if tc.call != "" { @@ -1908,18 +1895,18 @@ func (tc *calledAsTestcase) test(t *testing.T) { func TestCalledAs(t *testing.T) { tests := map[string]calledAsTestcase{ - "find/no-args": {nil, "parent", "parent", false, false}, - "find/real-name": {[]string{"child1"}, "child1", "child1", false, false}, - "find/full-alias": {[]string{"that"}, "child2", "that", false, false}, - "find/part-no-prefix": {[]string{"thi"}, "", "", false, false}, - "find/part-alias": {[]string{"thi"}, "child1", "this", true, false}, - "find/conflict": {[]string{"th"}, "", "", true, false}, - "traverse/no-args": {nil, "parent", "parent", false, true}, - "traverse/real-name": {[]string{"child1"}, "child1", "child1", false, true}, - "traverse/full-alias": {[]string{"that"}, "child2", "that", false, true}, - "traverse/part-no-prefix": {[]string{"thi"}, "", "", false, true}, - "traverse/part-alias": {[]string{"thi"}, "child1", "this", true, true}, - "traverse/conflict": {[]string{"th"}, "", "", true, true}, + "find/no-args": {nil, "parent", "parent", false}, + "find/real-name": {[]string{"child1"}, "child1", "child1", false}, + "find/full-alias": {[]string{"that"}, "child2", "that", false}, + "find/part-no-prefix": {[]string{"thi"}, "", "", false}, + "find/part-alias": {[]string{"thi"}, "child1", "this", true}, + "find/conflict": {[]string{"th"}, "", "", true}, + "traverse/no-args": {nil, "parent", "parent", false}, + "traverse/real-name": {[]string{"child1"}, "child1", "child1", false}, + "traverse/full-alias": {[]string{"that"}, "child2", "that", false}, + "traverse/part-no-prefix": {[]string{"thi"}, "", "", false}, + "traverse/part-alias": {[]string{"thi"}, "child1", "this", true}, + "traverse/conflict": {[]string{"th"}, "", "", true}, } for name, tc := range tests { diff --git a/custom_completions.go b/custom_completions.go index f9e88e08..fa060c14 100644 --- a/custom_completions.go +++ b/custom_completions.go @@ -527,13 +527,13 @@ func CompDebug(msg string, printToStdErr bool) { os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err == nil { defer f.Close() - f.WriteString(msg) + WriteStringAndCheck(f, msg) } } if printToStdErr { // Must print to stderr for this not to be read by the completion script. - fmt.Fprintf(os.Stderr, msg) + fmt.Fprint(os.Stderr, msg) } } diff --git a/custom_completions_test.go b/custom_completions_test.go index 14ec5a9e..ede809ed 100644 --- a/custom_completions_test.go +++ b/custom_completions_test.go @@ -780,17 +780,17 @@ func TestRequiredFlagNameCompletionInGo(t *testing.T) { rootCmd.AddCommand(childCmd) rootCmd.Flags().IntP("requiredFlag", "r", -1, "required flag") - rootCmd.MarkFlagRequired("requiredFlag") + assertNoErr(t, rootCmd.MarkFlagRequired("requiredFlag")) requiredFlag := rootCmd.Flags().Lookup("requiredFlag") rootCmd.PersistentFlags().IntP("requiredPersistent", "p", -1, "required persistent") - rootCmd.MarkPersistentFlagRequired("requiredPersistent") + assertNoErr(t, 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") + assertNoErr(t, childCmd.MarkFlagRequired("subRequired")) childCmd.Flags().BoolP("subNotRequired", "n", false, "sub not required flag") // Test that a required flag is suggested even without the - prefix @@ -964,19 +964,19 @@ func TestFlagFileExtFilterCompletionInGo(t *testing.T) { // No extensions. Should be ignored. rootCmd.Flags().StringP("file", "f", "", "file flag") - rootCmd.MarkFlagFilename("file") + assertNoErr(t, rootCmd.MarkFlagFilename("file")) // Single extension rootCmd.Flags().StringP("log", "l", "", "log flag") - rootCmd.MarkFlagFilename("log", "log") + assertNoErr(t, rootCmd.MarkFlagFilename("log", "log")) // Multiple extensions rootCmd.Flags().StringP("yaml", "y", "", "yaml flag") - rootCmd.MarkFlagFilename("yaml", "yaml", "yml") + assertNoErr(t, rootCmd.MarkFlagFilename("yaml", "yaml", "yml")) // Directly using annotation rootCmd.Flags().StringP("text", "t", "", "text flag") - rootCmd.Flags().SetAnnotation("text", BashCompFilenameExt, []string{"txt"}) + assertNoErr(t, 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 @@ -1086,15 +1086,15 @@ func TestFlagDirFilterCompletionInGo(t *testing.T) { // Filter directories rootCmd.Flags().StringP("dir", "d", "", "dir flag") - rootCmd.MarkFlagDirname("dir") + assertNoErr(t, rootCmd.MarkFlagDirname("dir")) // Filter directories within a directory rootCmd.Flags().StringP("subdir", "s", "", "subdir") - rootCmd.Flags().SetAnnotation("subdir", BashCompSubdirsInDir, []string{"themes"}) + assertNoErr(t, 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"}) + assertNoErr(t, 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 @@ -1430,7 +1430,7 @@ func TestValidArgsFuncInBashScript(t *testing.T) { rootCmd.AddCommand(child) buf := new(bytes.Buffer) - rootCmd.GenBashCompletion(buf) + assertNoErr(t, rootCmd.GenBashCompletion(buf)) output := buf.String() check(t, output, "has_completion_function=1") @@ -1445,7 +1445,7 @@ func TestNoValidArgsFuncInBashScript(t *testing.T) { rootCmd.AddCommand(child) buf := new(bytes.Buffer) - rootCmd.GenBashCompletion(buf) + assertNoErr(t, rootCmd.GenBashCompletion(buf)) output := buf.String() checkOmit(t, output, "has_completion_function=1") @@ -1461,7 +1461,7 @@ func TestCompleteCmdInBashScript(t *testing.T) { rootCmd.AddCommand(child) buf := new(bytes.Buffer) - rootCmd.GenBashCompletion(buf) + assertNoErr(t, rootCmd.GenBashCompletion(buf)) output := buf.String() check(t, output, ShellCompNoDescRequestCmd) @@ -1477,7 +1477,7 @@ func TestCompleteNoDesCmdInZshScript(t *testing.T) { rootCmd.AddCommand(child) buf := new(bytes.Buffer) - rootCmd.GenZshCompletionNoDesc(buf) + assertNoErr(t, rootCmd.GenZshCompletionNoDesc(buf)) output := buf.String() check(t, output, ShellCompNoDescRequestCmd) @@ -1493,7 +1493,7 @@ func TestCompleteCmdInZshScript(t *testing.T) { rootCmd.AddCommand(child) buf := new(bytes.Buffer) - rootCmd.GenZshCompletion(buf) + assertNoErr(t, rootCmd.GenZshCompletion(buf)) output := buf.String() check(t, output, ShellCompRequestCmd) @@ -1506,7 +1506,7 @@ 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, ShellCompDirective) { + assertNoErr(t, 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) { @@ -1514,9 +1514,9 @@ func TestFlagCompletionInGo(t *testing.T) { } } return completions, ShellCompDirectiveDefault - }) + })) rootCmd.Flags().String("filename", "", "Enter a filename") - rootCmd.RegisterFlagCompletionFunc("filename", func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { + assertNoErr(t, 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) { @@ -1524,7 +1524,7 @@ func TestFlagCompletionInGo(t *testing.T) { } } return completions, ShellCompDirectiveNoSpace | ShellCompDirectiveNoFileComp - }) + })) // Test completing an empty string output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--introot", "") @@ -1703,7 +1703,7 @@ func TestFlagCompletionInGoWithDesc(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, ShellCompDirective) { + assertNoErr(t, 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) { @@ -1711,9 +1711,9 @@ func TestFlagCompletionInGoWithDesc(t *testing.T) { } } return completions, ShellCompDirectiveDefault - }) + })) rootCmd.Flags().String("filename", "", "Enter a filename") - rootCmd.RegisterFlagCompletionFunc("filename", func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { + assertNoErr(t, 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) { @@ -1721,7 +1721,7 @@ func TestFlagCompletionInGoWithDesc(t *testing.T) { } } return completions, ShellCompDirectiveNoSpace | ShellCompDirectiveNoFileComp - }) + })) // Test completing an empty string output, err := executeCommand(rootCmd, ShellCompRequestCmd, "--introot", "") diff --git a/doc/man_docs.go b/doc/man_docs.go index b67ac1f1..916e3614 100644 --- a/doc/man_docs.go +++ b/doc/man_docs.go @@ -139,23 +139,23 @@ func fillHeader(header *GenManHeader, name string, disableAutoGen bool) error { return nil } -func manPreamble(buf *bytes.Buffer, header *GenManHeader, cmd *cobra.Command, dashedName string) { +func manPreamble(buf io.StringWriter, header *GenManHeader, cmd *cobra.Command, dashedName string) { description := cmd.Long if len(description) == 0 { description = cmd.Short } - buf.WriteString(fmt.Sprintf(`%% "%s" "%s" "%s" "%s" "%s" + cobra.WriteStringAndCheck(buf, fmt.Sprintf(`%% "%s" "%s" "%s" "%s" "%s" # NAME `, header.Title, header.Section, header.date, header.Source, header.Manual)) - buf.WriteString(fmt.Sprintf("%s \\- %s\n\n", dashedName, cmd.Short)) - buf.WriteString("# SYNOPSIS\n") - buf.WriteString(fmt.Sprintf("**%s**\n\n", cmd.UseLine())) - buf.WriteString("# DESCRIPTION\n") - buf.WriteString(description + "\n\n") + cobra.WriteStringAndCheck(buf, fmt.Sprintf("%s \\- %s\n\n", dashedName, cmd.Short)) + cobra.WriteStringAndCheck(buf, "# SYNOPSIS\n") + cobra.WriteStringAndCheck(buf, fmt.Sprintf("**%s**\n\n", cmd.UseLine())) + cobra.WriteStringAndCheck(buf, "# DESCRIPTION\n") + cobra.WriteStringAndCheck(buf, description+"\n\n") } -func manPrintFlags(buf *bytes.Buffer, flags *pflag.FlagSet) { +func manPrintFlags(buf io.StringWriter, flags *pflag.FlagSet) { flags.VisitAll(func(flag *pflag.Flag) { if len(flag.Deprecated) > 0 || flag.Hidden { return @@ -179,22 +179,22 @@ func manPrintFlags(buf *bytes.Buffer, flags *pflag.FlagSet) { format += "]" } format += "\n\t%s\n\n" - buf.WriteString(fmt.Sprintf(format, flag.DefValue, flag.Usage)) + cobra.WriteStringAndCheck(buf, fmt.Sprintf(format, flag.DefValue, flag.Usage)) }) } -func manPrintOptions(buf *bytes.Buffer, command *cobra.Command) { +func manPrintOptions(buf io.StringWriter, command *cobra.Command) { flags := command.NonInheritedFlags() if flags.HasAvailableFlags() { - buf.WriteString("# OPTIONS\n") + cobra.WriteStringAndCheck(buf, "# OPTIONS\n") manPrintFlags(buf, flags) - buf.WriteString("\n") + cobra.WriteStringAndCheck(buf, "\n") } flags = command.InheritedFlags() if flags.HasAvailableFlags() { - buf.WriteString("# OPTIONS INHERITED FROM PARENT COMMANDS\n") + cobra.WriteStringAndCheck(buf, "# OPTIONS INHERITED FROM PARENT COMMANDS\n") manPrintFlags(buf, flags) - buf.WriteString("\n") + cobra.WriteStringAndCheck(buf, "\n") } } diff --git a/doc/man_docs_test.go b/doc/man_docs_test.go index ee9b8753..aa3f5f2a 100644 --- a/doc/man_docs_test.go +++ b/doc/man_docs_test.go @@ -13,6 +13,12 @@ import ( "github.com/spf13/cobra" ) +func assertNoErr(t *testing.T, e error) { + if e != nil { + t.Error(e) + } +} + func translate(in string) string { return strings.Replace(in, "-", "\\-", -1) } @@ -133,7 +139,7 @@ func TestGenManSeeAlso(t *testing.T) { func TestManPrintFlagsHidesShortDeperecated(t *testing.T) { c := &cobra.Command{} c.Flags().StringP("foo", "f", "default", "Foo flag") - c.Flags().MarkShorthandDeprecated("foo", "don't use it no more") + assertNoErr(t, c.Flags().MarkShorthandDeprecated("foo", "don't use it no more")) buf := new(bytes.Buffer) manPrintFlags(buf, c.Flags()) diff --git a/doc/man_examples_test.go b/doc/man_examples_test.go index db660426..e20a34c3 100644 --- a/doc/man_examples_test.go +++ b/doc/man_examples_test.go @@ -17,7 +17,7 @@ func ExampleGenManTree() { Title: "MINE", Section: "3", } - doc.GenManTree(cmd, header, "/tmp") + cobra.CheckErr(doc.GenManTree(cmd, header, "/tmp")) } func ExampleGenMan() { @@ -30,6 +30,6 @@ func ExampleGenMan() { Section: "3", } out := new(bytes.Buffer) - doc.GenMan(cmd, header, out) + cobra.CheckErr(doc.GenMan(cmd, header, out)) fmt.Print(out.String()) } diff --git a/fish_completions.go b/fish_completions.go index eaae9bca..3e112347 100644 --- a/fish_completions.go +++ b/fish_completions.go @@ -8,7 +8,7 @@ import ( "strings" ) -func genFishComp(buf *bytes.Buffer, name string, includeDesc bool) { +func genFishComp(buf io.StringWriter, name string, includeDesc bool) { // Variables should not contain a '-' or ':' character nameForVar := name nameForVar = strings.Replace(nameForVar, "-", "_", -1) @@ -18,8 +18,8 @@ func genFishComp(buf *bytes.Buffer, name string, includeDesc bool) { if !includeDesc { compCmd = ShellCompNoDescRequestCmd } - buf.WriteString(fmt.Sprintf("# fish completion for %-36s -*- shell-script -*-\n", name)) - buf.WriteString(fmt.Sprintf(` + WriteStringAndCheck(buf, fmt.Sprintf("# fish completion for %-36s -*- shell-script -*-\n", name)) + WriteStringAndCheck(buf, fmt.Sprintf(` function __%[1]s_debug set file "$BASH_COMP_DEBUG_FILE" if test -n "$file" diff --git a/fish_completions_test.go b/fish_completions_test.go index 532b6055..a3171e48 100644 --- a/fish_completions_test.go +++ b/fish_completions_test.go @@ -15,7 +15,7 @@ func TestCompleteNoDesCmdInFishScript(t *testing.T) { rootCmd.AddCommand(child) buf := new(bytes.Buffer) - rootCmd.GenFishCompletion(buf, false) + assertNoErr(t, rootCmd.GenFishCompletion(buf, false)) output := buf.String() check(t, output, ShellCompNoDescRequestCmd) @@ -31,7 +31,7 @@ func TestCompleteCmdInFishScript(t *testing.T) { rootCmd.AddCommand(child) buf := new(bytes.Buffer) - rootCmd.GenFishCompletion(buf, true) + assertNoErr(t, rootCmd.GenFishCompletion(buf, true)) output := buf.String() check(t, output, ShellCompRequestCmd) @@ -41,7 +41,7 @@ func TestCompleteCmdInFishScript(t *testing.T) { func TestProgWithDash(t *testing.T) { rootCmd := &Command{Use: "root-dash", Args: NoArgs, Run: emptyRun} buf := new(bytes.Buffer) - rootCmd.GenFishCompletion(buf, false) + assertNoErr(t, rootCmd.GenFishCompletion(buf, false)) output := buf.String() // Functions name should have replace the '-' @@ -56,7 +56,7 @@ func TestProgWithDash(t *testing.T) { func TestProgWithColon(t *testing.T) { rootCmd := &Command{Use: "root:colon", Args: NoArgs, Run: emptyRun} buf := new(bytes.Buffer) - rootCmd.GenFishCompletion(buf, false) + assertNoErr(t, rootCmd.GenFishCompletion(buf, false)) output := buf.String() // Functions name should have replace the ':' diff --git a/powershell_completions.go b/powershell_completions.go index 48022ea5..6267f694 100644 --- a/powershell_completions.go +++ b/powershell_completions.go @@ -10,12 +10,12 @@ import ( "os" ) -func genPowerShellComp(buf *bytes.Buffer, name string, includeDesc bool) { +func genPowerShellComp(buf io.StringWriter, name string, includeDesc bool) { compCmd := ShellCompRequestCmd if !includeDesc { compCmd = ShellCompNoDescRequestCmd } - buf.WriteString(fmt.Sprintf(`# powershell completion for %-36[1]s -*- shell-script -*- + WriteStringAndCheck(buf, fmt.Sprintf(`# powershell completion for %-36[1]s -*- shell-script -*- function __%[1]s_debug { if ($env:BASH_COMP_DEBUG_FILE) { @@ -46,12 +46,12 @@ Register-ArgumentCompleter -CommandName '%[1]s' -ScriptBlock { # We need to trigger completion from the $CursorPosition location, so we need # to truncate the command-line ($Command) up to the $CursorPosition location. # Make sure the $Command is longer then the $CursorPosition before we truncate. - # This happens because the $Command does not include the last space. + # This happens because the $Command does not include the last space. if ($Command.Length -gt $CursorPosition) { $Command=$Command.Substring(0,$CursorPosition) } __%[1]s_debug "Truncated command: $Command" - + $ShellCompDirectiveError=%[3]d $ShellCompDirectiveNoSpace=%[4]d $ShellCompDirectiveNoFileComp=%[5]d @@ -64,7 +64,7 @@ Register-ArgumentCompleter -CommandName '%[1]s' -ScriptBlock { $RequestComp="$Program %[2]s $Arguments" __%[1]s_debug "RequestComp: $RequestComp" - # we cannot use $WordToComplete because it + # we cannot use $WordToComplete because it # has the wrong values if the cursor was moved # so use the last argument if ($WordToComplete -ne "" ) { @@ -102,7 +102,7 @@ Register-ArgumentCompleter -CommandName '%[1]s' -ScriptBlock { $Directive = 0 } __%[1]s_debug "The completion directive is: $Directive" - + # remove directive (last element) from out $Out = $Out | Where-Object { $_ -ne $Out[-1] } __%[1]s_debug "The completions are: $Out" @@ -142,18 +142,18 @@ Register-ArgumentCompleter -CommandName '%[1]s' -ScriptBlock { if (($Directive -band $ShellCompDirectiveNoFileComp) -ne 0 ) { __%[1]s_debug "ShellCompDirectiveNoFileComp is called" - + if ($Values.Length -eq 0) { - # Just print an empty string here so the + # Just print an empty string here so the # shell does not start to complete paths. - # We cannot use CompletionResult here because + # We cannot use CompletionResult here because # it does not accept an empty string as argument. "" return } } - if ((($Directive -band $ShellCompDirectiveFilterFileExt) -ne 0 ) -or + if ((($Directive -band $ShellCompDirectiveFilterFileExt) -ne 0 ) -or (($Directive -band $ShellCompDirectiveFilterDirs) -ne 0 )) { __%[1]s_debug "ShellCompDirectiveFilterFileExt ShellCompDirectiveFilterDirs are not supported" @@ -170,7 +170,7 @@ Register-ArgumentCompleter -CommandName '%[1]s' -ScriptBlock { __%[1]s_debug "Join the equal sign flag back to the completion value" $_.Name = $Flag + "=" + $_.Name } - } + } # Get the current mode $Mode = (Get-PSReadLineKeyHandler | Where-Object {$_.Key -eq "Tab" }).Function @@ -224,14 +224,14 @@ Register-ArgumentCompleter -CommandName '%[1]s' -ScriptBlock { # zsh like "MenuComplete" { # insert space after value - # MenuComplete will automatically show the ToolTip of + # MenuComplete will automatically show the ToolTip of # the highlighted value at the bottom of the suggestions. [System.Management.Automation.CompletionResult]::new($($comp.Name | __%[1]s_escapeStringWithSpecialChars) + $Space, "$($comp.Name)", 'ParameterValue', "$($comp.Description)") } # TabCompleteNext and in case we get something unknown Default { - # Like MenuComplete but we don't want to add a space here because + # Like MenuComplete but we don't want to add a space here because # the user need to press space anyway to get the completion. # Description will not be shown because thats not possible with TabCompleteNext [System.Management.Automation.CompletionResult]::new($($comp.Name | __%[1]s_escapeStringWithSpecialChars), "$($comp.Name)", 'ParameterValue', "$($comp.Description)") diff --git a/shell_completions.md b/shell_completions.md index 511b2f3c..d557d8a6 100644 --- a/shell_completions.md +++ b/shell_completions.md @@ -99,8 +99,7 @@ cmd := &cobra.Command{ Long: get_long, Example: get_example, Run: func(cmd *cobra.Command, args []string) { - err := RunGet(f, out, cmd, args) - util.CheckErr(err) + cobra.CheckErr(RunGet(f, out, cmd, args)) }, ValidArgs: validArgs, } @@ -132,7 +131,7 @@ the completion algorithm if entered manually, e.g. in: ```bash $ kubectl get rc [tab][tab] -backend frontend database +backend frontend database ``` Note that without declaring `rc` as an alias, the completion algorithm would not know to show the list of @@ -254,7 +253,7 @@ and you'll get something like ```bash $ kubectl exec [tab][tab] --c --container= -p --pod= +-c --container= -p --pod= ``` ### Specify dynamic flag completion diff --git a/zsh_completions.go b/zsh_completions.go index 92a70394..2e840285 100644 --- a/zsh_completions.go +++ b/zsh_completions.go @@ -70,12 +70,12 @@ func (c *Command) genZshCompletion(w io.Writer, includeDesc bool) error { return err } -func genZshComp(buf *bytes.Buffer, name string, includeDesc bool) { +func genZshComp(buf io.StringWriter, name string, includeDesc bool) { compCmd := ShellCompRequestCmd if !includeDesc { compCmd = ShellCompNoDescRequestCmd } - buf.WriteString(fmt.Sprintf(`#compdef _%[1]s %[1]s + WriteStringAndCheck(buf, fmt.Sprintf(`#compdef _%[1]s %[1]s # zsh completion for %-36[1]s -*- shell-script -*- From b73b344b63b6fab171f578f1385ac2506fe93187 Mon Sep 17 00:00:00 2001 From: Unai Martinez-Corral <38422348+umarcor@users.noreply.github.com> Date: Tue, 9 Feb 2021 15:48:59 +0000 Subject: [PATCH 163/211] ci: add GitHub Actions workflow 'Test' (#1339) Adds a "test" action which will run side by side (for now) with Travis Co-authored-by: John McBride --- .github/workflows/Test.yml | 83 ++++++++++++++++++++++++++++++++++++++ README.md | 1 + 2 files changed, 84 insertions(+) create mode 100644 .github/workflows/Test.yml diff --git a/.github/workflows/Test.yml b/.github/workflows/Test.yml new file mode 100644 index 00000000..260948cf --- /dev/null +++ b/.github/workflows/Test.yml @@ -0,0 +1,83 @@ +name: Test + +on: + push: + pull_request: + +env: + GO111MODULE: on + +jobs: + + + test-unix: + strategy: + fail-fast: false + matrix: + platform: + - ubuntu + - macOS + go: + - 1.14.x + - 1.15.x + name: '${{ matrix.platform }} | ${{ matrix.go }}' + runs-on: ${{ matrix.platform }}-latest + steps: + + - uses: actions/setup-go@v2 + with: + go-version: ${{ matrix.go }} + + - uses: actions/checkout@v2 + + - uses: actions/cache@v2 + with: + path: ~/go/pkg/mod + key: ${{ runner.os }}-${{ matrix.go }}-${{ hashFiles('**/go.sum') }} + restore-keys: ${{ runner.os }}-${{ matrix.go }}- + + - run: | + export GOBIN=$HOME/go/bin + curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | bash -s -- -b $GOBIN latest + go install github.com/kyoh86/richgo + go install github.com/mitchellh/gox + + - run: PATH=$HOME/go/bin/:$PATH make + + + test-win: + name: MINGW64 + defaults: + run: + shell: msys2 {0} + runs-on: windows-latest + steps: + + - shell: bash + run: git config --global core.autocrlf input + + - uses: msys2/setup-msys2@v2 + with: + msystem: MINGW64 + update: true + install: > + git + make + unzip + mingw-w64-x86_64-go + + - uses: actions/checkout@v2 + + - uses: actions/cache@v2 + with: + path: ~/go/pkg/mod + key: ${{ runner.os }}-${{ matrix.go }}-${{ hashFiles('**/go.sum') }} + restore-keys: ${{ runner.os }}-${{ matrix.go }}- + + - run: | + export GOBIN=$HOME/go/bin + curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | bash -s -- -b $GOBIN latest + go install github.com/kyoh86/richgo + go install github.com/mitchellh/gox + + - run: PATH=$HOME/go/bin:$PATH make diff --git a/README.md b/README.md index 4a8811f3..d143e323 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ 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. +[![](https://img.shields.io/github/workflow/status/spf13/cobra/Test?longCache=tru&label=Test&logo=github%20actions&logoColor=fff)](https://github.com/spf13/cobra/actions?query=workflow%3ATest) [![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) From 07445ea179fc2d9679c9a7fbbaaaafbd1221e6b4 Mon Sep 17 00:00:00 2001 From: Anthony Fok Date: Tue, 9 Feb 2021 14:08:42 -0700 Subject: [PATCH 164/211] Copyedit shell-completion related documentation --- README.md | 2 +- bash_completions.md | 2 +- powershell_completions.go | 2 +- shell_completions.md | 70 +++++++++++++++++++-------------------- 4 files changed, 38 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index d143e323..a1b13ddd 100644 --- a/README.md +++ b/README.md @@ -753,7 +753,7 @@ Cobra can generate documentation based on subcommands, flags, etc. Read more abo ## Generating shell completions -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). +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). # License diff --git a/bash_completions.md b/bash_completions.md index a82d5bb8..130f99b9 100644 --- a/bash_completions.md +++ b/bash_completions.md @@ -4,7 +4,7 @@ Please refer to [Shell Completions](shell_completions.md) for details. ## Bash legacy dynamic completions -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. +For backward 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. 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. diff --git a/powershell_completions.go b/powershell_completions.go index 6267f694..c55be71c 100644 --- a/powershell_completions.go +++ b/powershell_completions.go @@ -181,7 +181,7 @@ Register-ArgumentCompleter -CommandName '%[1]s' -ScriptBlock { # store temporay because switch will overwrite $_ $comp = $_ - # Powershell supports three different completion modes + # PowerShell supports three different completion modes # - TabCompleteNext (default windows style - on each key press the next option is displayed) # - Complete (works like bash) # - MenuComplete (works like zsh) diff --git a/shell_completions.md b/shell_completions.md index d557d8a6..cd533ac3 100644 --- a/shell_completions.md +++ b/shell_completions.md @@ -4,10 +4,10 @@ Cobra can generate shell completions for multiple shells. The currently supported shells are: - Bash - Zsh -- Fish +- fish - PowerShell -If you are using the generator you can create a completion command by running +If you are using the generator, you can create a completion command by running ```bash cobra add completion @@ -17,46 +17,46 @@ and then modifying the generated `cmd/completion.go` file to look something like ```go var completionCmd = &cobra.Command{ - Use: "completion [bash|zsh|fish|powershell]", - Short: "Generate completion script", + Use: "completion [bash|zsh|fish|powershell]", + Short: "Generate completion script", Long: `To load completions: Bash: -$ source <(yourprogram completion bash) + $ source <(yourprogram completion bash) -# To load completions for each session, execute once: -Linux: + # To load completions for each session, execute once: + # Linux: $ yourprogram completion bash > /etc/bash_completion.d/yourprogram -MacOS: + # macOS: $ yourprogram completion bash > /usr/local/etc/bash_completion.d/yourprogram Zsh: -# If shell completion is not already enabled in your environment you will need -# to enable it. You can execute the following once: + # 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 + $ echo "autoload -U compinit; compinit" >> ~/.zshrc -# To load completions for each session, execute once: -$ yourprogram completion zsh > "${fpath[1]}/_yourprogram" + # 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. + # You will need to start a new shell for this setup to take effect. -Fish: +fish: -$ yourprogram completion fish | source + $ yourprogram completion fish | source -# To load completions for each session, execute once: -$ yourprogram completion fish > ~/.config/fish/completions/yourprogram.fish + # To load completions for each session, execute once: + $ yourprogram completion fish > ~/.config/fish/completions/yourprogram.fish -Powershell: +PowerShell: -PS> yourprogram completion powershell | Out-String | Invoke-Expression + PS> yourprogram completion powershell | Out-String | Invoke-Expression -# To load completions for every new session, run: -PS> yourprogram completion powershell > yourprogram.ps1 -# and source this file from your powershell profile. + # To load completions for every new session, run: + PS> yourprogram completion powershell > yourprogram.ps1 + # and source this file from your PowerShell profile. `, DisableFlagsInUseLine: true, ValidArgs: []string{"bash", "zsh", "fish", "powershell"}, @@ -76,7 +76,7 @@ PS> yourprogram completion powershell > yourprogram.ps1 } ``` -**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. +**Note:** The cobra generator may include messages printed to stdout, for example, if the config file is loaded; this will break the auto-completion script so must be removed. # Customizing completions @@ -368,12 +368,12 @@ completion firstcommand secondcommand ``` ### Bash legacy dynamic completions -For backwards-compatibility, Cobra still supports its bash legacy dynamic completion solution. +For backward 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`. +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. @@ -392,7 +392,7 @@ status -- displays the status of the named release $ 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`. +*Note*: Because of backward-compatibility requirements, we were forced to have a different API to disable completion descriptions between `zsh` and `fish`. ### Limitations @@ -403,12 +403,12 @@ search show status ### 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. +Cobra 1.1 standardized its zsh completion support to align it with its other shell completions. Although the API was kept backward-compatible, some small changes in behavior were introduced. Please refer to [Zsh Completions](zsh_completions.md) for details. -## Fish completions +## 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. +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] @@ -418,11 +418,11 @@ search (search for a keyword in charts) show (show information of a chart) s $ 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`. +*Note*: Because of backward-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). +* 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`, `powershell`). * The function `MarkFlagCustom()` is not supported and will be ignored for `fish`. * You should instead use `RegisterFlagCompletionFunc()`. @@ -440,7 +440,7 @@ search show status Cobra supports native PowerShell completions generated from the root `cobra.Command`. You can use the `command.GenPowerShellCompletion()` or `command.GenPowerShellCompletionFile()` functions. To include descriptions use `command.GenPowerShellCompletionWithDesc()` and `command.GenPowerShellCompletionFileWithDesc()`. Cobra will provide the description automatically based on usage information. You can choose to make this option configurable by your users. -The script is designed to support all three Powershell completion modes: +The script is designed to support all three PowerShell completion modes: * TabCompleteNext (default windows style - on each key press the next option is displayed) * Complete (works like bash) @@ -468,7 +468,7 @@ search show status ### Limitations -* Custom completions implemented in Bash scripting (legacy) are not supported and will be ignored for `powershell` (including the use of the `BashCompCustom` flag annotation). +* Custom completions implemented in bash scripting (legacy) are not supported and will be ignored for `powershell` (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`, `powershell`). * The function `MarkFlagCustom()` is not supported and will be ignored for `powershell`. * You should instead use `RegisterFlagCompletionFunc()`. @@ -480,4 +480,4 @@ search show status * `MarkFlagDirname()` and `MarkPersistentFlagDirname()` (filtering by directory) * Similarly, the following completion directives are not supported and will be ignored for `powershell`: * `ShellCompDirectiveFilterFileExt` (filtering by file extension) - * `ShellCompDirectiveFilterDirs` (filtering by directory) \ No newline at end of file + * `ShellCompDirectiveFilterDirs` (filtering by directory) From 7f95502478346095b52d116f30f3dca0752b00b4 Mon Sep 17 00:00:00 2001 From: Anthony Fok Date: Tue, 9 Feb 2021 18:39:00 -0700 Subject: [PATCH 165/211] Update CHANGELOG.md for v1.1.2 --- CHANGELOG.md | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 742d6d6e..648c8f49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,36 @@ # Cobra Changelog -## Pending -* Fix man page doc generation - no auto generated tag when `cmd.DisableAutoGenTag = true` @jpmcb +## v1.1.2 + +### Notable Changes + +* Bump license year to 2021 in golden files (#1309) @Bowbaq +* Enhance PowerShell completion with custom comp (#1208) @Luap99 +* Update gopkg.in/yaml.v2 to v2.4.0: The previous breaking change in yaml.v2 v2.3.0 has been reverted, see go-yaml/yaml#670 +* Documentation readability improvements (#1228 etc.) @zaataylor etc. +* Use golangci-lint: Repair warnings and errors resulting from linting (#1044) @umarcor + +## v1.1.1 + +* **Fix:** yaml.v2 2.3.0 contained a unintended breaking change. This release reverts to yaml.v2 v2.2.8 which has recent critical CVE fixes, but does not have the breaking changes. See https://github.com/spf13/cobra/pull/1259 for context. +* **Fix:** correct internal formatting for go-md2man v2 (which caused man page generation to be broken). See https://github.com/spf13/cobra/issues/1049 for context. + +## v1.1.0 + +### Notable Changes + +* Extend Go completions and revamp zsh comp (#1070) +* Fix man page doc generation - no auto generated tag when `cmd.DisableAutoGenTag = true` (#1104) @jpmcb +* Add completion for help command (#1136) +* Complete subcommands when TraverseChildren is set (#1171) +* Fix stderr printing functions (#894) +* fix: fish output redirection (#1247) ## v1.0.0 + Announcing v1.0.0 of Cobra. 🎉 -**Notable Changes** + +### 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 From be1efc850ffcde0411cd78e785ca759498083d1d Mon Sep 17 00:00:00 2001 From: Anthony Fok Date: Wed, 10 Feb 2021 12:35:57 -0700 Subject: [PATCH 166/211] Update CHANGELOG.md for v1.1.3 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 648c8f49..ba89f136 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Cobra Changelog +## v1.1.3 + +* **Fix:** release-branch.cobra1.1 only: Revert "Deprecate Go < 1.14" that was to maintain backward compatibility + ## v1.1.2 ### Notable Changes From 893ebf6e36ac744887c934361ce95ce9ce8cffaf Mon Sep 17 00:00:00 2001 From: Anthony Fok Date: Wed, 10 Feb 2021 12:40:59 -0700 Subject: [PATCH 167/211] Fix typo --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ba89f136..8a23b4f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## v1.1.3 -* **Fix:** release-branch.cobra1.1 only: Revert "Deprecate Go < 1.14" that was to maintain backward compatibility +* **Fix:** release-branch.cobra1.1 only: Revert "Deprecate Go < 1.14" to maintain backward compatibility ## v1.1.2 From ded486a867a9f1457fd7ad65bf66ef62330eb184 Mon Sep 17 00:00:00 2001 From: Paul Holzinger <45212748+Luap99@users.noreply.github.com> Date: Sun, 14 Feb 2021 18:27:21 +0100 Subject: [PATCH 168/211] Fix trailing whitespace in the powershell completion script (#1342) Signed-off-by: Paul Holzinger --- powershell_completions.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/powershell_completions.go b/powershell_completions.go index c55be71c..383ad749 100644 --- a/powershell_completions.go +++ b/powershell_completions.go @@ -86,7 +86,7 @@ Register-ArgumentCompleter -CommandName '%[1]s' -ScriptBlock { # We add an extra empty parameter so we can indicate this to the go method. __%[1]s_debug "Adding extra empty parameter" `+" # We need to use `\"`\" to pass an empty argument a \"\" or '' does not work!!!"+` -`+" $RequestComp=\"$RequestComp\" + ' `\"`\"' "+` +`+" $RequestComp=\"$RequestComp\" + ' `\"`\"'"+` } __%[1]s_debug "Calling $RequestComp" From b312f0a8ef6f7ceb211af700083046da437db67c Mon Sep 17 00:00:00 2001 From: Marc Khouzam Date: Mon, 15 Feb 2021 12:47:01 -0500 Subject: [PATCH 169/211] Create 'completion' command automatically (#1192) Make it easier for programs to provide shell completion by creating the 'completion' command automatically. If a 'completion' command is already provided by the program, Cobra will use that one instead. Signed-off-by: Marc Khouzam --- command.go | 8 +- custom_completions.go => completions.go | 170 +++++++++++++++ ...completions_test.go => completions_test.go | 200 ++++++++++++++++++ shell_completions.md | 31 ++- 4 files changed, 406 insertions(+), 3 deletions(-) rename custom_completions.go => completions.go (79%) rename custom_completions_test.go => completions_test.go (89%) diff --git a/command.go b/command.go index d6732ad1..ce94d40f 100644 --- a/command.go +++ b/command.go @@ -168,6 +168,9 @@ type Command struct { //FParseErrWhitelist flag parse errors to be ignored FParseErrWhitelist FParseErrWhitelist + // CompletionOptions is a set of options to control the handling of shell completion + CompletionOptions CompletionOptions + // commandsAreSorted defines, if command slice are sorted or not. commandsAreSorted bool // commandCalledAs is the name or alias value used to call this command. @@ -914,9 +917,10 @@ func (c *Command) ExecuteC() (cmd *Command, err error) { preExecHookFn(c) } - // initialize help as the last point possible to allow for user - // overriding + // initialize help at the last point to allow for user overriding c.InitDefaultHelpCmd() + // initialize completion at the last point to allow for user overriding + c.initDefaultCompletionCmd() args := c.args diff --git a/custom_completions.go b/completions.go similarity index 79% rename from custom_completions.go rename to completions.go index fa060c14..fea2c6f1 100644 --- a/custom_completions.go +++ b/completions.go @@ -63,6 +63,32 @@ const ( ShellCompDirectiveDefault ShellCompDirective = 0 ) +const ( + // Constants for the completion command + compCmdName = "completion" + compCmdNoDescFlagName = "no-descriptions" + compCmdNoDescFlagDesc = "disable completion descriptions" + compCmdNoDescFlagDefault = false +) + +// CompletionOptions are the options to control shell completion +type CompletionOptions struct { + // DisableDefaultCmd prevents Cobra from creating a default 'completion' command + DisableDefaultCmd bool + // DisableNoDescFlag prevents Cobra from creating the '--no-descriptions' flag + // for shells that support completion descriptions + DisableNoDescFlag bool + // DisableDescriptions turns off all completion descriptions for shells + // that support them + DisableDescriptions bool +} + +// NoFileCompletions can be used to disable file completion for commands that should +// not trigger file completions. +func NoFileCompletions(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { + return nil, ShellCompDirectiveNoFileComp +} + // 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, ShellCompDirective)) error { flag := c.Flag(flagName) @@ -494,6 +520,150 @@ func checkIfFlagCompletion(finalCmd *Command, args []string, lastArg string) (*p return flag, trimmedArgs, lastArg, nil } +// initDefaultCompletionCmd adds a default 'completion' command to c. +// This function will do nothing if any of the following is true: +// 1- the feature has been explicitly disabled by the program, +// 2- c has no subcommands (to avoid creating one), +// 3- c already has a 'completion' command provided by the program. +func (c *Command) initDefaultCompletionCmd() { + if c.CompletionOptions.DisableDefaultCmd || !c.HasSubCommands() { + return + } + + for _, cmd := range c.commands { + if cmd.Name() == compCmdName || cmd.HasAlias(compCmdName) { + // A completion command is already available + return + } + } + + haveNoDescFlag := !c.CompletionOptions.DisableNoDescFlag && !c.CompletionOptions.DisableDescriptions + + completionCmd := &Command{ + Use: compCmdName, + Short: "generate the autocompletion script for the specified shell", + Long: fmt.Sprintf(` +Generate the autocompletion script for %[1]s for the specified shell. +See each sub-command's help for details on how to use the generated script. +`, c.Root().Name()), + Args: NoArgs, + ValidArgsFunction: NoFileCompletions, + } + c.AddCommand(completionCmd) + + out := c.OutOrStdout() + noDesc := c.CompletionOptions.DisableDescriptions + shortDesc := "generate the autocompletion script for %s" + bash := &Command{ + Use: "bash", + Short: fmt.Sprintf(shortDesc, "bash"), + Long: fmt.Sprintf(` +Generate the autocompletion script for the bash shell. + +This script depends on the 'bash-completion' package. +If it is not installed already, you can install it via your OS's package manager. + +To load completions in your current shell session: +$ source <(%[1]s completion bash) + +To load completions for every new session, execute once: +Linux: + $ %[1]s completion bash > /etc/bash_completion.d/%[1]s +MacOS: + $ %[1]s completion bash > /usr/local/etc/bash_completion.d/%[1]s + +You will need to start a new shell for this setup to take effect. + `, c.Root().Name()), + Args: NoArgs, + DisableFlagsInUseLine: true, + ValidArgsFunction: NoFileCompletions, + RunE: func(cmd *Command, args []string) error { + return cmd.Root().GenBashCompletion(out) + }, + } + + zsh := &Command{ + Use: "zsh", + Short: fmt.Sprintf(shortDesc, "zsh"), + Long: fmt.Sprintf(` +Generate the autocompletion script for the zsh shell. + +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 every new session, execute once: +$ %[1]s completion zsh > "${fpath[1]}/_%[1]s" + +You will need to start a new shell for this setup to take effect. +`, c.Root().Name()), + Args: NoArgs, + ValidArgsFunction: NoFileCompletions, + RunE: func(cmd *Command, args []string) error { + if noDesc { + return cmd.Root().GenZshCompletionNoDesc(out) + } + return cmd.Root().GenZshCompletion(out) + }, + } + if haveNoDescFlag { + zsh.Flags().BoolVar(&noDesc, compCmdNoDescFlagName, compCmdNoDescFlagDefault, compCmdNoDescFlagDesc) + } + + fish := &Command{ + Use: "fish", + Short: fmt.Sprintf(shortDesc, "fish"), + Long: fmt.Sprintf(` +Generate the autocompletion script for the fish shell. + +To load completions in your current shell session: +$ %[1]s completion fish | source + +To load completions for every new session, execute once: +$ %[1]s completion fish > ~/.config/fish/completions/%[1]s.fish + +You will need to start a new shell for this setup to take effect. +`, c.Root().Name()), + Args: NoArgs, + ValidArgsFunction: NoFileCompletions, + RunE: func(cmd *Command, args []string) error { + return cmd.Root().GenFishCompletion(out, !noDesc) + }, + } + if haveNoDescFlag { + fish.Flags().BoolVar(&noDesc, compCmdNoDescFlagName, compCmdNoDescFlagDefault, compCmdNoDescFlagDesc) + } + + powershell := &Command{ + Use: "powershell", + Short: fmt.Sprintf(shortDesc, "powershell"), + Long: fmt.Sprintf(` +Generate the autocompletion script for powershell. + +To load completions in your current shell session: +PS C:\> %[1]s completion powershell | Out-String | Invoke-Expression + +To load completions for every new session, add the output of the above command +to your powershell profile. +`, c.Root().Name()), + Args: NoArgs, + ValidArgsFunction: NoFileCompletions, + RunE: func(cmd *Command, args []string) error { + if noDesc { + return cmd.Root().GenPowerShellCompletion(out) + } + return cmd.Root().GenPowerShellCompletionWithDesc(out) + + }, + } + if haveNoDescFlag { + powershell.Flags().BoolVar(&noDesc, compCmdNoDescFlagName, compCmdNoDescFlagDefault, compCmdNoDescFlagDesc) + } + + completionCmd.AddCommand(bash, zsh, fish, powershell) +} + func findFlag(cmd *Command, name string) *pflag.Flag { flagSet := cmd.Flags() if len(name) == 1 { diff --git a/custom_completions_test.go b/completions_test.go similarity index 89% rename from custom_completions_test.go rename to completions_test.go index ede809ed..603c4096 100644 --- a/custom_completions_test.go +++ b/completions_test.go @@ -75,6 +75,7 @@ func TestCmdNameCompletionInGo(t *testing.T) { expected := strings.Join([]string{ "aliased", + "completion", "firstChild", "help", "secondChild", @@ -123,6 +124,7 @@ func TestCmdNameCompletionInGo(t *testing.T) { expected = strings.Join([]string{ "aliased\tA command with aliases", + "completion\tgenerate the autocompletion script for the specified shell", "firstChild\tFirst command", "help\tHelp about any command", "secondChild", @@ -202,6 +204,7 @@ func TestNoCmdNameCompletionInGo(t *testing.T) { expected = strings.Join([]string{ "childCmd1", + "completion", "help", ":4", "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") @@ -373,6 +376,7 @@ func TestValidArgsAndCmdCompletionInGo(t *testing.T) { } expected := strings.Join([]string{ + "completion", "help", "thechild", "one", @@ -423,6 +427,7 @@ func TestValidArgsFuncAndCmdCompletionInGo(t *testing.T) { } expected := strings.Join([]string{ + "completion", "help", "thechild", "one", @@ -490,6 +495,7 @@ func TestFlagNameCompletionInGo(t *testing.T) { expected := strings.Join([]string{ "childCmd", + "completion", "help", ":4", "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") @@ -573,6 +579,7 @@ func TestFlagNameCompletionInGoWithDesc(t *testing.T) { expected := strings.Join([]string{ "childCmd\tfirst command", + "completion\tgenerate the autocompletion script for the specified shell", "help\tHelp about any command", ":4", "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") @@ -801,6 +808,7 @@ func TestRequiredFlagNameCompletionInGo(t *testing.T) { expected := strings.Join([]string{ "childCmd", + "completion", "help", "--requiredFlag", "-r", @@ -926,6 +934,7 @@ func TestRequiredFlagNameCompletionInGo(t *testing.T) { expected = strings.Join([]string{ "childCmd", + "completion", "help", "--requiredFlag", "-r", @@ -1918,6 +1927,7 @@ func TestCompleteHelp(t *testing.T) { expected := strings.Join([]string{ "child1", "child2", + "completion", "help", ":4", "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") @@ -1935,6 +1945,7 @@ func TestCompleteHelp(t *testing.T) { expected = strings.Join([]string{ "child1", "child2", + "completion", "help", // " help help" is a valid command, so should be completed ":4", "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") @@ -1958,3 +1969,192 @@ func TestCompleteHelp(t *testing.T) { t.Errorf("expected: %q, got: %q", expected, output) } } + +func removeCompCmd(rootCmd *Command) { + // Remove completion command for the next test + for _, cmd := range rootCmd.commands { + if cmd.Name() == compCmdName { + rootCmd.RemoveCommand(cmd) + return + } + } +} + +func TestDefaultCompletionCmd(t *testing.T) { + rootCmd := &Command{ + Use: "root", + Args: NoArgs, + Run: emptyRun, + } + + // Test that no completion command is created if there are not other sub-commands + assertNoErr(t, rootCmd.Execute()) + for _, cmd := range rootCmd.commands { + if cmd.Name() == compCmdName { + t.Errorf("Should not have a 'completion' command when there are no other sub-commands of root") + break + } + } + + subCmd := &Command{ + Use: "sub", + Run: emptyRun, + } + rootCmd.AddCommand(subCmd) + + // Test that a completion command is created if there are other sub-commands + found := false + assertNoErr(t, rootCmd.Execute()) + for _, cmd := range rootCmd.commands { + if cmd.Name() == compCmdName { + found = true + break + } + } + if !found { + t.Errorf("Should have a 'completion' command when there are other sub-commands of root") + } + // Remove completion command for the next test + removeCompCmd(rootCmd) + + // Test that the default completion command can be disabled + rootCmd.CompletionOptions.DisableDefaultCmd = true + assertNoErr(t, rootCmd.Execute()) + for _, cmd := range rootCmd.commands { + if cmd.Name() == compCmdName { + t.Errorf("Should not have a 'completion' command when the feature is disabled") + break + } + } + // Re-enable for next test + rootCmd.CompletionOptions.DisableDefaultCmd = false + + // Test that completion descriptions are enabled by default + output, err := executeCommand(rootCmd, compCmdName, "zsh") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + check(t, output, ShellCompRequestCmd) + checkOmit(t, output, ShellCompNoDescRequestCmd) + // Remove completion command for the next test + removeCompCmd(rootCmd) + + // Test that completion descriptions can be disabled completely + rootCmd.CompletionOptions.DisableDescriptions = true + output, err = executeCommand(rootCmd, compCmdName, "zsh") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + check(t, output, ShellCompNoDescRequestCmd) + // Re-enable for next test + rootCmd.CompletionOptions.DisableDescriptions = false + // Remove completion command for the next test + removeCompCmd(rootCmd) + + var compCmd *Command + // Test that the --no-descriptions flag is present for the relevant shells only + assertNoErr(t, rootCmd.Execute()) + for _, shell := range []string{"fish", "powershell", "zsh"} { + if compCmd, _, err = rootCmd.Find([]string{compCmdName, shell}); err != nil { + t.Errorf("Unexpected error: %v", err) + } + if flag := compCmd.Flags().Lookup(compCmdNoDescFlagName); flag == nil { + t.Errorf("Missing --%s flag for %s shell", compCmdNoDescFlagName, shell) + } + } + for _, shell := range []string{"bash"} { + if compCmd, _, err = rootCmd.Find([]string{compCmdName, shell}); err != nil { + t.Errorf("Unexpected error: %v", err) + } + if flag := compCmd.Flags().Lookup(compCmdNoDescFlagName); flag != nil { + t.Errorf("Unexpected --%s flag for %s shell", compCmdNoDescFlagName, shell) + } + } + // Remove completion command for the next test + removeCompCmd(rootCmd) + + // Test that the '--no-descriptions' flag can be disabled + rootCmd.CompletionOptions.DisableNoDescFlag = true + assertNoErr(t, rootCmd.Execute()) + for _, shell := range []string{"fish", "zsh", "bash", "powershell"} { + if compCmd, _, err = rootCmd.Find([]string{compCmdName, shell}); err != nil { + t.Errorf("Unexpected error: %v", err) + } + if flag := compCmd.Flags().Lookup(compCmdNoDescFlagName); flag != nil { + t.Errorf("Unexpected --%s flag for %s shell", compCmdNoDescFlagName, shell) + } + } + // Re-enable for next test + rootCmd.CompletionOptions.DisableNoDescFlag = false + // Remove completion command for the next test + removeCompCmd(rootCmd) + + // Test that the '--no-descriptions' flag is disabled when descriptions are disabled + rootCmd.CompletionOptions.DisableDescriptions = true + assertNoErr(t, rootCmd.Execute()) + for _, shell := range []string{"fish", "zsh", "bash", "powershell"} { + if compCmd, _, err = rootCmd.Find([]string{compCmdName, shell}); err != nil { + t.Errorf("Unexpected error: %v", err) + } + if flag := compCmd.Flags().Lookup(compCmdNoDescFlagName); flag != nil { + t.Errorf("Unexpected --%s flag for %s shell", compCmdNoDescFlagName, shell) + } + } + // Re-enable for next test + rootCmd.CompletionOptions.DisableDescriptions = false + // Remove completion command for the next test + removeCompCmd(rootCmd) +} + +func TestCompleteCompletion(t *testing.T) { + rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun} + subCmd := &Command{ + Use: "sub", + Run: emptyRun, + } + rootCmd.AddCommand(subCmd) + + // Test sub-commands of the completion command + output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "completion", "") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected := strings.Join([]string{ + "bash", + "fish", + "powershell", + "zsh", + ":4", + "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Test there are no completions for the sub-commands of the completion command + var compCmd *Command + for _, cmd := range rootCmd.Commands() { + if cmd.Name() == compCmdName { + compCmd = cmd + break + } + } + + for _, shell := range compCmd.Commands() { + output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, compCmdName, shell.Name(), "") + 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) + } + } +} diff --git a/shell_completions.md b/shell_completions.md index cd533ac3..0b51cff9 100644 --- a/shell_completions.md +++ b/shell_completions.md @@ -7,6 +7,15 @@ The currently supported shells are: - fish - PowerShell +Cobra will automatically provide your program with a fully functional `completion` command, +similarly to how it provides the `help` command. + +## Creating your own completion command + +If you do not wish to use the default `completion` command, you can choose to +provide your own, which will take precedence over the default one. (This also provides +backwards-compatibility with programs that already have their own `completion` command.) + If you are using the generator, you can create a completion command by running ```bash @@ -70,7 +79,7 @@ PowerShell: case "fish": cmd.Root().GenFishCompletion(os.Stdout, true) case "powershell": - cmd.Root().GenPowerShellCompletion(os.Stdout) + cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout) } }, } @@ -78,6 +87,26 @@ PowerShell: **Note:** The cobra generator may include messages printed to stdout, for example, if the config file is loaded; this will break the auto-completion script so must be removed. +## Adapting the default completion command + +Cobra provides a few options for the default `completion` command. To configure such options you must set +the `CompletionOptions` field on the *root* command. + +To tell Cobra *not* to provide the default `completion` command: +``` +rootCmd.CompletionOptions.DisableDefaultCmd = true +``` + +To tell Cobra *not* to provide the user with the `--no-descriptions` flag to the completion sub-commands: +``` +rootCmd.CompletionOptions.DisableNoDescFlag = true +``` + +To tell Cobra to completely disable descriptions for completions: +``` +rootCmd.CompletionOptions.DisableDescriptions = true +``` + # 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. From b55fa79836c63eab81e884e6ec1d7e4ae5c7f9b5 Mon Sep 17 00:00:00 2001 From: John McBride Date: Mon, 15 Feb 2021 10:48:09 -0700 Subject: [PATCH 170/211] Add PR labeler with pull_request_target (#1338) * Add PR labeler with pull_request_target --- .github/labeler.yml | 16 ++++++++++++++++ .github/workflows/labeler.yml | 12 ++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 .github/labeler.yml create mode 100644 .github/workflows/labeler.yml diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 00000000..bd2b3bf5 --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,16 @@ +# changes to documentation generation +"area/doc-gen": doc/**/* + +# changes to the core Go cobra lib package +"area/lib": ./*.go + +# changes to the Cobra CLI +"area/cli": cobra/**/* + +# changes to Github workflows +"area/github": .github/**/* + +# changes to shell completions +"area/*sh completion": + - ./*completions* + diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml new file mode 100644 index 00000000..ce4fc1fd --- /dev/null +++ b/.github/workflows/labeler.yml @@ -0,0 +1,12 @@ +name: "Pull Request Labeler" +on: +- pull_request_target + +jobs: + triage: + runs-on: ubuntu-latest + steps: + - uses: actions/labeler@v3 + with: + repo-token: "${{ github.token }}" + From eb3b6397b1b5d1b0a2cd66a9afe0520f480c0a87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Thu, 18 Feb 2021 17:26:03 +0200 Subject: [PATCH 171/211] Bash completion variable leak fixes (#1352) Fixes bash variables leaking into the parent shell without `local` --- bash_completions.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bash_completions.go b/bash_completions.go index 71061479..b47f898a 100644 --- a/bash_completions.go +++ b/bash_completions.go @@ -384,7 +384,7 @@ func writePostscript(buf io.StringWriter, name string) { name = strings.Replace(name, ":", "__", -1) WriteStringAndCheck(buf, fmt.Sprintf("__start_%s()\n", name)) WriteStringAndCheck(buf, fmt.Sprintf(`{ - local cur prev words cword + local cur prev words cword split declare -A flaghash 2>/dev/null || : declare -A aliashash 2>/dev/null || : if declare -F _init_completion >/dev/null 2>&1; then @@ -400,11 +400,13 @@ func writePostscript(buf io.StringWriter, name string) { local flags_with_completion=() local flags_completion=() local commands=("%[1]s") + local command_aliases=() local must_have_one_flag=() local must_have_one_noun=() local has_completion_function local last_command local nouns=() + local noun_aliases=() __%[1]s_handle_word } From 3ed6a394b6ab0ee1148a985c500fee7c714ae0aa Mon Sep 17 00:00:00 2001 From: Unai Martinez-Corral <38422348+umarcor@users.noreply.github.com> Date: Mon, 3 May 2021 18:08:39 +0200 Subject: [PATCH 172/211] ci/MSYS2: go install @latest (#1366) --- .github/workflows/Test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/Test.yml b/.github/workflows/Test.yml index 260948cf..c34c2fbb 100644 --- a/.github/workflows/Test.yml +++ b/.github/workflows/Test.yml @@ -77,7 +77,7 @@ jobs: - run: | export GOBIN=$HOME/go/bin curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | bash -s -- -b $GOBIN latest - go install github.com/kyoh86/richgo - go install github.com/mitchellh/gox + go install github.com/kyoh86/richgo@latest + go install github.com/mitchellh/gox@latest - run: PATH=$HOME/go/bin:$PATH make From 06e4b59b206e16bb8c8f030f1a09bfc39ac5ff03 Mon Sep 17 00:00:00 2001 From: Marc Khouzam Date: Mon, 3 May 2021 12:23:34 -0400 Subject: [PATCH 173/211] Allow fish comp to support trailing empty lines (#1284) Some programs may output extra empty lines after the directive. Those lines must be ignored for fish shell completion to work. zsh and bash are not impacted. Signed-off-by: Marc Khouzam Co-authored-by: Johannes Altmanninger Co-authored-by: Johannes Altmanninger --- fish_completions.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/fish_completions.go b/fish_completions.go index 3e112347..0b2ff7df 100644 --- a/fish_completions.go +++ b/fish_completions.go @@ -53,6 +53,19 @@ function __%[1]s_perform_completion __%[1]s_debug "Calling $requestComp" set results (eval $requestComp 2> /dev/null) + + # Some programs may output extra empty lines after the directive. + # Let's ignore them or else it will break completion. + # Ref: https://github.com/spf13/cobra/issues/1279 + for line in $results[-1..1] + if test (string trim -- $line) = "" + # Found an empty line, remove it + set results $results[1..-2] + else + # Found non-empty line, we have our proper output + break + end + end set comps $results[1..-2] set directiveLine $results[-1] From 7223a997c8a0f38b2c57b384a96fef87b963275c Mon Sep 17 00:00:00 2001 From: Paul Holzinger <45212748+Luap99@users.noreply.github.com> Date: Mon, 3 May 2021 18:25:30 +0200 Subject: [PATCH 174/211] powershell completion fix no file comp directive (#1363) Make sure to filter the returned completions before we check if there are valid completions left. Fixes #1362 Signed-off-by: Paul Holzinger --- powershell_completions.go | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/powershell_completions.go b/powershell_completions.go index 383ad749..6a3f50fe 100644 --- a/powershell_completions.go +++ b/powershell_completions.go @@ -140,19 +140,6 @@ Register-ArgumentCompleter -CommandName '%[1]s' -ScriptBlock { $Space = "" } - if (($Directive -band $ShellCompDirectiveNoFileComp) -ne 0 ) { - __%[1]s_debug "ShellCompDirectiveNoFileComp is called" - - if ($Values.Length -eq 0) { - # Just print an empty string here so the - # shell does not start to complete paths. - # We cannot use CompletionResult here because - # it does not accept an empty string as argument. - "" - return - } - } - if ((($Directive -band $ShellCompDirectiveFilterFileExt) -ne 0 ) -or (($Directive -band $ShellCompDirectiveFilterDirs) -ne 0 )) { __%[1]s_debug "ShellCompDirectiveFilterFileExt ShellCompDirectiveFilterDirs are not supported" @@ -172,6 +159,19 @@ Register-ArgumentCompleter -CommandName '%[1]s' -ScriptBlock { } } + if (($Directive -band $ShellCompDirectiveNoFileComp) -ne 0 ) { + __%[1]s_debug "ShellCompDirectiveNoFileComp is called" + + if ($Values.Length -eq 0) { + # Just print an empty string here so the + # shell does not start to complete paths. + # We cannot use CompletionResult here because + # it does not accept an empty string as argument. + "" + return + } + } + # Get the current mode $Mode = (Get-PSReadLineKeyHandler | Where-Object {$_.Key -eq "Tab" }).Function __%[1]s_debug "Mode: $Mode" From 6d00909120c77b54b0c9974a4e20ffc540901b98 Mon Sep 17 00:00:00 2001 From: Lukas Malkmus Date: Mon, 3 May 2021 18:33:57 +0200 Subject: [PATCH 175/211] Pass context to completion (#1265) --- command.go | 11 ++++++++++- command_test.go | 40 ++++++++++++++++++++++++++++++++++++++++ completions.go | 1 + completions_test.go | 43 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 94 insertions(+), 1 deletion(-) diff --git a/command.go b/command.go index ce94d40f..5c85c899 100644 --- a/command.go +++ b/command.go @@ -887,7 +887,8 @@ 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. +// Retrieve ctx by calling cmd.Context() inside your *Run lifecycle or ValidArgs +// functions. func (c *Command) ExecuteContext(ctx context.Context) error { c.ctx = ctx return c.Execute() @@ -901,6 +902,14 @@ func (c *Command) Execute() error { return err } +// ExecuteContextC is the same as ExecuteC(), but sets the ctx on the command. +// Retrieve ctx by calling cmd.Context() inside your *Run lifecycle or ValidArgs +// functions. +func (c *Command) ExecuteContextC(ctx context.Context) (*Command, error) { + c.ctx = ctx + return c.ExecuteC() +} + // ExecuteC executes the command. func (c *Command) ExecuteC() (cmd *Command, err error) { if c.ctx == nil { diff --git a/command_test.go b/command_test.go index 9640fc5d..583cb023 100644 --- a/command_test.go +++ b/command_test.go @@ -42,6 +42,17 @@ func executeCommandC(root *Command, args ...string) (c *Command, output string, return c, buf.String(), err } +func executeCommandWithContextC(ctx context.Context, root *Command, args ...string) (c *Command, output string, err error) { + buf := new(bytes.Buffer) + root.SetOut(buf) + root.SetErr(buf) + root.SetArgs(args) + + c, err = root.ExecuteContextC(ctx) + + return c, buf.String(), err +} + func resetCommandLineFlagSet() { pflag.CommandLine = pflag.NewFlagSet(os.Args[0], pflag.ExitOnError) } @@ -178,6 +189,35 @@ func TestExecuteContext(t *testing.T) { } } +func TestExecuteContextC(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 := executeCommandWithContextC(ctx, rootCmd, ""); err != nil { + t.Errorf("Root command must not fail: %+v", err) + } + + if _, _, err := executeCommandWithContextC(ctx, rootCmd, "child"); err != nil { + t.Errorf("Subcommand must not fail: %+v", err) + } + + if _, _, err := executeCommandWithContextC(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() { diff --git a/completions.go b/completions.go index fea2c6f1..28d7dd03 100644 --- a/completions.go +++ b/completions.go @@ -221,6 +221,7 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi // Unable to find the real command. E.g., someInvalidCmd return c, []string{}, ShellCompDirectiveDefault, fmt.Errorf("Unable to find a command for arguments: %v", trimmedArgs) } + finalCmd.ctx = c.ctx // 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 diff --git a/completions_test.go b/completions_test.go index 603c4096..3e16bd04 100644 --- a/completions_test.go +++ b/completions_test.go @@ -2,6 +2,7 @@ package cobra import ( "bytes" + "context" "strings" "testing" ) @@ -1203,6 +1204,48 @@ func TestFlagDirFilterCompletionInGo(t *testing.T) { } } +func TestValidArgsFuncCmdContext(t *testing.T) { + validArgsFunc := func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { + ctx := cmd.Context() + + if ctx == nil { + t.Error("Received nil context in completion func") + } else if ctx.Value("testKey") != "123" { + t.Error("Received invalid context") + } + + return nil, ShellCompDirectiveDefault + } + + rootCmd := &Command{ + Use: "root", + Run: emptyRun, + } + childCmd := &Command{ + Use: "childCmd", + ValidArgsFunction: validArgsFunc, + Run: emptyRun, + } + rootCmd.AddCommand(childCmd) + + //nolint:golint,staticcheck // We can safely use a basic type as key in tests. + ctx := context.WithValue(context.Background(), "testKey", "123") + + // Test completing an empty string on the childCmd + _, output, err := executeCommandWithContextC(ctx, rootCmd, ShellCompNoDescRequestCmd, "childCmd", "") + 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 TestValidArgsFuncSingleCmd(t *testing.T) { rootCmd := &Command{ Use: "root", From 2d94892a8bec681d65a129e0a94b2e0d87e52cb9 Mon Sep 17 00:00:00 2001 From: Paul Holzinger <45212748+Luap99@users.noreply.github.com> Date: Mon, 3 May 2021 18:42:00 +0200 Subject: [PATCH 176/211] Custom completion handle multiple shorhand flags together (#1258) Flag definitions like `-asd` are not handled correctly by the custom completion logic. They should be treated as multiple flags. For details refer to #1257. Fixes #1257 Signed-off-by: Paul Holzinger --- completions.go | 23 +++++++++-- completions_test.go | 93 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 113 insertions(+), 3 deletions(-) diff --git a/completions.go b/completions.go index 28d7dd03..6982d9a8 100644 --- a/completions.go +++ b/completions.go @@ -469,7 +469,16 @@ func checkIfFlagCompletion(finalCmd *Command, args []string, lastArg string) (*p if len(lastArg) > 0 && lastArg[0] == '-' { if index := strings.Index(lastArg, "="); index >= 0 { // Flag with an = - flagName = strings.TrimLeft(lastArg[:index], "-") + if strings.HasPrefix(lastArg[:index], "--") { + // Flag has full name + flagName = lastArg[2:index] + } else { + // Flag is shorthand + // We have to get the last shorthand flag name + // e.g. `-asd` => d to provide the correct completion + // https://github.com/spf13/cobra/issues/1257 + flagName = lastArg[index-1 : index] + } lastArg = lastArg[index+1:] flagWithEqual = true } else { @@ -486,8 +495,16 @@ func checkIfFlagCompletion(finalCmd *Command, args []string, lastArg string) (*p // 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, "-") - + if strings.HasPrefix(prevArg, "--") { + // Flag has full name + flagName = prevArg[2:] + } else { + // Flag is shorthand + // We have to get the last shorthand flag name + // e.g. `-asd` => d to provide the correct completion + // https://github.com/spf13/cobra/issues/1257 + flagName = prevArg[len(prevArg)-1:] + } // Remove the uncompleted flag or else there could be an error created // for an invalid value for that flag trimmedArgs = args[:len(args)-1] diff --git a/completions_test.go b/completions_test.go index 3e16bd04..9bd6a410 100644 --- a/completions_test.go +++ b/completions_test.go @@ -2201,3 +2201,96 @@ func TestCompleteCompletion(t *testing.T) { } } } + +func TestMultipleShorthandFlagCompletion(t *testing.T) { + rootCmd := &Command{ + Use: "root", + ValidArgs: []string{"foo", "bar"}, + Run: emptyRun, + } + f := rootCmd.Flags() + f.BoolP("short", "s", false, "short flag 1") + f.BoolP("short2", "d", false, "short flag 2") + f.StringP("short3", "f", "", "short flag 3") + _ = rootCmd.RegisterFlagCompletionFunc("short3", func(*Command, []string, string) ([]string, ShellCompDirective) { + return []string{"works"}, ShellCompDirectiveNoFileComp + }) + + // Test that a single shorthand flag works + output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-s", "") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected := strings.Join([]string{ + "foo", + "bar", + ":4", + "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Test that multiple boolean shorthand flags work + output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-sd", "") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + "foo", + "bar", + ":4", + "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Test that multiple boolean + string shorthand flags work + output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-sdf", "") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + "works", + ":4", + "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Test that multiple boolean + string with equal sign shorthand flags work + output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-sdf=") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + "works", + ":4", + "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Test that multiple boolean + string with equal sign with value shorthand flags work + output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-sdf=abc", "") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + "foo", + "bar", + ":4", + "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } +} From 95d23d24ff0cd791a67489230e4c3631df4105eb Mon Sep 17 00:00:00 2001 From: Marc Khouzam Date: Mon, 3 May 2021 12:54:00 -0400 Subject: [PATCH 177/211] Fix zsh for DirectiveNoSpace and DirectiveNoFileComp (#1213) Fixes #1211 When handling ShellCompDirectiveNoSpace we must still properly handle descriptions. To do so we cannot simply use 'compadd', but must use zsh's '_describe' function. Also, when handling ShellCompDirectiveNoSpace we cannot assume that only a single completion will be given to the script. In fact, ValidArgsFunction can return multiple completions, even if they don't match the 'toComplete' argument prefix. Therefore, we cannot use the number of completions received in the completion script to determine if we should activate the "no space" directive. Instead, we can leave it all to the '_describe' function. Fixes #1212 When handling ShellCompDirectiveNoFileComp we cannot base ourself on the script receiving no valid completion. In fact, ValidArgsFunction can return multiple completions, even if they don't match the 'toComplete' argument prefix at all. Therefore, we cannot use the number of completions received by the completion script to determine if we should activate the "no file comp" directive. Instead, we can check if the '_describe' function has found any completions. Finally, it is important for the script to return the return code of the called zsh functions (_describe, _arguments). This tells zsh if completions were found or not, which if not, will trigger different matching attempts, such as matching what the user typed with the the content of possible completions (instead of just as the prefix). Signed-off-by: Marc Khouzam --- completions.go | 4 ---- zsh_completions.go | 52 +++++++++++++++++++++++++++++++--------------- 2 files changed, 35 insertions(+), 21 deletions(-) diff --git a/completions.go b/completions.go index 6982d9a8..c647fad1 100644 --- a/completions.go +++ b/completions.go @@ -175,10 +175,6 @@ func (c *Command) initCompleteCmd(args []string) { fmt.Fprintln(finalCmd.OutOrStdout(), comp) } - if directive >= shellCompDirectiveMaxValue { - directive = ShellCompDirectiveDefault - } - // 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 : diff --git a/zsh_completions.go b/zsh_completions.go index 2e840285..1afec30e 100644 --- a/zsh_completions.go +++ b/zsh_completions.go @@ -95,7 +95,7 @@ _%[1]s() local shellCompDirectiveFilterFileExt=%[6]d local shellCompDirectiveFilterDirs=%[7]d - local lastParam lastChar flagPrefix requestComp out directive compCount comp lastComp + local lastParam lastChar flagPrefix requestComp out directive comp lastComp noSpace local -a completions __%[1]s_debug "\n========= starting completion logic ==========" @@ -163,7 +163,6 @@ _%[1]s() return fi - compCount=0 while IFS='\n' read -r comp; do if [ -n "$comp" ]; then # If requested, completions are returned with a description. @@ -175,13 +174,17 @@ _%[1]s() 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 & shellCompDirectiveNoSpace)) -ne 0 ]; then + __%[1]s_debug "Activating nospace." + noSpace="-S ''" + fi + if [ $((directive & shellCompDirectiveFilterFileExt)) -ne 0 ]; then # File extension filtering local filteringCmd @@ -208,25 +211,40 @@ _%[1]s() __%[1]s_debug "Listing directories in ." fi + local result _arguments '*:dirname:_files -/'" ${flagPrefix}" + result=$? 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 + return $result else - _describe "completions" completions $(echo $flagPrefix) + __%[1]s_debug "Calling _describe" + if eval _describe "completions" completions $flagPrefix $noSpace; then + __%[1]s_debug "_describe found some completions" + + # Return the success of having called _describe + return 0 + else + __%[1]s_debug "_describe did not find completions." + __%[1]s_debug "Checking if we should do file completion." + if [ $((directive & shellCompDirectiveNoFileComp)) -ne 0 ]; then + __%[1]s_debug "deactivating file completion" + + # We must return an error code here to let zsh know that there were no + # completions found by _describe; this is what will trigger other + # matching algorithms to attempt to find completions. + # For example zsh can match letters in the middle of words. + return 1 + else + # Perform file completion + __%[1]s_debug "Activating file completion" + + # We must return the result of this command, so it must be the + # last command, or else we must store its result to return it. + _arguments '*:filename:_files'" ${flagPrefix}" + fi + fi fi } From c2e21bdc104f9ef54bba066ae12ccedd0ae13d73 Mon Sep 17 00:00:00 2001 From: Marc Khouzam Date: Mon, 3 May 2021 14:00:01 -0400 Subject: [PATCH 178/211] Fix multiple fish completion issues (#1249) * Fix fish for ShellDirectiveNoSpace and file comp For fish shell we achieve ShellDirectiveNoSpace by outputing a fake second completion with an extra character. However, this extra character was being added after the description string, instead of before. This commit fixes that. It also cleans up the script of useless code, now that fish completion details are better understood. Signed-off-by: Marc Khouzam * Handle case when completion starts with a space Fixes #1303 Signed-off-by: Marc Khouzam * Support fish completion with env vars in the path Fixes https://github.com/spf13/cobra/issues/1214 Fixes https://github.com/spf13/cobra/issues/1306 Signed-off-by: Marc Khouzam * Update based on review 1- We use `set -l` for local variable to make sure there are no conflicts with global variables 2- We use `commandline -opc` which: a) splits the command line into tokens (-o) b) only considers the current command (-p) (e.g., echo hello; helm ) c) stops at the cursor (-c) 3- We extract the last arg with `commandline -ct` and escape it to handle the case where it is a space, or unmatched quote. 4- We avoid looping when filtering on prefix. 5- We don't add a fake comp for ShellCompDirectiveNoSpace when the completion ends with any of @=/:., as fish won't add a space Signed-off-by: Marc Khouzam --- fish_completions.go | 165 ++++++++++++++++++++++---------------------- 1 file changed, 82 insertions(+), 83 deletions(-) diff --git a/fish_completions.go b/fish_completions.go index 0b2ff7df..b0db323a 100644 --- a/fish_completions.go +++ b/fish_completions.go @@ -21,38 +21,27 @@ func genFishComp(buf io.StringWriter, name string, includeDesc bool) { WriteStringAndCheck(buf, fmt.Sprintf("# fish completion for %-36s -*- shell-script -*-\n", name)) WriteStringAndCheck(buf, fmt.Sprintf(` function __%[1]s_debug - set file "$BASH_COMP_DEBUG_FILE" + set -l 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" + __%[1]s_debug "Starting __%[1]s_perform_completion" - set args (string split -- " " "$argv") - set lastArg "$args[-1]" + # Extract all args except the last one + set -l args (commandline -opc) + # Extract the last arg and escape it in case it is a space + set -l lastArg (string escape -- (commandline -ct)) __%[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 -l requestComp "$args[1] %[3]s $args[2..-1] $lastArg" - if not type -q "$args[1]" - # 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] %[3]s $args[2..-1] $emptyArg" __%[1]s_debug "Calling $requestComp" - - set results (eval $requestComp 2> /dev/null) + set -l results (eval $requestComp 2> /dev/null) # Some programs may output extra empty lines after the directive. # Let's ignore them or else it will break completion. @@ -66,12 +55,13 @@ function __%[1]s_perform_completion break end end - set comps $results[1..-2] - set directiveLine $results[-1] + + set -l comps $results[1..-2] + set -l 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") + set -l flagPrefix (string match -r -- '-.*=' "$lastArg") __%[1]s_debug "Comps: $comps" __%[1]s_debug "DirectiveLine: $directiveLine" @@ -84,115 +74,124 @@ function __%[1]s_perform_completion 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 +# This function does two things: +# - Obtain the completions and store them in the global __%[1]s_comp_results +# - Return false if file completion should be performed function __%[1]s_prepare_completions + __%[1]s_debug "" + __%[1]s_debug "========= starting completion logic ==========" + # 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 - # 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" - - set results (__%[1]s_perform_completion "$__%[1]s_comp_commandLine") - set --erase __%[1]s_comp_commandLine + set -l results (__%[1]s_perform_completion) __%[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 1 end - set directive (string sub --start 2 $results[-1]) + set -l 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" - set shellCompDirectiveError %[4]d - set shellCompDirectiveNoSpace %[5]d - set shellCompDirectiveNoFileComp %[6]d - set shellCompDirectiveFilterFileExt %[7]d - set shellCompDirectiveFilterDirs %[8]d + set -l shellCompDirectiveError %[4]d + set -l shellCompDirectiveNoSpace %[5]d + set -l shellCompDirectiveNoFileComp %[6]d + set -l shellCompDirectiveFilterFileExt %[7]d + set -l shellCompDirectiveFilterDirs %[8]d if test -z "$directive" set directive 0 end - set compErr (math (math --scale 0 $directive / $shellCompDirectiveError) %% 2) + set -l 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 1 end - set filefilter (math (math --scale 0 $directive / $shellCompDirectiveFilterFileExt) %% 2) - set dirfilter (math (math --scale 0 $directive / $shellCompDirectiveFilterDirs) %% 2) + set -l filefilter (math (math --scale 0 $directive / $shellCompDirectiveFilterFileExt) %% 2) + set -l 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) + set -l nospace (math (math --scale 0 $directive / $shellCompDirectiveNoSpace) %% 2) + set -l nofiles (math (math --scale 0 $directive / $shellCompDirectiveNoFileComp) %% 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 we want to prevent a space, or if file completion is NOT disabled, + # we need to count the number of valid completions. + # To do so, we will filter on prefix as the completions we have received + # may not already be filtered so as to allow fish to match on different + # criteria than the prefix. + if test $nospace -ne 0; or test $nofiles -eq 0 + set -l prefix (commandline -t | string escape --style=regex) + __%[1]s_debug "prefix: $prefix" - 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]. + set -l completions (string match -r -- "^$prefix.*" $__%[1]s_comp_results) + set --global __%[1]s_comp_results $completions + __%[1]s_debug "Filtered completions are: $__%[1]s_comp_results" + + # Important not to quote the variable for count to work + set -l numComps (count $__%[1]s_comp_results) + __%[1]s_debug "numComps: $numComps" + + if test $numComps -eq 1; and test $nospace -ne 0 + # We must first split on \t to get rid of the descriptions to be + # able to check what the actual completion will be. + # We don't need descriptions anyway since there is only a single + # real completion which the shell will expand immediately. + set -l split (string split --max 1 \t $__%[1]s_comp_results[1]) + + # Fish won't add a space if the completion ends with any + # of the following characters: @=/:., + set -l lastChar (string sub -s -1 -- $split) + if not string match -r -q "[@=/:.,]" -- "$lastChar" + # In other cases, 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 --global __%[1]s_comp_results $split[1] $split[1]. + __%[1]s_debug "Completions are now: $__%[1]s_comp_results" + end + end + + if test $numComps -eq 0; and test $nofiles -eq 0 + # To be consistent with bash and zsh, we only trigger file + # completion when there are no other completions + __%[1]s_debug "Requesting file completion" + return 1 + end 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) + return 0 end # 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 "%[2]s " > /dev/null 2>&1 -# Using '> /dev/null 2>&1' since '&>' is not supported in older versions of fish. +# Only do this if the program can be found, or else fish may print some errors; besides, +# the existing completions will only be loaded if the program can be found. +if type -q "%[2]s" + # The space after the program name is essential to trigger completion for the program + # and not completion of the program name itself. + # Also, we use '> /dev/null 2>&1' since '&>' is not supported in older versions of fish. + complete --do-complete "%[2]s " > /dev/null 2>&1 +end # Remove any pre-existing completions for the program since we will be handling all of them. 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 %[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 and __%[1]s_comp_do_file_comp. -# It provides the program's completion choices. +# The call to __%[1]s_prepare_completions will setup __%[1]s_comp_results +# which provides the program's completion choices. complete -c %[2]s -n '__%[1]s_prepare_completions' -f -a '$__%[1]s_comp_results' `, nameForVar, name, compCmd, From 4590150168e93f4b017c6e33469e26590ba839df Mon Sep 17 00:00:00 2001 From: tamo Date: Tue, 11 May 2021 08:19:33 +0900 Subject: [PATCH 179/211] Correcting misspelled words (#1349) * Correcting Misspelled Words * grammar fixes --- cobra/cmd/init_test.go | 2 +- completions.go | 2 +- powershell_completions.go | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cobra/cmd/init_test.go b/cobra/cmd/init_test.go index 6d21ef77..e364f409 100644 --- a/cobra/cmd/init_test.go +++ b/cobra/cmd/init_test.go @@ -49,7 +49,7 @@ func TestGoldenInitCmd(t *testing.T) { expectErr: true, }, { - name: "returns error when passing an relative path for project", + name: "returns error when passing a relative path for project", args: []string{"github.com/spf13/testproject"}, pkgName: "github.com/spf13/testproject", expectErr: true, diff --git a/completions.go b/completions.go index c647fad1..04dc71e4 100644 --- a/completions.go +++ b/completions.go @@ -325,7 +325,7 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi 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 or TraverseChildren is true + // - there are no local, non-persistent flags on the command-line or TraverseChildren is true for _, subCmd := range finalCmd.Commands() { if subCmd.IsAvailableCommand() || subCmd == finalCmd.helpCommand { if strings.HasPrefix(subCmd.Name(), toComplete) { diff --git a/powershell_completions.go b/powershell_completions.go index 6a3f50fe..59234c09 100644 --- a/powershell_completions.go +++ b/powershell_completions.go @@ -152,7 +152,7 @@ Register-ArgumentCompleter -CommandName '%[1]s' -ScriptBlock { # filter the result $_.Name -like "$WordToComplete*" - # Join the flag back if we have a equal sign flag + # Join the flag back if we have an equal sign flag if ( $IsEqualFlag ) { __%[1]s_debug "Join the equal sign flag back to the completion value" $_.Name = $Flag + "=" + $_.Name @@ -178,7 +178,7 @@ Register-ArgumentCompleter -CommandName '%[1]s' -ScriptBlock { $Values | ForEach-Object { - # store temporay because switch will overwrite $_ + # store temporary because switch will overwrite $_ $comp = $_ # PowerShell supports three different completion modes From 9a432671fd847f0faa5a5e4d9f9350ae289db2ac Mon Sep 17 00:00:00 2001 From: Rob Playford Date: Wed, 16 Jun 2021 02:52:13 +0100 Subject: [PATCH 180/211] fix home directory config not loading (#1282) leverage `viper.SetConfigType("yaml")` to fix issue regarding home directory configuration failing to load. --- README.md | 1 + cobra/cmd/root.go | 1 + cobra/cmd/testdata/root.go.golden | 1 + cobra/tpl/main.go | 1 + 4 files changed, 4 insertions(+) diff --git a/README.md b/README.md index a1b13ddd..3c290f4a 100644 --- a/README.md +++ b/README.md @@ -246,6 +246,7 @@ func initConfig() { // Search config in home directory with name ".cobra" (without extension). viper.AddConfigPath(home) + viper.SetConfigType("yaml") viper.SetConfigName(".cobra") } diff --git a/cobra/cmd/root.go b/cobra/cmd/root.go index f27ae7e6..86254c64 100644 --- a/cobra/cmd/root.go +++ b/cobra/cmd/root.go @@ -67,6 +67,7 @@ func initConfig() { // Search config in home directory with name ".cobra" (without extension). viper.AddConfigPath(home) + viper.SetConfigType("yaml") viper.SetConfigName(".cobra") } diff --git a/cobra/cmd/testdata/root.go.golden b/cobra/cmd/testdata/root.go.golden index be040365..26dedff7 100644 --- a/cobra/cmd/testdata/root.go.golden +++ b/cobra/cmd/testdata/root.go.golden @@ -73,6 +73,7 @@ func initConfig() { // Search config in home directory with name ".testproject" (without extension). viper.AddConfigPath(home) + viper.SetConfigType("yaml") viper.SetConfigName(".testproject") } diff --git a/cobra/tpl/main.go b/cobra/tpl/main.go index 0b9c6610..fbb3372b 100644 --- a/cobra/tpl/main.go +++ b/cobra/tpl/main.go @@ -86,6 +86,7 @@ func initConfig() { // Search config in home directory with name ".{{ .AppName }}" (without extension). viper.AddConfigPath(home) + viper.SetConfigType("yaml") viper.SetConfigName(".{{ .AppName }}") } From 701fa6c2bef293cf7cf82943c2104a83956feb56 Mon Sep 17 00:00:00 2001 From: Mark Sagi-Kazar Date: Wed, 16 Jun 2021 03:46:03 +0200 Subject: [PATCH 181/211] chore(deps): update viper Signed-off-by: Mark Sagi-Kazar --- go.mod | 2 +- go.sum | 459 ++++++++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 371 insertions(+), 90 deletions(-) diff --git a/go.mod b/go.mod index eb7d47fa..4e70bfee 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,6 @@ require ( 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 + github.com/spf13/viper v1.8.0 gopkg.in/yaml.v2 v2.4.0 ) diff --git a/go.sum b/go.sum index 9328ee3e..eca1565c 100644 --- a/go.sum +++ b/go.sum @@ -5,74 +5,141 @@ cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6A 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 v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= 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/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= 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/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= 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/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 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/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 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/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= 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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 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/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 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/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 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/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/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/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= 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/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 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/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= 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/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= 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= @@ -94,28 +161,27 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO 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/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 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/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= 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/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 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/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= 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/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= +github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= 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= @@ -125,30 +191,22 @@ github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eI 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/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 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/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ= +github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= 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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= 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= @@ -156,49 +214,65 @@ github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb 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/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= 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/spf13/viper v1.8.0 h1:QRwDgoG8xX+kp69di68D+YYTCWfYEckbZRfUlEIAal0= +github.com/spf13/viper v1.8.0/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= 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/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 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= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= 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= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= 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/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 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/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= 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= @@ -208,16 +282,26 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl 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/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 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/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 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= @@ -226,35 +310,107 @@ golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn 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/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 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/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 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/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/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/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 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/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 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/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 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= @@ -264,6 +420,7 @@ golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3 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-20190524140312-2c0ae7006135/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= @@ -271,16 +428,73 @@ golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtn 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/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/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/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= 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/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 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= @@ -290,24 +504,91 @@ google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98 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/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= 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= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 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/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= +gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 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.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= From ace6b14345897f1b757058291a84945a41f59f30 Mon Sep 17 00:00:00 2001 From: umarcor Date: Tue, 29 Jun 2021 06:51:57 +0200 Subject: [PATCH 182/211] readme: split 'Getting Started' into 'user_guide.md' --- README.md | 663 ++------------------------------------------------ user_guide.md | 638 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 652 insertions(+), 649 deletions(-) create mode 100644 user_guide.md diff --git a/README.md b/README.md index 3c290f4a..aa99cce8 100644 --- a/README.md +++ b/README.md @@ -19,18 +19,18 @@ name a few. [This list](./projects_using_cobra.md) contains a more extensive lis * [Commands](#commands) * [Flags](#flags) - [Installing](#installing) -- [Getting Started](#getting-started) - * [Using the Cobra Generator](#using-the-cobra-generator) - * [Using the Cobra Library](#using-the-cobra-library) - * [Working with Flags](#working-with-flags) - * [Positional and Custom Arguments](#positional-and-custom-arguments) - * [Example](#example) - * [Help Command](#help-command) - * [Usage Message](#usage-message) - * [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 shell completions](#generating-shell-completions) +- [Usage](#usage) + * [Using the Cobra Generator](user_guide.md#using-the-cobra-generator) + * [Using the Cobra Library](user_guide.md#using-the-cobra-library) + * [Working with Flags](user_guide.md#working-with-flags) + * [Positional and Custom Arguments](user_guide.md#positional-and-custom-arguments) + * [Example](user_guide.md#example) + * [Help Command](user_guide.md#help-command) + * [Usage Message](user_guide.md#usage-message) + * [PreRun and PostRun Hooks](user_guide.md#prerun-and-postrun-hooks) + * [Suggestions when "unknown command" happens](user_guide.md#suggestions-when-unknown-command-happens) + * [Generating documentation for your command](user_guide.md#generating-documentation-for-your-command) + * [Generating shell completions](user_guide.md#generating-shell-completions) - [Contributing](CONTRIBUTING.md) - [License](#license) @@ -117,644 +117,9 @@ Next, include Cobra in your application: import "github.com/spf13/cobra" ``` -# Getting Started +# Usage -While you are welcome to provide your own organization, typically a Cobra-based -application will follow the following organizational structure: - -``` - ▾ appName/ - ▾ cmd/ - add.go - your.go - commands.go - here.go - main.go -``` - -In a Cobra app, typically the main.go file is very bare. It serves one purpose: initializing Cobra. - -```go -package main - -import ( - "{pathToYourApp}/cmd" -) - -func main() { - cmd.Execute() -} -``` - -## Using the Cobra Generator - -Cobra provides its own program that will create your application and add any -commands you want. It's the easiest way to incorporate Cobra into your application. - -[Here](https://github.com/spf13/cobra/blob/master/cobra/README.md) you can find more information about it. - -## Using the Cobra Library - -To manually implement Cobra you need to create a bare main.go file and a rootCmd file. -You will optionally provide additional commands as you see fit. - -### Create rootCmd - -Cobra doesn't require any special constructors. Simply create your commands. - -Ideally you place this in app/cmd/root.go: - -```go -var rootCmd = &cobra.Command{ - Use: "hugo", - Short: "Hugo is a very fast static site generator", - Long: `A Fast and Flexible Static Site Generator built with - love by spf13 and friends in Go. - Complete documentation is available at http://hugo.spf13.com`, - Run: func(cmd *cobra.Command, args []string) { - // Do Stuff Here - }, -} - -func Execute() { - if err := rootCmd.Execute(); err != nil { - fmt.Fprintln(os.Stderr, err) - os.Exit(1) - } -} -``` - -You will additionally define flags and handle configuration in your init() function. - -For example cmd/root.go: - -```go -package cmd - -import ( - "fmt" - "os" - - homedir "github.com/mitchellh/go-homedir" - "github.com/spf13/cobra" - "github.com/spf13/viper" -) - -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().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() { - if cfgFile != "" { - // Use config file from the flag. - viper.SetConfigFile(cfgFile) - } else { - // Find home directory. - home, err := homedir.Dir() - cobra.CheckErr(err) - - // Search config in home directory with name ".cobra" (without extension). - viper.AddConfigPath(home) - viper.SetConfigType("yaml") - viper.SetConfigName(".cobra") - } - - viper.AutomaticEnv() - - if err := viper.ReadInConfig(); err == nil { - fmt.Println("Using config file:", viper.ConfigFileUsed()) - } -} -``` - -### Create your main.go - -With the root command you need to have your main function execute it. -Execute should be run on the root for clarity, though it can be called on any command. - -In a Cobra app, typically the main.go file is very bare. It serves one purpose: to initialize Cobra. - -```go -package main - -import ( - "{pathToYourApp}/cmd" -) - -func main() { - cmd.Execute() -} -``` - -### Create additional commands - -Additional commands can be defined and typically are each given their own file -inside of the cmd/ directory. - -If you wanted to create a version command you would create cmd/version.go and -populate it with the following: - -```go -package cmd - -import ( - "fmt" - - "github.com/spf13/cobra" -) - -func init() { - rootCmd.AddCommand(versionCmd) -} - -var versionCmd = &cobra.Command{ - Use: "version", - Short: "Print the version number of Hugo", - Long: `All software has versions. This is Hugo's`, - Run: func(cmd *cobra.Command, args []string) { - fmt.Println("Hugo Static Site Generator v0.9 -- HEAD") - }, -} -``` - -### 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 { - if err := someFunc(); err != nil { - return err - } - return nil - }, -} -``` - -The error can then be caught at the execute function call. - -## Working with Flags - -Flags provide modifiers to control how the action command operates. - -### Assign flags to a command - -Since the flags are defined and used in different locations, we need to -define a variable outside with the correct scope to assign the flag to -work with. - -```go -var Verbose bool -var Source string -``` - -There are two different approaches to assign a flag. - -### Persistent Flags - -A flag can be 'persistent', meaning that this flag will be available to the -command it's assigned to as well as every command under that command. For -global flags, assign a flag as a persistent flag on the root. - -```go -rootCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "verbose output") -``` - -### Local Flags - -A flag can also be assigned locally, which will only apply to that specific command. - -```go -localCmd.Flags().StringVarP(&Source, "source", "s", "", "Source directory to read from") -``` - -### Local Flag on Parent Commands - -By default, Cobra only parses local flags on the target command, and any local flags on -parent commands are ignored. By enabling `Command.TraverseChildren`, Cobra will -parse local flags on each command before executing the target command. - -```go -command := cobra.Command{ - Use: "print [OPTIONS] [COMMANDS]", - TraverseChildren: true, -} -``` - -### Bind Flags with Config - -You can also bind your flags with [viper](https://github.com/spf13/viper): -```go -var author string - -func init() { - rootCmd.PersistentFlags().StringVar(&author, "author", "YOUR NAME", "Author name for copyright attribution") - viper.BindPFlag("author", rootCmd.PersistentFlags().Lookup("author")) -} -``` - -In this example, the persistent flag `author` is bound with `viper`. -**Note**: the variable `author` will not be set to the value from config, -when the `--author` flag is not provided by user. - -More in [viper documentation](https://github.com/spf13/viper#working-with-flags). - -### Required flags - -Flags are optional by default. If instead you wish your command to report an error -when a flag has not been set, mark it as required: -```go -rootCmd.Flags().StringVarP(&Region, "region", "r", "", "AWS region (required)") -rootCmd.MarkFlagRequired("region") -``` - -Or, for persistent flags: -```go -rootCmd.PersistentFlags().StringVarP(&Region, "region", "r", "", "AWS region (required)") -rootCmd.MarkPersistentFlagRequired("region") -``` - -## Positional and Custom Arguments - -Validation of positional arguments can be specified using the `Args` field -of `Command`. - -The following validators are built in: - -- `NoArgs` - the command will report an error if there are any positional args. -- `ArbitraryArgs` - the command will accept any args. -- `OnlyValidArgs` - the command will report an error if there are any positional args that are not in the `ValidArgs` field of `Command`. -- `MinimumNArgs(int)` - the command will report an error if there are not at least N positional args. -- `MaximumNArgs(int)` - the command will report an error if there are more than N positional args. -- `ExactArgs(int)` - the command will report an error if there are not exactly N positional args. -- `ExactValidArgs(int)` - the command will report an error if there are not exactly N positional args OR if there are any positional args that are not in the `ValidArgs` field of `Command` -- `RangeArgs(min, max)` - the command will report an error if the number of args is not between the minimum and maximum number of expected args. - -An example of setting the custom validator: - -```go -var cmd = &cobra.Command{ - Short: "hello", - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return errors.New("requires a color argument") - } - if myapp.IsValidColor(args[0]) { - return nil - } - return fmt.Errorf("invalid color specified: %s", args[0]) - }, - Run: func(cmd *cobra.Command, args []string) { - fmt.Println("Hello, World!") - }, -} -``` - -## Example - -In the example below, we have defined three commands. Two are at the top level -and one (cmdTimes) is a child of one of the top commands. In this case the root -is not executable, meaning that a subcommand is required. This is accomplished -by not providing a 'Run' for the 'rootCmd'. - -We have only defined one flag for a single command. - -More documentation about flags is available at https://github.com/spf13/pflag - -```go -package main - -import ( - "fmt" - "strings" - - "github.com/spf13/cobra" -) - -func main() { - var echoTimes int - - var cmdPrint = &cobra.Command{ - Use: "print [string to print]", - Short: "Print anything to the screen", - Long: `print is for printing anything back to the screen. -For many years people have printed back to the screen.`, - Args: cobra.MinimumNArgs(1), - Run: func(cmd *cobra.Command, args []string) { - fmt.Println("Print: " + strings.Join(args, " ")) - }, - } - - var cmdEcho = &cobra.Command{ - Use: "echo [string to echo]", - Short: "Echo anything to the screen", - Long: `echo is for echoing anything back. -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("Echo: " + strings.Join(args, " ")) - }, - } - - var cmdTimes = &cobra.Command{ - Use: "times [string to echo]", - Short: "Echo anything to the screen more times", - Long: `echo things multiple times back to the user by providing -a count and a string.`, - Args: cobra.MinimumNArgs(1), - Run: func(cmd *cobra.Command, args []string) { - for i := 0; i < echoTimes; i++ { - fmt.Println("Echo: " + strings.Join(args, " ")) - } - }, - } - - cmdTimes.Flags().IntVarP(&echoTimes, "times", "t", 1, "times to echo the input") - - var rootCmd = &cobra.Command{Use: "app"} - rootCmd.AddCommand(cmdPrint, cmdEcho) - cmdEcho.AddCommand(cmdTimes) - rootCmd.Execute() -} -``` - -For a more complete example of a larger application, please checkout [Hugo](http://gohugo.io/). - -## Help Command - -Cobra automatically adds a help command to your application when you have subcommands. -This will be called when a user runs 'app help'. Additionally, help will also -support all other commands as input. Say, for instance, you have a command called -'create' without any additional configuration; Cobra will work when 'app help -create' is called. Every command will automatically have the '--help' flag added. - -### Example - -The following output is automatically generated by Cobra. Nothing beyond the -command and flag definitions are needed. - - $ cobra help - - 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. - - Usage: - cobra [command] - - Available Commands: - add Add a command to a Cobra Application - help Help about any command - init Initialize a Cobra Application - - Flags: - -a, --author string author name for copyright attribution (default "YOUR NAME") - --config string config file (default is $HOME/.cobra.yaml) - -h, --help help for cobra - -l, --license string name of license for the project - --viper use Viper for configuration (default true) - - Use "cobra [command] --help" for more information about a command. - - -Help is just a command like any other. There is no special logic or behavior -around it. In fact, you can provide your own if you want. - -### Defining your own help - -You can provide your own Help command or your own template for the default command to use -with following functions: - -```go -cmd.SetHelpCommand(cmd *Command) -cmd.SetHelpFunc(f func(*Command, []string)) -cmd.SetHelpTemplate(s string) -``` - -The latter two will also apply to any children commands. - -## Usage Message - -When the user provides an invalid flag or invalid command, Cobra responds by -showing the user the 'usage'. - -### Example -You may recognize this from the help above. That's because the default help -embeds the usage as part of its output. - - $ cobra --invalid - Error: unknown flag: --invalid - Usage: - cobra [command] - - Available Commands: - add Add a command to a Cobra Application - help Help about any command - init Initialize a Cobra Application - - Flags: - -a, --author string author name for copyright attribution (default "YOUR NAME") - --config string config file (default is $HOME/.cobra.yaml) - -h, --help help for cobra - -l, --license string name of license for the project - --viper use Viper for configuration (default true) - - Use "cobra [command] --help" for more information about a command. - -### Defining your own usage -You can provide your own usage function or template for Cobra to use. -Like help, the function and template are overridable through public methods: - -```go -cmd.SetUsageFunc(f func(*Command) error) -cmd.SetUsageTemplate(s string) -``` - -## Version Flag - -Cobra adds a top-level '--version' flag if the Version field is set on the root command. -Running an application with the '--version' flag will print the version to stdout using -the version template. The template can be customized using the -`cmd.SetVersionTemplate(s string)` function. - -## PreRun and PostRun Hooks - -It is possible to run functions before or after the main `Run` function of your command. The `PersistentPreRun` and `PreRun` functions will be executed before `Run`. `PersistentPostRun` and `PostRun` will be executed after `Run`. The `Persistent*Run` functions will be inherited by children if they do not declare their own. These functions are run in the following order: - -- `PersistentPreRun` -- `PreRun` -- `Run` -- `PostRun` -- `PersistentPostRun` - -An example of two commands which use all of these features is below. When the subcommand is executed, it will run the root command's `PersistentPreRun` but not the root command's `PersistentPostRun`: - -```go -package main - -import ( - "fmt" - - "github.com/spf13/cobra" -) - -func main() { - - var rootCmd = &cobra.Command{ - Use: "root [sub]", - Short: "My root command", - PersistentPreRun: func(cmd *cobra.Command, args []string) { - fmt.Printf("Inside rootCmd PersistentPreRun with args: %v\n", args) - }, - PreRun: func(cmd *cobra.Command, args []string) { - fmt.Printf("Inside rootCmd PreRun with args: %v\n", args) - }, - Run: func(cmd *cobra.Command, args []string) { - fmt.Printf("Inside rootCmd Run with args: %v\n", args) - }, - PostRun: func(cmd *cobra.Command, args []string) { - fmt.Printf("Inside rootCmd PostRun with args: %v\n", args) - }, - PersistentPostRun: func(cmd *cobra.Command, args []string) { - fmt.Printf("Inside rootCmd PersistentPostRun with args: %v\n", args) - }, - } - - var subCmd = &cobra.Command{ - Use: "sub [no options!]", - Short: "My subcommand", - PreRun: func(cmd *cobra.Command, args []string) { - fmt.Printf("Inside subCmd PreRun with args: %v\n", args) - }, - Run: func(cmd *cobra.Command, args []string) { - fmt.Printf("Inside subCmd Run with args: %v\n", args) - }, - PostRun: func(cmd *cobra.Command, args []string) { - fmt.Printf("Inside subCmd PostRun with args: %v\n", args) - }, - PersistentPostRun: func(cmd *cobra.Command, args []string) { - fmt.Printf("Inside subCmd PersistentPostRun with args: %v\n", args) - }, - } - - rootCmd.AddCommand(subCmd) - - rootCmd.SetArgs([]string{""}) - rootCmd.Execute() - fmt.Println() - rootCmd.SetArgs([]string{"sub", "arg1", "arg2"}) - rootCmd.Execute() -} -``` - -Output: -``` -Inside rootCmd PersistentPreRun with args: [] -Inside rootCmd PreRun with args: [] -Inside rootCmd Run with args: [] -Inside rootCmd PostRun with args: [] -Inside rootCmd PersistentPostRun with args: [] - -Inside rootCmd PersistentPreRun with args: [arg1 arg2] -Inside subCmd PreRun with args: [arg1 arg2] -Inside subCmd Run with args: [arg1 arg2] -Inside subCmd PostRun with args: [arg1 arg2] -Inside subCmd PersistentPostRun with args: [arg1 arg2] -``` - -## Suggestions when "unknown command" happens - -Cobra will print automatic suggestions when "unknown command" errors happen. This allows Cobra to behave similarly to the `git` command when a typo happens. For example: - -``` -$ hugo srever -Error: unknown command "srever" for "hugo" - -Did you mean this? - server - -Run 'hugo --help' for usage. -``` - -Suggestions are automatic based on every subcommand registered and use an implementation of [Levenshtein distance](http://en.wikipedia.org/wiki/Levenshtein_distance). Every registered command that matches a minimum distance of 2 (ignoring case) will be displayed as a suggestion. - -If you need to disable suggestions or tweak the string distance in your command, use: - -```go -command.DisableSuggestions = true -``` - -or - -```go -command.SuggestionsMinimumDistance = 1 -``` - -You can also explicitly set names for which a given command will be suggested using the `SuggestFor` attribute. This allows suggestions for strings that are not close in terms of string distance, but makes sense in your set of commands and for some which you don't want aliases. Example: - -``` -$ kubectl remove -Error: unknown command "remove" for "kubectl" - -Did you mean this? - delete - -Run 'kubectl help' for usage. -``` - -## Generating documentation for your command - -Cobra can generate documentation based on subcommands, flags, etc. Read more about it in the [docs generation documentation](doc/README.md). - -## Generating shell completions - -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). +See [User Guide](user_guide.md). # License diff --git a/user_guide.md b/user_guide.md new file mode 100644 index 00000000..7013eda5 --- /dev/null +++ b/user_guide.md @@ -0,0 +1,638 @@ +# User Guide + +While you are welcome to provide your own organization, typically a Cobra-based +application will follow the following organizational structure: + +``` + ▾ appName/ + ▾ cmd/ + add.go + your.go + commands.go + here.go + main.go +``` + +In a Cobra app, typically the main.go file is very bare. It serves one purpose: initializing Cobra. + +```go +package main + +import ( + "{pathToYourApp}/cmd" +) + +func main() { + cmd.Execute() +} +``` + +## Using the Cobra Generator + +Cobra provides its own program that will create your application and add any +commands you want. It's the easiest way to incorporate Cobra into your application. + +[Here](https://github.com/spf13/cobra/blob/master/cobra/README.md) you can find more information about it. + +## Using the Cobra Library + +To manually implement Cobra you need to create a bare main.go file and a rootCmd file. +You will optionally provide additional commands as you see fit. + +### Create rootCmd + +Cobra doesn't require any special constructors. Simply create your commands. + +Ideally you place this in app/cmd/root.go: + +```go +var rootCmd = &cobra.Command{ + Use: "hugo", + Short: "Hugo is a very fast static site generator", + Long: `A Fast and Flexible Static Site Generator built with + love by spf13 and friends in Go. + Complete documentation is available at http://hugo.spf13.com`, + Run: func(cmd *cobra.Command, args []string) { + // Do Stuff Here + }, +} + +func Execute() { + if err := rootCmd.Execute(); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} +``` + +You will additionally define flags and handle configuration in your init() function. + +For example cmd/root.go: + +```go +package cmd + +import ( + "fmt" + "os" + + homedir "github.com/mitchellh/go-homedir" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +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().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() { + if cfgFile != "" { + // Use config file from the flag. + viper.SetConfigFile(cfgFile) + } else { + // Find home directory. + home, err := homedir.Dir() + cobra.CheckErr(err) + + // Search config in home directory with name ".cobra" (without extension). + viper.AddConfigPath(home) + viper.SetConfigType("yaml") + viper.SetConfigName(".cobra") + } + + viper.AutomaticEnv() + + if err := viper.ReadInConfig(); err == nil { + fmt.Println("Using config file:", viper.ConfigFileUsed()) + } +} +``` + +### Create your main.go + +With the root command you need to have your main function execute it. +Execute should be run on the root for clarity, though it can be called on any command. + +In a Cobra app, typically the main.go file is very bare. It serves one purpose: to initialize Cobra. + +```go +package main + +import ( + "{pathToYourApp}/cmd" +) + +func main() { + cmd.Execute() +} +``` + +### Create additional commands + +Additional commands can be defined and typically are each given their own file +inside of the cmd/ directory. + +If you wanted to create a version command you would create cmd/version.go and +populate it with the following: + +```go +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +func init() { + rootCmd.AddCommand(versionCmd) +} + +var versionCmd = &cobra.Command{ + Use: "version", + Short: "Print the version number of Hugo", + Long: `All software has versions. This is Hugo's`, + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("Hugo Static Site Generator v0.9 -- HEAD") + }, +} +``` + +### 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 { + if err := someFunc(); err != nil { + return err + } + return nil + }, +} +``` + +The error can then be caught at the execute function call. + +## Working with Flags + +Flags provide modifiers to control how the action command operates. + +### Assign flags to a command + +Since the flags are defined and used in different locations, we need to +define a variable outside with the correct scope to assign the flag to +work with. + +```go +var Verbose bool +var Source string +``` + +There are two different approaches to assign a flag. + +### Persistent Flags + +A flag can be 'persistent', meaning that this flag will be available to the +command it's assigned to as well as every command under that command. For +global flags, assign a flag as a persistent flag on the root. + +```go +rootCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "verbose output") +``` + +### Local Flags + +A flag can also be assigned locally, which will only apply to that specific command. + +```go +localCmd.Flags().StringVarP(&Source, "source", "s", "", "Source directory to read from") +``` + +### Local Flag on Parent Commands + +By default, Cobra only parses local flags on the target command, and any local flags on +parent commands are ignored. By enabling `Command.TraverseChildren`, Cobra will +parse local flags on each command before executing the target command. + +```go +command := cobra.Command{ + Use: "print [OPTIONS] [COMMANDS]", + TraverseChildren: true, +} +``` + +### Bind Flags with Config + +You can also bind your flags with [viper](https://github.com/spf13/viper): +```go +var author string + +func init() { + rootCmd.PersistentFlags().StringVar(&author, "author", "YOUR NAME", "Author name for copyright attribution") + viper.BindPFlag("author", rootCmd.PersistentFlags().Lookup("author")) +} +``` + +In this example, the persistent flag `author` is bound with `viper`. +**Note**: the variable `author` will not be set to the value from config, +when the `--author` flag is not provided by user. + +More in [viper documentation](https://github.com/spf13/viper#working-with-flags). + +### Required flags + +Flags are optional by default. If instead you wish your command to report an error +when a flag has not been set, mark it as required: +```go +rootCmd.Flags().StringVarP(&Region, "region", "r", "", "AWS region (required)") +rootCmd.MarkFlagRequired("region") +``` + +Or, for persistent flags: +```go +rootCmd.PersistentFlags().StringVarP(&Region, "region", "r", "", "AWS region (required)") +rootCmd.MarkPersistentFlagRequired("region") +``` + +## Positional and Custom Arguments + +Validation of positional arguments can be specified using the `Args` field +of `Command`. + +The following validators are built in: + +- `NoArgs` - the command will report an error if there are any positional args. +- `ArbitraryArgs` - the command will accept any args. +- `OnlyValidArgs` - the command will report an error if there are any positional args that are not in the `ValidArgs` field of `Command`. +- `MinimumNArgs(int)` - the command will report an error if there are not at least N positional args. +- `MaximumNArgs(int)` - the command will report an error if there are more than N positional args. +- `ExactArgs(int)` - the command will report an error if there are not exactly N positional args. +- `ExactValidArgs(int)` - the command will report an error if there are not exactly N positional args OR if there are any positional args that are not in the `ValidArgs` field of `Command` +- `RangeArgs(min, max)` - the command will report an error if the number of args is not between the minimum and maximum number of expected args. + +An example of setting the custom validator: + +```go +var cmd = &cobra.Command{ + Short: "hello", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("requires a color argument") + } + if myapp.IsValidColor(args[0]) { + return nil + } + return fmt.Errorf("invalid color specified: %s", args[0]) + }, + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("Hello, World!") + }, +} +``` + +## Example + +In the example below, we have defined three commands. Two are at the top level +and one (cmdTimes) is a child of one of the top commands. In this case the root +is not executable, meaning that a subcommand is required. This is accomplished +by not providing a 'Run' for the 'rootCmd'. + +We have only defined one flag for a single command. + +More documentation about flags is available at https://github.com/spf13/pflag + +```go +package main + +import ( + "fmt" + "strings" + + "github.com/spf13/cobra" +) + +func main() { + var echoTimes int + + var cmdPrint = &cobra.Command{ + Use: "print [string to print]", + Short: "Print anything to the screen", + Long: `print is for printing anything back to the screen. +For many years people have printed back to the screen.`, + Args: cobra.MinimumNArgs(1), + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("Print: " + strings.Join(args, " ")) + }, + } + + var cmdEcho = &cobra.Command{ + Use: "echo [string to echo]", + Short: "Echo anything to the screen", + Long: `echo is for echoing anything back. +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("Echo: " + strings.Join(args, " ")) + }, + } + + var cmdTimes = &cobra.Command{ + Use: "times [string to echo]", + Short: "Echo anything to the screen more times", + Long: `echo things multiple times back to the user by providing +a count and a string.`, + Args: cobra.MinimumNArgs(1), + Run: func(cmd *cobra.Command, args []string) { + for i := 0; i < echoTimes; i++ { + fmt.Println("Echo: " + strings.Join(args, " ")) + } + }, + } + + cmdTimes.Flags().IntVarP(&echoTimes, "times", "t", 1, "times to echo the input") + + var rootCmd = &cobra.Command{Use: "app"} + rootCmd.AddCommand(cmdPrint, cmdEcho) + cmdEcho.AddCommand(cmdTimes) + rootCmd.Execute() +} +``` + +For a more complete example of a larger application, please checkout [Hugo](http://gohugo.io/). + +## Help Command + +Cobra automatically adds a help command to your application when you have subcommands. +This will be called when a user runs 'app help'. Additionally, help will also +support all other commands as input. Say, for instance, you have a command called +'create' without any additional configuration; Cobra will work when 'app help +create' is called. Every command will automatically have the '--help' flag added. + +### Example + +The following output is automatically generated by Cobra. Nothing beyond the +command and flag definitions are needed. + + $ cobra help + + 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. + + Usage: + cobra [command] + + Available Commands: + add Add a command to a Cobra Application + help Help about any command + init Initialize a Cobra Application + + Flags: + -a, --author string author name for copyright attribution (default "YOUR NAME") + --config string config file (default is $HOME/.cobra.yaml) + -h, --help help for cobra + -l, --license string name of license for the project + --viper use Viper for configuration (default true) + + Use "cobra [command] --help" for more information about a command. + + +Help is just a command like any other. There is no special logic or behavior +around it. In fact, you can provide your own if you want. + +### Defining your own help + +You can provide your own Help command or your own template for the default command to use +with following functions: + +```go +cmd.SetHelpCommand(cmd *Command) +cmd.SetHelpFunc(f func(*Command, []string)) +cmd.SetHelpTemplate(s string) +``` + +The latter two will also apply to any children commands. + +## Usage Message + +When the user provides an invalid flag or invalid command, Cobra responds by +showing the user the 'usage'. + +### Example +You may recognize this from the help above. That's because the default help +embeds the usage as part of its output. + + $ cobra --invalid + Error: unknown flag: --invalid + Usage: + cobra [command] + + Available Commands: + add Add a command to a Cobra Application + help Help about any command + init Initialize a Cobra Application + + Flags: + -a, --author string author name for copyright attribution (default "YOUR NAME") + --config string config file (default is $HOME/.cobra.yaml) + -h, --help help for cobra + -l, --license string name of license for the project + --viper use Viper for configuration (default true) + + Use "cobra [command] --help" for more information about a command. + +### Defining your own usage +You can provide your own usage function or template for Cobra to use. +Like help, the function and template are overridable through public methods: + +```go +cmd.SetUsageFunc(f func(*Command) error) +cmd.SetUsageTemplate(s string) +``` + +## Version Flag + +Cobra adds a top-level '--version' flag if the Version field is set on the root command. +Running an application with the '--version' flag will print the version to stdout using +the version template. The template can be customized using the +`cmd.SetVersionTemplate(s string)` function. + +## PreRun and PostRun Hooks + +It is possible to run functions before or after the main `Run` function of your command. The `PersistentPreRun` and `PreRun` functions will be executed before `Run`. `PersistentPostRun` and `PostRun` will be executed after `Run`. The `Persistent*Run` functions will be inherited by children if they do not declare their own. These functions are run in the following order: + +- `PersistentPreRun` +- `PreRun` +- `Run` +- `PostRun` +- `PersistentPostRun` + +An example of two commands which use all of these features is below. When the subcommand is executed, it will run the root command's `PersistentPreRun` but not the root command's `PersistentPostRun`: + +```go +package main + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +func main() { + + var rootCmd = &cobra.Command{ + Use: "root [sub]", + Short: "My root command", + PersistentPreRun: func(cmd *cobra.Command, args []string) { + fmt.Printf("Inside rootCmd PersistentPreRun with args: %v\n", args) + }, + PreRun: func(cmd *cobra.Command, args []string) { + fmt.Printf("Inside rootCmd PreRun with args: %v\n", args) + }, + Run: func(cmd *cobra.Command, args []string) { + fmt.Printf("Inside rootCmd Run with args: %v\n", args) + }, + PostRun: func(cmd *cobra.Command, args []string) { + fmt.Printf("Inside rootCmd PostRun with args: %v\n", args) + }, + PersistentPostRun: func(cmd *cobra.Command, args []string) { + fmt.Printf("Inside rootCmd PersistentPostRun with args: %v\n", args) + }, + } + + var subCmd = &cobra.Command{ + Use: "sub [no options!]", + Short: "My subcommand", + PreRun: func(cmd *cobra.Command, args []string) { + fmt.Printf("Inside subCmd PreRun with args: %v\n", args) + }, + Run: func(cmd *cobra.Command, args []string) { + fmt.Printf("Inside subCmd Run with args: %v\n", args) + }, + PostRun: func(cmd *cobra.Command, args []string) { + fmt.Printf("Inside subCmd PostRun with args: %v\n", args) + }, + PersistentPostRun: func(cmd *cobra.Command, args []string) { + fmt.Printf("Inside subCmd PersistentPostRun with args: %v\n", args) + }, + } + + rootCmd.AddCommand(subCmd) + + rootCmd.SetArgs([]string{""}) + rootCmd.Execute() + fmt.Println() + rootCmd.SetArgs([]string{"sub", "arg1", "arg2"}) + rootCmd.Execute() +} +``` + +Output: +``` +Inside rootCmd PersistentPreRun with args: [] +Inside rootCmd PreRun with args: [] +Inside rootCmd Run with args: [] +Inside rootCmd PostRun with args: [] +Inside rootCmd PersistentPostRun with args: [] + +Inside rootCmd PersistentPreRun with args: [arg1 arg2] +Inside subCmd PreRun with args: [arg1 arg2] +Inside subCmd Run with args: [arg1 arg2] +Inside subCmd PostRun with args: [arg1 arg2] +Inside subCmd PersistentPostRun with args: [arg1 arg2] +``` + +## Suggestions when "unknown command" happens + +Cobra will print automatic suggestions when "unknown command" errors happen. This allows Cobra to behave similarly to the `git` command when a typo happens. For example: + +``` +$ hugo srever +Error: unknown command "srever" for "hugo" + +Did you mean this? + server + +Run 'hugo --help' for usage. +``` + +Suggestions are automatic based on every subcommand registered and use an implementation of [Levenshtein distance](http://en.wikipedia.org/wiki/Levenshtein_distance). Every registered command that matches a minimum distance of 2 (ignoring case) will be displayed as a suggestion. + +If you need to disable suggestions or tweak the string distance in your command, use: + +```go +command.DisableSuggestions = true +``` + +or + +```go +command.SuggestionsMinimumDistance = 1 +``` + +You can also explicitly set names for which a given command will be suggested using the `SuggestFor` attribute. This allows suggestions for strings that are not close in terms of string distance, but makes sense in your set of commands and for some which you don't want aliases. Example: + +``` +$ kubectl remove +Error: unknown command "remove" for "kubectl" + +Did you mean this? + delete + +Run 'kubectl help' for usage. +``` + +## Generating documentation for your command + +Cobra can generate documentation based on subcommands, flags, etc. Read more about it in the [docs generation documentation](doc/README.md). + +## Generating shell completions + +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). From 8eaca5f0f49ad747a0722d39dca7a75c34abd21a Mon Sep 17 00:00:00 2001 From: umarcor Date: Fri, 7 Jun 2019 21:48:51 +0200 Subject: [PATCH 183/211] drop mitchellh/go-homedir (#853) --- cobra/cmd/root.go | 4 ++-- cobra/cmd/testdata/root.go.golden | 3 +-- cobra/tpl/main.go | 3 +-- go.mod | 1 - go.sum | 2 -- user_guide.md | 3 +-- 6 files changed, 5 insertions(+), 11 deletions(-) diff --git a/cobra/cmd/root.go b/cobra/cmd/root.go index 86254c64..350df103 100644 --- a/cobra/cmd/root.go +++ b/cobra/cmd/root.go @@ -15,8 +15,8 @@ package cmd import ( "fmt" + "os" - homedir "github.com/mitchellh/go-homedir" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -62,7 +62,7 @@ func initConfig() { viper.SetConfigFile(cfgFile) } else { // Find home directory. - home, err := homedir.Dir() + home, err := os.UserHomeDir() cobra.CheckErr(err) // Search config in home directory with name ".cobra" (without extension). diff --git a/cobra/cmd/testdata/root.go.golden b/cobra/cmd/testdata/root.go.golden index 26dedff7..32c1529e 100644 --- a/cobra/cmd/testdata/root.go.golden +++ b/cobra/cmd/testdata/root.go.golden @@ -20,7 +20,6 @@ import ( "os" "github.com/spf13/cobra" - homedir "github.com/mitchellh/go-homedir" "github.com/spf13/viper" ) @@ -68,7 +67,7 @@ func initConfig() { viper.SetConfigFile(cfgFile) } else { // Find home directory. - home, err := homedir.Dir() + home, err := os.UserHomeDir() cobra.CheckErr(err) // Search config in home directory with name ".testproject" (without extension). diff --git a/cobra/tpl/main.go b/cobra/tpl/main.go index fbb3372b..ed3a98e1 100644 --- a/cobra/tpl/main.go +++ b/cobra/tpl/main.go @@ -27,7 +27,6 @@ import ( "os" "github.com/spf13/cobra" {{ if .Viper }} - homedir "github.com/mitchellh/go-homedir" "github.com/spf13/viper"{{ end }} ) @@ -81,7 +80,7 @@ func initConfig() { viper.SetConfigFile(cfgFile) } else { // Find home directory. - home, err := homedir.Dir() + home, err := os.UserHomeDir() cobra.CheckErr(err) // Search config in home directory with name ".{{ .AppName }}" (without extension). diff --git a/go.mod b/go.mod index 4e70bfee..460109e7 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,6 @@ go 1.14 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.8.0 gopkg.in/yaml.v2 v2.4.0 diff --git a/go.sum b/go.sum index eca1565c..0230d574 100644 --- a/go.sum +++ b/go.sum @@ -185,8 +185,6 @@ github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNx 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= diff --git a/user_guide.md b/user_guide.md index 7013eda5..311abce2 100644 --- a/user_guide.md +++ b/user_guide.md @@ -76,7 +76,6 @@ import ( "fmt" "os" - homedir "github.com/mitchellh/go-homedir" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -122,7 +121,7 @@ func initConfig() { viper.SetConfigFile(cfgFile) } else { // Find home directory. - home, err := homedir.Dir() + home, err := os.UserHomeDir() cobra.CheckErr(err) // Search config in home directory with name ".cobra" (without extension). From d0f318d45bdb46e3c1bc314c6096674426f1a620 Mon Sep 17 00:00:00 2001 From: umarcor Date: Wed, 30 Jun 2021 22:15:35 +0200 Subject: [PATCH 184/211] ci: deprecate Travis CI --- .travis.yml | 28 ---------------------------- README.md | 1 - 2 files changed, 29 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 0821efdc..00000000 --- a/.travis.yml +++ /dev/null @@ -1,28 +0,0 @@ -language: go - -stages: - - test - - build - -go: - - 1.14.x - - 1.15.x - - tip - -env: GO111MODULE=on - -before_install: - - go get -u github.com/kyoh86/richgo - - go get -u github.com/mitchellh/gox - - curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin latest - -matrix: - allow_failures: - - go: tip - include: - - stage: build - go: 1.14.x - script: make cobra_generator - -script: - - make test diff --git a/README.md b/README.md index aa99cce8..074e3979 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,6 @@ Cobra is used in many Go projects such as [Kubernetes](http://kubernetes.io/), name a few. [This list](./projects_using_cobra.md) contains a more extensive list of projects using Cobra. [![](https://img.shields.io/github/workflow/status/spf13/cobra/Test?longCache=tru&label=Test&logo=github%20actions&logoColor=fff)](https://github.com/spf13/cobra/actions?query=workflow%3ATest) -[![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) From b36196066e3b97b3cc87a352c81279af77028cc8 Mon Sep 17 00:00:00 2001 From: Marc Khouzam Date: Wed, 30 Jun 2021 17:24:58 -0400 Subject: [PATCH 185/211] Bash completion V2 with completion descriptions (#1146) * Bash completion v2 This v2 version of bash completion is based on Go completions. It also supports descriptions like the other shells. Signed-off-by: Marc Khouzam * Only consider matching completions for formatting Signed-off-by: Marc Khouzam * Use bash compV2 for the default completion command Signed-off-by: Marc Khouzam * Update comments that still referred to bash completion Signed-off-by: Marc Khouzam --- bash_completions.md | 2 + bash_completionsV2.go | 302 ++++++++++++++++++++++++++++++++++++++++++ command.go | 11 +- completions.go | 6 +- completions_test.go | 12 +- shell_completions.md | 30 +++++ 6 files changed, 346 insertions(+), 17 deletions(-) create mode 100644 bash_completionsV2.go diff --git a/bash_completions.md b/bash_completions.md index 130f99b9..52919b2f 100644 --- a/bash_completions.md +++ b/bash_completions.md @@ -6,6 +6,8 @@ Please refer to [Shell Completions](shell_completions.md) for details. For backward 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. +**Note**: Cobra's default `completion` command uses bash completion V2. If you are currently using Cobra's legacy dynamic completion solution, you should not use the default `completion` command but continue using your own. + 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. Some code that works in kubernetes: diff --git a/bash_completionsV2.go b/bash_completionsV2.go new file mode 100644 index 00000000..8859b57c --- /dev/null +++ b/bash_completionsV2.go @@ -0,0 +1,302 @@ +package cobra + +import ( + "bytes" + "fmt" + "io" + "os" +) + +func (c *Command) genBashCompletion(w io.Writer, includeDesc bool) error { + buf := new(bytes.Buffer) + genBashComp(buf, c.Name(), includeDesc) + _, err := buf.WriteTo(w) + return err +} + +func genBashComp(buf io.StringWriter, name string, includeDesc bool) { + compCmd := ShellCompRequestCmd + if !includeDesc { + compCmd = ShellCompNoDescRequestCmd + } + + WriteStringAndCheck(buf, fmt.Sprintf(`# bash completion V2 for %-36[1]s -*- shell-script -*- + +__%[1]s_debug() +{ + if [[ -n ${BASH_COMP_DEBUG_FILE:-} ]]; then + echo "$*" >> "${BASH_COMP_DEBUG_FILE}" + fi +} + +# Macs have bash3 for which the bash-completion package doesn't include +# _init_completion. This is a minimal version of that function. +__%[1]s_init_completion() +{ + COMPREPLY=() + _get_comp_words_by_ref "$@" cur prev words cword +} + +# This function calls the %[1]s program to obtain the completion +# results and the directive. It fills the 'out' and 'directive' vars. +__%[1]s_get_completion_results() { + local requestComp lastParam lastChar 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 "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 "Adding extra empty parameter" + requestComp="${requestComp} ''" + fi + + # When completing a flag with an = (e.g., %[1]s -n=) + # bash focuses on the part after the =, so we need to remove + # the flag part from $cur + if [[ "${cur}" == -*=* ]]; then + cur="${cur#*=}" + fi + + __%[1]s_debug "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 "The completion directive is: ${directive}" + __%[1]s_debug "The completions are: ${out[*]}" +} + +__%[1]s_process_completion_results() { + local shellCompDirectiveError=%[3]d + local shellCompDirectiveNoSpace=%[4]d + local shellCompDirectiveNoFileComp=%[5]d + local shellCompDirectiveFilterFileExt=%[6]d + local shellCompDirectiveFilterDirs=%[7]d + + if [ $((directive & shellCompDirectiveError)) -ne 0 ]; then + # Error code. No completion. + __%[1]s_debug "Received error from custom completion go code" + return + else + if [ $((directive & shellCompDirectiveNoSpace)) -ne 0 ]; then + if [[ $(type -t compopt) = "builtin" ]]; then + __%[1]s_debug "Activating no space" + compopt -o nospace + else + __%[1]s_debug "No space directive not supported in this version of bash" + fi + fi + if [ $((directive & shellCompDirectiveNoFileComp)) -ne 0 ]; then + if [[ $(type -t compopt) = "builtin" ]]; then + __%[1]s_debug "Activating no file completion" + compopt +o default + else + __%[1]s_debug "No file completion directive not supported in this version of bash" + 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 + + # Use printf to strip any trailing newline + local subdir + subdir=$(printf "%%s" "${out[0]}") + if [ -n "$subdir" ]; then + __%[1]s_debug "Listing directories in $subdir" + pushd "$subdir" >/dev/null 2>&1 && _filedir -d && popd >/dev/null 2>&1 || return + else + __%[1]s_debug "Listing directories in ." + _filedir -d + fi + else + __%[1]s_handle_standard_completion_case + fi + + __%[1]s_handle_special_char "$cur" : + __%[1]s_handle_special_char "$cur" = +} + +__%[1]s_handle_standard_completion_case() { + local tab comp + tab=$(printf '\t') + + local longest=0 + # Look for the longest completion so that we can format things nicely + while IFS='' read -r comp; do + # Strip any description before checking the length + comp=${comp%%%%$tab*} + # Only consider the completions that match + comp=$(compgen -W "$comp" -- "$cur") + if ((${#comp}>longest)); then + longest=${#comp} + fi + done < <(printf "%%s\n" "${out[@]}") + + local completions=() + while IFS='' read -r comp; do + if [ -z "$comp" ]; then + continue + fi + + __%[1]s_debug "Original comp: $comp" + comp="$(__%[1]s_format_comp_descriptions "$comp" "$longest")" + __%[1]s_debug "Final comp: $comp" + completions+=("$comp") + done < <(printf "%%s\n" "${out[@]}") + + while IFS='' read -r comp; do + COMPREPLY+=("$comp") + done < <(compgen -W "${completions[*]}" -- "$cur") + + # If there is a single completion left, remove the description text + if [ ${#COMPREPLY[*]} -eq 1 ]; then + __%[1]s_debug "COMPREPLY[0]: ${COMPREPLY[0]}" + comp="${COMPREPLY[0]%%%% *}" + __%[1]s_debug "Removed description from single completion, which is now: ${comp}" + COMPREPLY=() + COMPREPLY+=("$comp") + fi +} + +__%[1]s_handle_special_char() +{ + local comp="$1" + local char=$2 + if [[ "$comp" == *${char}* && "$COMP_WORDBREAKS" == *${char}* ]]; then + local word=${comp%%"${comp##*${char}}"} + local idx=${#COMPREPLY[*]} + while [[ $((--idx)) -ge 0 ]]; do + COMPREPLY[$idx]=${COMPREPLY[$idx]#"$word"} + done + fi +} + +__%[1]s_format_comp_descriptions() +{ + local tab + tab=$(printf '\t') + local comp="$1" + local longest=$2 + + # Properly format the description string which follows a tab character if there is one + if [[ "$comp" == *$tab* ]]; then + desc=${comp#*$tab} + comp=${comp%%%%$tab*} + + # $COLUMNS stores the current shell width. + # Remove an extra 4 because we add 2 spaces and 2 parentheses. + maxdesclength=$(( COLUMNS - longest - 4 )) + + # Make sure we can fit a description of at least 8 characters + # if we are to align the descriptions. + if [[ $maxdesclength -gt 8 ]]; then + # Add the proper number of spaces to align the descriptions + for ((i = ${#comp} ; i < longest ; i++)); do + comp+=" " + done + else + # Don't pad the descriptions so we can fit more text after the completion + maxdesclength=$(( COLUMNS - ${#comp} - 4 )) + fi + + # If there is enough space for any description text, + # truncate the descriptions that are too long for the shell width + if [ $maxdesclength -gt 0 ]; then + if [ ${#desc} -gt $maxdesclength ]; then + desc=${desc:0:$(( maxdesclength - 1 ))} + desc+="…" + fi + comp+=" ($desc)" + fi + fi + + # Must use printf to escape all special characters + printf "%%q" "${comp}" +} + +__start_%[1]s() +{ + local cur prev words cword split + + COMPREPLY=() + + # Call _init_completion from the bash-completion package + # to prepare the arguments properly + if declare -F _init_completion >/dev/null 2>&1; then + _init_completion -n "=:" || return + else + __%[1]s_init_completion -n "=:" || return + fi + + __%[1]s_debug + __%[1]s_debug "========= starting completion logic ==========" + __%[1]s_debug "cur is ${cur}, words[*] is ${words[*]}, #words[@] is ${#words[@]}, cword is $cword" + + # The user could have moved the cursor backwards on the command-line. + # We need to trigger completion from the $cword location, so we need + # to truncate the command-line ($words) up to the $cword location. + words=("${words[@]:0:$cword+1}") + __%[1]s_debug "Truncated words[*]: ${words[*]}," + + local out directive + __%[1]s_get_completion_results + __%[1]s_process_completion_results +} + +if [[ $(type -t compopt) = "builtin" ]]; then + complete -o default -F __start_%[1]s %[1]s +else + complete -o default -o nospace -F __start_%[1]s %[1]s +fi + +# ex: ts=4 sw=4 et filetype=sh +`, name, compCmd, + ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp, + ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs)) +} + +// GenBashCompletionFileV2 generates Bash completion version 2. +func (c *Command) GenBashCompletionFileV2(filename string, includeDesc bool) error { + outFile, err := os.Create(filename) + if err != nil { + return err + } + defer outFile.Close() + + return c.GenBashCompletionV2(outFile, includeDesc) +} + +// GenBashCompletionV2 generates Bash completion file version 2 +// and writes it to the passed writer. +func (c *Command) GenBashCompletionV2(w io.Writer, includeDesc bool) error { + return c.genBashCompletion(w, includeDesc) +} diff --git a/command.go b/command.go index 5c85c899..2cc18891 100644 --- a/command.go +++ b/command.go @@ -63,9 +63,9 @@ type Command struct { // Example is examples of how to use the command. Example string - // ValidArgs is list of all valid non-flag arguments that are accepted in bash completions + // ValidArgs is list of all valid non-flag arguments that are accepted in shell completions ValidArgs []string - // ValidArgsFunction is an optional function that provides valid non-flag arguments for bash completion. + // ValidArgsFunction is an optional function that provides valid non-flag arguments for shell 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, ShellCompDirective) @@ -74,11 +74,12 @@ type Command struct { Args PositionalArgs // ArgAliases is List of aliases for ValidArgs. - // These are not suggested to the user in the bash completion, + // These are not suggested to the user in the shell completion, // but accepted if entered manually. ArgAliases []string - // BashCompletionFunction is custom functions used by the bash autocompletion generator. + // BashCompletionFunction is custom bash functions used by the legacy bash autocompletion generator. + // For portability with other shells, it is recommended to instead use ValidArgsFunction BashCompletionFunction string // Deprecated defines, if this command is deprecated and should print this string when used. @@ -938,7 +939,7 @@ func (c *Command) ExecuteC() (cmd *Command, err error) { args = os.Args[1:] } - // initialize the hidden command to be used for bash completion + // initialize the hidden command to be used for shell completion c.initCompleteCmd(args) var flags []string diff --git a/completions.go b/completions.go index 04dc71e4..5dfea616 100644 --- a/completions.go +++ b/completions.go @@ -34,7 +34,6 @@ const ( // 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 ShellCompDirectiveNoFileComp // ShellCompDirectiveFilterFileExt indicates that the provided completions @@ -592,9 +591,12 @@ You will need to start a new shell for this setup to take effect. DisableFlagsInUseLine: true, ValidArgsFunction: NoFileCompletions, RunE: func(cmd *Command, args []string) error { - return cmd.Root().GenBashCompletion(out) + return cmd.Root().GenBashCompletionV2(out, !noDesc) }, } + if haveNoDescFlag { + bash.Flags().BoolVar(&noDesc, compCmdNoDescFlagName, compCmdNoDescFlagDefault, compCmdNoDescFlagDesc) + } zsh := &Command{ Use: "zsh", diff --git a/completions_test.go b/completions_test.go index 9bd6a410..db363ae9 100644 --- a/completions_test.go +++ b/completions_test.go @@ -2097,9 +2097,9 @@ func TestDefaultCompletionCmd(t *testing.T) { removeCompCmd(rootCmd) var compCmd *Command - // Test that the --no-descriptions flag is present for the relevant shells only + // Test that the --no-descriptions flag is present on all shells assertNoErr(t, rootCmd.Execute()) - for _, shell := range []string{"fish", "powershell", "zsh"} { + for _, shell := range []string{"bash", "fish", "powershell", "zsh"} { if compCmd, _, err = rootCmd.Find([]string{compCmdName, shell}); err != nil { t.Errorf("Unexpected error: %v", err) } @@ -2107,14 +2107,6 @@ func TestDefaultCompletionCmd(t *testing.T) { t.Errorf("Missing --%s flag for %s shell", compCmdNoDescFlagName, shell) } } - for _, shell := range []string{"bash"} { - if compCmd, _, err = rootCmd.Find([]string{compCmdName, shell}); err != nil { - t.Errorf("Unexpected error: %v", err) - } - if flag := compCmd.Flags().Lookup(compCmdNoDescFlagName); flag != nil { - t.Errorf("Unexpected --%s flag for %s shell", compCmdNoDescFlagName, shell) - } - } // Remove completion command for the next test removeCompCmd(rootCmd) diff --git a/shell_completions.md b/shell_completions.md index 0b51cff9..b2b261d4 100644 --- a/shell_completions.md +++ b/shell_completions.md @@ -400,6 +400,36 @@ completion firstcommand secondcommand For backward compatibility, Cobra still supports its bash legacy dynamic completion solution. Please refer to [Bash Completions](bash_completions.md) for details. +### Bash completion V2 + +Cobra provides two versions for bash completion. The original bash completion (which started it all!) can be used by calling +`GenBashCompletion()` or `GenBashCompletionFile()`. + +A new V2 bash completion version is also available. This version can be used by calling `GenBashCompletionV2()` or +`GenBashCompletionFileV2()`. The V2 version does **not** support the legacy dynamic completion (see [Bash Completions] +(bash_completions.md)) but instead works only with the Go dynamic completion solution described in this Readme. +Unless your program already uses the legacy dynamic completion solution, it is recommended that you use the bash +completion V2 solution which provides the following extra features: +- Supports completion descriptions (like the other shells) +- Small completion script of less than 300 lines (v1 generates scripts of thousands of lines; `kubectl` for example has a bash v1 completion script of over 13K lines) +- Streamlined user experience thanks to a completion behavior aligned with the other shells + +`Bash` completion V2 supports descriptions for completions. When calling `GenBashCompletionV2()` or `GenBashCompletionFileV2()` +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][tab] +search (search for a keyword in charts) status (display the status of the named release) +show (show information of a chart) + +# Without descriptions +$ helm s[tab][tab] +search show status +``` +**Note**: Cobra's default `completion` command uses bash completion V2. If for some reason you need to use bash completion V1, you will need to implement your own `completion` command. ## Zsh completions Cobra supports native zsh completion generated from the root `cobra.Command`. From 2dea4f2ef38d7df919cdca44ac6a7039d30a6594 Mon Sep 17 00:00:00 2001 From: John McBride Date: Wed, 30 Jun 2021 15:45:46 -0600 Subject: [PATCH 186/211] Bump to viper 1.8.1 (#1433) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 460109e7..1fb9439d 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,6 @@ require ( github.com/cpuguy83/go-md2man/v2 v2.0.0 github.com/inconshreveable/mousetrap v1.0.0 github.com/spf13/pflag v1.0.5 - github.com/spf13/viper v1.8.0 + github.com/spf13/viper v1.8.1 gopkg.in/yaml.v2 v2.4.0 ) diff --git a/go.sum b/go.sum index 0230d574..3e22df29 100644 --- a/go.sum +++ b/go.sum @@ -224,8 +224,8 @@ github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmq github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= 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.8.0 h1:QRwDgoG8xX+kp69di68D+YYTCWfYEckbZRfUlEIAal0= -github.com/spf13/viper v1.8.0/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= +github.com/spf13/viper v1.8.1 h1:Kq1fyeebqsBfbjZj4EL7gj2IO0mMaiyjYUWcUsl2O44= +github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/stretchr/objx v0.1.0/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/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= From 3c8a19ecd384dc4229545bb174310a50c493f4ae Mon Sep 17 00:00:00 2001 From: silenceshell Date: Thu, 1 Jul 2021 05:49:30 +0800 Subject: [PATCH 187/211] fix RegisterFlagCompletionFunc concurrent map writes error (#1423) * fix-RegisterFlagCompletionFunc-concurrent * set to root command * move to non-public fields --- bash_completions.go | 2 +- command.go | 3 +++ completions.go | 14 ++++++++------ 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/bash_completions.go b/bash_completions.go index b47f898a..925e6e78 100644 --- a/bash_completions.go +++ b/bash_completions.go @@ -512,7 +512,7 @@ func writeLocalNonPersistentFlag(buf io.StringWriter, flag *pflag.Flag) { // Setup annotations for go completions for registered flags func prepareCustomAnnotationsForFlags(cmd *Command) { - for flag := range flagCompletionFunctions { + for flag := range cmd.Root().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 diff --git a/command.go b/command.go index 2cc18891..9f33a461 100644 --- a/command.go +++ b/command.go @@ -142,6 +142,9 @@ type Command struct { // that we can use on every pflag set and children commands globNormFunc func(f *flag.FlagSet, name string) flag.NormalizedName + //flagCompletionFunctions is map of flag completion functions. + flagCompletionFunctions map[*flag.Flag]func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) + // usageFunc is usage func defined by user. usageFunc func(*Command) error // usageTemplate is usage template defined by user. diff --git a/completions.go b/completions.go index 5dfea616..5f925ccf 100644 --- a/completions.go +++ b/completions.go @@ -17,9 +17,6 @@ const ( ShellCompNoDescRequestCmd = "__completeNoDesc" ) -// Global map of flag completion functions. -var flagCompletionFunctions = map[*pflag.Flag]func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective){} - // ShellCompDirective is a bit map representing the different behaviors the shell // can be instructed to have once completions have been provided. type ShellCompDirective int @@ -94,10 +91,15 @@ func (c *Command) RegisterFlagCompletionFunc(flagName string, f func(cmd *Comman if flag == nil { return fmt.Errorf("RegisterFlagCompletionFunc: flag '%s' does not exist", flagName) } - if _, exists := flagCompletionFunctions[flag]; exists { + + root := c.Root() + if _, exists := root.flagCompletionFunctions[flag]; exists { return fmt.Errorf("RegisterFlagCompletionFunc: flag '%s' already registered", flagName) } - flagCompletionFunctions[flag] = f + if root.flagCompletionFunctions == nil { + root.flagCompletionFunctions = map[*pflag.Flag]func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective){} + } + root.flagCompletionFunctions[flag] = f return nil } @@ -374,7 +376,7 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi // Find the completion function for the flag or command var completionFn func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) if flag != nil { - completionFn = flagCompletionFunctions[flag] + completionFn = c.Root().flagCompletionFunctions[flag] } else { completionFn = finalCmd.ValidArgsFunction } From 5d46ac904df31583e1c824a3f5d7198cfa1911c0 Mon Sep 17 00:00:00 2001 From: Paul Holzinger <45212748+Luap99@users.noreply.github.com> Date: Thu, 1 Jul 2021 17:47:10 +0200 Subject: [PATCH 188/211] custom comp: do not complete flags after args when interspersed is false (#1308) If the interspersed option is set false and one arg is already set all following arguments are counted as arg and not parsed as flags. Because of that we should not offer flag completion. The same applies to arguments followed after `--`. Signed-off-by: Paul Holzinger --- completions.go | 48 +++++++--- completions_test.go | 222 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 259 insertions(+), 11 deletions(-) diff --git a/completions.go b/completions.go index 5f925ccf..3c5f8632 100644 --- a/completions.go +++ b/completions.go @@ -21,6 +21,15 @@ const ( // can be instructed to have once completions have been provided. type ShellCompDirective int +type flagCompError struct { + subCommand string + flagName string +} + +func (e *flagCompError) Error() string { + return "Subcommand '" + e.subCommand + "' does not support flag '" + e.flagName + "'" +} + const ( // ShellCompDirectiveError indicates an error occurred and completions should be ignored. ShellCompDirectiveError ShellCompDirective = 1 << iota @@ -224,18 +233,35 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi // 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 - } + flag, finalArgs, toComplete, flagErr := checkIfFlagCompletion(finalCmd, finalArgs, toComplete) + + // Check if interspersed is false or -- was set on a previous arg. + // This works by counting the arguments. Normally -- is not counted as arg but + // if -- was already set or interspersed is false and there is already one arg then + // the extra added -- is counted as arg. + flagCompletion := true + _ = finalCmd.ParseFlags(append(finalArgs, "--")) + newArgCount := finalCmd.Flags().NArg() // 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 { + realArgCount := finalCmd.Flags().NArg() + if newArgCount > realArgCount { + // don't do flag completion (see above) + flagCompletion = false + } + // Error while attempting to parse flags + if flagErr != nil { + // If error type is flagCompError and we don't want flagCompletion we should ignore the error + if _, ok := flagErr.(*flagCompError); !(ok && !flagCompletion) { + return finalCmd, []string{}, ShellCompDirectiveDefault, flagErr + } + } + + if flag != nil && flagCompletion { // Check if we are completing a flag value subject to annotations if validExts, present := flag.Annotations[BashCompFilenameExt]; present { if len(validExts) != 0 { @@ -262,7 +288,7 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi // 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 name to be complete - if flag == nil && len(toComplete) > 0 && toComplete[0] == '-' && !strings.Contains(toComplete, "=") { + if flag == nil && len(toComplete) > 0 && toComplete[0] == '-' && !strings.Contains(toComplete, "=") && flagCompletion { var completions []string // First check for required flags @@ -375,7 +401,7 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi // Find the completion function for the flag or command var completionFn func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) - if flag != nil { + if flag != nil && flagCompletion { completionFn = c.Root().flagCompletionFunctions[flag] } else { completionFn = finalCmd.ValidArgsFunction @@ -459,6 +485,7 @@ func checkIfFlagCompletion(finalCmd *Command, args []string, lastArg string) (*p var flagName string trimmedArgs := args flagWithEqual := false + orgLastArg := 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 @@ -517,9 +544,8 @@ func checkIfFlagCompletion(finalCmd *Command, args []string, lastArg string) (*p 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 + // Flag not supported by this command, the interspersed option might be set so return the original args + return nil, args, orgLastArg, &flagCompError{subCommand: finalCmd.Name(), flagName: flagName} } if !flagWithEqual { diff --git a/completions_test.go b/completions_test.go index db363ae9..aea06a24 100644 --- a/completions_test.go +++ b/completions_test.go @@ -1749,6 +1749,228 @@ func TestValidArgsFuncChildCmdsWithDesc(t *testing.T) { } } +func TestFlagCompletionWithNotInterspersedArgs(t *testing.T) { + rootCmd := &Command{Use: "root", Run: emptyRun} + childCmd := &Command{ + Use: "child", + Run: emptyRun, + ValidArgsFunction: func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { + return []string{"--validarg", "test"}, ShellCompDirectiveDefault + }, + } + childCmd2 := &Command{ + Use: "child2", + Run: emptyRun, + ValidArgs: []string{"arg1", "arg2"}, + } + rootCmd.AddCommand(childCmd, childCmd2) + childCmd.Flags().Bool("bool", false, "test bool flag") + childCmd.Flags().String("string", "", "test string flag") + _ = childCmd.RegisterFlagCompletionFunc("string", func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { + return []string{"myval"}, ShellCompDirectiveDefault + }) + + // Test flag completion with no argument + output, err := executeCommand(rootCmd, ShellCompRequestCmd, "child", "--") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected := strings.Join([]string{ + "--bool\ttest bool flag", + "--string\ttest string flag", + ":4", + "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Test that no flags are completed after the -- arg + output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child", "--", "-") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + "--validarg", + "test", + ":0", + "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Test that no flags are completed after the -- arg with a flag set + output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child", "--bool", "--", "-") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + "--validarg", + "test", + ":0", + "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // set Interspersed to false which means that no flags should be completed after the first arg + childCmd.Flags().SetInterspersed(false) + + // Test that no flags are completed after the first arg + output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child", "arg", "--") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + "--validarg", + "test", + ":0", + "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Test that no flags are completed after the fist arg with a flag set + output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child", "--string", "t", "arg", "--") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + "--validarg", + "test", + ":0", + "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Check that args are still completed after -- + output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child", "--", "") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + "--validarg", + "test", + ":0", + "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Check that args are still completed even if flagname with ValidArgsFunction exists + output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child", "--", "--string", "") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + "--validarg", + "test", + ":0", + "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Check that args are still completed even if flagname with ValidArgsFunction exists + output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child2", "--", "a") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + "arg1", + "arg2", + ":4", + "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Check that --validarg is not parsed as flag after -- + output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child", "--", "--validarg", "") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + "--validarg", + "test", + ":0", + "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Check that --validarg is not parsed as flag after an arg + output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child", "arg", "--validarg", "") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + "--validarg", + "test", + ":0", + "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Check that --validarg is added to args for the ValidArgsFunction + childCmd.ValidArgsFunction = func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { + return args, ShellCompDirectiveDefault + } + output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child", "--", "--validarg", "") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + "--validarg", + ":0", + "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Check that --validarg is added to args for the ValidArgsFunction and toComplete is also set correctly + childCmd.ValidArgsFunction = func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { + return append(args, toComplete), ShellCompDirectiveDefault + } + output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child", "--", "--validarg", "--toComp=ab") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected = strings.Join([]string{ + "--validarg", + "--toComp=ab", + ":0", + "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } +} + func TestFlagCompletionInGoWithDesc(t *testing.T) { rootCmd := &Command{ Use: "root", From 5738d6b72d7323f2e6fbdb7a559ebeccdb93d27d Mon Sep 17 00:00:00 2001 From: Kyle Lemons Date: Thu, 1 Jul 2021 08:49:12 -0700 Subject: [PATCH 189/211] Add install instructions for zsh on Mac OS (#1417) zsh is now the default on Mac OS, but the $_fpath version of the installation instructions are likely to put the completion in a strange location that the user might not expect (e.g. an oh-my-zsh plugin's function directory). So, since Mac OS seems to (as far as I can tell) provide a stable location, this PR recommends using that path instead. --- completions.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/completions.go b/completions.go index 3c5f8632..4687674a 100644 --- a/completions.go +++ b/completions.go @@ -638,7 +638,10 @@ to enable it. You can execute the following once: $ echo "autoload -U compinit; compinit" >> ~/.zshrc To load completions for every new session, execute once: +# Linux: $ %[1]s completion zsh > "${fpath[1]}/_%[1]s" +# macOS: +$ %[1]s completion zsh > /usr/local/share/zsh/site-functions/_%[1]s You will need to start a new shell for this setup to take effect. `, c.Root().Name()), From 07861c800d0779867f821501b892e635660847d2 Mon Sep 17 00:00:00 2001 From: Marc Khouzam Date: Thu, 1 Jul 2021 11:49:30 -0400 Subject: [PATCH 190/211] Fix documentation (#1434) Signed-off-by: Marc Khouzam --- shell_completions.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/shell_completions.md b/shell_completions.md index b2b261d4..4ba06a11 100644 --- a/shell_completions.md +++ b/shell_completions.md @@ -352,7 +352,10 @@ cmd.RegisterFlagCompletionFunc(flagName, func(cmd *cobra.Command, args []string, ``` ### Descriptions for completions -`zsh`, `fish` and `powershell` 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: +Cobra provides support for completion descriptions. Such descriptions are supported for each shell +(however, for bash, it is only available in the [completion V2 version](#bash-completion-v2)). +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 @@ -365,7 +368,7 @@ $ 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: +Cobra allows you to add descriptions to your own completions. Simply add the description 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 @@ -406,8 +409,9 @@ Cobra provides two versions for bash completion. The original bash completion ( `GenBashCompletion()` or `GenBashCompletionFile()`. A new V2 bash completion version is also available. This version can be used by calling `GenBashCompletionV2()` or -`GenBashCompletionFileV2()`. The V2 version does **not** support the legacy dynamic completion (see [Bash Completions] -(bash_completions.md)) but instead works only with the Go dynamic completion solution described in this Readme. +`GenBashCompletionFileV2()`. The V2 version does **not** support the legacy dynamic completion +(see [Bash Completions](bash_completions.md)) but instead works only with the Go dynamic completion +solution described in this document. Unless your program already uses the legacy dynamic completion solution, it is recommended that you use the bash completion V2 solution which provides the following extra features: - Supports completion descriptions (like the other shells) From de187e874d1ca382320088f8f6d76333408e5c2e Mon Sep 17 00:00:00 2001 From: Paul Holzinger <45212748+Luap99@users.noreply.github.com> Date: Fri, 2 Jul 2021 17:25:47 +0200 Subject: [PATCH 191/211] Fix flag completion (#1438) * Fix flag completion The flag completion functions should not be stored in the root cmd. There is no requirement that the root cmd should be the same when `RegisterFlagCompletionFunc` was called. Storing the flags there does not work when you add the the flags to your cmd struct before you add the cmd to the parent/root cmd. The flags can no longer be found in the rigth place when the completion command is called and thus the flag completion does not work. Also #1423 claims that this would be thread safe but we still have a map which will fail when accessed concurrently. To truly fix this issue use a RWMutex. Fixes #1437 Fixes #1320 Signed-off-by: Paul Holzinger * Fix trailing whitespaces in fish comp scripts Signed-off-by: Paul Holzinger --- bash_completions.go | 4 +++- command.go | 3 --- completions.go | 21 ++++++++++++++------- completions_test.go | 35 +++++++++++++++++++++++++++++++++++ fish_completions.go | 4 ++-- 5 files changed, 54 insertions(+), 13 deletions(-) diff --git a/bash_completions.go b/bash_completions.go index 925e6e78..733f4d12 100644 --- a/bash_completions.go +++ b/bash_completions.go @@ -512,7 +512,9 @@ func writeLocalNonPersistentFlag(buf io.StringWriter, flag *pflag.Flag) { // Setup annotations for go completions for registered flags func prepareCustomAnnotationsForFlags(cmd *Command) { - for flag := range cmd.Root().flagCompletionFunctions { + flagCompletionMutex.RLock() + defer flagCompletionMutex.RUnlock() + 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 diff --git a/command.go b/command.go index 9f33a461..2cc18891 100644 --- a/command.go +++ b/command.go @@ -142,9 +142,6 @@ type Command struct { // that we can use on every pflag set and children commands globNormFunc func(f *flag.FlagSet, name string) flag.NormalizedName - //flagCompletionFunctions is map of flag completion functions. - flagCompletionFunctions map[*flag.Flag]func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) - // usageFunc is usage func defined by user. usageFunc func(*Command) error // usageTemplate is usage template defined by user. diff --git a/completions.go b/completions.go index 4687674a..b849b9c8 100644 --- a/completions.go +++ b/completions.go @@ -4,6 +4,7 @@ import ( "fmt" "os" "strings" + "sync" "github.com/spf13/pflag" ) @@ -17,6 +18,12 @@ const ( ShellCompNoDescRequestCmd = "__completeNoDesc" ) +// Global map of flag completion functions. Make sure to use flagCompletionMutex before you try to read and write from it. +var flagCompletionFunctions = map[*pflag.Flag]func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective){} + +// lock for reading and writing from flagCompletionFunctions +var flagCompletionMutex = &sync.RWMutex{} + // ShellCompDirective is a bit map representing the different behaviors the shell // can be instructed to have once completions have been provided. type ShellCompDirective int @@ -100,15 +107,13 @@ func (c *Command) RegisterFlagCompletionFunc(flagName string, f func(cmd *Comman if flag == nil { return fmt.Errorf("RegisterFlagCompletionFunc: flag '%s' does not exist", flagName) } + flagCompletionMutex.Lock() + defer flagCompletionMutex.Unlock() - root := c.Root() - if _, exists := root.flagCompletionFunctions[flag]; exists { + if _, exists := flagCompletionFunctions[flag]; exists { return fmt.Errorf("RegisterFlagCompletionFunc: flag '%s' already registered", flagName) } - if root.flagCompletionFunctions == nil { - root.flagCompletionFunctions = map[*pflag.Flag]func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective){} - } - root.flagCompletionFunctions[flag] = f + flagCompletionFunctions[flag] = f return nil } @@ -402,7 +407,9 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi // Find the completion function for the flag or command var completionFn func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) if flag != nil && flagCompletion { - completionFn = c.Root().flagCompletionFunctions[flag] + flagCompletionMutex.RLock() + completionFn = flagCompletionFunctions[flag] + flagCompletionMutex.RUnlock() } else { completionFn = finalCmd.ValidArgsFunction } diff --git a/completions_test.go b/completions_test.go index aea06a24..70c455af 100644 --- a/completions_test.go +++ b/completions_test.go @@ -1971,6 +1971,41 @@ func TestFlagCompletionWithNotInterspersedArgs(t *testing.T) { } } +func TestFlagCompletionWorksRootCommandAddedAfterFlags(t *testing.T) { + rootCmd := &Command{Use: "root", Run: emptyRun} + childCmd := &Command{ + Use: "child", + Run: emptyRun, + ValidArgsFunction: func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { + return []string{"--validarg", "test"}, ShellCompDirectiveDefault + }, + } + childCmd.Flags().Bool("bool", false, "test bool flag") + childCmd.Flags().String("string", "", "test string flag") + _ = childCmd.RegisterFlagCompletionFunc("string", func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { + return []string{"myval"}, ShellCompDirectiveDefault + }) + + // Important: This is a test for https://github.com/spf13/cobra/issues/1437 + // Only add the subcommand after RegisterFlagCompletionFunc was called, do not change this order! + rootCmd.AddCommand(childCmd) + + // Test that flag completion works for the subcmd + output, err := executeCommand(rootCmd, ShellCompRequestCmd, "child", "--string", "") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected := strings.Join([]string{ + "myval", + ":0", + "Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } +} + func TestFlagCompletionInGoWithDesc(t *testing.T) { rootCmd := &Command{ Use: "root", diff --git a/fish_completions.go b/fish_completions.go index b0db323a..bb57fd56 100644 --- a/fish_completions.go +++ b/fish_completions.go @@ -152,11 +152,11 @@ function __%[1]s_prepare_completions # We don't need descriptions anyway since there is only a single # real completion which the shell will expand immediately. set -l split (string split --max 1 \t $__%[1]s_comp_results[1]) - + # Fish won't add a space if the completion ends with any # of the following characters: @=/:., set -l lastChar (string sub -s -1 -- $split) - if not string match -r -q "[@=/:.,]" -- "$lastChar" + if not string match -r -q "[@=/:.,]" -- "$lastChar" # In other cases, 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" From 56060d19f88533a20f635116f64aff2065cf260a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Barroso?= Date: Mon, 2 Aug 2021 16:19:25 +0200 Subject: [PATCH 192/211] Add Meroxa CLI (#1377) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 👋, I'm reaching out in behalf of [Meroxa](https://meroxa.com/) as one of its developers. We've been using [Cobra](https://github.com/spf13/cobra) since our first [initial commit](meroxa/cli@a3301a0) back from January 2020, and we recently [launched our Platform](https://techcrunch.com/2021/04/13/meroxa-raises-15m-series-a-for-its-real-time-data-platform/) to the public. We continue considering using Cobra as our CLI framework and are [actively](https://github.com/meroxa/cli/releases) working on it, and it'd be really nice to be featured as one of the projects using Cobra in the wild. Thank you! --- projects_using_cobra.md | 1 + 1 file changed, 1 insertion(+) diff --git a/projects_using_cobra.md b/projects_using_cobra.md index d98a71e3..62bdbffc 100644 --- a/projects_using_cobra.md +++ b/projects_using_cobra.md @@ -21,6 +21,7 @@ - [Kubernetes](http://kubernetes.io/) - [Linkerd](https://linkerd.io/) - [Mattermost-server](https://github.com/mattermost/mattermost-server) +- [Meroxa CLI](https://github.com/meroxa/cli) - [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) From 2a5277810f24ae1b8a200198be8dd855a9041a0e Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 5 Aug 2021 19:08:25 +0200 Subject: [PATCH 193/211] go.mod: cpuguy83/go-md2man/v2 v2.0.1 (#1460) full diff: https://github.com/cpuguy83/go-md2man/compare/v2.0.0...v2.0.1 - Fix handling multiple definition descriptions - Fix inline markup causing table cells to split - Remove escaping tilde character (prevents tildes (`~`) from disappearing). - Do not escape dash, underscore, and ampersand (prevents ampersands (`&`) from disappearing). - Ignore unknown HTML tags to prevent noisy warnings Signed-off-by: Sebastiaan van Stijn --- doc/man_docs_test.go | 2 +- go.mod | 2 +- go.sum | 10 ++++------ 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/doc/man_docs_test.go b/doc/man_docs_test.go index aa3f5f2a..f3fe3d61 100644 --- a/doc/man_docs_test.go +++ b/doc/man_docs_test.go @@ -131,7 +131,7 @@ func TestGenManSeeAlso(t *testing.T) { if err := assertNextLineEquals(scanner, ".PP"); err != nil { t.Fatalf("First line after SEE ALSO wasn't break-indent: %v", err) } - if err := assertNextLineEquals(scanner, `\fBroot\-bbb(1)\fP, \fBroot\-ccc(1)\fP`); err != nil { + if err := assertNextLineEquals(scanner, `\fBroot-bbb(1)\fP, \fBroot-ccc(1)\fP`); err != nil { t.Fatalf("Second line after SEE ALSO wasn't correct: %v", err) } } diff --git a/go.mod b/go.mod index 1fb9439d..b4c870e8 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/spf13/cobra go 1.14 require ( - github.com/cpuguy83/go-md2man/v2 v2.0.0 + github.com/cpuguy83/go-md2man/v2 v2.0.1 github.com/inconshreveable/mousetrap v1.0.0 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.8.1 diff --git a/go.sum b/go.sum index 3e22df29..6e832371 100644 --- a/go.sum +++ b/go.sum @@ -55,8 +55,8 @@ github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnht github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -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/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU= +github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 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= @@ -206,12 +206,10 @@ github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndr github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= 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/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/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/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= From 4fd30b69ee2b62cf3bbecf0a423f8a1ee47f5f24 Mon Sep 17 00:00:00 2001 From: Unai Martinez-Corral <38422348+umarcor@users.noreply.github.com> Date: Thu, 26 Aug 2021 04:18:53 +0100 Subject: [PATCH 194/211] ci: test golang 1.16.x and 1.17.x too (#1425) * ci: test golang 1.16.x too * ci: style * ci: test golang 1.17.x too * bump go.mod to 1.15 * run gofmt --- .github/workflows/Test.yml | 22 ++++++++++++++-------- command_notwin.go | 1 + command_win.go | 1 + go.mod | 2 +- 4 files changed, 17 insertions(+), 9 deletions(-) diff --git a/.github/workflows/Test.yml b/.github/workflows/Test.yml index c34c2fbb..bc748030 100644 --- a/.github/workflows/Test.yml +++ b/.github/workflows/Test.yml @@ -18,29 +18,35 @@ jobs: - ubuntu - macOS go: - - 1.14.x - - 1.15.x - name: '${{ matrix.platform }} | ${{ matrix.go }}' + - 14 + - 15 + - 16 + - 17 + name: '${{ matrix.platform }} | 1.${{ matrix.go }}.x' runs-on: ${{ matrix.platform }}-latest steps: - uses: actions/setup-go@v2 with: - go-version: ${{ matrix.go }} + go-version: 1.${{ matrix.go }}.x - uses: actions/checkout@v2 - uses: actions/cache@v2 with: path: ~/go/pkg/mod - key: ${{ runner.os }}-${{ matrix.go }}-${{ hashFiles('**/go.sum') }} - restore-keys: ${{ runner.os }}-${{ matrix.go }}- + key: ${{ runner.os }}-1.${{ matrix.go }}.x-${{ hashFiles('**/go.sum') }} + restore-keys: ${{ runner.os }}-1.${{ matrix.go }}.x- - run: | export GOBIN=$HOME/go/bin curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | bash -s -- -b $GOBIN latest - go install github.com/kyoh86/richgo - go install github.com/mitchellh/gox + case "${{ matrix.go }}" in + 16|17) _version='@latest';; + *) _version='';; + esac + go install github.com/kyoh86/richgo"${_version}" + go install github.com/mitchellh/gox"${_version}" - run: PATH=$HOME/go/bin/:$PATH make diff --git a/command_notwin.go b/command_notwin.go index 6159c1cc..bb5dad90 100644 --- a/command_notwin.go +++ b/command_notwin.go @@ -1,3 +1,4 @@ +//go:build !windows // +build !windows package cobra diff --git a/command_win.go b/command_win.go index 8768b173..a84f5a82 100644 --- a/command_win.go +++ b/command_win.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows package cobra diff --git a/go.mod b/go.mod index b4c870e8..97eab3c9 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/spf13/cobra -go 1.14 +go 1.15 require ( github.com/cpuguy83/go-md2man/v2 v2.0.1 From c1973d31bfd78d5f5c5a487a1af6627c5101a020 Mon Sep 17 00:00:00 2001 From: John McBride Date: Mon, 4 Oct 2021 14:58:08 -0600 Subject: [PATCH 195/211] Update projects-using markdown with Tanzu (#1501) Signed-off-by: John McBride --- projects_using_cobra.md | 1 + 1 file changed, 1 insertion(+) diff --git a/projects_using_cobra.md b/projects_using_cobra.md index 62bdbffc..843ad9b8 100644 --- a/projects_using_cobra.md +++ b/projects_using_cobra.md @@ -36,4 +36,5 @@ - [Skaffold](https://skaffold.dev/) - [Tendermint](https://github.com/tendermint/tendermint) - [Twitch CLI](https://github.com/twitchdev/twitch-cli) +- [VMware Tanzu Community Edition](https://github.com/vmware-tanzu/community-edition) - [Werf](https://werf.io/) From d2c0cb310d527ce26e8a173aa0fbc901e5ce8780 Mon Sep 17 00:00:00 2001 From: Marc Khouzam Date: Mon, 1 Nov 2021 15:01:33 -0400 Subject: [PATCH 196/211] DisableFlagParsing must disable flag completion (#1161) When a command has set DisableFlagParsing=true, it means Cobra should not be handling flags for that command but should let the command handle them itself. In fact, Cobra normally won't have been told at all about flags for this command. Not knowing about the flags of the command also implies that Cobra cannot perform flag completion as it does not have the necessary info. This commit still tries to do flag name completion, but when DisableFlagParsing==true, it continues on so that ValidArgsFunction will be called; this allows the program to handle completion for its own flags when DisableFlagParsing==true. Signed-off-by: Marc Khouzam --- bash_completions.go | 14 ++++- completions.go | 134 ++++++++++++++++++++++++-------------------- completions_test.go | 61 ++++++++++++++++++++ 3 files changed, 146 insertions(+), 63 deletions(-) diff --git a/bash_completions.go b/bash_completions.go index 733f4d12..7411aba5 100644 --- a/bash_completions.go +++ b/bash_completions.go @@ -193,7 +193,13 @@ __%[1]s_handle_reply() fi fi fi - return 0; + + if [[ -z "${flag_parsing_disabled}" ]]; then + # If flag parsing is enabled, we have completed the flags and can return. + # If flag parsing is disabled, we may not know all (or any) of the flags, so we fallthrough + # to possibly call handle_go_custom_completion. + return 0; + fi ;; esac @@ -394,6 +400,7 @@ func writePostscript(buf io.StringWriter, name string) { fi local c=0 + local flag_parsing_disabled= local flags=() local two_word_flags=() local local_nonpersistent_flags=() @@ -535,6 +542,11 @@ func writeFlags(buf io.StringWriter, cmd *Command) { flags_completion=() `) + + if cmd.DisableFlagParsing { + WriteStringAndCheck(buf, " flag_parsing_disabled=1\n") + } + localNonPersistentFlags := cmd.LocalNonPersistentFlags() cmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) { if nonCompletableFlag(flag) { diff --git a/completions.go b/completions.go index b849b9c8..7aeccb38 100644 --- a/completions.go +++ b/completions.go @@ -266,6 +266,12 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi } } + // 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() + } + if flag != nil && flagCompletion { // Check if we are completing a flag value subject to annotations if validExts, present := flag.Annotations[BashCompFilenameExt]; present { @@ -290,12 +296,16 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi } } + var completions []string + var directive ShellCompDirective + + // Note that we want to perform flagname completion even if finalCmd.DisableFlagParsing==true; + // doing this allows for completion of persistant flag names even for commands that disable flag parsing. + // // 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 name to be complete if flag == nil && len(toComplete) > 0 && toComplete[0] == '-' && !strings.Contains(toComplete, "=") && flagCompletion { - var completions []string - // First check for required flags completions = completeRequireFlags(finalCmd, toComplete) @@ -322,86 +332,86 @@ func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDi }) } - directive := ShellCompDirectiveNoFileComp + 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 - } - // 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 { - foundLocalNonPersistentFlag := false - // If TraverseChildren is true on the root command we don't check for - // local flags because we can use a local flag on a parent command - if !finalCmd.Root().TraverseChildren { - // Check if there are any local, non-persistent flags on the command-line - localNonPersistentFlags := finalCmd.LocalNonPersistentFlags() - finalCmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) { - if localNonPersistentFlags.Lookup(flag.Name) != nil && flag.Changed { - foundLocalNonPersistentFlag = true - } - }) + if !finalCmd.DisableFlagParsing { + // If DisableFlagParsing==false, we have completed the flags as known by Cobra; + // we can return what we found. + // If DisableFlagParsing==true, Cobra may not be aware of all flags, so we + // let the logic continue to see if ValidArgsFunction needs to be called. + return finalCmd, completions, directive, nil } - - // 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-persistent flags on the command-line or TraverseChildren is true - 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)) + } else { + directive = ShellCompDirectiveDefault + if flag == nil { + foundLocalNonPersistentFlag := false + // If TraverseChildren is true on the root command we don't check for + // local flags because we can use a local flag on a parent command + if !finalCmd.Root().TraverseChildren { + // Check if there are any local, non-persistent flags on the command-line + 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-persistent flags on the command-line or TraverseChildren is true + 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 } - directive = ShellCompDirectiveNoFileComp } } - } - // Complete required flags even without the '-' prefix - completions = append(completions, completeRequireFlags(finalCmd, toComplete)...) + // 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) + // 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 + 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 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 } - // 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. } - - // 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 diff --git a/completions_test.go b/completions_test.go index 70c455af..ecbc723f 100644 --- a/completions_test.go +++ b/completions_test.go @@ -2543,3 +2543,64 @@ func TestMultipleShorthandFlagCompletion(t *testing.T) { t.Errorf("expected: %q, got: %q", expected, output) } } + +func TestCompleteWithDisableFlagParsing(t *testing.T) { + + flagValidArgs := func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) { + return []string{"--flag", "-f"}, ShellCompDirectiveNoFileComp + } + + rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun} + childCmd := &Command{ + Use: "child", + Run: emptyRun, + DisableFlagParsing: true, + ValidArgsFunction: flagValidArgs, + } + rootCmd.AddCommand(childCmd) + + rootCmd.PersistentFlags().StringP("persistent", "p", "", "persistent flag") + childCmd.Flags().StringP("nonPersistent", "n", "", "non-persistent flag") + + // Test that when DisableFlagParsing==true, ValidArgsFunction is called to complete flag names, + // after Cobra tried to complete the flags it knows about. + childCmd.DisableFlagParsing = true + output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "child", "-") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + expected := strings.Join([]string{ + "--persistent", + "-p", + "--nonPersistent", + "-n", + "--flag", + "-f", + ":4", + "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } + + // Test that when DisableFlagParsing==false, Cobra completes the flags itself and ValidArgsFunction is not called + childCmd.DisableFlagParsing = false + output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "child", "-") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + // Cobra was not told of any flags, so it returns nothing + expected = strings.Join([]string{ + "--persistent", + "-p", + "--nonPersistent", + "-n", + ":4", + "Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n") + + if output != expected { + t.Errorf("expected: %q, got: %q", expected, output) + } +} From f09e9476376ba2fa9b29ec321e2944f4bc14503c Mon Sep 17 00:00:00 2001 From: Unai Martinez-Corral <38422348+umarcor@users.noreply.github.com> Date: Tue, 2 Nov 2021 15:10:14 +0000 Subject: [PATCH 197/211] readme: remove ToC, since it's now shown by GitHub (#1429) --- README.md | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/README.md b/README.md index 074e3979..70f9b508 100644 --- a/README.md +++ b/README.md @@ -11,28 +11,6 @@ name a few. [This list](./projects_using_cobra.md) contains a more extensive lis [![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 - -- [Overview](#overview) -- [Concepts](#concepts) - * [Commands](#commands) - * [Flags](#flags) -- [Installing](#installing) -- [Usage](#usage) - * [Using the Cobra Generator](user_guide.md#using-the-cobra-generator) - * [Using the Cobra Library](user_guide.md#using-the-cobra-library) - * [Working with Flags](user_guide.md#working-with-flags) - * [Positional and Custom Arguments](user_guide.md#positional-and-custom-arguments) - * [Example](user_guide.md#example) - * [Help Command](user_guide.md#help-command) - * [Usage Message](user_guide.md#usage-message) - * [PreRun and PostRun Hooks](user_guide.md#prerun-and-postrun-hooks) - * [Suggestions when "unknown command" happens](user_guide.md#suggestions-when-unknown-command-happens) - * [Generating documentation for your command](user_guide.md#generating-documentation-for-your-command) - * [Generating shell completions](user_guide.md#generating-shell-completions) -- [Contributing](CONTRIBUTING.md) -- [License](#license) - # Overview Cobra is a library providing a simple interface to create powerful modern CLI From c0dd5cdef534498cc8a7f929c76f7eb819671526 Mon Sep 17 00:00:00 2001 From: Steve Francia Date: Thu, 1 Jul 2021 17:16:39 -0400 Subject: [PATCH 198/211] Removing unused imports when not using Viper --- cobra/tpl/main.go | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/cobra/tpl/main.go b/cobra/tpl/main.go index ed3a98e1..d2f77424 100644 --- a/cobra/tpl/main.go +++ b/cobra/tpl/main.go @@ -1,3 +1,16 @@ +// Copyright © 2021 Steve Francia . +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package tpl func MainTemplate() []byte { @@ -23,10 +36,12 @@ func RootTemplate() []byte { package cmd import ( +{{- if .Viper }} "fmt" "os" +{{ end }} "github.com/spf13/cobra" -{{ if .Viper }} +{{- if .Viper }} "github.com/spf13/viper"{{ end }} ) From dcf42b25f7f1980135e744f099ecbbc6ec85eadc Mon Sep 17 00:00:00 2001 From: Steve Francia Date: Thu, 1 Jul 2021 17:40:39 -0400 Subject: [PATCH 199/211] Change generator to require opting in to viper. Cobra and Viper are great together, but it's not uncommon to use them apart. New Cobra users don't know better and including Viper by default adds complexity to the skeleton. --- cobra/cmd/add_test.go | 3 +++ cobra/cmd/root.go | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/cobra/cmd/add_test.go b/cobra/cmd/add_test.go index 0b32ca67..c9b185b5 100644 --- a/cobra/cmd/add_test.go +++ b/cobra/cmd/add_test.go @@ -4,9 +4,12 @@ import ( "fmt" "os" "testing" + + "github.com/spf13/viper" ) func TestGoldenAddCmd(t *testing.T) { + viper.Set("useViper", true) command := &Command{ CmdName: "test", CmdParent: parentName, diff --git a/cobra/cmd/root.go b/cobra/cmd/root.go index 350df103..ae961bea 100644 --- a/cobra/cmd/root.go +++ b/cobra/cmd/root.go @@ -1,4 +1,4 @@ -// Copyright © 2015 Steve Francia . +// Copyright © 2021 Steve Francia . // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -46,7 +46,7 @@ func init() { 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") + rootCmd.PersistentFlags().Bool("viper", false, "use Viper for configuration") cobra.CheckErr(viper.BindPFlag("author", rootCmd.PersistentFlags().Lookup("author"))) cobra.CheckErr(viper.BindPFlag("useViper", rootCmd.PersistentFlags().Lookup("viper"))) viper.SetDefault("author", "NAME HERE ") From c9edb78accc109d0fa42d449417815fc83db979d Mon Sep 17 00:00:00 2001 From: Steve Francia Date: Thu, 1 Jul 2021 17:45:46 -0400 Subject: [PATCH 200/211] Change generator default license to none It's questionable that a default license makes any sense from a legal perspective. If the tool created the license without the user choosing it, then it may not even be applicable. Best to let the user choose their license with intent. --- cobra/cmd/add_test.go | 1 + cobra/cmd/init_test.go | 1 + cobra/cmd/licenses.go | 8 ++++---- cobra/cmd/root.go | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/cobra/cmd/add_test.go b/cobra/cmd/add_test.go index c9b185b5..20bc34f2 100644 --- a/cobra/cmd/add_test.go +++ b/cobra/cmd/add_test.go @@ -10,6 +10,7 @@ import ( func TestGoldenAddCmd(t *testing.T) { viper.Set("useViper", true) + viper.Set("license", "apache") command := &Command{ CmdName: "test", CmdParent: parentName, diff --git a/cobra/cmd/init_test.go b/cobra/cmd/init_test.go index e364f409..ea80250c 100644 --- a/cobra/cmd/init_test.go +++ b/cobra/cmd/init_test.go @@ -61,6 +61,7 @@ func TestGoldenInitCmd(t *testing.T) { assertNoErr(t, initCmd.Flags().Set("pkg-name", tt.pkgName)) viper.Set("useViper", true) + viper.Set("license", "apache") projectPath, err := initializeProject(tt.args) defer func() { if projectPath != "" { diff --git a/cobra/cmd/licenses.go b/cobra/cmd/licenses.go index 2b3a4243..30c7b242 100644 --- a/cobra/cmd/licenses.go +++ b/cobra/cmd/licenses.go @@ -1,4 +1,4 @@ -// Copyright © 2015 Steve Francia . +// Copyright © 2021 Steve Francia . // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -53,7 +53,7 @@ func init() { } // getLicense returns license specified by user in flag or in config. -// If user didn't specify the license, it returns Apache License 2.0. +// If user didn't specify the license, it returns none // // TODO: Inspect project for existing license func getLicense() License { @@ -73,8 +73,8 @@ func getLicense() License { return findLicense(viper.GetString("license")) } - // If user didn't set any license, use Apache 2.0 by default. - return Licenses["apache"] + // If user didn't set any license, use none by default + return Licenses["none"] } func copyrightLine() string { diff --git a/cobra/cmd/root.go b/cobra/cmd/root.go index ae961bea..51bba2cc 100644 --- a/cobra/cmd/root.go +++ b/cobra/cmd/root.go @@ -50,7 +50,7 @@ func init() { cobra.CheckErr(viper.BindPFlag("author", rootCmd.PersistentFlags().Lookup("author"))) cobra.CheckErr(viper.BindPFlag("useViper", rootCmd.PersistentFlags().Lookup("viper"))) viper.SetDefault("author", "NAME HERE ") - viper.SetDefault("license", "apache") + viper.SetDefault("license", "none") rootCmd.AddCommand(addCmd) rootCmd.AddCommand(initCmd) From 9388e79fb48d6af993c8d4fb90a7de9c7533725b Mon Sep 17 00:00:00 2001 From: Steve Francia Date: Thu, 1 Jul 2021 17:47:45 -0400 Subject: [PATCH 201/211] Cobra generator now works within Go modules Pretty major change in behavior, but with modules a change is needed. Now cobra can initialize and add from within any Go module. The experience is simplified and streamlined, but requires `go mod init` to happen first. --- cobra/cmd/init.go | 78 +++++++++++++++++++++++++------ cobra/cmd/init_test.go | 19 ++------ cobra/cmd/testdata/main.go.golden | 2 +- cobra/cmd/testdata/root.go.golden | 10 ++-- 4 files changed, 73 insertions(+), 36 deletions(-) diff --git a/cobra/cmd/init.go b/cobra/cmd/init.go index 8c0e617a..6e4c4f17 100644 --- a/cobra/cmd/init.go +++ b/cobra/cmd/init.go @@ -1,4 +1,4 @@ -// Copyright © 2015 Steve Francia . +// Copyright © 2021 Steve Francia . // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -14,42 +14,41 @@ package cmd import ( + "encoding/json" "fmt" "os" + "os/exec" "path" + "path/filepath" + "strings" "github.com/spf13/cobra" "github.com/spf13/viper" ) var ( - pkgName string - initCmd = &cobra.Command{ - Use: "init [name]", + Use: "init [path]", Aliases: []string{"initialize", "initialise", "create"}, Short: "Initialize a Cobra Application", 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, a directory with that name will be created in the current directory; - * If no name is provided, the current directory will be assumed; +Cobra init must be run inside of a go module (please run "go mod init " first) `, Run: func(_ *cobra.Command, args []string) { - projectPath, err := initializeProject(args) cobra.CheckErr(err) + cobra.CheckErr(goGet("github.com/spf13/cobra")) + if viper.GetBool("useViper") { + cobra.CheckErr(goGet("github.com/spf13/viper")) + } fmt.Printf("Your Cobra application is ready at\n%s\n", projectPath) }, } ) -func init() { - initCmd.Flags().StringVar(&pkgName, "pkg-name", "", "fully qualified pkg name") - cobra.CheckErr(initCmd.MarkFlagRequired("pkg-name")) -} - func initializeProject(args []string) (string, error) { wd, err := os.Getwd() if err != nil { @@ -62,13 +61,15 @@ func initializeProject(args []string) (string, error) { } } + modName := getModImportPath() + project := &Project{ AbsolutePath: wd, - PkgName: pkgName, + PkgName: modName, Legal: getLicense(), Copyright: copyrightLine(), Viper: viper.GetBool("useViper"), - AppName: path.Base(pkgName), + AppName: path.Base(modName), } if err := project.Create(); err != nil { @@ -77,3 +78,52 @@ func initializeProject(args []string) (string, error) { return project.AbsolutePath, nil } + +func getModImportPath() string { + mod, cd := parseModInfo() + return path.Join(mod.Path, fileToURL(strings.TrimPrefix(cd.Dir, mod.Dir))) +} + +func fileToURL(in string) string { + i := strings.Split(in, string(filepath.Separator)) + return path.Join(i...) +} + +func parseModInfo() (Mod, CurDir) { + var mod Mod + var dir CurDir + + m := modInfoJSON("-m") + cobra.CheckErr(json.Unmarshal(m, &mod)) + + // Unsure why, but if no module is present Path is set to this string. + if mod.Path == "command-line-arguments" { + cobra.CheckErr("Please run `go mod init ` before `cobra init`") + } + + e := modInfoJSON("-e") + cobra.CheckErr(json.Unmarshal(e, &dir)) + + return mod, dir +} + +type Mod struct { + Path, Dir, GoMod string +} + +type CurDir struct { + Dir string +} + +func goGet(mod string) error { + cmd := exec.Command("go", "get", mod) + return cmd.Run() +} + +func modInfoJSON(args ...string) []byte { + cmdArgs := append([]string{"list", "-json"}, args...) + out, err := exec.Command("go", cmdArgs...).Output() + cobra.CheckErr(err) + + return out +} diff --git a/cobra/cmd/init_test.go b/cobra/cmd/init_test.go index ea80250c..930313c5 100644 --- a/cobra/cmd/init_test.go +++ b/cobra/cmd/init_test.go @@ -16,8 +16,8 @@ func getProject() *Project { AbsolutePath: fmt.Sprintf("%s/testproject", wd), Legal: getLicense(), Copyright: copyrightLine(), - AppName: "testproject", - PkgName: "github.com/spf13/testproject", + AppName: "cmd", + PkgName: "github.com/spf13/cobra/cobra/cmd/cmd", Viper: true, } } @@ -37,29 +37,16 @@ func TestGoldenInitCmd(t *testing.T) { expectErr bool }{ { - name: "successfully creates a project with name", + name: "successfully creates a project based on module", 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 a 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) { - assertNoErr(t, initCmd.Flags().Set("pkg-name", tt.pkgName)) viper.Set("useViper", true) viper.Set("license", "apache") projectPath, err := initializeProject(tt.args) diff --git a/cobra/cmd/testdata/main.go.golden b/cobra/cmd/testdata/main.go.golden index f69d34aa..0af77e17 100644 --- a/cobra/cmd/testdata/main.go.golden +++ b/cobra/cmd/testdata/main.go.golden @@ -15,7 +15,7 @@ limitations under the License. */ package main -import "github.com/spf13/testproject/cmd" +import "github.com/spf13/cobra/cobra/cmd/cmd" func main() { cmd.Execute() diff --git a/cobra/cmd/testdata/root.go.golden b/cobra/cmd/testdata/root.go.golden index 32c1529e..2adeaea5 100644 --- a/cobra/cmd/testdata/root.go.golden +++ b/cobra/cmd/testdata/root.go.golden @@ -18,8 +18,8 @@ package cmd import ( "fmt" "os" - "github.com/spf13/cobra" + "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -27,7 +27,7 @@ var cfgFile string // rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ - Use: "testproject", + Use: "cmd", 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: @@ -53,7 +53,7 @@ func init() { // 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/.cmd.yaml)") // Cobra also supports local flags, which will only run // when this action is called directly. @@ -70,10 +70,10 @@ func initConfig() { home, err := os.UserHomeDir() cobra.CheckErr(err) - // Search config in home directory with name ".testproject" (without extension). + // Search config in home directory with name ".cmd" (without extension). viper.AddConfigPath(home) viper.SetConfigType("yaml") - viper.SetConfigName(".testproject") + viper.SetConfigName(".cmd") } viper.AutomaticEnv() // read in environment variables that match From c97b7ece0b14323cbbb8edb6e20a383c71cbef3d Mon Sep 17 00:00:00 2001 From: Steve Francia Date: Thu, 1 Jul 2021 18:22:36 -0400 Subject: [PATCH 202/211] Update documentation to reflect the module aware generator --- README.md | 4 ++-- user_guide.md | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 63 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 70f9b508..9d942c7c 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Cobra provides: * Fully POSIX-compliant flags (including short & long versions) * Nested subcommands * Global, local and cascading flags -* Easy generation of applications & commands with `cobra init appname` & `cobra add cmdname` +* Easy generation of applications & commands with `cobra init` & `cobra add cmdname` * Intelligent suggestions (`app srver`... did you mean `app server`?) * Automatic help generation for commands and flags * Automatic help flag recognition of `-h`, `--help`, etc. @@ -32,7 +32,7 @@ Cobra provides: * 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. -* Optional tight integration with [viper](http://github.com/spf13/viper) for 12-factor apps +* Optional seamless integration with [viper](http://github.com/spf13/viper) for 12-factor apps # Concepts diff --git a/user_guide.md b/user_guide.md index 311abce2..ad478890 100644 --- a/user_guide.md +++ b/user_guide.md @@ -32,7 +32,67 @@ func main() { Cobra provides its own program that will create your application and add any commands you want. It's the easiest way to incorporate Cobra into your application. -[Here](https://github.com/spf13/cobra/blob/master/cobra/README.md) you can find more information about it. +Install the cobra generator with the command `go install github.com/spf13/cobra/cobra`. +Go will automatically install it in your $GOPATH/bin directory which should be in your $PATH. + +Once installed you should have the `cobra` command available. Confirm by typing `cobra` at a +command line. + +There are only two operations currently supported by Cobra generator. + +### 1. Initializing a new project + +The Cobra generator works from within a Go module. + +If you haven't yet setup your project as a Go module: + + 1. Create a new directory + 2. `cd` into that directory + 3. run `go mod init ` + +From within a Go module run `cobra init`. This will create a new barebones project +for you to edit. + +You should be able to run you new application immediately. Try it with +`go run main.go`. + +You will want to open up and edit 'cmd/root.go' and provide your own description and logic. + +#### Optional flags: +You can provide it your author name with the `--author` flag. +e.g. `cobra init --author "Steve Francia spf@spf13.com"` + +You can provide a license to use with `--license` +e.g. `cobra init --license apache` + +Use the `--viper` flag to automatically setup [viper](https://github.com/spf13/viper) + +Viper is a companion to Cobra intended to provide easy handling of environment variables and config files +and seamlessly connecting them to the application flags. + +### 2. Add a command to a project + +Once a cobra application is initialized you can continue to use cobra generator to +add additional commands to your application. The command to do this is `cobra add`. + +As an example, if I was designing a todo application I would want to have my base `todo` command list the items. +I would then add additional commands to display, create, mark complete and delete items. + +To add a command to an existing application, make sure you are in the directory with the main.go file and run: +`cobra add `. + +#### Optional flags: + +`cobra add` supports all the same optional flags as `cobra init` does. + +Additionally you can provide a parent command for your new command. This defaults to rootCmd if not provided. +If you want to place your command under a different command, just provide the name of the command. + +A todo is a bit too simple to really need a sub sub command. So let's use git as an example. + +If I wanted to create a new git stash command I would do the following: +`cobra add stash` +`cobra add pop --parent=stash` ## Using the Cobra Library From cf87fc4e3064921646ceb395e86f7517e41ffd36 Mon Sep 17 00:00:00 2001 From: Steve Francia Date: Fri, 2 Jul 2021 10:58:34 -0400 Subject: [PATCH 203/211] Updating generator documentation and links Merging the updated documentation from the user_guide into the cobra/README.md. Adding links as appropriate to both guides. --- README.md | 8 +++-- cobra/README.md | 88 ++++++++++++++++++++++++++++++++++++++----------- user_guide.md | 62 +--------------------------------- 3 files changed, 76 insertions(+), 82 deletions(-) diff --git a/README.md b/README.md index 9d942c7c..1ade1081 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ have children commands and optionally run an action. In the example above, 'server' is the command. -[More about cobra.Command](https://godoc.org/github.com/spf13/cobra#Command) +[More about cobra.Command](https://pkg.go.dev/github.com/spf13/cobra#Command) ## Flags @@ -95,8 +95,12 @@ import "github.com/spf13/cobra" ``` # Usage +Cobra provides its own program that will create your application and add any +commands you want. It's the easiest way to incorporate Cobra into your application. -See [User Guide](user_guide.md). +For complete details on using the Cobra generator, please read [The Cobra Generator README](https://github.com/spf13/cobra/blob/master/cobra/README.md) + +For complete details on using the Cobra library, please read the [The Cobra User Guide](user_guide.md). # License diff --git a/cobra/README.md b/cobra/README.md index dcaf0c5f..eed09c11 100644 --- a/cobra/README.md +++ b/cobra/README.md @@ -3,41 +3,78 @@ Cobra provides its own program that will create your application and add any commands you want. It's the easiest way to incorporate Cobra into your application. -In order to use the cobra command, compile it using the following command: +Install the cobra generator with the command `go install github.com/spf13/cobra/cobra`. +Go will automatically install it in your `$GOPATH/bin` directory which should be in your $PATH. - go get github.com/spf13/cobra/cobra +Once installed you should have the `cobra` command available. Confirm by typing `cobra` at a +command line. -This will create the cobra executable under your `$GOPATH/bin` directory. +There are only two operations currently supported by Cobra generator: ### cobra init The `cobra init [app]` command will create your initial application code for you. It is a very powerful application that will populate your program with -the right structure so you can immediately enjoy all the benefits of Cobra. It -will also automatically apply the license you specify to your application. +the right structure so you can immediately enjoy all the benefits of Cobra. +It can also apply the license you specify to your application. -Cobra init is pretty smart. You can either run it in your current application directory -or you can specify a relative path to an existing project. If the directory does not exist, it will be created for you. +With the introduction of Go modules, the Cobra generator has been simplified to +take advantage of modules. The Cobra generator works from within a Go module. -Updates to the Cobra generator have now decoupled it from the GOPATH. -As such `--pkg-name` is required. +#### Initalizing a module -**Note:** init will no longer fail on non-empty directories. +__If you already have a module, skip this step.__ +If you want to initialize a new Go module: + + 1. Create a new directory + 2. `cd` into that directory + 3. run `go mod init ` + +e.g. ``` -mkdir -p newApp && cd newApp -cobra init --pkg-name github.com/spf13/newApp +cd $HOME/code +mkdir myapp +cd myapp +go mod init github.com/spf13/myapp ``` -or +#### Initalizing an Cobra CLI application +From within a Go module run `cobra init`. This will create a new barebones project +for you to edit. + +You should be able to run you new application immediately. Try it with +`go run main.go`. + +You will want to open up and edit 'cmd/root.go' and provide your own description and logic. + +e.g. ``` -cobra init --pkg-name github.com/spf13/newApp path/to/newApp +cd $HOME/code/myapp +cobra init +go run main.go ``` -### cobra add +Cobra init can also be run from a subdirectory such as how the [cobra generator itself is organized](https://github.com/spf13/cobra). +This is useful if you want to keep your application code separate from your library code. + +#### Optional flags: +You can provide it your author name with the `--author` flag. +e.g. `cobra init --author "Steve Francia spf@spf13.com"` + +You can provide a license to use with `--license` +e.g. `cobra init --license apache` + +Use the `--viper` flag to automatically setup [viper](https://github.com/spf13/viper) + +Viper is a companion to Cobra intended to provide easy handling of environment variables and config files and seamlessly connecting them to the application flags. + +### Add commands to a project + +Once a cobra application is initialized you can continue to use cobra generator to +add additional commands to your application. The command to do this is `cobra add`. -Once an application is initialized, Cobra can create additional commands for you. Let's say you created an app and you wanted the following commands for it: * app serve @@ -52,6 +89,14 @@ cobra add config cobra add create -p 'configCmd' ``` +`cobra add` supports all the same optional flags as `cobra init` does mentioned above. + +You'll notice that this final command has a `-p` flag. This is used to assign a +parent command to the newly added command. In this case, we want to assign the +"create" command to the "config" command. All commands have a default parent of rootCmd if not specified. + +By default `cobra` will append `Cmd` to the name provided and uses this to name for the internal variable name. When specifying a parent, be sure to match the variable name used in the code. + *Note: Use camelCase (not snake_case/kebab-case) for command names. Otherwise, you will encounter errors. For example, `cobra add add-user` is incorrect, but `cobra add addUser` is valid.* @@ -62,9 +107,10 @@ the following: ``` ▾ app/ ▾ cmd/ - serve.go config.go create.go + serve.go + root.go main.go ``` @@ -72,8 +118,11 @@ At this point you can run `go run main.go` and it would run your app. `go run main.go serve`, `go run main.go config`, `go run main.go config create` along with `go run main.go help serve`, etc. would all work. -Obviously you haven't added your own code to these yet. The commands are ready -for you to give them their tasks. Have fun! +You now have a basic Cobra-based application up and running. Next step is to edit the files in cmd and customize them for your application. + +For complete details on using the Cobra library, please read the [The Cobra User Guide](https://github.com/spf13/cobra/blob/master/user_guide.md#using-the-cobra-library). + +Have fun! ### Configuring the cobra generator @@ -86,6 +135,7 @@ An example ~/.cobra.yaml file: ```yaml author: Steve Francia license: MIT +viper: true ``` You can also use built-in licenses. For example, **GPLv2**, **GPLv3**, **LGPL**, diff --git a/user_guide.md b/user_guide.md index ad478890..923392b7 100644 --- a/user_guide.md +++ b/user_guide.md @@ -32,67 +32,7 @@ func main() { Cobra provides its own program that will create your application and add any commands you want. It's the easiest way to incorporate Cobra into your application. -Install the cobra generator with the command `go install github.com/spf13/cobra/cobra`. -Go will automatically install it in your $GOPATH/bin directory which should be in your $PATH. - -Once installed you should have the `cobra` command available. Confirm by typing `cobra` at a -command line. - -There are only two operations currently supported by Cobra generator. - -### 1. Initializing a new project - -The Cobra generator works from within a Go module. - -If you haven't yet setup your project as a Go module: - - 1. Create a new directory - 2. `cd` into that directory - 3. run `go mod init ` - -From within a Go module run `cobra init`. This will create a new barebones project -for you to edit. - -You should be able to run you new application immediately. Try it with -`go run main.go`. - -You will want to open up and edit 'cmd/root.go' and provide your own description and logic. - -#### Optional flags: -You can provide it your author name with the `--author` flag. -e.g. `cobra init --author "Steve Francia spf@spf13.com"` - -You can provide a license to use with `--license` -e.g. `cobra init --license apache` - -Use the `--viper` flag to automatically setup [viper](https://github.com/spf13/viper) - -Viper is a companion to Cobra intended to provide easy handling of environment variables and config files -and seamlessly connecting them to the application flags. - -### 2. Add a command to a project - -Once a cobra application is initialized you can continue to use cobra generator to -add additional commands to your application. The command to do this is `cobra add`. - -As an example, if I was designing a todo application I would want to have my base `todo` command list the items. -I would then add additional commands to display, create, mark complete and delete items. - -To add a command to an existing application, make sure you are in the directory with the main.go file and run: -`cobra add `. - -#### Optional flags: - -`cobra add` supports all the same optional flags as `cobra init` does. - -Additionally you can provide a parent command for your new command. This defaults to rootCmd if not provided. -If you want to place your command under a different command, just provide the name of the command. - -A todo is a bit too simple to really need a sub sub command. So let's use git as an example. - -If I wanted to create a new git stash command I would do the following: -`cobra add stash` -`cobra add pop --parent=stash` +For complete details on using the Cobra generator, please read [The Cobra Generator README](https://github.com/spf13/cobra/blob/master/cobra/README.md) ## Using the Cobra Library From 26825627c284b4f53d5e7ef0b3358a83a80bd95c Mon Sep 17 00:00:00 2001 From: Steve Francia Date: Fri, 2 Jul 2021 11:01:58 -0400 Subject: [PATCH 204/211] Simplifying goGet function --- cobra/cmd/init.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cobra/cmd/init.go b/cobra/cmd/init.go index 6e4c4f17..dbdc25bb 100644 --- a/cobra/cmd/init.go +++ b/cobra/cmd/init.go @@ -116,8 +116,7 @@ type CurDir struct { } func goGet(mod string) error { - cmd := exec.Command("go", "get", mod) - return cmd.Run() + return exec.Command("go", "get", mod).Run() } func modInfoJSON(args ...string) []byte { From bfacc59f62c67ffd43e93655a8d933cefab0fa99 Mon Sep 17 00:00:00 2001 From: Steve Francia Date: Tue, 2 Nov 2021 15:36:17 -0400 Subject: [PATCH 205/211] Addressing typos identified by @marckhouzam --- cobra/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cobra/README.md b/cobra/README.md index eed09c11..f1a60565 100644 --- a/cobra/README.md +++ b/cobra/README.md @@ -9,7 +9,7 @@ Go will automatically install it in your `$GOPATH/bin` directory which should be Once installed you should have the `cobra` command available. Confirm by typing `cobra` at a command line. -There are only two operations currently supported by Cobra generator: +There are only two operations currently supported by the Cobra generator: ### cobra init @@ -44,7 +44,7 @@ go mod init github.com/spf13/myapp From within a Go module run `cobra init`. This will create a new barebones project for you to edit. -You should be able to run you new application immediately. Try it with +You should be able to run your new application immediately. Try it with `go run main.go`. You will want to open up and edit 'cmd/root.go' and provide your own description and logic. @@ -72,7 +72,7 @@ Viper is a companion to Cobra intended to provide easy handling of environment v ### Add commands to a project -Once a cobra application is initialized you can continue to use cobra generator to +Once a cobra application is initialized you can continue to use the Cobra generator to add additional commands to your application. The command to do this is `cobra add`. Let's say you created an app and you wanted the following commands for it: @@ -89,13 +89,13 @@ cobra add config cobra add create -p 'configCmd' ``` -`cobra add` supports all the same optional flags as `cobra init` does mentioned above. +`cobra add` supports all the same optional flags as `cobra init` does (described above). You'll notice that this final command has a `-p` flag. This is used to assign a parent command to the newly added command. In this case, we want to assign the "create" command to the "config" command. All commands have a default parent of rootCmd if not specified. -By default `cobra` will append `Cmd` to the name provided and uses this to name for the internal variable name. When specifying a parent, be sure to match the variable name used in the code. +By default `cobra` will append `Cmd` to the name provided and uses this name for the internal variable name. When specifying a parent, be sure to match the variable name used in the code. *Note: Use camelCase (not snake_case/kebab-case) for command names. Otherwise, you will encounter errors. From 3ba5f15ba7acbab0d090411fb631b82b34769b95 Mon Sep 17 00:00:00 2001 From: Unai Martinez-Corral <38422348+umarcor@users.noreply.github.com> Date: Mon, 15 Nov 2021 20:21:07 +0000 Subject: [PATCH 206/211] Projects using cobra update (#1454) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [projects_using_cobra] add moldy * [projects_using_cobra] add UpCloud CLI (upctl) * [projects_using_cobra] add scaleway-cli * [projects_using_cobra] add qrcp * [projects_using_cobra] add multi-gitter * [projects_using_cobra] add Mercure * [projects_using_cobra] add goreleaser and nfpm * [projects_using_cobra] add Datree * [projects_using_cobra] add Infracost * [projects_using_cobra] add VMware Tanzu Framework Co-authored-by: TeoDev1611 Co-authored-by: Ville Skyttä Co-authored-by: Rémy Léone Co-authored-by: Claudio d'Angelis Co-authored-by: Johan Lindell Co-authored-by: Kévin Dunglas Co-authored-by: Carlos Alexandro Becker Co-authored-by: Yishay Mendelsohn Co-authored-by: Vadim Golub Co-authored-by: Max Brauer --- projects_using_cobra.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/projects_using_cobra.md b/projects_using_cobra.md index 843ad9b8..8410c993 100644 --- a/projects_using_cobra.md +++ b/projects_using_cobra.md @@ -4,6 +4,7 @@ - [Bleve](http://www.blevesearch.com/) - [CockroachDB](http://www.cockroachlabs.com/) - [Cosmos SDK](https://github.com/cosmos/cosmos-sdk) +- [Datree](https://github.com/datreeio/datree) - [Delve](https://github.com/derekparker/delve) - [Docker (distribution)](https://github.com/docker/distribution) - [Etcd](https://etcd.io/) @@ -14,27 +15,36 @@ - [GitHub Labeler](https://github.com/erdaltsksn/gh-label) - [Golangci-lint](https://golangci-lint.run) - [GopherJS](http://www.gopherjs.org/) +- [GoReleaser](https://goreleaser.com) - [Helm](https://helm.sh) - [Hugo](https://gohugo.io) +- [Infracost](https://github.com/infracost/infracost) - [Istio](https://istio.io) - [Kool](https://github.com/kool-dev/kool) - [Kubernetes](http://kubernetes.io/) - [Linkerd](https://linkerd.io/) - [Mattermost-server](https://github.com/mattermost/mattermost-server) +- [Mercure](https://mercure.rocks/) - [Meroxa CLI](https://github.com/meroxa/cli) - [Metal Stack CLI](https://github.com/metal-stack/metalctl) - [Moby (former Docker)](https://github.com/moby/moby) +- [Moldy](https://github.com/Moldy-Community/moldy) +- [Multi-gitter](https://github.com/lindell/multi-gitter) - [Nanobox](https://github.com/nanobox-io/nanobox)/[Nanopack](https://github.com/nanopack) +- [nFPM](https://nfpm.goreleaser.com) - [OpenShift](https://www.openshift.com/) - [Ory Hydra](https://github.com/ory/hydra) - [Ory Kratos](https://github.com/ory/kratos) - [Pouch](https://github.com/alibaba/pouch) - [ProjectAtomic (enterprise)](http://www.projectatomic.io/) - [Prototool](https://github.com/uber/prototool) +- [QRcp](https://github.com/claudiodangelis/qrcp) - [Random](https://github.com/erdaltsksn/random) - [Rclone](https://rclone.org/) +- [Scaleway CLI](https://github.com/scaleway/scaleway-cli) - [Skaffold](https://skaffold.dev/) - [Tendermint](https://github.com/tendermint/tendermint) - [Twitch CLI](https://github.com/twitchdev/twitch-cli) -- [VMware Tanzu Community Edition](https://github.com/vmware-tanzu/community-edition) +- [UpCloud CLI (`upctl`)](https://github.com/UpCloudLtd/upcloud-cli) +- VMware's [Tanzu Community Edition](https://github.com/vmware-tanzu/community-edition) & [Tanzu Framework](https://github.com/vmware-tanzu/tanzu-framework) - [Werf](https://werf.io/) From 3c84bf87042f0263dcce202a790d80996814aa9a Mon Sep 17 00:00:00 2001 From: Nickolas Kraus Date: Mon, 15 Nov 2021 14:26:11 -0600 Subject: [PATCH 207/211] Small correction in User Guide (#1009) --- user_guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/user_guide.md b/user_guide.md index 923392b7..3ab111c1 100644 --- a/user_guide.md +++ b/user_guide.md @@ -281,7 +281,7 @@ func init() { In this example, the persistent flag `author` is bound with `viper`. **Note**: the variable `author` will not be set to the value from config, -when the `--author` flag is not provided by user. +when the `--author` flag is provided by user. More in [viper documentation](https://github.com/spf13/viper#working-with-flags). From 78969f9c816ba3c627a0db3468ea37b315613330 Mon Sep 17 00:00:00 2001 From: Steve Winslow Date: Mon, 15 Nov 2021 15:33:16 -0500 Subject: [PATCH 208/211] Remove "Lesser" from header for GPL-2.0 template (#880) This removes "Lesser" from the GPL-2.0 header template, since that header is meant to be referring to GPL-2.0 and not LGPL-2.0. Fixes #879 Signed-off-by: Steve Winslow --- cobra/cmd/license_gpl_2.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cobra/cmd/license_gpl_2.go b/cobra/cmd/license_gpl_2.go index 03e05b3a..a3c2f31c 100644 --- a/cobra/cmd/license_gpl_2.go +++ b/cobra/cmd/license_gpl_2.go @@ -30,7 +30,7 @@ but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. -You should have received a copy of the GNU Lesser General Public License +You should have received a copy of the GNU General Public License along with this program. If not, see .`, Text: ` GNU GENERAL PUBLIC LICENSE Version 2, June 1991 From 442031e4ff6962f955d49d1b1336a9064704c2e3 Mon Sep 17 00:00:00 2001 From: Yann Soubeyrand Date: Mon, 15 Nov 2021 21:39:40 +0100 Subject: [PATCH 209/211] Allow specifying licenses using their SPDX identifier (#1159) --- cobra/cmd/license_agpl.go | 2 +- cobra/cmd/license_apache_2.go | 2 +- cobra/cmd/license_bsd_clause_2.go | 2 +- cobra/cmd/license_bsd_clause_3.go | 2 +- cobra/cmd/license_gpl_2.go | 2 +- cobra/cmd/license_gpl_3.go | 2 +- cobra/cmd/license_lgpl.go | 2 +- cobra/cmd/license_mit.go | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/cobra/cmd/license_agpl.go b/cobra/cmd/license_agpl.go index bc22e973..f6ec0af9 100644 --- a/cobra/cmd/license_agpl.go +++ b/cobra/cmd/license_agpl.go @@ -3,7 +3,7 @@ package cmd func initAgpl() { Licenses["agpl"] = License{ Name: "GNU Affero General Public License", - PossibleMatches: []string{"agpl", "affero gpl", "gnu agpl"}, + PossibleMatches: []string{"AGPL-3.0", "agpl", "affero gpl", "gnu agpl"}, Header: ` This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by diff --git a/cobra/cmd/license_apache_2.go b/cobra/cmd/license_apache_2.go index 38393d54..8c43e586 100644 --- a/cobra/cmd/license_apache_2.go +++ b/cobra/cmd/license_apache_2.go @@ -18,7 +18,7 @@ package cmd func initApache2() { Licenses["apache"] = License{ Name: "Apache 2.0", - PossibleMatches: []string{"apache", "apache20", "apache 2.0", "apache2.0", "apache-2.0"}, + PossibleMatches: []string{"Apache-2.0", "apache", "apache20", "apache 2.0", "apache2.0", "apache-2.0"}, Header: ` 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/license_bsd_clause_2.go b/cobra/cmd/license_bsd_clause_2.go index 4a847e04..797c82e2 100644 --- a/cobra/cmd/license_bsd_clause_2.go +++ b/cobra/cmd/license_bsd_clause_2.go @@ -18,7 +18,7 @@ package cmd func initBsdClause2() { Licenses["freebsd"] = License{ Name: "Simplified BSD License", - PossibleMatches: []string{"freebsd", "simpbsd", "simple bsd", "2-clause bsd", + PossibleMatches: []string{"BSD-2-Clause", "freebsd", "simpbsd", "simple bsd", "2-clause bsd", "2 clause bsd", "simplified bsd license"}, Header: `All rights reserved. diff --git a/cobra/cmd/license_bsd_clause_3.go b/cobra/cmd/license_bsd_clause_3.go index c7476b31..5c795f33 100644 --- a/cobra/cmd/license_bsd_clause_3.go +++ b/cobra/cmd/license_bsd_clause_3.go @@ -18,7 +18,7 @@ package cmd func initBsdClause3() { Licenses["bsd"] = License{ Name: "NewBSD", - PossibleMatches: []string{"bsd", "newbsd", "3 clause bsd", "3-clause bsd"}, + PossibleMatches: []string{"BSD-3-Clause", "bsd", "newbsd", "3 clause bsd", "3-clause bsd"}, Header: `All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/cobra/cmd/license_gpl_2.go b/cobra/cmd/license_gpl_2.go index a3c2f31c..b20dddb1 100644 --- a/cobra/cmd/license_gpl_2.go +++ b/cobra/cmd/license_gpl_2.go @@ -18,7 +18,7 @@ package cmd func initGpl2() { Licenses["gpl2"] = License{ Name: "GNU General Public License 2.0", - PossibleMatches: []string{"gpl2", "gnu gpl2", "gplv2"}, + PossibleMatches: []string{"GPL-2.0", "gpl2", "gnu gpl2", "gplv2"}, Header: ` This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License diff --git a/cobra/cmd/license_gpl_3.go b/cobra/cmd/license_gpl_3.go index ce07679c..8f9063ec 100644 --- a/cobra/cmd/license_gpl_3.go +++ b/cobra/cmd/license_gpl_3.go @@ -18,7 +18,7 @@ package cmd func initGpl3() { Licenses["gpl3"] = License{ Name: "GNU General Public License 3.0", - PossibleMatches: []string{"gpl3", "gplv3", "gpl", "gnu gpl3", "gnu gpl"}, + PossibleMatches: []string{"GPL-3.0", "gpl3", "gplv3", "gpl", "gnu gpl3", "gnu gpl"}, Header: ` This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/cobra/cmd/license_lgpl.go b/cobra/cmd/license_lgpl.go index 0f8b96ca..350b8899 100644 --- a/cobra/cmd/license_lgpl.go +++ b/cobra/cmd/license_lgpl.go @@ -3,7 +3,7 @@ package cmd func initLgpl() { Licenses["lgpl"] = License{ Name: "GNU Lesser General Public License", - PossibleMatches: []string{"lgpl", "lesser gpl", "gnu lgpl"}, + PossibleMatches: []string{"LGPL-3.0", "lgpl", "lesser gpl", "gnu lgpl"}, Header: ` This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/cobra/cmd/license_mit.go b/cobra/cmd/license_mit.go index bd2d0c4f..62a865da 100644 --- a/cobra/cmd/license_mit.go +++ b/cobra/cmd/license_mit.go @@ -18,7 +18,7 @@ package cmd func initMit() { Licenses["mit"] = License{ Name: "MIT License", - PossibleMatches: []string{"mit"}, + PossibleMatches: []string{"MIT", "mit"}, Header: ` Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 62a72cdd0fa76b1905684d786f31c3edb43028c1 Mon Sep 17 00:00:00 2001 From: Unai Martinez-Corral <38422348+umarcor@users.noreply.github.com> Date: Tue, 16 Nov 2021 22:17:12 +0000 Subject: [PATCH 210/211] fix(diff): use arg '--strip-trailing-cr' (#949) In tests with diff, ignores trailing carriage returns (so tests pass on windows) --- cobra/cmd/golden_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cobra/cmd/golden_test.go b/cobra/cmd/golden_test.go index 99b92e31..832ea533 100644 --- a/cobra/cmd/golden_test.go +++ b/cobra/cmd/golden_test.go @@ -43,7 +43,7 @@ func compareFiles(pathA, pathB string) error { // Don't execute diff if it can't be found. return nil } - diffCmd := exec.Command(diffPath, "-u", pathA, pathB) + diffCmd := exec.Command(diffPath, "-u", "--strip-trailing-cr", pathA, pathB) diffCmd.Stdout = output diffCmd.Stderr = output From 9e1d6f1c2aa8df64b6a6ba39e92517f68580d653 Mon Sep 17 00:00:00 2001 From: Unai Martinez-Corral <38422348+umarcor@users.noreply.github.com> Date: Tue, 16 Nov 2021 22:20:18 +0000 Subject: [PATCH 211/211] args_test: add helper functions (#1426) * args_test: add helper function expectSuccess * args_test: add helper function getCommand * args_test: add additional helper functions * noArgsWithArgs * validWithInvalidArgs * minimumNArgsWithLessArgs * maximumNArgsWithMoreArgs * exactArgsWithInvalidCount * rangeArgsWithInvalidCount --- args_test.go | 290 ++++++++++++++++++++++----------------------------- 1 file changed, 124 insertions(+), 166 deletions(-) diff --git a/args_test.go b/args_test.go index c81b212e..0c25b97a 100644 --- a/args_test.go +++ b/args_test.go @@ -5,26 +5,42 @@ import ( "testing" ) -func TestNoArgs(t *testing.T) { - c := &Command{Use: "c", Args: NoArgs, Run: emptyRun} +func getCommand(args PositionalArgs, withValid bool) *Command { + c := &Command{ + Use: "c", + Args: args, + Run: emptyRun, + } + if withValid { + c.ValidArgs = []string{"one", "two", "three"} + } + return c +} - output, err := executeCommand(c) +func expectSuccess(output string, err error, t *testing.T) { if output != "" { - t.Errorf("Unexpected string: %v", output) + t.Errorf("Unexpected output: %v", output) } if err != nil { t.Fatalf("Unexpected error: %v", err) } } -func TestNoArgsWithArgs(t *testing.T) { - c := &Command{Use: "c", Args: NoArgs, Run: emptyRun} - - _, err := executeCommand(c, "illegal") +func validWithInvalidArgs(err error, t *testing.T) { if err == nil { t.Fatal("Expected an error") } + got := err.Error() + expected := `invalid argument "a" for "c"` + if got != expected { + t.Errorf("Expected: %q, got: %q", expected, got) + } +} +func noArgsWithArgs(err error, t *testing.T) { + if err == nil { + t.Fatal("Expected an error") + } got := err.Error() expected := `unknown command "illegal" for "c"` if got != expected { @@ -32,73 +48,10 @@ func TestNoArgsWithArgs(t *testing.T) { } } -func TestOnlyValidArgs(t *testing.T) { - c := &Command{ - Use: "c", - Args: OnlyValidArgs, - ValidArgs: []string{"one", "two"}, - Run: emptyRun, - } - - output, err := executeCommand(c, "one", "two") - if output != "" { - t.Errorf("Unexpected output: %v", output) - } - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } -} - -func TestOnlyValidArgsWithInvalidArgs(t *testing.T) { - c := &Command{ - Use: "c", - Args: OnlyValidArgs, - ValidArgs: []string{"one", "two"}, - Run: emptyRun, - } - - _, err := executeCommand(c, "three") +func minimumNArgsWithLessArgs(err error, t *testing.T) { if err == nil { t.Fatal("Expected an error") } - - got := err.Error() - expected := `invalid argument "three" for "c"` - if got != expected { - t.Errorf("Expected: %q, got: %q", expected, got) - } -} - -func TestArbitraryArgs(t *testing.T) { - c := &Command{Use: "c", Args: ArbitraryArgs, Run: emptyRun} - output, err := executeCommand(c, "a", "b") - if output != "" { - t.Errorf("Unexpected output: %v", output) - } - if err != nil { - t.Errorf("Unexpected error: %v", err) - } -} - -func TestMinimumNArgs(t *testing.T) { - c := &Command{Use: "c", Args: MinimumNArgs(2), Run: emptyRun} - output, err := executeCommand(c, "a", "b", "c") - if output != "" { - t.Errorf("Unexpected output: %v", output) - } - if err != nil { - t.Errorf("Unexpected error: %v", err) - } -} - -func TestMinimumNArgsWithLessArgs(t *testing.T) { - c := &Command{Use: "c", Args: MinimumNArgs(2), Run: emptyRun} - _, err := executeCommand(c, "a") - - if err == nil { - t.Fatal("Expected an error") - } - got := err.Error() expected := "requires at least 2 arg(s), only received 1" if got != expected { @@ -106,25 +59,10 @@ func TestMinimumNArgsWithLessArgs(t *testing.T) { } } -func TestMaximumNArgs(t *testing.T) { - c := &Command{Use: "c", Args: MaximumNArgs(3), Run: emptyRun} - output, err := executeCommand(c, "a", "b") - if output != "" { - t.Errorf("Unexpected output: %v", output) - } - if err != nil { - t.Errorf("Unexpected error: %v", err) - } -} - -func TestMaximumNArgsWithMoreArgs(t *testing.T) { - c := &Command{Use: "c", Args: MaximumNArgs(2), Run: emptyRun} - _, err := executeCommand(c, "a", "b", "c") - +func maximumNArgsWithMoreArgs(err error, t *testing.T) { if err == nil { t.Fatal("Expected an error") } - got := err.Error() expected := "accepts at most 2 arg(s), received 3" if got != expected { @@ -132,25 +70,10 @@ func TestMaximumNArgsWithMoreArgs(t *testing.T) { } } -func TestExactArgs(t *testing.T) { - c := &Command{Use: "c", Args: ExactArgs(3), Run: emptyRun} - output, err := executeCommand(c, "a", "b", "c") - if output != "" { - t.Errorf("Unexpected output: %v", output) - } - if err != nil { - t.Errorf("Unexpected error: %v", err) - } -} - -func TestExactArgsWithInvalidCount(t *testing.T) { - c := &Command{Use: "c", Args: ExactArgs(2), Run: emptyRun} - _, err := executeCommand(c, "a", "b", "c") - +func exactArgsWithInvalidCount(err error, t *testing.T) { if err == nil { t.Fatal("Expected an error") } - got := err.Error() expected := "accepts 2 arg(s), received 3" if got != expected { @@ -158,71 +81,10 @@ func TestExactArgsWithInvalidCount(t *testing.T) { } } -func TestExactValidArgs(t *testing.T) { - c := &Command{Use: "c", Args: ExactValidArgs(3), ValidArgs: []string{"a", "b", "c"}, Run: emptyRun} - output, err := executeCommand(c, "a", "b", "c") - if output != "" { - t.Errorf("Unexpected output: %v", output) - } - if err != nil { - t.Errorf("Unexpected error: %v", err) - } -} - -func TestExactValidArgsWithInvalidCount(t *testing.T) { - c := &Command{Use: "c", Args: ExactValidArgs(2), Run: emptyRun} - _, err := executeCommand(c, "a", "b", "c") - +func rangeArgsWithInvalidCount(err error, t *testing.T) { if err == nil { t.Fatal("Expected an error") } - - got := err.Error() - expected := "accepts 2 arg(s), received 3" - if got != expected { - t.Fatalf("Expected %q, got %q", expected, got) - } -} - -func TestExactValidArgsWithInvalidArgs(t *testing.T) { - c := &Command{ - Use: "c", - Args: ExactValidArgs(1), - ValidArgs: []string{"one", "two"}, - Run: emptyRun, - } - - _, err := executeCommand(c, "three") - if err == nil { - t.Fatal("Expected an error") - } - - got := err.Error() - expected := `invalid argument "three" for "c"` - if got != expected { - t.Errorf("Expected: %q, got: %q", expected, got) - } -} - -func TestRangeArgs(t *testing.T) { - c := &Command{Use: "c", Args: RangeArgs(2, 4), Run: emptyRun} - output, err := executeCommand(c, "a", "b", "c") - if output != "" { - t.Errorf("Unexpected output: %v", output) - } - if err != nil { - t.Errorf("Unexpected error: %v", err) - } -} - -func TestRangeArgsWithInvalidCount(t *testing.T) { - c := &Command{Use: "c", Args: RangeArgs(2, 4), Run: emptyRun} - _, err := executeCommand(c, "a") - - if err == nil { - t.Fatal("Expected an error") - } - got := err.Error() expected := "accepts between 2 and 4 arg(s), received 1" if got != expected { @@ -230,6 +92,102 @@ func TestRangeArgsWithInvalidCount(t *testing.T) { } } +func TestNoArgs(t *testing.T) { + c := getCommand(NoArgs, false) + output, err := executeCommand(c) + expectSuccess(output, err, t) +} + +func TestNoArgsWithArgs(t *testing.T) { + c := getCommand(NoArgs, false) + _, err := executeCommand(c, "illegal") + noArgsWithArgs(err, t) +} + +func TestOnlyValidArgs(t *testing.T) { + c := getCommand(OnlyValidArgs, true) + output, err := executeCommand(c, "one", "two") + expectSuccess(output, err, t) +} + +func TestOnlyValidArgsWithInvalidArgs(t *testing.T) { + c := getCommand(OnlyValidArgs, true) + _, err := executeCommand(c, "a") + validWithInvalidArgs(err, t) +} + +func TestArbitraryArgs(t *testing.T) { + c := getCommand(ArbitraryArgs, false) + output, err := executeCommand(c, "a", "b") + expectSuccess(output, err, t) +} + +func TestMinimumNArgs(t *testing.T) { + c := getCommand(MinimumNArgs(2), false) + output, err := executeCommand(c, "a", "b", "c") + expectSuccess(output, err, t) +} + +func TestMinimumNArgsWithLessArgs(t *testing.T) { + c := getCommand(MinimumNArgs(2), false) + _, err := executeCommand(c, "a") + minimumNArgsWithLessArgs(err, t) +} + +func TestMaximumNArgs(t *testing.T) { + c := getCommand(MaximumNArgs(3), false) + output, err := executeCommand(c, "a", "b") + expectSuccess(output, err, t) +} + +func TestMaximumNArgsWithMoreArgs(t *testing.T) { + c := getCommand(MaximumNArgs(2), false) + _, err := executeCommand(c, "a", "b", "c") + maximumNArgsWithMoreArgs(err, t) +} + +func TestExactArgs(t *testing.T) { + c := getCommand(ExactArgs(3), false) + output, err := executeCommand(c, "a", "b", "c") + expectSuccess(output, err, t) +} + +func TestExactArgsWithInvalidCount(t *testing.T) { + c := getCommand(ExactArgs(2), false) + _, err := executeCommand(c, "a", "b", "c") + exactArgsWithInvalidCount(err, t) +} + +func TestExactValidArgs(t *testing.T) { + c := getCommand(ExactValidArgs(3), true) + output, err := executeCommand(c, "three", "one", "two") + expectSuccess(output, err, t) +} + +func TestExactValidArgsWithInvalidCount(t *testing.T) { + c := getCommand(ExactValidArgs(2), false) + _, err := executeCommand(c, "three", "one", "two") + exactArgsWithInvalidCount(err, t) +} + +func TestExactValidArgsWithInvalidArgs(t *testing.T) { + c := getCommand(ExactValidArgs(3), true) + _, err := executeCommand(c, "three", "a", "two") + validWithInvalidArgs(err, t) +} + +func TestRangeArgs(t *testing.T) { + c := getCommand(RangeArgs(2, 4), false) + output, err := executeCommand(c, "a", "b", "c") + expectSuccess(output, err, t) +} + +func TestRangeArgsWithInvalidCount(t *testing.T) { + c := getCommand(RangeArgs(2, 4), false) + _, err := executeCommand(c, "a") + rangeArgsWithInvalidCount(err, t) +} + func TestRootTakesNoArgs(t *testing.T) { rootCmd := &Command{Use: "root", Run: emptyRun} childCmd := &Command{Use: "child", Run: emptyRun}