Repository: jawher/mow.cli
Pull request #96 (2019-04-30 11:12:25)

Handle multi line option and argument description in the generated help message
The help output will resemble:

```
Usage: app [-o] ARG

Longer App Desc

Arguments:
  ARG           Argument
                Description
                Multiple
                Lines

Options:
  -o, --opt     Option
                does something
                another line (env $XXX_TEST) (default "default")
  -f, --force   Force
                does something
                another line (env $YYY_TEST)
```

coveralls: 
[![Coverage Status](https://coveralls.io/builds/23104354/badge)](https://coveralls.io/builds/23104354)

Coverage increased (+0.03%) to 95.573% when pulling **01e6246af8fe0c91d7de11f85230d757882f2dac on multi-line-desc** into **483c839d919a98fbaf913bffe1f5135302b423c9 on master**.


Patch:
diff --git a/commands.go b/commands.go
index 3f17999..775c52e 100644
--- a/commands.go
+++ b/commands.go
@@ -3,6 +3,7 @@ package cli
 import (
 	"flag"
 	"fmt"
+	"io"
 	"strings"
 	"text/tabwriter"
 
@@ -545,7 +546,7 @@ func (c *Cmd) printHelp(longDesc bool) {
 				env   = formatEnvVarsForHelp(arg.EnvVar)
 				value = formatValueForHelp(arg.HideValue, arg.Value)
 			)
-			fmt.Fprintf(w, "  %s\t%s\n", arg.Name, joinStrings(arg.Desc, env, value))
+			printTabbedRow(w, arg.Name, joinStrings(arg.Desc, env, value))
 		}
 	}
 
@@ -558,7 +559,7 @@ func (c *Cmd) printHelp(longDesc bool) {
 				env      = formatEnvVarsForHelp(opt.EnvVar)
 				value    = formatValueForHelp(opt.HideValue, opt.Value)
 			)
-			fmt.Fprintf(w, "  %s\t%s\n", optNames, joinStrings(opt.Desc, env, value))
+			printTabbedRow(w, optNames, joinStrings(opt.Desc, env, value))
 		}
 	}
 
@@ -765,3 +766,16 @@ func joinStrings(parts ...string) string {
 	}
 	return res
 }
+
+func printTabbedRow(w io.Writer, s1 string, s2 string) {
+	lines := strings.Split(s2, "\n")
+	fmt.Fprintf(w, "  %s\t%s\n", s1, strings.TrimSpace(lines[0]))
+
+	if len(lines) == 1 {
+		return
+	}
+
+	for _, line := range lines[1:] {
+		fmt.Fprintf(w, "  %s\t%s\n", "", strings.TrimSpace(line))
+	}
+}


Test patch:
diff --git a/cli_test.go b/cli_test.go
index d53fa80..d750eb1 100644
--- a/cli_test.go
+++ b/cli_test.go
@@ -887,6 +887,36 @@ func TestLongHelpMessage(t *testing.T) {
 	require.Equal(t, expected, []byte(err))
 }
 
+func TestMultiLineDescInHelpMessage(t *testing.T) {
+	var out, err string
+	defer captureAndRestoreOutput(&out, &err)()
+
+	exitCalled := false
+	defer exitShouldBeCalledWith(t, 0, &exitCalled)()
+
+	app := App("app", "App Desc")
+	app.LongDesc = "Longer App Desc"
+	app.Spec = "[-o] ARG"
+
+	app.String(StringOpt{Name: "o opt", Value: "default", EnvVar: "XXX_TEST", Desc: "Option\ndoes something\n  another line"})
+	app.Bool(BoolOpt{Name: "f force", Value: false, EnvVar: "YYY_TEST", Desc: "Force\ndoes something\n  another line"})
+	app.String(StringArg{Name: "ARG", Value: "", Desc: "Argument\nDescription\nMultiple\nLines"})
+
+	app.Action = func() {}
+	require.NoError(t,
+		app.Run([]string{"app", "-h"}))
+
+	if *genGolden {
+		require.NoError(t,
+			ioutil.WriteFile("testdata/multi-line-desc-help-output.txt.golden", []byte(err), 0644))
+	}
+
+	expected, e := ioutil.ReadFile("testdata/multi-line-desc-help-output.txt")
+	require.NoError(t, e, "Failed to read the expected help output from testdata/long-help-output.txt")
+
+	require.Equal(t, expected, []byte(err))
+}
+
 func TestVersionShortcut(t *testing.T) {
 	defer suppressOutput()()
 	exitCalled := false
diff --git a/commands_test.go b/commands_test.go
index 5039603..0ea8aac 100644
--- a/commands_test.go
+++ b/commands_test.go
@@ -1379,3 +1379,27 @@ func TestJoinStrings(t *testing.T) {
 		})
 	}
 }
+
+func TestPrintTabbedRow(t *testing.T) {
+	cases := []struct {
+		s1, s2   string
+		expected string
+	}{
+		{"", "", "  \t\n"},
+		{"a", "", "  a\t\n"},
+		{"a", "b", "  a\tb\n"},
+		{"a", "  b", "  a\tb\n"},
+		{"a", "b\n", "  a\tb\n  \t\n"},
+		{"a", "b\nc", "  a\tb\n  \tc\n"},
+	}
+
+	for _, cas := range cases {
+		t.Run(fmt.Sprintf("%q, %q", cas.s1, cas.s2), func(t *testing.T) {
+			t.Logf("Testing %q, %q", cas.s1, cas.s2)
+			var out strings.Builder
+			printTabbedRow(&out, cas.s1, cas.s2)
+
+			require.Equal(t, cas.expected, out.String())
+		})
+	}
+}
diff --git a/testdata/multi-line-desc-help-output.txt b/testdata/multi-line-desc-help-output.txt
new file mode 100644
index 0000000..5a6c6b6
--- /dev/null
+++ b/testdata/multi-line-desc-help-output.txt
@@ -0,0 +1,18 @@
+
+Usage: app [-o] ARG
+
+Longer App Desc
+                
+Arguments:      
+  ARG           Argument
+                Description
+                Multiple
+                Lines
+                
+Options:        
+  -o, --opt     Option
+                does something
+                another line (env $XXX_TEST) (default "default")
+  -f, --force   Force
+                does something
+                another line (env $YYY_TEST)