Merge pull request #128 from eparis/genManPages

Auto generation of a man pages
This commit is contained in:
Eric Paris 2015-08-21 01:11:45 -07:00
commit e4993076d8
7 changed files with 325 additions and 8 deletions

View file

@ -422,6 +422,10 @@ func main() {
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) 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 man pages for your command
Cobra can generate a man page based on the subcommands, flags, etc. A simple example of how to do this for your command can be found in [Man Docs](man_docs.md)
## Generating bash completions for your command ## 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) 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)

View file

@ -212,7 +212,7 @@ func postscript(out *bytes.Buffer, name string) {
func writeCommands(cmd *Command, out *bytes.Buffer) { func writeCommands(cmd *Command, out *bytes.Buffer) {
fmt.Fprintf(out, " commands=()\n") fmt.Fprintf(out, " commands=()\n")
for _, c := range cmd.Commands() { for _, c := range cmd.Commands() {
if len(c.Deprecated) > 0 { if len(c.Deprecated) > 0 || c == cmd.helpCommand {
continue continue
} }
fmt.Fprintf(out, " commands+=(%q)\n", c.Name()) fmt.Fprintf(out, " commands+=(%q)\n", c.Name())
@ -321,7 +321,7 @@ func writeRequiredNoun(cmd *Command, out *bytes.Buffer) {
func gen(cmd *Command, out *bytes.Buffer) { func gen(cmd *Command, out *bytes.Buffer) {
for _, c := range cmd.Commands() { for _, c := range cmd.Commands() {
if len(c.Deprecated) > 0 { if len(c.Deprecated) > 0 || c == cmd.helpCommand {
continue continue
} }
gen(c, out) gen(c, out)

36
doc_util.go Normal file
View file

@ -0,0 +1,36 @@
// 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 ()
// Test to see if we have a reason to print See Also information in docs
// Basically this is a test for a parent commend or a subcommand which is
// both not deprecated and not the autogenerated help command.
func (cmd *Command) hasSeeAlso() bool {
if cmd.HasParent() {
return true
}
children := cmd.Commands()
if len(children) == 0 {
return false
}
for _, c := range children {
if len(c.Deprecated) != 0 || c == cmd.helpCommand {
continue
}
return true
}
return false
}

166
man_docs.go Normal file
View file

@ -0,0 +1,166 @@
// 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"
mangen "github.com/cpuguy83/go-md2man/md2man"
"github.com/spf13/pflag"
)
func GenManTree(cmd *Command, projectName, dir string) {
cmd.GenManTree(projectName, dir)
}
func (cmd *Command) GenManTree(projectName, dir string) {
for _, c := range cmd.Commands() {
if len(c.Deprecated) != 0 || c == cmd.helpCommand {
continue
}
GenManTree(c, projectName, dir)
}
out := new(bytes.Buffer)
cmd.GenMan(projectName, out)
filename := cmd.CommandPath()
filename = dir + strings.Replace(filename, " ", "-", -1) + ".1"
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)
}
}
func GenMan(cmd *Command, projectName string, out *bytes.Buffer) {
cmd.GenMan(projectName, out)
}
func (cmd *Command) GenMan(projectName string, out *bytes.Buffer) {
buf := genMarkdown(cmd, projectName)
final := mangen.Render(buf)
out.Write(final)
}
func manPreamble(out *bytes.Buffer, projectName, name, short, long string) {
fmt.Fprintf(out, `%% %s(1) kubernetes User Manuals
%% Eric Paris
%% Jan 2015
# NAME
`, projectName)
fmt.Fprintf(out, "%s \\- %s\n\n", name, short)
fmt.Fprintf(out, "# SYNOPSIS\n")
fmt.Fprintf(out, "**%s** [OPTIONS]\n\n", name)
fmt.Fprintf(out, "# DESCRIPTION\n")
fmt.Fprintf(out, "%s\n\n", long)
}
func manPrintFlags(out *bytes.Buffer, flags *pflag.FlagSet) {
flags.VisitAll(func(flag *pflag.Flag) {
if len(flag.Deprecated) > 0 {
return
}
format := ""
if len(flag.Shorthand) > 0 {
format = "**-%s**, **--%s**"
} else {
format = "%s**--%s**"
}
if len(flag.NoOptDefVal) > 0 {
format = format + "["
}
if flag.Value.Type() == "string" {
// put quotes on the value
format = format + "=%q"
} else {
format = format + "=%s"
}
if len(flag.NoOptDefVal) > 0 {
format = format + "]"
}
format = format + "\n\t%s\n\n"
fmt.Fprintf(out, format, flag.Shorthand, flag.Name, flag.DefValue, flag.Usage)
})
}
func manPrintOptions(out *bytes.Buffer, command *Command) {
flags := command.NonInheritedFlags()
if flags.HasFlags() {
fmt.Fprintf(out, "# OPTIONS\n")
manPrintFlags(out, flags)
fmt.Fprintf(out, "\n")
}
flags = command.InheritedFlags()
if flags.HasFlags() {
fmt.Fprintf(out, "# OPTIONS INHERITED FROM PARENT COMMANDS\n")
manPrintFlags(out, flags)
fmt.Fprintf(out, "\n")
}
}
func genMarkdown(cmd *Command, projectName string) []byte {
// something like `rootcmd subcmd1 subcmd2`
commandName := cmd.CommandPath()
// something like `rootcmd-subcmd1-subcmd2`
dashCommandName := strings.Replace(commandName, " ", "-", -1)
buf := new(bytes.Buffer)
short := cmd.Short
long := cmd.Long
if len(long) == 0 {
long = short
}
manPreamble(buf, projectName, commandName, short, long)
manPrintOptions(buf, cmd)
if len(cmd.Example) > 0 {
fmt.Fprintf(buf, "# EXAMPLE\n")
fmt.Fprintf(buf, "```\n%s\n```\n", cmd.Example)
}
if cmd.hasSeeAlso() {
fmt.Fprintf(buf, "# SEE ALSO\n")
if cmd.HasParent() {
fmt.Fprintf(buf, "**%s(1)**, ", cmd.Parent().CommandPath())
}
children := cmd.Commands()
sort.Sort(byName(children))
for _, c := range children {
if len(c.Deprecated) != 0 || c == cmd.helpCommand {
continue
}
fmt.Fprintf(buf, "**%s-%s(1)**, ", dashCommandName, c.Name())
}
fmt.Fprintf(buf, "\n")
}
fmt.Fprintf(buf, "# HISTORY\n%s Auto generated by spf13/cobra\n", time.Now().UTC())
return buf.Bytes()
}

21
man_docs.md Normal file
View file

@ -0,0 +1,21 @@
# Generating Man Pages For Your Own cobra.Command
Generating bash completions from a cobra command is incredibly easy. An example is as follows:
```go
package main
import (
"github.com/spf13/cobra"
)
func main() {
cmd := &cobra.Command{
Use: "test",
Short: "my test program",
}
cmd.GenManTree("/tmp")
}
```
That will get you a man page `/tmp/test.1`

71
man_docs_test.go Normal file
View file

@ -0,0 +1,71 @@
package cobra
import (
"bytes"
"fmt"
"os"
"strings"
"testing"
)
var _ = fmt.Println
var _ = os.Stderr
func translate(in string) string {
return strings.Replace(in, "-", "\\-", -1)
}
func TestGenManDoc(t *testing.T) {
c := initializeWithRootCmd()
// Need two commands to run the command alphabetical sort
cmdEcho.AddCommand(cmdTimes, cmdEchoSub, cmdDeprecated)
c.AddCommand(cmdPrint, cmdEcho)
cmdRootWithRun.PersistentFlags().StringVarP(&flags2a, "rootflag", "r", "two", strtwoParentHelp)
out := new(bytes.Buffer)
// We generate on a subcommand so we have both subcommands and parents
cmdEcho.GenMan("PROJECT", out)
found := out.String()
// Our description
expected := translate(cmdEcho.Name())
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 = translate(cmdEcho.Name())
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 = translate(cmdRootWithRun.Name())
if !strings.Contains(found, expected) {
t.Errorf("Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", expected, found)
}
// And about subcommands
expected = translate(cmdEchoSub.Name())
if !strings.Contains(found, expected) {
t.Errorf("Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", expected, found)
}
unexpected := translate(cmdDeprecated.Name())
if strings.Contains(found, unexpected) {
t.Errorf("Unexpected response.\nFound: %v\nBut should not have!!\n", unexpected)
}
}

View file

@ -47,10 +47,18 @@ 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 (s byName) Less(i, j int) bool { return s[i].Name() < s[j].Name() }
func GenMarkdown(cmd *Command, out *bytes.Buffer) { func GenMarkdown(cmd *Command, out *bytes.Buffer) {
GenMarkdownCustom(cmd, out, func(s string) string { return s }) cmd.GenMarkdown(out)
}
func (cmd *Command) GenMarkdown(out *bytes.Buffer) {
cmd.GenMarkdownCustom(out, func(s string) string { return s })
} }
func GenMarkdownCustom(cmd *Command, out *bytes.Buffer, linkHandler func(string) string) { func GenMarkdownCustom(cmd *Command, out *bytes.Buffer, linkHandler func(string) string) {
cmd.GenMarkdownCustom(out, linkHandler)
}
func (cmd *Command) GenMarkdownCustom(out *bytes.Buffer, linkHandler func(string) string) {
name := cmd.CommandPath() name := cmd.CommandPath()
short := cmd.Short short := cmd.Short
@ -75,7 +83,7 @@ func GenMarkdownCustom(cmd *Command, out *bytes.Buffer, linkHandler func(string)
printOptions(out, cmd, name) printOptions(out, cmd, name)
if len(cmd.Commands()) > 0 || cmd.HasParent() { if cmd.hasSeeAlso() {
fmt.Fprintf(out, "### SEE ALSO\n") fmt.Fprintf(out, "### SEE ALSO\n")
if cmd.HasParent() { if cmd.HasParent() {
parent := cmd.Parent() parent := cmd.Parent()
@ -89,7 +97,7 @@ func GenMarkdownCustom(cmd *Command, out *bytes.Buffer, linkHandler func(string)
sort.Sort(byName(children)) sort.Sort(byName(children))
for _, child := range children { for _, child := range children {
if len(child.Deprecated) > 0 { if len(child.Deprecated) > 0 || child == cmd.helpCommand {
continue continue
} }
cname := name + " " + child.Name() cname := name + " " + child.Name()
@ -104,18 +112,29 @@ func GenMarkdownCustom(cmd *Command, out *bytes.Buffer, linkHandler func(string)
} }
func GenMarkdownTree(cmd *Command, dir string) { func GenMarkdownTree(cmd *Command, dir string) {
cmd.GenMarkdownTree(dir)
}
func (cmd *Command) GenMarkdownTree(dir string) {
identity := func(s string) string { return s } identity := func(s string) string { return s }
emptyStr := func(s string) string { return "" } emptyStr := func(s string) string { return "" }
GenMarkdownTreeCustom(cmd, dir, emptyStr, identity) cmd.GenMarkdownTreeCustom(dir, emptyStr, identity)
} }
func GenMarkdownTreeCustom(cmd *Command, dir string, filePrepender func(string) string, linkHandler func(string) string) { func GenMarkdownTreeCustom(cmd *Command, dir string, filePrepender func(string) string, linkHandler func(string) string) {
cmd.GenMarkdownTreeCustom(dir, filePrepender, linkHandler)
}
func (cmd *Command) GenMarkdownTreeCustom(dir string, filePrepender func(string) string, linkHandler func(string) string) {
for _, c := range cmd.Commands() { for _, c := range cmd.Commands() {
GenMarkdownTreeCustom(c, dir, filePrepender, linkHandler) if len(c.Deprecated) != 0 || c == cmd.helpCommand {
continue
}
c.GenMarkdownTreeCustom(dir, filePrepender, linkHandler)
} }
out := new(bytes.Buffer) out := new(bytes.Buffer)
GenMarkdownCustom(cmd, out, linkHandler) cmd.GenMarkdownCustom(out, linkHandler)
filename := cmd.CommandPath() filename := cmd.CommandPath()
filename = dir + strings.Replace(filename, " ", "_", -1) + ".md" filename = dir + strings.Replace(filename, " ", "_", -1) + ".md"