Skip to content

Commit

Permalink
Merge pull request #90 from juju/JUJU-2829_split_files_for_docs
Browse files Browse the repository at this point in the history
[JUJU-2829] Prepare option to split docs into files.
  • Loading branch information
juanmanuel-tirado authored Feb 28, 2023
2 parents e2abd23 + c023cde commit 1f8fbf7
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 35 deletions.
2 changes: 1 addition & 1 deletion aliasfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"strings"
)

// ParseAliasFile will read the specified file and convert
// ParseAliasFile will read the specified file and convert
// the content to a map of names to the command line arguments
// they relate to. The function will always return a valid map, even
// if it is empty.
Expand Down
2 changes: 1 addition & 1 deletion cmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -390,4 +390,4 @@ type CmdDocumentationSuite struct {
testing.LoggingCleanupSuite

targetCmd cmd.Command
}
}
159 changes: 131 additions & 28 deletions documentation.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package cmd

import (
"bufio"
"errors"
"fmt"
"os"
"sort"
Expand All @@ -13,6 +14,11 @@ import (
"github.com/juju/gnuflag"
)

const (
DocumentationFileName = "documentation.md"
DocumentationIndexFileName = "index.md"
)

var doc string = `
This command generates a markdown formatted document with all the commands, their descriptions, arguments, and examples.
`
Expand All @@ -22,6 +28,8 @@ type documentationCommand struct {
super *SuperCommand
out string
noIndex bool
split bool
url string
}

func newDocumentationCommand(s *SuperCommand) *documentationCommand {
Expand All @@ -31,22 +39,42 @@ func newDocumentationCommand(s *SuperCommand) *documentationCommand {
func (c *documentationCommand) Info() *Info {
return &Info{
Name: "documentation",
Args: "--out <target-file> --noindex",
Args: "--out <target-folder> --noindex --split --url <base-url>",
Purpose: "Generate the documentation for all commands",
Doc: doc,
}
}

// SetFlags adds command specific flags to the flag set.
func (c *documentationCommand) SetFlags(f *gnuflag.FlagSet) {
f.StringVar(&c.out, "out", "", "Documentation output file")
f.StringVar(&c.out, "out", "", "Documentation output folder if not set the result is displayed using the standard output")
f.BoolVar(&c.noIndex, "no-index", false, "Do not generate the commands index")
f.BoolVar(&c.split, "split", false, "Generate one file per command")
f.StringVar(&c.url, "url", "", "Documentation host URL")
}

func (c *documentationCommand) Run(ctx *Context) error {
if c.split {
if c.out == "" {
return errors.New("set the output folder when using the split option")
}
return c.dumpSeveralFiles()
}
return c.dumpOneFile(ctx)
}

// dumpeOneFile is invoked when the output is contained in a single output
func (c *documentationCommand) dumpOneFile(ctx *Context) error {
var writer *bufio.Writer
if c.out != "" {
f, err := os.Create(c.out)
_, err := os.Stat(c.out)
if err != nil {
return err
}

target := fmt.Sprintf("%s/%s", c.out, DocumentationFileName)

f, err := os.Create(target)
if err != nil {
return err
}
Expand All @@ -55,15 +83,13 @@ func (c *documentationCommand) Run(ctx *Context) error {
} else {
writer = bufio.NewWriter(ctx.Stdout)
}

return c.dumpEntries(writer)
}

func (c *documentationCommand) dumpEntries(writer *bufio.Writer) error {
if len(c.super.subcmds) == 0 {
fmt.Printf("No commands found for %s", c.super.Name)
return nil
}

// getSortedListCommands returns an array with the sorted list of
// command names
func (c *documentationCommand) getSortedListCommands() []string {
// sort the commands
sorted := make([]string, len(c.super.subcmds))
i := 0
Expand All @@ -72,6 +98,67 @@ func (c *documentationCommand) dumpEntries(writer *bufio.Writer) error {
i++
}
sort.Strings(sorted)
return sorted
}

// dumpSeveralFiles is invoked when every command is dumped into
// a separated entity
func (c *documentationCommand) dumpSeveralFiles() error {
_, err := os.Stat(c.out)
if err != nil {
return err
}

if len(c.super.subcmds) == 0 {
fmt.Printf("No commands found for %s", c.super.Name)
return nil
}

sorted := c.getSortedListCommands()

// create index if indicated
if !c.noIndex {
target := fmt.Sprintf("%s/%s", c.out, DocumentationIndexFileName)
f, err := os.Create(target)
if err != nil {
return err
}

writer := bufio.NewWriter(f)
_, err = writer.WriteString(c.commandsIndex(sorted))
if err != nil {
return err
}
f.Close()
}

folder := c.out + "/%s.md"
for _, command := range sorted {
target := fmt.Sprintf(folder, command)
f, err := os.Create(target)
if err != nil {
return err
}
writer := bufio.NewWriter(f)
formatted := c.formatCommand(c.super.subcmds[command], false)
_, err = writer.WriteString(formatted)
if err != nil {
return err
}
writer.Flush()
f.Close()
}

return err
}

func (c *documentationCommand) dumpEntries(writer *bufio.Writer) error {
if len(c.super.subcmds) == 0 {
fmt.Printf("No commands found for %s", c.super.Name)
return nil
}

sorted := c.getSortedListCommands()

if !c.noIndex {
_, err := writer.WriteString(c.commandsIndex(sorted))
Expand All @@ -82,7 +169,7 @@ func (c *documentationCommand) dumpEntries(writer *bufio.Writer) error {

var err error
for _, nameCmd := range sorted {
_, err = writer.WriteString(c.formatCommand(c.super.subcmds[nameCmd]))
_, err = writer.WriteString(c.formatCommand(c.super.subcmds[nameCmd], true))
if err != nil {
return err
}
Expand All @@ -92,15 +179,40 @@ func (c *documentationCommand) dumpEntries(writer *bufio.Writer) error {

func (c *documentationCommand) commandsIndex(listCommands []string) string {
index := "# Index\n"
prefix := "#"
if c.url != "" {
prefix = c.url + "/"
}
for id, name := range listCommands {
index += fmt.Sprintf("%d. [%s](#%s)\n", id, name, name)
index += fmt.Sprintf("%d. [%s](%s%s)\n", id, name, prefix, name)
}
index += "---\n\n"
return index
}

func (c *documentationCommand) formatCommand(ref commandReference) string {
formatted := "# " + strings.ToUpper(ref.name) + "\n"
// formatCommand returns a string representation of the information contained
// by a command in Markdown format. The title param can be used to set
// whether the command name should be a title or not. This is particularly
// handy when splitting the commands in different files.
func (c *documentationCommand) formatCommand(ref commandReference, title bool) string {
formatted := ""
if title {
formatted = "# " + strings.ToUpper(ref.name) + "\n"
}

// See Also
if len(ref.command.Info().SeeAlso) > 0 {
formatted += "## See Also\n"
prefix := "#"
if c.url != "" {
prefix = c.url + "/"
}
for _, s := range ref.command.Info().SeeAlso {
formatted += fmt.Sprintf("[%s](%s%s)\n", s, prefix, s)
}
formatted += "\n"
}

if ref.alias != "" {
formatted += "**Alias:** " + ref.alias + "\n"
}
Expand All @@ -109,20 +221,14 @@ func (c *documentationCommand) formatCommand(ref commandReference) string {
}
formatted += "\n"

// Description
// Summary
formatted += "## Summary\n" + ref.command.Info().Purpose + "\n\n"

// Usage
if ref.command.Info().Args != "" {
formatted += "## Usage\n```" + ref.command.Info().Args + "```\n\n"
}

// Options
formattedFlags := c.formatFlags(ref.command)
if len(formattedFlags) > 0 {
formatted += "## Options\n" + formattedFlags + "\n"
}

// Description
doc := ref.command.Info().Doc
if doc != "" {
Expand All @@ -138,16 +244,13 @@ func (c *documentationCommand) formatCommand(ref commandReference) string {
formatted += "\n"
}

// See Also
if len(ref.command.Info().SeeAlso) > 0 {
formatted += "## See Also\n"
for _, s := range ref.command.Info().SeeAlso {
formatted += fmt.Sprintf("[%s](#%s)\n", s, s)
}
formatted += "\n"
// Options
formattedFlags := c.formatFlags(ref.command)
if len(formattedFlags) > 0 {
formatted += "## Options\n" + formattedFlags + "\n"
}

formatted += "---\n"
formatted += "---\n\n"

return formatted

Expand Down
10 changes: 5 additions & 5 deletions output.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,11 @@ func FormatJson(writer io.Writer, value interface{}) error {
}

// FormatSmart marshals value into a []byte according to the following rules:
// * string: untouched
// * bool: converted to `True` or `False` (to match pyjuju)
// * int or float: converted to sensible strings
// * []string: joined by `\n`s into a single string
// * anything else: delegate to FormatYaml
// - string: untouched
// - bool: converted to `True` or `False` (to match pyjuju)
// - int or float: converted to sensible strings
// - []string: joined by `\n`s into a single string
// - anything else: delegate to FormatYaml
func FormatSmart(writer io.Writer, value interface{}) error {
if value == nil {
return nil
Expand Down

0 comments on commit 1f8fbf7

Please sign in to comment.