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] 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 -*-