Repository: spf13/cobra
Pull request #737 (2018-08-28 21:04:18)

Run tests against go 1.11 and drop 1.9
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 PR adapts to the new formatting applied by gofmt and changes the configs for travis and circleci to run gofmt only with go 1.11.


Patch:
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 066df22..6d248bc 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -1,38 +1,53 @@
-workflows:
-  version: 2
-  main:
-    jobs:
-      - 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
+
+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.10.0
-    <<: *base
+      - 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.9.4
-    <<: *base
+      - image: circleci/golang:1.10
+    working_directory: *workspace
+    steps:
+      - checkout
+      - *run_tests
   go-latest:
     docker:
       - image: circleci/golang:latest
-    <<: *base
+    working_directory: *workspace
+    steps:
+      - checkout
+      - *run_tests
+
+workflows:
+  version: 2
+  main:
+    jobs:
+      - go-current
+      - go-previous
+      - go-latest
diff --git a/.travis.yml b/.travis.yml
index 5afcb20..5f15743 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/README.md b/README.md
index da25608..5e73ffe 100644
--- a/README.md
+++ b/README.md
@@ -16,7 +16,7 @@ Many of the most widely used Go projects are built using Cobra including:
 * [CockroachDB](http://www.cockroachlabs.com/)
 * [Bleve](http://www.blevesearch.com/)
 * [ProjectAtomic (enterprise)](http://www.projectatomic.io/)
-* [GiantSwarm's swarm](https://github.com/giantswarm/cli)
+* [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)
@@ -152,7 +152,6 @@ In a Cobra app, typically the main.go file is very bare. It serves one purpose:
 package main
 
 import (
-
   "{pathToYourApp}/cmd"
 )
 
@@ -263,9 +262,6 @@ In a Cobra app, typically the main.go file is very bare. It serves, one purpose,
 package main
 
 import (
-  "fmt"
-  "os"
-
   "{pathToYourApp}/cmd"
 )
 
@@ -393,6 +389,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`
 - `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:
diff --git a/args.go b/args.go
index a5d8a92..c4d820b 100644
--- a/args.go
+++ b/args.go
@@ -78,6 +78,18 @@ func ExactArgs(n int) PositionalArgs {
 	}
 }
 
+// ExactValidArgs returns an error if
+// there are not exactly N positional args OR
+// there are any positional args that are not in the `ValidArgs` field of `Command`
+func ExactValidArgs(n int) PositionalArgs {
+	return func(cmd *Command, args []string) error {
+		if err := ExactArgs(n)(cmd, args); err != nil {
+			return err
+		}
+		return OnlyValidArgs(cmd, args)
+	}
+}
+
 // RangeArgs returns an error if the number of args is not within the expected range.
 func RangeArgs(min int, max int) PositionalArgs {
 	return func(cmd *Command, args []string) error {
diff --git a/bash_completions.go b/bash_completions.go
index 63274d9..30e7427 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 3bd32e0..4ac61ee 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()` (`__<command-use>_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()` (`__<command-use>_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'
 
diff --git a/doc/man_docs.go b/doc/man_docs.go
index e5dbda2..4a06233 100644
--- a/doc/man_docs.go
+++ b/doc/man_docs.go
@@ -20,6 +20,7 @@ import (
 	"os"
 	"path/filepath"
 	"sort"
+	"strconv"
 	"strings"
 	"time"
 
@@ -104,14 +105,16 @@ func GenMan(cmd *cobra.Command, header *GenManHeader, w io.Writer) error {
 	if header == nil {
 		header = &GenManHeader{}
 	}
-	fillHeader(header, cmd.CommandPath())
+	if err := fillHeader(header, cmd.CommandPath()); err != nil {
+		return err
+	}
 
 	b := genMan(cmd, header)
 	_, err := w.Write(md2man.Render(b))
 	return err
 }
 
-func fillHeader(header *GenManHeader, name string) {
+func fillHeader(header *GenManHeader, name string) error {
 	if header.Title == "" {
 		header.Title = strings.ToUpper(strings.Replace(name, " ", "\\-", -1))
 	}
@@ -120,12 +123,20 @@ func fillHeader(header *GenManHeader, name string) {
 	}
 	if header.Date == nil {
 		now := time.Now()
+		if epoch := os.Getenv("SOURCE_DATE_EPOCH"); epoch != "" {
+			unixEpoch, err := strconv.ParseInt(epoch, 10, 64)
+			if err != nil {
+				return fmt.Errorf("invalid SOURCE_DATE_EPOCH: %v", err)
+			}
+			now = time.Unix(unixEpoch, 0)
+		}
 		header.Date = &now
 	}
 	header.date = (*header.Date).Format("Jan 2006")
 	if header.Source == "" {
 		header.Source = "Auto generated by spf13/cobra"
 	}
+	return nil
 }
 
 func manPreamble(buf *bytes.Buffer, header *GenManHeader, cmd *cobra.Command, dashedName string) {


Test patch:
diff --git a/args_test.go b/args_test.go
index d797b6f..c81b212 100644
--- a/args_test.go
+++ b/args_test.go
@@ -158,6 +158,52 @@ 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")
+
+	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")
diff --git a/bash_completions_test.go b/bash_completions_test.go
index 94b965d..dd0fa52 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")