Repository: spf13/cobra
Pull request #87 (2015-04-07 13:53:14)

Auto generation of markdown docs!
An example from the kubernetes project, for the `kubectl config`
command, which as subcommands, and flags, and all sorts of stuff, it
will generate markdown like so:

config modifies .kubeconfig files

config modifies .kubeconfig files using subcommands like "kubectl config set current-context my-context"

```
kubectl config SUBCOMMAND
```

```
      --envvar=false: use the .kubeconfig from $KUBECONFIG
      --global=false: use the .kubeconfig from /home/username
  -h, --help=false: help for config
      --kubeconfig="": use a particular .kubeconfig file
      --local=false: use the .kubeconfig in the current directory
```

```
      --alsologtostderr=false: log to standard error as well as files
      --api-version="": The API version to use when talking to the server
  -a, --auth-path="": Path to the auth info file. If missing, prompt the user. Only used if using https.
      --certificate-authority="": Path to a cert. file for the certificate authority.
      --client-certificate="": Path to a client key file for TLS.
      --client-key="": Path to a client key file for TLS.
      --cluster="": The name of the kubeconfig cluster to use
      --context="": The name of the kubeconfig context to use
      --insecure-skip-tls-verify=false: If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure.
      --log_backtrace_at=:0: when logging hits line file:N, emit a stack trace
      --log_dir=: If non-empty, write log files in this directory
      --log_flush_frequency=5s: Maximum number of seconds between log flushes
      --logtostderr=true: log to standard error instead of files
      --match-server-version=false: Require server version to match client version
      --namespace="": If present, the namespace scope for this CLI request.
      --password="": Password for basic authentication to the API server.
  -s, --server="": The address and port of the Kubernetes API server
      --stderrthreshold=2: logs at or above this threshold go to stderr
      --token="": Bearer token for authentication to the API server.
      --user="": The name of the kubeconfig user to use
      --username="": Username for basic authentication to the API server.
      --v=0: log level for V logs
      --validate=false: If true, use a schema to validate the input before sending it
      --vmodule=: comma-separated list of pattern=N settings for file-filtered logging
```

* [kubectl](kubectl.md)	 - kubectl controls the Kubernetes cluster manager
* [kubectl config set](kubectl_config_set.md)	 - Sets an individual value in a .kubeconfig file
* [kubectl config set-cluster](kubectl_config_set-cluster.md)	 - Sets a cluster entry in .kubeconfig
* [kubectl config set-context](kubectl_config_set-context.md)	 - Sets a context entry in .kubeconfig
* [kubectl config set-credentials](kubectl_config_set-credentials.md)	 - Sets a user entry in .kubeconfig
* [kubectl config unset](kubectl_config_unset.md)	 - Unsets an individual value in a .kubeconfig file
* [kubectl config use-context](kubectl_config_use-context.md)	 - Sets the current-context in a .kubeconfig file
* [kubectl config view](kubectl_config_view.md)	 - displays merged .kubeconfig settings or a specified .kubeconfig file.

fabianofranz: Add README docs about it, otherwise LGTM.

Patch:
diff --git a/README.md b/README.md
index c4155d6..1b29a86 100644
--- a/README.md
+++ b/README.md
@@ -348,6 +348,10 @@ Like help the function and template are over ridable through public methods.
 
     command.SetUsageTemplate(s string)
 
+## Generating markdown formatted documentation for your command
+
+Cobra can generate a markdown formatted document based on the subcommands, flags, etc. A simple example of how to do this for your command can be found in [Markdown Docs](md_docs.md)
+
 ## Generating bash completions for your command
 
 Cobra can generate a bash completions file. If you add more information to your command these completions can be amazingly powerful and flexible.  Read more about [Bash Completions](bash_completions.md)
diff --git a/md_docs.go b/md_docs.go
new file mode 100644
index 0000000..71bc8d5
--- /dev/null
+++ b/md_docs.go
@@ -0,0 +1,121 @@
+//Copyright 2015 Red Hat Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cobra
+
+import (
+	"bytes"
+	"fmt"
+	"os"
+	"sort"
+	"strings"
+	"time"
+)
+
+func printOptions(out *bytes.Buffer, cmd *Command, name string) {
+	flags := cmd.NonInheritedFlags()
+	flags.SetOutput(out)
+	if flags.HasFlags() {
+		fmt.Fprintf(out, "### Options\n\n```\n")
+		flags.PrintDefaults()
+		fmt.Fprintf(out, "```\n\n")
+	}
+
+	parentFlags := cmd.InheritedFlags()
+	parentFlags.SetOutput(out)
+	if parentFlags.HasFlags() {
+		fmt.Fprintf(out, "### Options inherrited from parent commands\n\n```\n")
+		parentFlags.PrintDefaults()
+		fmt.Fprintf(out, "```\n\n")
+	}
+}
+
+type byName []*Command
+
+func (s byName) Len() int           { return len(s) }
+func (s byName) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
+func (s byName) Less(i, j int) bool { return s[i].Name() < s[j].Name() }
+
+func GenMarkdown(cmd *Command, out *bytes.Buffer) {
+	name := cmd.CommandPath()
+
+	short := cmd.Short
+	long := cmd.Long
+	if len(long) == 0 {
+		long = short
+	}
+
+	fmt.Fprintf(out, "## %s\n\n", name)
+	fmt.Fprintf(out, "%s\n\n", short)
+	fmt.Fprintf(out, "### Synopsis\n\n")
+	fmt.Fprintf(out, "\n%s\n\n", long)
+
+	if cmd.Runnable() {
+		fmt.Fprintf(out, "```\n%s\n```\n\n", cmd.UseLine())
+	}
+
+	if len(cmd.Example) > 0 {
+		fmt.Fprintf(out, "### Examples\n\n")
+		fmt.Fprintf(out, "```\n%s\n```\n\n", cmd.Example)
+	}
+
+	printOptions(out, cmd, name)
+
+	if len(cmd.Commands()) > 0 || cmd.HasParent() {
+		fmt.Fprintf(out, "### SEE ALSO\n")
+		if cmd.HasParent() {
+			parent := cmd.Parent()
+			pname := parent.CommandPath()
+			link := pname + ".md"
+			link = strings.Replace(link, " ", "_", -1)
+			fmt.Fprintf(out, "* [%s](%s)\t - %s\n", pname, link, parent.Short)
+		}
+
+		children := cmd.Commands()
+		sort.Sort(byName(children))
+
+		for _, child := range children {
+			cname := name + " " + child.Name()
+			link := cname + ".md"
+			link = strings.Replace(link, " ", "_", -1)
+			fmt.Fprintf(out, "* [%s](%s)\t - %s\n", cname, link, child.Short)
+		}
+		fmt.Fprintf(out, "\n")
+	}
+
+	fmt.Fprintf(out, "###### Auto generated by spf13/cobra at %s\n", time.Now().UTC())
+}
+
+func GenMarkdownTree(cmd *Command, dir string) {
+	for _, c := range cmd.Commands() {
+		GenMarkdownTree(c, dir)
+	}
+
+	out := new(bytes.Buffer)
+
+	GenMarkdown(cmd, out)
+
+	filename := cmd.CommandPath()
+	filename = dir + strings.Replace(filename, " ", "_", -1) + ".md"
+	outFile, err := os.Create(filename)
+	if err != nil {
+		fmt.Println(err)
+		os.Exit(1)
+	}
+	defer outFile.Close()
+	_, err = outFile.Write(out.Bytes())
+	if err != nil {
+		fmt.Println(err)
+		os.Exit(1)
+	}
+}
diff --git a/md_docs.md b/md_docs.md
new file mode 100644
index 0000000..43b6c99
--- /dev/null
+++ b/md_docs.md
@@ -0,0 +1,35 @@
+# Generating Markdown Docs For Your Own cobra.Command
+
+## Generate markdown docs for the entire command tree
+
+This program can actually generate docs for the kubectl command in the kubernetes project
+
+```go
+package main
+
+import (
+	"io/ioutil"
+	"os"
+
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd"
+	"github.com/spf13/cobra"
+)
+
+func main() {
+	kubectl := cmd.NewFactory(nil).NewKubectlCommand(os.Stdin, ioutil.Discard, ioutil.Discard)
+	cobra.GenMarkdownTree(kubectl, "./")
+}
+```
+
+This will generate a whole series of files, one for each command in the tree, in the directory specified (in this case "./")
+
+## Generate markdown docs for a single command
+
+You may wish to have more control over the output, or only generate for a single command, instead of the entire command tree. If this is the case you may prefer to `GenMarkdown()` instead of `GenMarkdownTree`
+
+```go
+	out := new(bytes.Buffer)
+	cobra.GenMarkdown(cmd, out)
+```
+
+This will write the markdown doc for ONLY "cmd" into the out, buffer.


Test patch:
diff --git a/cobra_test.go b/cobra_test.go
index eb60b95..a258508 100644
--- a/cobra_test.go
+++ b/cobra_test.go
@@ -37,11 +37,20 @@ var cmdEcho = &Command{
 	Aliases: []string{"say"},
 	Short:   "Echo anything to the screen",
 	Long:    `an utterly useless command for testing.`,
+	Example: "Just run cobra-test echo",
 	Run: func(cmd *Command, args []string) {
 		te = args
 	},
 }
 
+var cmdEchoSub = &Command{
+	Use:   "echosub [string to print]",
+	Short: "second sub command for echo",
+	Long:  `an absolutely utterly useless command for testing gendocs!.`,
+	Run: func(cmd *Command, args []string) {
+	},
+}
+
 var cmdTimes = &Command{
 	Use:   "times [# times] [string to echo]",
 	Short: "Echo anything to the screen more times",
diff --git a/md_docs_test.go b/md_docs_test.go
new file mode 100644
index 0000000..33613af
--- /dev/null
+++ b/md_docs_test.go
@@ -0,0 +1,64 @@
+package cobra
+
+import (
+	"bytes"
+	"fmt"
+	"os"
+	"strings"
+	"testing"
+)
+
+var _ = fmt.Println
+var _ = os.Stderr
+
+func TestGenMdDoc(t *testing.T) {
+	c := initializeWithRootCmd()
+	// Need two commands to run the command alphabetical sort
+	cmdEcho.AddCommand(cmdTimes, cmdEchoSub)
+	c.AddCommand(cmdPrint, cmdEcho)
+	cmdRootWithRun.PersistentFlags().StringVarP(&flags2a, "rootflag", "r", "two", strtwoParentHelp)
+
+	out := new(bytes.Buffer)
+
+	// We generate on s subcommand so we have both subcommands and parents
+	GenMarkdown(cmdEcho, out)
+	found := out.String()
+
+	// Our description
+	expected := cmdEcho.Long
+	if !strings.Contains(found, expected) {
+		t.Errorf("Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", expected, found)
+	}
+
+	// Better have our example
+	expected = cmdEcho.Example
+	if !strings.Contains(found, expected) {
+		t.Errorf("Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", expected, found)
+	}
+
+	// A local flag
+	expected = "boolone"
+	if !strings.Contains(found, expected) {
+		t.Errorf("Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", expected, found)
+	}
+
+	// persistent flag on parent
+	expected = "rootflag"
+	if !strings.Contains(found, expected) {
+		t.Errorf("Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", expected, found)
+	}
+
+	// We better output info about our parent
+	expected = cmdRootWithRun.Short
+	if !strings.Contains(found, expected) {
+		t.Errorf("Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", expected, found)
+	}
+
+	// And about subcommands
+	expected = cmdEchoSub.Short
+	if !strings.Contains(found, expected) {
+		t.Errorf("Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", expected, found)
+	}
+
+	fmt.Fprintf(os.Stdout, "%s\n", found)
+}