Treat write errors in markdown doc generation

This code was already using io.Writer, but was completely ignoring write
errors.

The most worrying part is how GenMarkdownTreeCustom used an unnecessary
buffer to then dump all of its contents on a file, and instead of
returning an error on file creation/writing, it would just exit the
entire program.
This commit is contained in:
Daniel Martí 2016-01-06 11:59:28 +01:00
parent ea06b29c10
commit eb5040e69e
3 changed files with 82 additions and 47 deletions

View File

@ -14,7 +14,6 @@
package doc package doc
import ( import (
"bytes"
"fmt" "fmt"
"io" "io"
"os" "os"
@ -25,29 +24,38 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
func printOptions(out io.Writer, cmd *cobra.Command, name string) { func printOptions(w io.Writer, cmd *cobra.Command, name string) error {
flags := cmd.NonInheritedFlags() flags := cmd.NonInheritedFlags()
flags.SetOutput(out) flags.SetOutput(w)
if flags.HasFlags() { if flags.HasFlags() {
fmt.Fprintf(out, "### Options\n\n```\n") if _, err := fmt.Fprintf(w, "### Options\n\n```\n"); err != nil {
return err
}
flags.PrintDefaults() flags.PrintDefaults()
fmt.Fprintf(out, "```\n\n") if _, err := fmt.Fprintf(w, "```\n\n"); err != nil {
return err
}
} }
parentFlags := cmd.InheritedFlags() parentFlags := cmd.InheritedFlags()
parentFlags.SetOutput(out) parentFlags.SetOutput(w)
if parentFlags.HasFlags() { if parentFlags.HasFlags() {
fmt.Fprintf(out, "### Options inherited from parent commands\n\n```\n") if _, err := fmt.Fprintf(w, "### Options inherited from parent commands\n\n```\n"); err != nil {
parentFlags.PrintDefaults() return err
fmt.Fprintf(out, "```\n\n")
} }
parentFlags.PrintDefaults()
if _, err := fmt.Fprintf(w, "```\n\n"); err != nil {
return err
}
}
return nil
} }
func GenMarkdown(cmd *cobra.Command, out io.Writer) { func GenMarkdown(cmd *cobra.Command, w io.Writer) error {
GenMarkdownCustom(cmd, out, func(s string) string { return s }) return GenMarkdownCustom(cmd, w, func(s string) string { return s })
} }
func GenMarkdownCustom(cmd *cobra.Command, out io.Writer, linkHandler func(string) string) { func GenMarkdownCustom(cmd *cobra.Command, w io.Writer, linkHandler func(string) string) error {
name := cmd.CommandPath() name := cmd.CommandPath()
short := cmd.Short short := cmd.Short
@ -56,29 +64,49 @@ func GenMarkdownCustom(cmd *cobra.Command, out io.Writer, linkHandler func(strin
long = short long = short
} }
fmt.Fprintf(out, "## %s\n\n", name) if _, err := fmt.Fprintf(w, "## %s\n\n", name); err != nil {
fmt.Fprintf(out, "%s\n\n", short) return err
fmt.Fprintf(out, "### Synopsis\n\n") }
fmt.Fprintf(out, "\n%s\n\n", long) if _, err := fmt.Fprintf(w, "%s\n\n", short); err != nil {
return err
}
if _, err := fmt.Fprintf(w, "### Synopsis\n\n"); err != nil {
return err
}
if _, err := fmt.Fprintf(w, "\n%s\n\n", long); err != nil {
return err
}
if cmd.Runnable() { if cmd.Runnable() {
fmt.Fprintf(out, "```\n%s\n```\n\n", cmd.UseLine()) if _, err := fmt.Fprintf(w, "```\n%s\n```\n\n", cmd.UseLine()); err != nil {
return err
}
} }
if len(cmd.Example) > 0 { if len(cmd.Example) > 0 {
fmt.Fprintf(out, "### Examples\n\n") if _, err := fmt.Fprintf(w, "### Examples\n\n"); err != nil {
fmt.Fprintf(out, "```\n%s\n```\n\n", cmd.Example) return err
}
if _, err := fmt.Fprintf(w, "```\n%s\n```\n\n", cmd.Example); err != nil {
return err
}
} }
printOptions(out, cmd, name) if err := printOptions(w, cmd, name); err != nil {
return err
}
if hasSeeAlso(cmd) { if hasSeeAlso(cmd) {
fmt.Fprintf(out, "### SEE ALSO\n") if _, err := fmt.Fprintf(w, "### SEE ALSO\n"); err != nil {
return err
}
if cmd.HasParent() { if cmd.HasParent() {
parent := cmd.Parent() parent := cmd.Parent()
pname := parent.CommandPath() pname := parent.CommandPath()
link := pname + ".md" link := pname + ".md"
link = strings.Replace(link, " ", "_", -1) link = strings.Replace(link, " ", "_", -1)
fmt.Fprintf(out, "* [%s](%s)\t - %s\n", pname, linkHandler(link), parent.Short) if _, err := fmt.Fprintf(w, "* [%s](%s)\t - %s\n", pname, linkHandler(link), parent.Short); err != nil {
return err
}
cmd.VisitParents(func(c *cobra.Command) { cmd.VisitParents(func(c *cobra.Command) {
if c.DisableAutoGenTag { if c.DisableAutoGenTag {
cmd.DisableAutoGenTag = c.DisableAutoGenTag cmd.DisableAutoGenTag = c.DisableAutoGenTag
@ -96,48 +124,51 @@ func GenMarkdownCustom(cmd *cobra.Command, out io.Writer, linkHandler func(strin
cname := name + " " + child.Name() cname := name + " " + child.Name()
link := cname + ".md" link := cname + ".md"
link = strings.Replace(link, " ", "_", -1) link = strings.Replace(link, " ", "_", -1)
fmt.Fprintf(out, "* [%s](%s)\t - %s\n", cname, linkHandler(link), child.Short) if _, err := fmt.Fprintf(w, "* [%s](%s)\t - %s\n", cname, linkHandler(link), child.Short); err != nil {
return err
}
}
if _, err := fmt.Fprintf(w, "\n"); err != nil {
return err
} }
fmt.Fprintf(out, "\n")
} }
if !cmd.DisableAutoGenTag { if !cmd.DisableAutoGenTag {
fmt.Fprintf(out, "###### Auto generated by spf13/cobra on %s\n", time.Now().Format("2-Jan-2006")) if _, err := fmt.Fprintf(w, "###### Auto generated by spf13/cobra on %s\n", time.Now().Format("2-Jan-2006")); err != nil {
return err
} }
}
return nil
} }
func GenMarkdownTree(cmd *cobra.Command, dir string) { func GenMarkdownTree(cmd *cobra.Command, dir string) error {
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) return GenMarkdownTreeCustom(cmd, dir, emptyStr, identity)
} }
func GenMarkdownTreeCustom(cmd *cobra.Command, dir string, filePrepender, linkHandler func(string) string) { func GenMarkdownTreeCustom(cmd *cobra.Command, dir string, filePrepender, linkHandler func(string) string) error {
for _, c := range cmd.Commands() { for _, c := range cmd.Commands() {
if !c.IsAvailableCommand() || c.IsHelpCommand() { if !c.IsAvailableCommand() || c.IsHelpCommand() {
continue continue
} }
GenMarkdownTreeCustom(c, dir, filePrepender, linkHandler) if err := GenMarkdownTreeCustom(c, dir, filePrepender, linkHandler); err != nil {
return err
}
} }
out := new(bytes.Buffer)
GenMarkdownCustom(cmd, out, linkHandler)
filename := cmd.CommandPath() filename := cmd.CommandPath()
filename = dir + strings.Replace(filename, " ", "_", -1) + ".md" filename = dir + strings.Replace(filename, " ", "_", -1) + ".md"
outFile, err := os.Create(filename) f, err := os.Create(filename)
if err != nil { if err != nil {
fmt.Println(err) return err
os.Exit(1)
} }
defer outFile.Close() defer f.Close()
_, err = outFile.WriteString(filePrepender(filename))
if err != nil { if _, err := io.WriteString(f, filePrepender(filename)); err != nil {
fmt.Println(err) return err
os.Exit(1)
} }
_, err = outFile.Write(out.Bytes()) if err := GenMarkdownCustom(cmd, f, linkHandler); err != nil {
if err != nil { return err
fmt.Println(err)
os.Exit(1)
} }
return nil
} }

View File

@ -39,13 +39,13 @@ This will write the markdown doc for ONLY "cmd" into the out, buffer.
Both `GenMarkdown` and `GenMarkdownTree` have alternate versions with callbacks to get some control of the output: Both `GenMarkdown` and `GenMarkdownTree` have alternate versions with callbacks to get some control of the output:
```go ```go
func GenMarkdownTreeCustom(cmd *Command, dir string, filePrepender, linkHandler func(string) string) { func GenMarkdownTreeCustom(cmd *Command, dir string, filePrepender, linkHandler func(string) string) error {
//... //...
} }
``` ```
```go ```go
func GenMarkdownCustom(cmd *Command, out *bytes.Buffer, linkHandler func(string) string) { func GenMarkdownCustom(cmd *Command, out *bytes.Buffer, linkHandler func(string) string) error {
//... //...
} }
``` ```

View File

@ -21,7 +21,9 @@ func TestGenMdDoc(t *testing.T) {
out := new(bytes.Buffer) out := new(bytes.Buffer)
// We generate on s subcommand so we have both subcommands and parents // We generate on s subcommand so we have both subcommands and parents
GenMarkdown(cmdEcho, out) if err := GenMarkdown(cmdEcho, out); err != nil {
t.Fatal(err)
}
found := out.String() found := out.String()
// Our description // Our description
@ -75,7 +77,9 @@ func TestGenMdNoTag(t *testing.T) {
cmdRootWithRun.PersistentFlags().StringVarP(&flags2a, "rootflag", "r", "two", strtwoParentHelp) cmdRootWithRun.PersistentFlags().StringVarP(&flags2a, "rootflag", "r", "two", strtwoParentHelp)
out := new(bytes.Buffer) out := new(bytes.Buffer)
GenMarkdown(c, out) if err := GenMarkdown(c, out); err != nil {
t.Fatal(err)
}
found := out.String() found := out.String()
unexpected := "Auto generated" unexpected := "Auto generated"