diff --git a/clidoc/generate.go b/clidoc/generate.go index 0442d21d..c2dbcaba 100644 --- a/clidoc/generate.go +++ b/clidoc/generate.go @@ -3,7 +3,6 @@ package clidoc import ( "bytes" "fmt" - "html" "io" "os" "path/filepath" @@ -12,7 +11,6 @@ import ( "github.com/pkg/errors" "github.com/spf13/cobra" - "github.com/spf13/cobra/doc" ) // Generate generates markdown documentation for a cobra command and its children. @@ -72,10 +70,10 @@ To improve this file please make your change against the appropriate "./cmd/*.go } var b bytes.Buffer - if err := doc.GenMarkdownCustom(cmd, &b, trimExt); err != nil { + if err := GenMarkdownCustom(cmd, &b, trimExt); err != nil { return err } - _, err = f.WriteString(html.EscapeString(b.String())) + _, err = f.WriteString(b.String()) return err } diff --git a/clidoc/generate_test.go b/clidoc/generate_test.go index 4ce973b1..06424103 100644 --- a/clidoc/generate_test.go +++ b/clidoc/generate_test.go @@ -15,10 +15,26 @@ import ( func noopRun(_ *cobra.Command, _ []string) {} var ( - root = &cobra.Command{Use: "root", Run: noopRun} - child1 = &cobra.Command{Use: "child1", Run: noopRun} - child2 = &cobra.Command{Use: "child2", Run: noopRun} - subChild1 = &cobra.Command{Use: "subChild1", Run: noopRun} + root = &cobra.Command{Use: "root", Run: noopRun, Long: `A sample text +root + +<[some argument]> +`} + child1 = &cobra.Command{Use: "child1", Run: noopRun, Long: `A sample text +child1 + +<[some argument]> +`} + child2 = &cobra.Command{Use: "child2", Run: noopRun, Long: `A sample text +child2 + +<[some argument]> +`} + subChild1 = &cobra.Command{Use: "subChild1 ", Run: noopRun, Long: `A sample text +subChild1 + +<[some argument]> +`} ) func snapshotDir(t *testing.T, path ...string) (assertNoChange func(t *testing.T)) { diff --git a/clidoc/md_docs.go b/clidoc/md_docs.go new file mode 100644 index 00000000..fb7e9e02 --- /dev/null +++ b/clidoc/md_docs.go @@ -0,0 +1,153 @@ +//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 clidoc + +import ( + "bytes" + "fmt" + "html" + "io" + "os" + "path/filepath" + "sort" + "strings" + + "github.com/spf13/cobra" +) + +func printOptions(buf *bytes.Buffer, cmd *cobra.Command, name string) error { + flags := cmd.NonInheritedFlags() + flags.SetOutput(buf) + if flags.HasAvailableFlags() { + buf.WriteString("### Options\n\n```\n") + flags.PrintDefaults() + buf.WriteString("```\n\n") + } + + parentFlags := cmd.InheritedFlags() + parentFlags.SetOutput(buf) + if parentFlags.HasAvailableFlags() { + buf.WriteString("### Options inherited from parent commands\n\n```\n") + parentFlags.PrintDefaults() + buf.WriteString("```\n\n") + } + return nil +} + +// GenMarkdown creates markdown output. +func GenMarkdown(cmd *cobra.Command, w io.Writer) error { + return GenMarkdownCustom(cmd, w, func(s string) string { return s }) +} + +// GenMarkdownCustom creates custom markdown output. +func GenMarkdownCustom(cmd *cobra.Command, w io.Writer, linkHandler func(string) string) error { + cmd.InitDefaultHelpCmd() + cmd.InitDefaultHelpFlag() + + buf := new(bytes.Buffer) + name := cmd.CommandPath() + + buf.WriteString("## " + html.EscapeString(name) + "\n\n") + buf.WriteString(cmd.Short + "\n\n") + if len(cmd.Long) > 0 { + buf.WriteString("### Synopsis\n\n") + buf.WriteString(html.EscapeString(cmd.Long) + "\n\n") + } + + if cmd.Runnable() { + buf.WriteString(fmt.Sprintf("```\n%s\n```\n\n", cmd.UseLine())) + } + + if len(cmd.Example) > 0 { + buf.WriteString("### Examples\n\n") + buf.WriteString(fmt.Sprintf("```\n%s\n```\n\n", cmd.Example)) + } + + if err := printOptions(buf, cmd, name); err != nil { + return err + } + if hasSeeAlso(cmd) { + buf.WriteString("### SEE ALSO\n\n") + if cmd.HasParent() { + parent := cmd.Parent() + pname := parent.CommandPath() + link := pname + ".md" + link = strings.Replace(link, " ", "_", -1) + buf.WriteString(fmt.Sprintf("* [%s](%s)\t - %s\n", pname, linkHandler(link), parent.Short)) + cmd.VisitParents(func(c *cobra.Command) { + if c.DisableAutoGenTag { + cmd.DisableAutoGenTag = c.DisableAutoGenTag + } + }) + } + + children := cmd.Commands() + sort.Sort(byName(children)) + + for _, child := range children { + if !child.IsAvailableCommand() || child.IsAdditionalHelpTopicCommand() { + continue + } + cname := name + " " + child.Name() + link := cname + ".md" + link = strings.Replace(link, " ", "_", -1) + buf.WriteString(fmt.Sprintf("* [%s](%s)\t - %s\n", cname, linkHandler(link), child.Short)) + } + buf.WriteString("\n") + } + + _, err := buf.WriteTo(w) + return err +} + +// GenMarkdownTree will generate a markdown page for this command and all +// descendants in the directory given. The header may be nil. +// This function may not work correctly if your command names have `-` in them. +// If you have `cmd` with two subcmds, `sub` and `sub-third`, +// and `sub` has a subcommand called `third`, it is undefined which +// help output will be in the file `cmd-sub-third.1`. +func GenMarkdownTree(cmd *cobra.Command, dir string) error { + identity := func(s string) string { return s } + emptyStr := func(s string) string { return "" } + return GenMarkdownTreeCustom(cmd, dir, emptyStr, identity) +} + +// GenMarkdownTreeCustom is the the same as GenMarkdownTree, but +// with custom filePrepender and linkHandler. +func GenMarkdownTreeCustom(cmd *cobra.Command, dir string, filePrepender, linkHandler func(string) string) error { + for _, c := range cmd.Commands() { + if !c.IsAvailableCommand() || c.IsAdditionalHelpTopicCommand() { + continue + } + if err := GenMarkdownTreeCustom(c, dir, filePrepender, linkHandler); err != nil { + return err + } + } + + basename := strings.Replace(cmd.CommandPath(), " ", "_", -1) + ".md" + filename := filepath.Join(dir, basename) + f, err := os.Create(filename) + if err != nil { + return err + } + defer f.Close() + + if _, err := io.WriteString(f, filePrepender(filename)); err != nil { + return err + } + if err := GenMarkdownCustom(cmd, f, linkHandler); err != nil { + return err + } + return nil +} diff --git a/clidoc/testdata/root-child1-subChild1.md b/clidoc/testdata/root-child1-subChild1.md index 43d43114..fce9c8ee 100644 --- a/clidoc/testdata/root-child1-subChild1.md +++ b/clidoc/testdata/root-child1-subChild1.md @@ -13,8 +13,16 @@ To improve this file please make your change against the appropriate "./cmd/*.go +### Synopsis + +A sample text +subChild1 + +<[some argument]> + + ``` -root child1 subChild1 [flags] +root child1 subChild1 [flags] ``` ### Options diff --git a/clidoc/testdata/root-child1.md b/clidoc/testdata/root-child1.md index cb8005cf..b68c6de7 100644 --- a/clidoc/testdata/root-child1.md +++ b/clidoc/testdata/root-child1.md @@ -13,6 +13,14 @@ To improve this file please make your change against the appropriate "./cmd/*.go +### Synopsis + +A sample text +child1 + +<[some argument]> + + ``` root child1 [flags] ``` diff --git a/clidoc/testdata/root-child2.md b/clidoc/testdata/root-child2.md index 6b76cb71..622b1ed4 100644 --- a/clidoc/testdata/root-child2.md +++ b/clidoc/testdata/root-child2.md @@ -13,6 +13,14 @@ To improve this file please make your change against the appropriate "./cmd/*.go +### Synopsis + +A sample text +child2 + +<[some argument]> + + ``` root child2 [flags] ``` diff --git a/clidoc/testdata/root.md b/clidoc/testdata/root.md index 1f4ad811..b058d65c 100644 --- a/clidoc/testdata/root.md +++ b/clidoc/testdata/root.md @@ -13,6 +13,14 @@ To improve this file please make your change against the appropriate "./cmd/*.go +### Synopsis + +A sample text +root + +<[some argument]> + + ``` root [flags] ``` diff --git a/clidoc/util.go b/clidoc/util.go new file mode 100644 index 00000000..c3c2cc46 --- /dev/null +++ b/clidoc/util.go @@ -0,0 +1,51 @@ +// 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 clidoc + +import ( + "strings" + + "github.com/spf13/cobra" +) + +// Test to see if we have a reason to print See Also information in docs +// Basically this is a test for a parent command or a subcommand which is +// both not deprecated and not the autogenerated help command. +func hasSeeAlso(cmd *cobra.Command) bool { + if cmd.HasParent() { + return true + } + for _, c := range cmd.Commands() { + if !c.IsAvailableCommand() || c.IsAdditionalHelpTopicCommand() { + continue + } + return true + } + return false +} + +// Temporary workaround for yaml lib generating incorrect yaml with long strings +// that do not contain \n. +func forceMultiLine(s string) string { + if len(s) > 60 && !strings.Contains(s, "\n") { + s = s + "\n" + } + return s +} + +type byName []*cobra.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() }