Repository: urfave/cli
Pull request #830 (2019-08-03 10:51:43)

Add markdown and man page docs generation methods
This adds two new methods to the `App` struct:

- `ToMarkdown`: creates a markdown documentation string
- `ToMan`: creates a man page string

Example markdown output (had to modify the triple backticks to not render here):
```
% Harrison
% August 2019

# NAME

greet - Some app

# SYNOPSIS

greet

``
[--another-flag|-b]
[--flag|--fl|-f]=[value]
[--socket|-s]=[value]
``

# DESCRIPTION

app [first_arg] [second_arg]

**Usage**:

``
greet [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...]
``

# GLOBAL OPTIONS

**--another-flag, -b**: another usage text
**--flag, --fl, -f**="": _no description available_
**--socket, -s**="": some usage text (default: value)

# COMMANDS

## config, c

another usage test

**--another-flag, -b**: another usage text
**--flag, --fl, -f**="": _no description available_

### sub-config, s, ss

another usage test

**--sub-command-flag, -s**: some usage text
**--sub-flag, --sub-fl, -s**="": _no description available_

## info, i, in

retrieve generic information

## some-command

_no description available_
```

I'm now totally open to discussions about the look and feel of the
markdown / man page. But please consider that the generated markdown
has to be consumed by md2man, so we're maybe not as free as we want to be.

What do you think? Any suggestions? :)

Closes https://github.com/urfave/cli/issues/340

AudriusButkevicius: Sprintf only solves part of the problem. Any reason why you are not using https://www.calhoun.io/concatenating-and-building-strings-in-go/ etc?

I guess this is nitpicking at this point and I don't care as it looks ok, but just genuinely curious.
saschagrunert: > Sprintf only solves part of the problem. Any reason why you are not using https://www.calhoun.io/concatenating-and-building-strings-in-go/ etc?
> 
> I guess this is nitpicking at this point and I don't care as it looks ok, but just genuinely curious.

Ah sorry then I got you wrong. For sure we could use `strings.Builder`

Patch:
diff --git a/docs.go b/docs.go
new file mode 100644
index 0000000..ef5699a
--- /dev/null
+++ b/docs.go
@@ -0,0 +1,146 @@
+package cli
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+	"sort"
+	"strings"
+	"text/template"
+
+	"github.com/cpuguy83/go-md2man/md2man"
+)
+
+// ToMarkdown creates a markdown string for the `*App`
+// The function errors if either parsing or writing of the string fails.
+func (a *App) ToMarkdown() (string, error) {
+	var w bytes.Buffer
+	if err := a.writeDocTemplate(&w); err != nil {
+		return "", err
+	}
+	return w.String(), nil
+}
+
+// ToMan creates a man page string for the `*App`
+// The function errors if either parsing or writing of the string fails.
+func (a *App) ToMan() (string, error) {
+	var w bytes.Buffer
+	if err := a.writeDocTemplate(&w); err != nil {
+		return "", err
+	}
+	man := md2man.Render(w.Bytes())
+	return string(man), nil
+}
+
+type cliTemplate struct {
+	App          *App
+	Date         string
+	Commands     []string
+	GlobalArgs   []string
+	SynopsisArgs []string
+}
+
+func (a *App) writeDocTemplate(w io.Writer) error {
+	const name = "cli"
+	t, err := template.New(name).Parse(MarkdownDocTemplate)
+	if err != nil {
+		return err
+	}
+	return t.ExecuteTemplate(w, name, &cliTemplate{
+		App:          a,
+		Commands:     prepareCommands(a.Commands, 0),
+		GlobalArgs:   prepareArgsWithValues(a.Flags),
+		SynopsisArgs: prepareArgsSynopsis(a.Flags),
+	})
+}
+
+func prepareCommands(commands []Command, level int) []string {
+	coms := []string{}
+	for i := range commands {
+		command := &commands[i]
+		usage := ""
+		if command.Usage != "" {
+			usage = command.Usage
+		}
+
+		prepared := fmt.Sprintf("%s %s\n\n%s\n",
+			strings.Repeat("#", level+2),
+			strings.Join(command.Names(), ", "),
+			usage,
+		)
+
+		flags := prepareArgsWithValues(command.Flags)
+		if len(flags) > 0 {
+			prepared += fmt.Sprintf("\n%s", strings.Join(flags, "\n"))
+		}
+
+		coms = append(coms, prepared)
+
+		// recursevly iterate subcommands
+		if len(command.Subcommands) > 0 {
+			coms = append(
+				coms,
+				prepareCommands(command.Subcommands, level+1)...,
+			)
+		}
+	}
+
+	return coms
+}
+
+func prepareArgsWithValues(flags []Flag) []string {
+	return prepareFlags(flags, ", ", "**", "**", `""`, true)
+}
+
+func prepareArgsSynopsis(flags []Flag) []string {
+	return prepareFlags(flags, "|", "[", "]", "[value]", false)
+}
+
+func prepareFlags(
+	flags []Flag,
+	sep, opener, closer, value string,
+	addDetails bool,
+) []string {
+	args := []string{}
+	for _, f := range flags {
+		flag, ok := f.(DocGenerationFlag)
+		if !ok {
+			continue
+		}
+		modifiedArg := opener
+		for _, s := range strings.Split(flag.GetName(), ",") {
+			trimmed := strings.TrimSpace(s)
+			if len(modifiedArg) > len(opener) {
+				modifiedArg += sep
+			}
+			if len(trimmed) > 1 {
+				modifiedArg += fmt.Sprintf("--%s", trimmed)
+			} else {
+				modifiedArg += fmt.Sprintf("-%s", trimmed)
+			}
+		}
+		modifiedArg += closer
+		if flag.TakesValue() {
+			modifiedArg += fmt.Sprintf("=%s", value)
+		}
+
+		if addDetails {
+			modifiedArg += flagDetails(flag)
+		}
+
+		args = append(args, modifiedArg+"\n")
+
+	}
+	sort.Strings(args)
+	return args
+}
+
+// flagDetails returns a string containing the flags metadata
+func flagDetails(flag DocGenerationFlag) string {
+	description := flag.GetUsage()
+	value := flag.GetValue()
+	if value != "" {
+		description += " (default: " + value + ")"
+	}
+	return ": " + description
+}
diff --git a/flag-gen/assets_vfsdata.go b/flag-gen/assets_vfsdata.go
index 024039a..2e31627 100644
--- a/flag-gen/assets_vfsdata.go
+++ b/flag-gen/assets_vfsdata.go
@@ -28,9 +28,9 @@ var assets = func() http.FileSystem {
 		"/source/flag-types.json": &vfsgen۰CompressedFileInfo{
 			name:             "flag-types.json",
 			modTime:          time.Time{},
-			uncompressedSize: 2559,
+			uncompressedSize: 3364,
 
-			compressedContent: []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xc4\x94\xc1\x6e\x9c\x30\x10\x86\xef\x3c\xc5\xc8\xbd\x40\xb5\x82\x1c\x56\x7b\xd8\x63\x55\xb5\xda\x5b\xa5\x34\xbd\x24\x51\xe4\x80\x21\x56\x1d\x1b\xd9\x43\xd4\x28\xca\xbb\x57\xf6\x2e\xbb\x60\x0c\x62\x5b\x2d\xb9\xfe\x23\x98\xef\x9b\xb1\xe6\x36\x02\x78\x8b\x00\x00\x88\xa4\xcf\x8c\x6c\x81\x7c\x51\x4a\x90\xd5\x3e\xc3\xd7\xda\x65\x8f\x9d\xec\x85\x8a\xc6\x86\x25\x15\x86\x1d\xb2\x82\x19\x24\x5b\x40\xdd\xb4\x49\xae\x24\xb2\x3f\xf8\x50\xb0\x92\x36\xc2\x16\x89\xfb\xa0\xfd\x4b\x4d\xb5\x61\xda\xc6\x06\x75\xae\xe4\x4b\xfa\xc3\x26\xb6\x79\x5c\xa6\xbf\x6c\x8f\xf4\x1a\x35\x97\x55\x9c\x24\x24\x02\x78\x5f\x85\x51\x7f\xfe\x3f\x6b\xa1\x72\xa4\x5c\xd8\x8f\x01\x9f\x28\x02\x37\xae\x0c\x8f\xaf\xd0\xf2\x2f\xa9\xf5\xb5\xd1\x14\xb9\x92\xbe\x19\xf2\x67\x96\xfa\xc5\x56\xb1\xeb\x33\x69\x18\x1b\xc6\xe0\x09\xb1\x36\xdb\x2c\xab\x94\xa0\xb2\x4a\x95\xae\xb2\xfa\x77\x95\xd9\x0e\xd9\x27\x87\xdc\xf6\x49\x26\xd4\xaf\x86\xda\x8e\xb1\xf7\x83\xf9\xde\xdf\x84\xa2\xb8\x59\xfb\xda\x65\x3f\x9e\x25\x3c\x0f\xb6\xb7\x23\xd7\x7d\x00\xbb\x82\xcd\x7a\x14\xf8\x3b\x93\x4c\xf3\xdc\x07\xf6\xe2\x71\xe0\xee\xb3\x0c\x10\x4b\x2e\xfc\xe1\xb7\x2d\xb8\x44\xa6\x4b\x9a\xb3\xb7\xf7\x31\xb8\x9d\x0c\xcc\x92\xcb\x05\x26\xb9\x93\xa1\x39\x5e\x4d\x8e\x72\x27\x31\xc0\xfa\xa1\xa4\xbd\x4f\x1f\x72\xea\xfa\x59\xaa\xd8\x45\xc5\x94\xcb\xb5\xe0\x39\xf3\x85\x3e\xfb\x85\x4b\xbc\x8c\xdb\xfb\xce\xdc\x4e\xda\x47\xcd\xf8\x48\x91\x24\xfb\xc8\x2a\x33\xad\x95\x8e\x25\x17\x53\x52\x9b\xf5\xa8\x96\x57\xba\x98\xd8\xe9\xf1\x8e\xa9\x1d\x48\xce\x93\xdb\xef\xde\x17\x33\xbd\xf4\x5f\xdf\xe0\x1d\xb9\x23\x43\xe8\xe1\xab\x9b\x4b\x19\xde\x41\xa0\x76\x99\x25\xf4\x87\x12\xdc\x42\x87\xe5\xbc\x35\xdc\xf0\xd0\xcd\x6a\x16\x39\x5a\xb6\xf7\xd9\x57\xeb\x86\x0f\xcf\x56\xb3\xc0\xdd\x9a\x86\x0d\x1f\xae\xc6\xbf\x5c\xd1\x7d\xf4\x37\x00\x00\xff\xff\x66\x52\x85\x44\xff\x09\x00\x00"),
+			compressedContent: []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xcc\x95\x41\x6f\x9b\x40\x10\x85\xef\xfc\x8a\xe9\x56\x95\xa0\xb2\x20\x87\x28\x07\x4b\xbd\x54\x55\x2b\xdf\x2a\xb9\xe9\x25\x44\xd1\x06\x16\xb2\xea\x7a\x16\x2d\x43\xd4\xc8\xf2\x7f\xaf\x16\x9b\x04\x16\x70\x48\x5b\x2c\x5f\x7c\x78\xe3\xe5\xed\xfb\x66\x47\x73\xe3\x01\x6c\x3d\x00\x00\x86\x7c\x23\xd8\x12\xd8\x67\xad\x15\x5b\xec\x35\x7a\x2a\x6a\xed\xbe\xa5\x3d\x72\x55\x59\x31\xe3\xaa\x14\x07\x2d\x15\x25\xb1\x25\x90\xa9\x1a\x25\xd1\x48\xe2\x37\xdd\xa5\x22\xe3\x95\xb2\x45\x56\x1f\x68\xbe\x52\x70\x53\x0a\x63\xe5\x92\x4c\xa2\xf1\x31\xfc\x6e\x15\x6b\xee\x67\xe1\x4f\xeb\x11\xae\xc9\x48\xcc\xfd\x20\xe8\x58\xef\x55\x7b\xd2\x08\xaa\x0c\x42\xcc\x62\xc6\x3c\x80\xdd\x62\x38\xcc\x8f\x7f\x4f\x93\xea\x84\xb8\x54\xf6\x30\xd0\x03\x27\x90\x65\x5d\x86\xfb\x27\x68\x12\x9e\x57\xf0\x2f\x95\xe1\x24\x35\xba\xd9\x49\x6e\x44\xe8\x16\x1b\x08\xed\xc4\x47\x19\xf8\xa5\x10\xf0\x40\x54\x94\xcb\x28\xca\xb5\xe2\x98\x87\xda\xe4\x51\xf1\x2b\x8f\xac\x43\xf4\xbe\x0e\xd5\xf8\x04\x47\xe0\x5c\xf4\xc1\xd4\x77\xec\x7c\xe0\xad\x64\xdc\xbf\x8f\x51\xfa\xaa\x34\xa7\xab\x4b\x17\x52\xd6\x95\x27\xe1\x99\x16\xad\xd3\xf3\xda\xbd\x17\x6d\x01\x57\x97\xaf\xc5\xdb\x50\xb8\x2e\x8c\x44\xca\xfc\x98\x7d\xc8\x62\xb6\x68\x22\x8f\x46\xfd\x26\x50\x18\x99\xb8\x51\x1d\x79\x3c\x6a\x7b\x40\x06\xb2\xa2\x54\x6e\x93\x1b\x0b\x89\x24\x4c\xc6\x13\xb1\xdd\x8d\xc4\x92\x59\x73\x7d\x78\xf7\x09\x50\x2a\xd8\xc6\x18\x53\x4c\x23\xed\xb4\xc5\x9d\xfd\x99\x30\x08\x2b\x1c\x68\xb0\xc4\x13\xb4\x77\x85\x43\xcd\xbd\x78\x7b\x7f\xd3\x29\xfd\x5d\x21\x0d\xa4\x3c\x87\x8c\xfb\xa3\x77\x09\xaf\xfd\xec\xad\xfc\x5a\x4a\xe7\xa1\xb0\x56\x32\x11\x2e\x8a\x8f\x6e\x61\x8e\x87\x7e\x73\xdb\x22\xfe\x02\xec\x19\x90\xff\x7c\x8b\x20\xd8\x4b\x16\x96\x30\x46\x1b\x1f\xa5\x1a\xc3\x31\xfb\x74\x8c\x12\x73\x4a\xb3\x31\x7b\x99\xc5\x31\x6a\x87\x9b\x9c\x0d\xb7\x83\x8b\xc3\xac\xec\xa8\x7f\x3b\x73\xb5\x6f\x8f\x47\x7f\xca\x5e\x05\xd0\xcd\x79\x3c\xca\xf0\x1b\x18\xa8\xcd\xf3\x08\xba\xe4\x06\x5f\x41\xeb\x2e\x67\xf3\x0c\xae\xe5\xd0\x76\xa9\x4e\xb2\x5e\xac\xf7\x09\xf7\xcb\xb5\xec\x2f\x98\xea\x04\x1b\x66\x52\x4c\x67\xc5\x54\xff\x6f\xc7\x78\xb7\xde\x9f\x00\x00\x00\xff\xff\x61\x79\x63\x47\x24\x0d\x00\x00"),
 		},
 		"/templates": &vfsgen۰DirInfo{
 			name:    "templates",
@@ -46,9 +46,9 @@ var assets = func() http.FileSystem {
 		"/templates/cli_flags_generated.gotpl": &vfsgen۰CompressedFileInfo{
 			name:             "cli_flags_generated.gotpl",
 			modTime:          time.Time{},
-			uncompressedSize: 2102,
+			uncompressedSize: 2575,
 
-			compressedContent: []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xbc\x54\xc1\x52\xdb\x30\x10\x3d\xdb\x5f\xb1\xcd\x70\xb0\x99\x34\xb9\x97\xe1\x44\xa0\x65\xa6\x03\x4c\xa1\xdc\x15\x7b\xe5\x68\x10\x52\x90\xd6\x50\x26\x93\x7f\xef\xac\x24\x3b\x4e\x48\x21\x17\x7a\xd3\x6a\x9f\xde\x7b\x7a\x5a\x7b\x3a\x85\x33\x5b\x23\x34\x68\xd0\x09\xc2\x1a\xe6\xaf\x20\x9b\x13\x98\x5d\xc3\xd5\xf5\x1d\x9c\xcf\x2e\xef\x26\x79\xbe\x14\xd5\x83\x68\x10\x56\x2b\x98\xdc\xc4\xf5\x95\x78\x44\x58\xaf\xf3\x5c\x3d\x2e\xad\x23\x28\xf2\x6c\x24\xb5\x68\x46\x79\x36\xf2\xe4\x2a\x6b\x9e\x79\x49\xea\x11\x47\x79\x99\xaf\x56\xe0\x84\x69\x10\x8e\xd4\x18\x8e\x18\x08\xdf\x4e\x61\x72\xa1\x45\xe3\x99\x66\x3a\x65\xf2\xd0\x98\x24\x6a\xee\x81\xf2\x20\x20\xc0\x5f\x14\x2d\x80\x5e\x97\xb8\x01\xde\x71\xb5\x5e\xf7\xf5\xcc\x56\x24\x94\x66\xbe\x6d\xe0\x90\xd1\x93\x6b\x2b\x82\x55\x9e\xf1\x6e\x96\x79\x72\xca\x34\x79\xf6\xdb\x8b\x66\x50\x9e\x9b\xe7\x7b\xe1\x36\xf5\x85\xd2\x78\x23\x68\xd1\x6f\xfc\xc2\xa7\x56\x39\xac\xb3\xb9\xb5\x3a\xcf\x7e\xa8\xba\x46\x93\xa5\x6a\xb5\xfa\x0a\x4a\x02\x3e\x25\x03\xf7\x42\xb7\x08\xe4\xda\x90\x59\x16\xca\x6c\xf7\x1e\xf1\x18\x9a\xba\x5f\x0f\x29\x66\xe8\x49\x19\x41\xca\x9a\x0d\xd1\x60\x33\x3b\x7e\x97\x6f\x9d\x73\xc6\xb7\xc1\x3c\x38\xa4\xd6\x19\x8e\xd6\xa1\xa8\xc5\x5c\x23\x38\x5c\x3a\xf4\x68\x28\x2a\x58\x09\xb4\x50\x1e\x9e\xd9\x29\x9f\x2c\xa4\x75\xd0\x72\x46\x50\xa3\x14\xad\x26\x5f\xe6\xb2\x35\x15\x14\x72\x6f\xd0\x65\x12\x2b\x4a\x88\x91\x71\xe4\x51\x18\xb8\x1f\xbb\xe8\x0a\x59\x26\x73\xdf\x91\xc2\xf9\xce\x1d\x2d\x10\x0c\x6f\x04\x33\x18\xc6\xe0\x03\xc9\x44\xb1\x4f\x53\x06\x64\x92\xba\xf4\xdd\xf3\xf5\x6a\x2f\x0b\xa4\x05\x3a\xb0\x0e\x8c\xa5\x5e\x90\x27\xd0\x25\xec\x07\xe2\x1b\xd2\xa2\x04\x9e\x83\x2d\xf5\xae\x97\x1c\xec\x52\x80\xb6\xf6\xc1\x43\xbb\x0c\xca\x21\x76\xbe\xb7\x00\x6d\x2b\xa1\xf7\x2a\x8e\x3b\xef\x5b\x7c\x67\xd6\x10\xfe\xa1\x59\x7c\x24\x66\x56\x32\xdc\x48\xda\xd6\x74\x57\xa8\xe0\x38\xe1\xca\x37\xd4\x45\xc8\x3c\xe6\x17\xba\x7c\x1e\xa1\x23\x0e\xc3\x35\x1a\x0d\xbf\xbb\x61\x27\x6c\xa3\xf6\x5b\x1f\x66\xdf\xe9\xc6\x71\x90\x0c\xdf\xbb\x5d\xee\x35\x31\x86\x6a\xc2\x9b\xb7\x48\xfd\x8c\x68\x3b\x17\xfa\xe0\xf0\x9a\x00\xff\xcc\xf4\xf6\x1b\xfa\x5f\x19\x2a\x09\xd2\xf3\x9f\x34\xa6\x18\xcd\x5c\xc4\xc8\xba\x08\xcb\x13\xc6\x7c\x39\x05\xa3\xc2\x44\x1e\x14\xbc\xf4\x65\x9e\xad\xfb\x47\xfa\x77\x3e\xfc\x2e\x21\x95\x77\xe8\x52\x10\x63\xf0\x48\x70\x1c\xda\xc9\xe3\x67\x66\x23\x39\x17\x8f\x34\xf9\x19\x9c\x05\x27\x65\x8c\x6c\x98\xc6\xc6\xc0\x8d\x70\x1e\x5d\xd4\x5e\xf2\xba\x1e\x03\x3a\xc7\x34\xbd\x52\xc2\x0c\x5d\xec\x40\x65\xfc\xd9\x87\xda\xba\xc2\x28\x5d\x32\x36\xfd\xd5\x59\x9e\x81\x03\x03\x07\x64\x9c\xf1\x53\xbc\x71\x7a\x26\x3c\x45\xb7\xbb\x0c\x83\xfe\xd0\x69\x82\x45\xc3\x03\x53\x07\xbe\x73\x7f\xe2\x6f\x00\x00\x00\xff\xff\x9b\xde\x9c\x04\x36\x08\x00\x00"),
+			compressedContent: []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xbc\x55\x4d\x8f\xdb\x36\x10\x3d\x4b\xbf\x62\x6a\xe4\x20\x2d\x5c\xfb\xde\x20\xa7\x75\x36\x0d\x50\x24\x8b\xc6\xcd\x9d\x96\x86\x32\xb1\x34\xa9\x90\xa3\xdd\x2e\x0c\xff\xf7\x62\x48\xea\xc3\x1f\x58\xfb\xb2\x3d\x89\xe4\x0c\xdf\x7b\x7c\x33\xa4\x96\x4b\xb8\xb7\x35\x42\x83\x06\x9d\x20\xac\x61\xf3\x0a\xb2\xf9\x08\xab\xef\xf0\xed\xfb\x1a\x3e\xaf\xbe\xae\x17\x79\xde\x8a\xea\x49\x34\x08\xfb\x3d\x2c\x1e\xe3\xf8\x9b\xd8\x21\x1c\x0e\x79\xae\x76\xad\x75\x04\x45\x9e\xcd\xa4\x16\xcd\x8c\xbf\x3b\xe2\x8f\x27\x57\x59\xf3\xcc\x43\x52\x3b\x9c\xe5\x65\xbe\xdf\x83\x13\xa6\x41\xf8\xa0\xe6\xf0\x81\xf3\xe1\x8f\x4f\xb0\x78\xd0\xa2\xf1\x8c\xb6\x5c\x32\x47\x08\x2c\x12\x03\xc7\x40\x79\x10\x10\xd2\x5f\x14\x6d\x81\x5e\x5b\x1c\x13\xd7\x3c\x3b\x1c\x86\xf9\xca\x56\x24\x94\x66\xbc\xe3\xc4\x29\xa2\x27\xd7\x55\x04\xfb\x3c\xe3\xd5\x2c\xf3\xe4\x94\x69\xf2\xec\x1f\x2f\x9a\xc9\xf4\xb3\x79\xfe\x29\xdc\x38\x7f\x50\x1a\x1f\x05\x6d\x87\x85\xbf\xf1\x57\xa7\x1c\xd6\xd9\xc6\x5a\x9d\x67\x7f\xaa\xba\x46\x93\xa5\xd9\x7e\xff\x3b\x28\x09\xf8\x2b\x09\xf8\x29\x74\x87\x40\xae\x0b\xd6\x65\x61\x9a\x9d\x9e\x23\x6e\x43\x53\x0f\xe3\x29\xc4\x0a\x3d\x29\x23\x48\x59\x33\x02\x4d\x16\xb3\xbb\x37\xf1\x0e\x39\x7b\xfc\x23\x88\x07\x87\xd4\x39\xc3\xd6\x3a\x14\xb5\xd8\x68\x04\x87\xad\x43\x8f\x86\x22\x83\x95\x40\x5b\xe5\xe1\x99\x95\xf2\xce\x42\x5a\x07\x1d\x7b\x04\x35\x4a\xd1\x69\xf2\x65\x2e\x3b\x53\x41\x21\x2f\x1a\x5d\x26\xb2\xa2\x84\x68\x19\x5b\x1e\x89\x81\xe3\x31\x8a\xae\x90\x65\x12\xf7\x05\x29\xec\xef\xd5\xd1\x16\xc1\xf0\x42\x10\x83\xa1\x0d\xae\x50\x26\x88\x4b\x9c\x32\x64\x26\xaa\xaf\xbe\x2f\xdf\xc0\xf6\xb2\x45\xda\xa2\x03\xeb\xc0\x58\x1a\x08\xb9\x03\x5d\xca\xbd\x42\x3e\x82\x16\x25\x70\x1f\x1c\xb1\xf7\xb1\xa4\x60\x2d\x9e\xd0\xc7\xb6\x18\xce\xcb\x55\x9d\x9c\x15\x88\x73\x40\xc4\x22\xcc\xc1\xb2\xbe\x17\xe5\x11\xa4\xd0\x1e\xaf\xa8\x19\x09\xce\xd5\x0c\x3b\xa2\x80\xa1\x3d\xbe\x20\x85\x6b\x70\x54\x82\x58\xf4\xe4\x27\x77\xc1\xed\xb5\x08\x60\x97\x8b\x11\x42\x23\xed\x89\x13\x89\x21\xf5\x1f\x08\xdf\x43\x9c\xb4\xa9\x30\x35\x08\x03\xb8\x6b\xe9\x95\x91\x52\x96\x3a\x33\xd1\xd8\x1e\x8a\x40\x68\xbd\xb8\xae\xbd\xb7\x6e\xd4\x7e\xec\x5a\xba\x4a\x83\x77\xa7\x40\xa0\xad\x7d\xf2\xd0\xb5\x41\x4a\x24\xb7\x12\x04\x68\x5b\x09\x7d\x91\x77\xde\x1b\x70\x84\x77\x6f\x0d\xe1\xbf\xb4\x8a\xb7\x8e\x91\x95\x0c\x2d\x2a\x6d\x67\xfa\x9e\xac\xe0\x2e\xe5\x95\x67\xd0\x45\xb8\x44\xf1\x1c\x21\xca\xfb\x11\x7a\xe0\xf0\x5a\xcc\x66\xd3\x87\x74\x1a\x09\xcb\xa8\xfd\xd1\x4b\x3b\x44\xfa\xf7\x65\x52\x5b\x3e\x77\xd7\x5e\x14\x31\x87\x6a\xc1\x8b\x3f\x90\x86\x4b\xaf\xed\x46\xe8\x9b\xcd\x6b\x42\xfa\x7b\xba\x77\x59\xd0\xff\xe5\xa1\x92\x20\x3d\xff\x1a\xa3\x8b\x51\xcc\x43\xb4\xac\xb7\xb0\xfc\xc8\x39\xbf\x7d\x02\xa3\xc2\xa5\xbe\xc9\x78\xe9\xcb\x3c\x3b\x9c\xbf\x00\x67\xfe\x70\x5d\x82\x2b\x6f\xc0\x25\x23\xe6\xe0\x91\xe0\x2e\x84\x93\xc6\xf7\xf4\x46\xb2\x2f\x1e\x69\xf1\x57\x50\x16\x94\x94\xd1\xb2\xa9\x1b\xa3\x80\x47\xe1\x3c\xba\xc8\xdd\xf2\xb8\x9e\x03\x3a\xc7\x30\x03\x53\xca\x99\xaa\x38\x49\x95\xf1\xbe\x87\xb9\x75\x85\x51\xba\xe4\xdc\xf4\x9b\x66\x7a\x4e\x9c\x08\xb8\xc1\xe3\x8c\x4b\x71\xa6\xf4\x5e\x78\x8a\x6a\x4f\x11\x26\xf1\xa9\xd2\x94\x16\x05\x4f\x44\xdd\x58\xe7\x71\xc7\x7f\x01\x00\x00\xff\xff\xa4\x4d\x6e\xce\x0f\x0a\x00\x00"),
 		},
 	}
 	fs["/"].(*vfsgen۰DirInfo).entries = []os.FileInfo{
diff --git a/flag-gen/main.go b/flag-gen/main.go
index 9caa94e..579a7dd 100644
--- a/flag-gen/main.go
+++ b/flag-gen/main.go
@@ -27,6 +27,7 @@ type FlagType struct {
 	ContextType    string `json:"context_type"`
 	Parser         string `json:"parser"`
 	ParserCast     string `json:"parser_cast"`
+	ValueString    string `json:"valueString"`
 }
 
 func main() {
diff --git a/flag-gen/source/flag-types.json b/flag-gen/source/flag-types.json
index 3306025..7ec5ddd 100644
--- a/flag-gen/source/flag-types.json
+++ b/flag-gen/source/flag-types.json
@@ -5,7 +5,8 @@
     "value": false,
     "dest": true,
     "context_default": "false",
-    "parser": "strconv.ParseBool(f.Value.String())"
+    "parser": "strconv.ParseBool(f.Value.String())",
+    "valueString": "return \"\""
   },
   {
     "name": "BoolT",
@@ -14,7 +15,8 @@
     "dest": true,
     "doctail": " that is true by default",
     "context_default": "false",
-    "parser": "strconv.ParseBool(f.Value.String())"
+    "parser": "strconv.ParseBool(f.Value.String())",
+    "valueString": "return \"\""
   },
   {
     "name": "Duration",
@@ -23,7 +25,8 @@
     "dest": true,
     "doctail": " (see https://golang.org/pkg/time/#ParseDuration)",
     "context_default": "0",
-    "parser": "time.ParseDuration(f.Value.String())"
+    "parser": "time.ParseDuration(f.Value.String())",
+    "valueString": "return f.Value.String()"
   },
   {
     "name": "Float64",
@@ -31,7 +34,8 @@
     "value": true,
     "dest": true,
     "context_default": "0",
-    "parser": "strconv.ParseFloat(f.Value.String(), 64)"
+    "parser": "strconv.ParseFloat(f.Value.String(), 64)",
+    "valueString": "return fmt.Sprintf(\"%f\", f.Value)"
   },
   {
     "name": "Generic",
@@ -39,7 +43,8 @@
     "value": true,
     "dest": false,
     "context_default": "nil",
-    "context_type": "interface{}"
+    "context_type": "interface{}",
+    "valueString": "if f.Value != nil {\n\t\treturn f.Value.String()\n\t}\n\treturn \"\""
   },
   {
     "name": "Int64",
@@ -47,7 +52,8 @@
     "value": true,
     "dest": true,
     "context_default": "0",
-    "parser": "strconv.ParseInt(f.Value.String(), 0, 64)"
+    "parser": "strconv.ParseInt(f.Value.String(), 0, 64)",
+    "valueString": "return fmt.Sprintf(\"%d\", f.Value)"
   },
   {
     "name": "Int",
@@ -56,7 +62,8 @@
     "dest": true,
     "context_default": "0",
     "parser": "strconv.ParseInt(f.Value.String(), 0, 64)",
-    "parser_cast": "int(parsed)"
+    "parser_cast": "int(parsed)",
+    "valueString": "return fmt.Sprintf(\"%d\", f.Value)"
   },
   {
     "name": "IntSlice",
@@ -65,7 +72,8 @@
     "dest": false,
     "context_default": "nil",
     "context_type": "[]int",
-    "parser": "(f.Value.(*IntSlice)).Value(), error(nil)"
+    "parser": "(f.Value.(*IntSlice)).Value(), error(nil)",
+    "valueString": "if f.Value != nil {\n\t\treturn f.Value.String()\n\t}\n\treturn \"\""
   },
   {
     "name": "Int64Slice",
@@ -74,7 +82,8 @@
     "dest": false,
     "context_default": "nil",
     "context_type": "[]int64",
-    "parser": "(f.Value.(*Int64Slice)).Value(), error(nil)"
+    "parser": "(f.Value.(*Int64Slice)).Value(), error(nil)",
+    "valueString": "if f.Value != nil {\n\t\treturn f.Value.String()\n\t}\n\treturn \"\""
   },
   {
     "name": "String",
@@ -82,7 +91,8 @@
     "value": true,
     "dest": true,
     "context_default": "\"\"",
-    "parser": "f.Value.String(), error(nil)"
+    "parser": "f.Value.String(), error(nil)",
+    "valueString": "return f.Value"
   },
   {
     "name": "StringSlice",
@@ -91,7 +101,8 @@
     "dest": false,
     "context_default": "nil",
     "context_type": "[]string",
-    "parser": "(f.Value.(*StringSlice)).Value(), error(nil)"
+    "parser": "(f.Value.(*StringSlice)).Value(), error(nil)",
+    "valueString": "if f.Value != nil {\n\t\treturn f.Value.String()\n\t}\n\treturn \"\""
   },
   {
     "name": "Uint64",
@@ -99,7 +110,8 @@
     "value": true,
     "dest": true,
     "context_default": "0",
-    "parser": "strconv.ParseUint(f.Value.String(), 0, 64)"
+    "parser": "strconv.ParseUint(f.Value.String(), 0, 64)",
+    "valueString": "return fmt.Sprintf(\"%d\", f.Value)"
   },
   {
     "name": "Uint",
@@ -108,6 +120,7 @@
     "dest": true,
     "context_default": "0",
     "parser": "strconv.ParseUint(f.Value.String(), 0, 64)",
-    "parser_cast": "uint(parsed)"
+    "parser_cast": "uint(parsed)",
+    "valueString": "return fmt.Sprintf(\"%d\", f.Value)"
   }
 ]
diff --git a/flag-gen/templates/cli_flags_generated.gotpl b/flag-gen/templates/cli_flags_generated.gotpl
index 97bd49f..ffd18ae 100644
--- a/flag-gen/templates/cli_flags_generated.gotpl
+++ b/flag-gen/templates/cli_flags_generated.gotpl
@@ -4,6 +4,7 @@ package {{ .PackageName }}
 
 import (
 	"flag"
+	"fmt"
 	"strconv"
 	"time"
 )
@@ -40,6 +41,22 @@ func (f {{ $flag.Name }}Flag) IsRequired() bool {
 	return f.Required
 }
 
+// TakesValue returns true of the flag takes a value, otherwise false
+func (f {{ $flag.Name }}Flag) TakesValue() bool {
+	return {{ $flag.Value }}
+}
+
+// GetUsage returns the usage string for the flag
+func (f {{ $flag.Name }}Flag) GetUsage() string {
+	return f.Usage
+}
+
+// GetValue returns the flags value as string representation and an empty
+// string if the flag takes no value at all.
+func (f {{ $flag.Name }}Flag) GetValue() string {
+	{{ $flag.ValueString }}
+}
+
 // {{ $flag.Name }} looks up the value of a local {{ $flag.Name }}Flag, returns
 // {{ $flag.ContextDefault }} if not found
 func (c *Context) {{ $flag.Name }}(name string) {{ if ne .ContextType "" }}{{ $flag.ContextType }}{{ else }}{{ $flag.Type }}{{- end }} {
@@ -66,4 +83,4 @@ func lookup{{ $flag.Name }}(name string, set *flag.FlagSet) {{ if ne .ContextTyp
 	}
 	return {{ $flag.ContextDefault }}
 }
-{{ end }}
\ No newline at end of file
+{{ end }}
diff --git a/flag.go b/flag.go
index d98c808..5b6db0c 100644
--- a/flag.go
+++ b/flag.go
@@ -83,6 +83,21 @@ type RequiredFlag interface {
 	IsRequired() bool
 }
 
+// DocGenerationFlag is an interface that allows documentation generation for the flag
+type DocGenerationFlag interface {
+	Flag
+
+	// TakesValue returns true of the flag takes a value, otherwise false
+	TakesValue() bool
+
+	// GetUsage returns the usage string for the flag
+	GetUsage() string
+
+	// GetValue returns the flags value as string representation and an empty
+	// string if the flag takes no value at all.
+	GetValue() string
+}
+
 // errorableFlag is an interface that allows us to return errors during apply
 // it allows flags defined in this library to return errors in a fashion backwards compatible
 // TODO remove in v2 and modify the existing Flag interface to return errors
diff --git a/flag_generated.go b/flag_generated.go
index 83e10d7..af68f95 100644
--- a/flag_generated.go
+++ b/flag_generated.go
@@ -4,6 +4,7 @@ package cli
 
 import (
 	"flag"
+	"fmt"
 	"strconv"
 	"time"
 )
@@ -35,6 +36,22 @@ func (f BoolFlag) IsRequired() bool {
 	return f.Required
 }
 
+// TakesValue returns true of the flag takes a value, otherwise false
+func (f BoolFlag) TakesValue() bool {
+	return false
+}
+
+// GetUsage returns the usage string for the flag
+func (f BoolFlag) GetUsage() string {
+	return f.Usage
+}
+
+// GetValue returns the flags value as string representation and an empty
+// string if the flag takes no value at all.
+func (f BoolFlag) GetValue() string {
+	return ""
+}
+
 // Bool looks up the value of a local BoolFlag, returns
 // false if not found
 func (c *Context) Bool(name string) bool {
@@ -89,6 +106,22 @@ func (f BoolTFlag) IsRequired() bool {
 	return f.Required
 }
 
+// TakesValue returns true of the flag takes a value, otherwise false
+func (f BoolTFlag) TakesValue() bool {
+	return false
+}
+
+// GetUsage returns the usage string for the flag
+func (f BoolTFlag) GetUsage() string {
+	return f.Usage
+}
+
+// GetValue returns the flags value as string representation and an empty
+// string if the flag takes no value at all.
+func (f BoolTFlag) GetValue() string {
+	return ""
+}
+
 // BoolT looks up the value of a local BoolTFlag, returns
 // false if not found
 func (c *Context) BoolT(name string) bool {
@@ -144,6 +177,22 @@ func (f DurationFlag) IsRequired() bool {
 	return f.Required
 }
 
+// TakesValue returns true of the flag takes a value, otherwise false
+func (f DurationFlag) TakesValue() bool {
+	return true
+}
+
+// GetUsage returns the usage string for the flag
+func (f DurationFlag) GetUsage() string {
+	return f.Usage
+}
+
+// GetValue returns the flags value as string representation and an empty
+// string if the flag takes no value at all.
+func (f DurationFlag) GetValue() string {
+	return f.Value.String()
+}
+
 // Duration looks up the value of a local DurationFlag, returns
 // 0 if not found
 func (c *Context) Duration(name string) time.Duration {
@@ -199,6 +248,22 @@ func (f Float64Flag) IsRequired() bool {
 	return f.Required
 }
 
+// TakesValue returns true of the flag takes a value, otherwise false
+func (f Float64Flag) TakesValue() bool {
+	return true
+}
+
+// GetUsage returns the usage string for the flag
+func (f Float64Flag) GetUsage() string {
+	return f.Usage
+}
+
+// GetValue returns the flags value as string representation and an empty
+// string if the flag takes no value at all.
+func (f Float64Flag) GetValue() string {
+	return fmt.Sprintf("%f", f.Value)
+}
+
 // Float64 looks up the value of a local Float64Flag, returns
 // 0 if not found
 func (c *Context) Float64(name string) float64 {
@@ -253,6 +318,25 @@ func (f GenericFlag) IsRequired() bool {
 	return f.Required
 }
 
+// TakesValue returns true of the flag takes a value, otherwise false
+func (f GenericFlag) TakesValue() bool {
+	return true
+}
+
+// GetUsage returns the usage string for the flag
+func (f GenericFlag) GetUsage() string {
+	return f.Usage
+}
+
+// GetValue returns the flags value as string representation and an empty
+// string if the flag takes no value at all.
+func (f GenericFlag) GetValue() string {
+	if f.Value != nil {
+		return f.Value.String()
+	}
+	return ""
+}
+
 // Generic looks up the value of a local GenericFlag, returns
 // nil if not found
 func (c *Context) Generic(name string) interface{} {
@@ -308,6 +392,22 @@ func (f Int64Flag) IsRequired() bool {
 	return f.Required
 }
 
+// TakesValue returns true of the flag takes a value, otherwise false
+func (f Int64Flag) TakesValue() bool {
+	return true
+}
+
+// GetUsage returns the usage string for the flag
+func (f Int64Flag) GetUsage() string {
+	return f.Usage
+}
+
+// GetValue returns the flags value as string representation and an empty
+// string if the flag takes no value at all.
+func (f Int64Flag) GetValue() string {
+	return fmt.Sprintf("%d", f.Value)
+}
+
 // Int64 looks up the value of a local Int64Flag, returns
 // 0 if not found
 func (c *Context) Int64(name string) int64 {
@@ -363,6 +463,22 @@ func (f IntFlag) IsRequired() bool {
 	return f.Required
 }
 
+// TakesValue returns true of the flag takes a value, otherwise false
+func (f IntFlag) TakesValue() bool {
+	return true
+}
+
+// GetUsage returns the usage string for the flag
+func (f IntFlag) GetUsage() string {
+	return f.Usage
+}
+
+// GetValue returns the flags value as string representation and an empty
+// string if the flag takes no value at all.
+func (f IntFlag) GetValue() string {
+	return fmt.Sprintf("%d", f.Value)
+}
+
 // Int looks up the value of a local IntFlag, returns
 // 0 if not found
 func (c *Context) Int(name string) int {
@@ -417,6 +533,25 @@ func (f IntSliceFlag) IsRequired() bool {
 	return f.Required
 }
 
+// TakesValue returns true of the flag takes a value, otherwise false
+func (f IntSliceFlag) TakesValue() bool {
+	return true
+}
+
+// GetUsage returns the usage string for the flag
+func (f IntSliceFlag) GetUsage() string {
+	return f.Usage
+}
+
+// GetValue returns the flags value as string representation and an empty
+// string if the flag takes no value at all.
+func (f IntSliceFlag) GetValue() string {
+	if f.Value != nil {
+		return f.Value.String()
+	}
+	return ""
+}
+
 // IntSlice looks up the value of a local IntSliceFlag, returns
 // nil if not found
 func (c *Context) IntSlice(name string) []int {
@@ -471,6 +606,25 @@ func (f Int64SliceFlag) IsRequired() bool {
 	return f.Required
 }
 
+// TakesValue returns true of the flag takes a value, otherwise false
+func (f Int64SliceFlag) TakesValue() bool {
+	return true
+}
+
+// GetUsage returns the usage string for the flag
+func (f Int64SliceFlag) GetUsage() string {
+	return f.Usage
+}
+
+// GetValue returns the flags value as string representation and an empty
+// string if the flag takes no value at all.
+func (f Int64SliceFlag) GetValue() string {
+	if f.Value != nil {
+		return f.Value.String()
+	}
+	return ""
+}
+
 // Int64Slice looks up the value of a local Int64SliceFlag, returns
 // nil if not found
 func (c *Context) Int64Slice(name string) []int64 {
@@ -526,6 +680,22 @@ func (f StringFlag) IsRequired() bool {
 	return f.Required
 }
 
+// TakesValue returns true of the flag takes a value, otherwise false
+func (f StringFlag) TakesValue() bool {
+	return true
+}
+
+// GetUsage returns the usage string for the flag
+func (f StringFlag) GetUsage() string {
+	return f.Usage
+}
+
+// GetValue returns the flags value as string representation and an empty
+// string if the flag takes no value at all.
+func (f StringFlag) GetValue() string {
+	return f.Value
+}
+
 // String looks up the value of a local StringFlag, returns
 // "" if not found
 func (c *Context) String(name string) string {
@@ -580,6 +750,25 @@ func (f StringSliceFlag) IsRequired() bool {
 	return f.Required
 }
 
+// TakesValue returns true of the flag takes a value, otherwise false
+func (f StringSliceFlag) TakesValue() bool {
+	return true
+}
+
+// GetUsage returns the usage string for the flag
+func (f StringSliceFlag) GetUsage() string {
+	return f.Usage
+}
+
+// GetValue returns the flags value as string representation and an empty
+// string if the flag takes no value at all.
+func (f StringSliceFlag) GetValue() string {
+	if f.Value != nil {
+		return f.Value.String()
+	}
+	return ""
+}
+
 // StringSlice looks up the value of a local StringSliceFlag, returns
 // nil if not found
 func (c *Context) StringSlice(name string) []string {
@@ -635,6 +824,22 @@ func (f Uint64Flag) IsRequired() bool {
 	return f.Required
 }
 
+// TakesValue returns true of the flag takes a value, otherwise false
+func (f Uint64Flag) TakesValue() bool {
+	return true
+}
+
+// GetUsage returns the usage string for the flag
+func (f Uint64Flag) GetUsage() string {
+	return f.Usage
+}
+
+// GetValue returns the flags value as string representation and an empty
+// string if the flag takes no value at all.
+func (f Uint64Flag) GetValue() string {
+	return fmt.Sprintf("%d", f.Value)
+}
+
 // Uint64 looks up the value of a local Uint64Flag, returns
 // 0 if not found
 func (c *Context) Uint64(name string) uint64 {
@@ -690,6 +895,22 @@ func (f UintFlag) IsRequired() bool {
 	return f.Required
 }
 
+// TakesValue returns true of the flag takes a value, otherwise false
+func (f UintFlag) TakesValue() bool {
+	return true
+}
+
+// GetUsage returns the usage string for the flag
+func (f UintFlag) GetUsage() string {
+	return f.Usage
+}
+
+// GetValue returns the flags value as string representation and an empty
+// string if the flag takes no value at all.
+func (f UintFlag) GetValue() string {
+	return fmt.Sprintf("%d", f.Value)
+}
+
 // Uint looks up the value of a local UintFlag, returns
 // 0 if not found
 func (c *Context) Uint(name string) uint {
@@ -716,3 +937,4 @@ func lookupUint(name string, set *flag.FlagSet) uint {
 	}
 	return 0
 }
+
diff --git a/go.mod b/go.mod
index de7b6e5..91106bc 100644
--- a/go.mod
+++ b/go.mod
@@ -4,6 +4,7 @@ go 1.10
 
 require (
 	github.com/BurntSushi/toml v0.3.1
+	github.com/cpuguy83/go-md2man v1.0.10
 	github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 // indirect
 	github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd // indirect
 	gopkg.in/yaml.v2 v2.2.2
diff --git a/go.sum b/go.sum
index f3cb3d0..2b8b863 100644
--- a/go.sum
+++ b/go.sum
@@ -1,5 +1,9 @@
 github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk=
+github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
+github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
+github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
 github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 h1:bUGsEnyNbVPw06Bs80sCeARAlK8lhwqGyi6UT8ymuGk=
 github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
 github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd h1:ug7PpSOB5RBPK1Kg6qskGBoP3Vnj/aNYFTznWvlkGo0=
diff --git a/help.go b/help.go
index e504fc2..33985d3 100644
--- a/help.go
+++ b/help.go
@@ -10,79 +10,6 @@ import (
 	"unicode/utf8"
 )
 
-// AppHelpTemplate is the text template for the Default help topic.
-// cli.go uses text/template to render templates. You can
-// render custom help text by setting this variable.
-var AppHelpTemplate = `NAME:
-   {{.Name}}{{if .Usage}} - {{.Usage}}{{end}}
-
-USAGE:
-   {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Version}}{{if not .HideVersion}}
-
-VERSION:
-   {{.Version}}{{end}}{{end}}{{if .Description}}
-
-DESCRIPTION:
-   {{.Description}}{{end}}{{if len .Authors}}
-
-AUTHOR{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}:
-   {{range $index, $author := .Authors}}{{if $index}}
-   {{end}}{{$author}}{{end}}{{end}}{{if .VisibleCommands}}
-
-COMMANDS:{{range .VisibleCategories}}{{if .Name}}
-
-   {{.Name}}:{{range .VisibleCommands}}
-     {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{range .VisibleCommands}}
-   {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{end}}{{if .VisibleFlags}}
-
-GLOBAL OPTIONS:
-   {{range $index, $option := .VisibleFlags}}{{if $index}}
-   {{end}}{{$option}}{{end}}{{end}}{{if .Copyright}}
-
-COPYRIGHT:
-   {{.Copyright}}{{end}}
-`
-
-// CommandHelpTemplate is the text template for the command help topic.
-// cli.go uses text/template to render templates. You can
-// render custom help text by setting this variable.
-var CommandHelpTemplate = `NAME:
-   {{.HelpName}} - {{.Usage}}
-
-USAGE:
-   {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}}{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Category}}
-
-CATEGORY:
-   {{.Category}}{{end}}{{if .Description}}
-
-DESCRIPTION:
-   {{.Description}}{{end}}{{if .VisibleFlags}}
-
-OPTIONS:
-   {{range .VisibleFlags}}{{.}}
-   {{end}}{{end}}
-`
-
-// SubcommandHelpTemplate is the text template for the subcommand help topic.
-// cli.go uses text/template to render templates. You can
-// render custom help text by setting this variable.
-var SubcommandHelpTemplate = `NAME:
-   {{.HelpName}} - {{if .Description}}{{.Description}}{{else}}{{.Usage}}{{end}}
-
-USAGE:
-   {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}
-
-COMMANDS:{{range .VisibleCategories}}{{if .Name}}
-
-   {{.Name}}:{{range .VisibleCommands}}
-     {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{range .VisibleCommands}}
-   {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{if .VisibleFlags}}
-
-OPTIONS:
-   {{range .VisibleFlags}}{{.}}
-   {{end}}{{end}}
-`
-
 var helpCommand = Command{
 	Name:      "help",
 	Aliases:   []string{"h"},
diff --git a/template.go b/template.go
new file mode 100644
index 0000000..24c44c8
--- /dev/null
+++ b/template.go
@@ -0,0 +1,107 @@
+package cli
+
+// AppHelpTemplate is the text template for the Default help topic.
+// cli.go uses text/template to render templates. You can
+// render custom help text by setting this variable.
+var AppHelpTemplate = `NAME:
+   {{.Name}}{{if .Usage}} - {{.Usage}}{{end}}
+
+USAGE:
+   {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Version}}{{if not .HideVersion}}
+
+VERSION:
+   {{.Version}}{{end}}{{end}}{{if .Description}}
+
+DESCRIPTION:
+   {{.Description}}{{end}}{{if len .Authors}}
+
+AUTHOR{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}:
+   {{range $index, $author := .Authors}}{{if $index}}
+   {{end}}{{$author}}{{end}}{{end}}{{if .VisibleCommands}}
+
+COMMANDS:{{range .VisibleCategories}}{{if .Name}}
+
+   {{.Name}}:{{range .VisibleCommands}}
+     {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{range .VisibleCommands}}
+   {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{end}}{{if .VisibleFlags}}
+
+GLOBAL OPTIONS:
+   {{range $index, $option := .VisibleFlags}}{{if $index}}
+   {{end}}{{$option}}{{end}}{{end}}{{if .Copyright}}
+
+COPYRIGHT:
+   {{.Copyright}}{{end}}
+`
+
+// CommandHelpTemplate is the text template for the command help topic.
+// cli.go uses text/template to render templates. You can
+// render custom help text by setting this variable.
+var CommandHelpTemplate = `NAME:
+   {{.HelpName}} - {{.Usage}}
+
+USAGE:
+   {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}}{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Category}}
+
+CATEGORY:
+   {{.Category}}{{end}}{{if .Description}}
+
+DESCRIPTION:
+   {{.Description}}{{end}}{{if .VisibleFlags}}
+
+OPTIONS:
+   {{range .VisibleFlags}}{{.}}
+   {{end}}{{end}}
+`
+
+// SubcommandHelpTemplate is the text template for the subcommand help topic.
+// cli.go uses text/template to render templates. You can
+// render custom help text by setting this variable.
+var SubcommandHelpTemplate = `NAME:
+   {{.HelpName}} - {{if .Description}}{{.Description}}{{else}}{{.Usage}}{{end}}
+
+USAGE:
+   {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}
+
+COMMANDS:{{range .VisibleCategories}}{{if .Name}}
+
+   {{.Name}}:{{range .VisibleCommands}}
+     {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{range .VisibleCommands}}
+   {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{if .VisibleFlags}}
+
+OPTIONS:
+   {{range .VisibleFlags}}{{.}}
+   {{end}}{{end}}
+`
+
+var MarkdownDocTemplate = `% {{ .App.Name }}(8) {{ .App.Description }}
+
+% {{ .App.Author }}
+
+# NAME
+
+{{ .App.Name }}{{ if .App.Usage }} - {{ .App.Usage }}{{ end }}
+
+# SYNOPSIS
+
+{{ .App.Name }}
+{{ if .SynopsisArgs }}
+` + "```" + `
+{{ range $v := .SynopsisArgs }}{{ $v }}{{ end }}` + "```" + `
+{{ end }}{{ if .App.UsageText }}
+# DESCRIPTION
+
+{{ .App.UsageText }}
+{{ end }}
+**Usage**:
+
+` + "```" + `
+{{ .App.Name }} [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...]
+` + "```" + `
+{{ if .GlobalArgs }}
+# GLOBAL OPTIONS
+{{ range $v := .GlobalArgs }}
+{{ $v }}{{ end }}
+{{ end }}{{ if .Commands }}
+# COMMANDS
+{{ range $v := .Commands }}
+{{ $v }}{{ end }}{{ end }}`


Test patch:
diff --git a/app_test.go b/app_test.go
index 3fc27b5..f7f53b7 100644
--- a/app_test.go
+++ b/app_test.go
@@ -978,7 +978,7 @@ func TestRequiredFlagAppRunBehavior(t *testing.T) {
 		{
 			testCase:    "error_case_empty_input_with_required_flag_on_command",
 			appRunInput: []string{"myCLI", "myCommand"},
-			appCommands: []Command{Command{
+			appCommands: []Command{{
 				Name:  "myCommand",
 				Flags: []Flag{StringFlag{Name: "requiredFlag", Required: true}},
 			}},
@@ -987,9 +987,9 @@ func TestRequiredFlagAppRunBehavior(t *testing.T) {
 		{
 			testCase:    "error_case_empty_input_with_required_flag_on_subcommand",
 			appRunInput: []string{"myCLI", "myCommand", "mySubCommand"},
-			appCommands: []Command{Command{
+			appCommands: []Command{{
 				Name: "myCommand",
-				Subcommands: []Command{Command{
+				Subcommands: []Command{{
 					Name:  "mySubCommand",
 					Flags: []Flag{StringFlag{Name: "requiredFlag", Required: true}},
 				}},
@@ -1005,7 +1005,7 @@ func TestRequiredFlagAppRunBehavior(t *testing.T) {
 		{
 			testCase:    "valid_case_help_input_with_required_flag_on_command",
 			appRunInput: []string{"myCLI", "myCommand", "--help"},
-			appCommands: []Command{Command{
+			appCommands: []Command{{
 				Name:  "myCommand",
 				Flags: []Flag{StringFlag{Name: "requiredFlag", Required: true}},
 			}},
@@ -1013,9 +1013,9 @@ func TestRequiredFlagAppRunBehavior(t *testing.T) {
 		{
 			testCase:    "valid_case_help_input_with_required_flag_on_subcommand",
 			appRunInput: []string{"myCLI", "myCommand", "mySubCommand", "--help"},
-			appCommands: []Command{Command{
+			appCommands: []Command{{
 				Name: "myCommand",
-				Subcommands: []Command{Command{
+				Subcommands: []Command{{
 					Name:  "mySubCommand",
 					Flags: []Flag{StringFlag{Name: "requiredFlag", Required: true}},
 				}},
@@ -1031,7 +1031,7 @@ func TestRequiredFlagAppRunBehavior(t *testing.T) {
 		{
 			testCase:    "error_case_optional_input_with_required_flag_on_command",
 			appRunInput: []string{"myCLI", "myCommand", "--optional", "cats"},
-			appCommands: []Command{Command{
+			appCommands: []Command{{
 				Name:  "myCommand",
 				Flags: []Flag{StringFlag{Name: "requiredFlag", Required: true}, StringFlag{Name: "optional"}},
 			}},
@@ -1040,9 +1040,9 @@ func TestRequiredFlagAppRunBehavior(t *testing.T) {
 		{
 			testCase:    "error_case_optional_input_with_required_flag_on_subcommand",
 			appRunInput: []string{"myCLI", "myCommand", "mySubCommand", "--optional", "cats"},
-			appCommands: []Command{Command{
+			appCommands: []Command{{
 				Name: "myCommand",
-				Subcommands: []Command{Command{
+				Subcommands: []Command{{
 					Name:  "mySubCommand",
 					Flags: []Flag{StringFlag{Name: "requiredFlag", Required: true}, StringFlag{Name: "optional"}},
 				}},
@@ -1058,7 +1058,7 @@ func TestRequiredFlagAppRunBehavior(t *testing.T) {
 		{
 			testCase:    "valid_case_required_flag_input_on_command",
 			appRunInput: []string{"myCLI", "myCommand", "--requiredFlag", "cats"},
-			appCommands: []Command{Command{
+			appCommands: []Command{{
 				Name:  "myCommand",
 				Flags: []Flag{StringFlag{Name: "requiredFlag", Required: true}},
 			}},
@@ -1066,9 +1066,9 @@ func TestRequiredFlagAppRunBehavior(t *testing.T) {
 		{
 			testCase:    "valid_case_required_flag_input_on_subcommand",
 			appRunInput: []string{"myCLI", "myCommand", "mySubCommand", "--requiredFlag", "cats"},
-			appCommands: []Command{Command{
+			appCommands: []Command{{
 				Name: "myCommand",
-				Subcommands: []Command{Command{
+				Subcommands: []Command{{
 					Name:  "mySubCommand",
 					Flags: []Flag{StringFlag{Name: "requiredFlag", Required: true}},
 				}},
@@ -1825,6 +1825,18 @@ func (c *customBoolFlag) GetName() string {
 	return c.Nombre
 }
 
+func (c *customBoolFlag) TakesValue() bool {
+	return false
+}
+
+func (c *customBoolFlag) GetValue() string {
+	return "value"
+}
+
+func (c *customBoolFlag) GetUsage() string {
+	return "usage"
+}
+
 func (c *customBoolFlag) Apply(set *flag.FlagSet) {
 	set.String(c.Nombre, c.Nombre, "")
 }
diff --git a/docs_test.go b/docs_test.go
new file mode 100644
index 0000000..177eb19
--- /dev/null
+++ b/docs_test.go
@@ -0,0 +1,115 @@
+package cli
+
+import (
+	"io/ioutil"
+	"testing"
+)
+
+func testApp() *App {
+	app := NewApp()
+	app.Name = "greet"
+	app.Flags = []Flag{
+		StringFlag{
+			Name:  "socket, s",
+			Usage: "some usage text",
+			Value: "value",
+		},
+		StringFlag{Name: "flag, fl, f"},
+		BoolFlag{
+			Name:  "another-flag, b",
+			Usage: "another usage text",
+		},
+	}
+	app.Commands = []Command{{
+		Aliases: []string{"c"},
+		Flags: []Flag{
+			StringFlag{Name: "flag, fl, f"},
+			BoolFlag{
+				Name:  "another-flag, b",
+				Usage: "another usage text",
+			},
+		},
+		Name:  "config",
+		Usage: "another usage test",
+		Subcommands: []Command{{
+			Aliases: []string{"s", "ss"},
+			Flags: []Flag{
+				StringFlag{Name: "sub-flag, sub-fl, s"},
+				BoolFlag{
+					Name:  "sub-command-flag, s",
+					Usage: "some usage text",
+				},
+			},
+			Name:  "sub-config",
+			Usage: "another usage test",
+		}},
+	}, {
+		Aliases: []string{"i", "in"},
+		Name:    "info",
+		Usage:   "retrieve generic information",
+	}, {
+		Name: "some-command",
+	}}
+	app.UsageText = "app [first_arg] [second_arg]"
+	app.Usage = "Some app"
+	app.Author = "Harrison"
+	app.Email = "harrison@lolwut.com"
+	app.Authors = []Author{{Name: "Oliver Allen", Email: "oliver@toyshop.com"}}
+	return app
+}
+
+func expectFileContent(t *testing.T, file, expected string) {
+	data, err := ioutil.ReadFile(file)
+	expect(t, err, nil)
+	expect(t, string(data), expected)
+}
+
+func TestToMarkdownFull(t *testing.T) {
+	// Given
+	app := testApp()
+
+	// When
+	res, err := app.ToMarkdown()
+
+	// Then
+	expect(t, err, nil)
+	expectFileContent(t, "testdata/expected-doc-full.md", res)
+}
+
+func TestToMarkdownNoFlags(t *testing.T) {
+	// Given
+	app := testApp()
+	app.Flags = nil
+
+	// When
+	res, err := app.ToMarkdown()
+
+	// Then
+	expect(t, err, nil)
+	expectFileContent(t, "testdata/expected-doc-no-flags.md", res)
+}
+
+func TestToMarkdownNoCommands(t *testing.T) {
+	// Given
+	app := testApp()
+	app.Commands = nil
+
+	// When
+	res, err := app.ToMarkdown()
+
+	// Then
+	expect(t, err, nil)
+	expectFileContent(t, "testdata/expected-doc-no-commands.md", res)
+}
+
+func TestToMan(t *testing.T) {
+	// Given
+	app := testApp()
+
+	// When
+	res, err := app.ToMan()
+
+	// Then
+	expect(t, err, nil)
+	expectFileContent(t, "testdata/expected-doc-full.man", res)
+}
diff --git a/testdata/expected-doc-full.man b/testdata/expected-doc-full.man
new file mode 100644
index 0000000..a2d0e6d
--- /dev/null
+++ b/testdata/expected-doc-full.man
@@ -0,0 +1,80 @@
+.nh
+.TH greet(8) 
+
+.SH Harrison
+
+.SH NAME
+.PP
+greet \- Some app
+
+
+.SH SYNOPSIS
+.PP
+greet
+
+.PP
+.RS
+
+.nf
+[\-\-another\-flag|\-b]
+[\-\-flag|\-\-fl|\-f]=[value]
+[\-\-socket|\-s]=[value]
+
+.fi
+.RE
+
+
+.SH DESCRIPTION
+.PP
+app [first\_arg] [second\_arg]
+
+.PP
+\fBUsage\fP:
+
+.PP
+.RS
+
+.nf
+greet [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...]
+
+.fi
+.RE
+
+
+.SH GLOBAL OPTIONS
+.PP
+\fB\-\-another\-flag, \-b\fP: another usage text
+
+.PP
+\fB\-\-flag, \-\-fl, \-f\fP="":
+
+.PP
+\fB\-\-socket, \-s\fP="": some usage text (default: value)
+
+
+.SH COMMANDS
+.SH config, c
+.PP
+another usage test
+
+.PP
+\fB\-\-another\-flag, \-b\fP: another usage text
+
+.PP
+\fB\-\-flag, \-\-fl, \-f\fP="":
+
+.SS sub\-config, s, ss
+.PP
+another usage test
+
+.PP
+\fB\-\-sub\-command\-flag, \-s\fP: some usage text
+
+.PP
+\fB\-\-sub\-flag, \-\-sub\-fl, \-s\fP="":
+
+.SH info, i, in
+.PP
+retrieve generic information
+
+.SH some\-command
\ No newline at end of file
diff --git a/testdata/expected-doc-full.md b/testdata/expected-doc-full.md
new file mode 100644
index 0000000..8fb9f58
--- /dev/null
+++ b/testdata/expected-doc-full.md
@@ -0,0 +1,62 @@
+% greet(8) 
+
+% Harrison
+
+# NAME
+
+greet - Some app
+
+# SYNOPSIS
+
+greet
+
+```
+[--another-flag|-b]
+[--flag|--fl|-f]=[value]
+[--socket|-s]=[value]
+```
+
+# DESCRIPTION
+
+app [first_arg] [second_arg]
+
+**Usage**:
+
+```
+greet [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...]
+```
+
+# GLOBAL OPTIONS
+
+**--another-flag, -b**: another usage text
+
+**--flag, --fl, -f**="": 
+
+**--socket, -s**="": some usage text (default: value)
+
+
+# COMMANDS
+
+## config, c
+
+another usage test
+
+**--another-flag, -b**: another usage text
+
+**--flag, --fl, -f**="": 
+
+### sub-config, s, ss
+
+another usage test
+
+**--sub-command-flag, -s**: some usage text
+
+**--sub-flag, --sub-fl, -s**="": 
+
+## info, i, in
+
+retrieve generic information
+
+## some-command
+
+
diff --git a/testdata/expected-doc-no-commands.md b/testdata/expected-doc-no-commands.md
new file mode 100644
index 0000000..dba7bb0
--- /dev/null
+++ b/testdata/expected-doc-no-commands.md
@@ -0,0 +1,36 @@
+% greet(8) 
+
+% Harrison
+
+# NAME
+
+greet - Some app
+
+# SYNOPSIS
+
+greet
+
+```
+[--another-flag|-b]
+[--flag|--fl|-f]=[value]
+[--socket|-s]=[value]
+```
+
+# DESCRIPTION
+
+app [first_arg] [second_arg]
+
+**Usage**:
+
+```
+greet [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...]
+```
+
+# GLOBAL OPTIONS
+
+**--another-flag, -b**: another usage text
+
+**--flag, --fl, -f**="": 
+
+**--socket, -s**="": some usage text (default: value)
+
diff --git a/testdata/expected-doc-no-flags.md b/testdata/expected-doc-no-flags.md
new file mode 100644
index 0000000..8ce60fd
--- /dev/null
+++ b/testdata/expected-doc-no-flags.md
@@ -0,0 +1,47 @@
+% greet(8) 
+
+% Harrison
+
+# NAME
+
+greet - Some app
+
+# SYNOPSIS
+
+greet
+
+# DESCRIPTION
+
+app [first_arg] [second_arg]
+
+**Usage**:
+
+```
+greet [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...]
+```
+
+# COMMANDS
+
+## config, c
+
+another usage test
+
+**--another-flag, -b**: another usage text
+
+**--flag, --fl, -f**="": 
+
+### sub-config, s, ss
+
+another usage test
+
+**--sub-command-flag, -s**: some usage text
+
+**--sub-flag, --sub-fl, -s**="": 
+
+## info, i, in
+
+retrieve generic information
+
+## some-command
+
+