Repository: jawher/mow.cli (go)

Issue:
The default value of the empty default option is not printed correctly
```
package main

import (
	"fmt"
	"os"

	"github.com/jawher/mow.cli"
)

// Global options available to any of the commands
var filename *string

func main() {
	app := cli.App("vault", "Password Keeper")

	// Define our top-level global options
	filename = app.StringOpt("f file", "", "Path to safe")

	// Define our command structure for usage like this:
	app.Command("list", "list accounts", cmdList)
	app.Run(os.Args)
}

// Sample use: vault list OR vault config list
func cmdList(cmd *cli.Cmd) {
	cmd.Action = func() {
		fmt.Printf("list the contents of the safe here")
	}
}
```

The default value of filename is abc.txt which is specified when I execute the program.

```
 ~ ./vault -f abc.txt

Usage: vault [OPTIONS] COMMAND [arg...]

Password Keeper
               
Options:       
  -f, --file   Path to safe (default "abc.txt")
               
Commands:      
  list         list accounts
               
Run 'vault COMMAND --help' for more information on a command.
```

Pull request description:
Fix default value display in help messages
To display the default value as set by the developer, ignoring the later assignments (from env vars, user input, ...)

Fixes #98

Patch:
diff --git a/args.go b/args.go
index 3fb69a9..be80b2f 100644
--- a/args.go
+++ b/args.go
@@ -394,6 +394,8 @@ func (c *Cmd) mkArg(arg container.Container) {
 		panic(fmt.Sprintf("duplicate argument name %q", arg.Name))
 	}
 
+	arg.DefaultValue = values.DefaultValue(arg.Value)
+
 	arg.ValueSetFromEnv = values.SetFromEnv(arg.Value, arg.EnvVar)
 
 	c.args = append(c.args, &arg)
diff --git a/commands.go b/commands.go
index e737793..2bcb335 100644
--- a/commands.go
+++ b/commands.go
@@ -12,7 +12,6 @@ import (
 	"github.com/jawher/mow.cli/internal/fsm"
 	"github.com/jawher/mow.cli/internal/lexer"
 	"github.com/jawher/mow.cli/internal/parser"
-	"github.com/jawher/mow.cli/internal/values"
 )
 
 /*
@@ -546,7 +545,7 @@ func (c *Cmd) printHelp(longDesc bool) {
 		for _, arg := range c.args {
 			var (
 				env   = formatEnvVarsForHelp(arg.EnvVar)
-				value = formatValueForHelp(arg.HideValue, arg.Value)
+				value = formatValueForHelp(arg.HideValue, arg.DefaultValue)
 			)
 			printTabbedRow(w, arg.Name, joinStrings(arg.Desc, env, value))
 		}
@@ -559,7 +558,7 @@ func (c *Cmd) printHelp(longDesc bool) {
 			var (
 				optNames = formatOptNamesForHelp(opt)
 				env      = formatEnvVarsForHelp(opt.EnvVar)
-				value    = formatValueForHelp(opt.HideValue, opt.Value)
+				value    = formatValueForHelp(opt.HideValue, opt.DefaultValue)
 			)
 			printTabbedRow(w, optNames, joinStrings(opt.Desc, env, value))
 		}
@@ -619,18 +618,16 @@ func formatOptNamesForHelp(o *container.Container) string {
 	}
 }
 
-func formatValueForHelp(hide bool, v flag.Value) string {
+func formatValueForHelp(hide bool, v string) string {
 	if hide {
 		return ""
 	}
 
-	if dv, ok := v.(values.DefaultValued); ok {
-		if dv.IsDefault() {
-			return ""
-		}
+	if v == "" {
+		return ""
 	}
 
-	return fmt.Sprintf("(default %s)", v.String())
+	return fmt.Sprintf("(default %s)", v)
 }
 
 func formatEnvVarsForHelp(envVars string) string {
diff --git a/internal/container/container.go b/internal/container/container.go
index d5d858f..88c1cc1 100644
--- a/internal/container/container.go
+++ b/internal/container/container.go
@@ -14,4 +14,5 @@ type Container struct {
 	ValueSetFromEnv bool
 	ValueSetByUser  *bool
 	Value           flag.Value
+	DefaultValue    string
 }
diff --git a/internal/values/utils.go b/internal/values/utils.go
index af66c4b..1df142b 100644
--- a/internal/values/utils.go
+++ b/internal/values/utils.go
@@ -54,3 +54,12 @@ func setMultivalued(into MultiValued, values []string) error {
 
 	return nil
 }
+
+func DefaultValue(v flag.Value) string {
+	if dv, ok := v.(DefaultValued); ok {
+		if dv.IsDefault() {
+			return ""
+		}
+	}
+	return v.String()
+}
diff --git a/options.go b/options.go
index 469d97d..cb723b1 100644
--- a/options.go
+++ b/options.go
@@ -467,6 +467,7 @@ func mkOptStrs(optName string) []string {
 }
 
 func (c *Cmd) mkOpt(opt container.Container) {
+	opt.DefaultValue = values.DefaultValue(opt.Value)
 	opt.ValueSetFromEnv = values.SetFromEnv(opt.Value, opt.EnvVar)
 
 	opt.Names = mkOptStrs(opt.Name)


Test patch:
diff --git a/cli_test.go b/cli_test.go
index c83175d..6617d16 100644
--- a/cli_test.go
+++ b/cli_test.go
@@ -790,10 +790,14 @@ var genGolden = flag.Bool("g", false, "Generate golden file(s)")
 
 func TestHelpMessage(t *testing.T) {
 	cases := []struct {
-		name   string
-		params []string
+		name     string
+		params   []string
+		env      map[string]string
+		exitCode int
 	}{
 		{name: "top-help", params: []string{"app", "-h"}},
+		{name: "top-help-i-user", params: []string{"app", "-i=5"}, exitCode: 2},
+		{name: "top-help-i-env", params: []string{"app"}, env: map[string]string{"INT1": "25"}, exitCode: 2},
 		{name: "command1", params: []string{"app", "command1", "-h"}},
 		{name: "command2", params: []string{"app", "command2", "-h"}},
 		{name: "command3", params: []string{"app", "command3", "-h"}},
@@ -807,9 +811,10 @@ func TestHelpMessage(t *testing.T) {
 			t.Logf("case: %+v", cas)
 			var out, stdErr string
 			defer captureAndRestoreOutput(&out, &stdErr)()
+			defer setAndRestoreEnv(cas.env)()
 
 			exitCalled := false
-			defer exitShouldBeCalledWith(t, 0, &exitCalled)()
+			defer exitShouldBeCalledWith(t, cas.exitCode, &exitCalled)()
 
 			app := App("app", "App Desc")
 			app.Spec = "[-bdsuikqs] [BOOL1 STR1 INT3...]"
@@ -856,8 +861,6 @@ func TestHelpMessage(t *testing.T) {
 			app.Ints(IntsArg{Name: "INTS2", Value: []int{1, 2, 3}, EnvVar: "INTS2", Desc: "Ints Argument 2"})
 			app.Ints(IntsArg{Name: "INTS3", Value: []int{1}, EnvVar: "INTS3", Desc: "Ints Argument 3", HideValue: true})
 
-			app.Action = func() {}
-
 			app.Command("command1", "command1 description", func(cmd *Cmd) {})
 			app.Command("command2", "command2 description", func(cmd *Cmd) {})
 			app.Command("command3", "command3 description", func(cmd *Cmd) {
@@ -879,7 +882,6 @@ func TestHelpMessage(t *testing.T) {
 
 			filename := fmt.Sprintf("testdata/help-output-%s.txt", cas.name)
 
-			fmt.Printf("golden file: %s\n", filename)
 			if *genGolden {
 				require.NoError(t,
 					ioutil.WriteFile(filename, []byte(stdErr), 0644))
@@ -2236,6 +2238,20 @@ func suppressOutput() func() {
 	return captureAndRestoreOutput(nil, nil)
 }
 
+func setAndRestoreEnv(env map[string]string) func() {
+	backup := map[string]string{}
+	for k, v := range env {
+		backup[k] = os.Getenv(k)
+		os.Setenv(k, v)
+	}
+
+	return func() {
+		for k, v := range backup {
+			os.Setenv(k, v)
+		}
+	}
+}
+
 func captureAndRestoreOutput(out, err *string) func() {
 	oldStdOut := stdOut
 	oldStdErr := stdErr
diff --git a/testdata/help-output-top-help-i-env.txt b/testdata/help-output-top-help-i-env.txt
new file mode 100644
index 0000000..dd4772f
--- /dev/null
+++ b/testdata/help-output-top-help-i-env.txt
@@ -0,0 +1,45 @@
+
+Usage: app [-bdsuikqs] [BOOL1 STR1 INT3...] COMMAND [arg...]
+
+App Desc
+                
+Arguments:      
+  BOOL1         Bool Argument 1 (env $BOOL1)
+  BOOL2         Bool Argument 2 (default true)
+  BOOL3         Bool Argument 3 (env $BOOL3)
+  STR1          String Argument 1 (env $STR1)
+  STR2          String Argument 2 (env $STR2) (default "a value")
+  STR3          String Argument 3 (env $STR3)
+  INT1          Int Argument 1 (env $INT1) (default 0)
+  INT2          Int Argument 2 (env $INT2) (default 1)
+  INT3          Int Argument 3 (env $INT3)
+  STRS1         Strings Argument 1 (env $STRS1)
+  STRS2         (env $STRS2) (default ["value1", "value2"])
+  STRS3         Strings Argument 3 (env $STRS3)
+  INTS1         Ints Argument 1 (env $INTS1)
+  INTS2         Ints Argument 2 (env $INTS2) (default [1, 2, 3])
+  INTS3         Ints Argument 3 (env $INTS3)
+                
+Options:        
+  -b, --bool1   Bool Option 1 (env $BOOL1)
+      --bool2   Bool Option 2 (default true)
+  -d            Bool Option 3 (env $BOOL3)
+  -s, --str1    String Option 1 (env $STR1)
+      --str2    String Option 2 (default "a value")
+  -v            String Option 3 (env $STR3)
+  -i, --int1    (env $INT1, $ALIAS_INT1) (default 0)
+      --int2    Int Option 2 (env $INT2) (default 1)
+  -k            Int Option 3 (env $INT3)
+  -x, --strs1   Strings Option 1 (env $STRS1)
+      --strs2   Strings Option 2 (env $STRS2) (default ["value1", "value2"])
+  -z            Strings Option 3 (env $STRS3)
+  -q, --ints1   Ints Option 1 (env $INTS1)
+      --ints2   Ints Option 2 (env $INTS2) (default [1, 2, 3])
+  -j            Ints Option 3 (env $INTS3)
+                
+Commands:       
+  command1      command1 description
+  command2      command2 description
+  command3      command3 description
+                
+Run 'app COMMAND --help' for more information on a command.
diff --git a/testdata/help-output-top-help-i-user.txt b/testdata/help-output-top-help-i-user.txt
new file mode 100644
index 0000000..dd4772f
--- /dev/null
+++ b/testdata/help-output-top-help-i-user.txt
@@ -0,0 +1,45 @@
+
+Usage: app [-bdsuikqs] [BOOL1 STR1 INT3...] COMMAND [arg...]
+
+App Desc
+                
+Arguments:      
+  BOOL1         Bool Argument 1 (env $BOOL1)
+  BOOL2         Bool Argument 2 (default true)
+  BOOL3         Bool Argument 3 (env $BOOL3)
+  STR1          String Argument 1 (env $STR1)
+  STR2          String Argument 2 (env $STR2) (default "a value")
+  STR3          String Argument 3 (env $STR3)
+  INT1          Int Argument 1 (env $INT1) (default 0)
+  INT2          Int Argument 2 (env $INT2) (default 1)
+  INT3          Int Argument 3 (env $INT3)
+  STRS1         Strings Argument 1 (env $STRS1)
+  STRS2         (env $STRS2) (default ["value1", "value2"])
+  STRS3         Strings Argument 3 (env $STRS3)
+  INTS1         Ints Argument 1 (env $INTS1)
+  INTS2         Ints Argument 2 (env $INTS2) (default [1, 2, 3])
+  INTS3         Ints Argument 3 (env $INTS3)
+                
+Options:        
+  -b, --bool1   Bool Option 1 (env $BOOL1)
+      --bool2   Bool Option 2 (default true)
+  -d            Bool Option 3 (env $BOOL3)
+  -s, --str1    String Option 1 (env $STR1)
+      --str2    String Option 2 (default "a value")
+  -v            String Option 3 (env $STR3)
+  -i, --int1    (env $INT1, $ALIAS_INT1) (default 0)
+      --int2    Int Option 2 (env $INT2) (default 1)
+  -k            Int Option 3 (env $INT3)
+  -x, --strs1   Strings Option 1 (env $STRS1)
+      --strs2   Strings Option 2 (env $STRS2) (default ["value1", "value2"])
+  -z            Strings Option 3 (env $STRS3)
+  -q, --ints1   Ints Option 1 (env $INTS1)
+      --ints2   Ints Option 2 (env $INTS2) (default [1, 2, 3])
+  -j            Ints Option 3 (env $INTS3)
+                
+Commands:       
+  command1      command1 description
+  command2      command2 description
+  command3      command3 description
+                
+Run 'app COMMAND --help' for more information on a command.