-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add markdown and man page docs generation methods
This adds two new methods to the `App` struct: - `ToMarkdown`: creates a markdown documentation string - `ToMan`: creates a man page string Signed-off-by: Sascha Grunert <mail@saschagrunert.de>
- Loading branch information
1 parent
7745000
commit 062c549
Showing
9 changed files
with
604 additions
and
86 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
package cli | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
"io" | ||
"sort" | ||
"strings" | ||
"text/template" | ||
"time" | ||
|
||
"github.com/cpuguy83/go-md2man/md2man" | ||
) | ||
|
||
// ToMarkdown creates a markdown string for the `*App` | ||
// The function errors if either parsing or writing of the string fails. | ||
func (a *App) ToMarkdown() (string, error) { | ||
var w bytes.Buffer | ||
if err := a.writeDocTemplate(&w); err != nil { | ||
return "", err | ||
} | ||
return w.String(), nil | ||
} | ||
|
||
// ToMan creates a man page string for the `*App` | ||
// The function errors if either parsing or writing of the string fails. | ||
func (a *App) ToMan() (string, error) { | ||
var w bytes.Buffer | ||
if err := a.writeDocTemplate(&w); err != nil { | ||
return "", err | ||
} | ||
man := md2man.Render(w.Bytes()) | ||
return string(man), nil | ||
} | ||
|
||
type CliTemplate struct { | ||
App *App | ||
Date string | ||
Commands []string | ||
GlobalArgs []string | ||
SynopsisArgs []string | ||
} | ||
|
||
func (a *App) writeDocTemplate(w io.Writer) error { | ||
now := time.Now() | ||
const name = "cli" | ||
t, err := template.New(name).Parse(MarkdownDocTemplate) | ||
if err != nil { | ||
return err | ||
} | ||
return t.ExecuteTemplate(w, name, &CliTemplate{ | ||
App: a, | ||
Date: fmt.Sprintf("%s %d", now.Month(), now.Year()), | ||
Commands: prepareCommands(a.Commands, 0), | ||
GlobalArgs: prepareArgsWithValues(a.Flags), | ||
SynopsisArgs: prepareArgsSynopsis(a.Flags), | ||
}) | ||
} | ||
|
||
const nl = "\n" | ||
const noDescription = "_no description available_" | ||
|
||
func prepareCommands(commands []Command, level int) []string { | ||
coms := []string{} | ||
for i := range commands { | ||
command := &commands[i] | ||
prepared := strings.Repeat("#", level+2) + " " + | ||
strings.Join(command.Names(), ", ") + nl | ||
|
||
usage := noDescription | ||
if command.Usage != "" { | ||
usage = command.Usage | ||
} | ||
prepared += nl + usage + nl | ||
|
||
flags := prepareArgsWithValues(command.Flags) | ||
if len(flags) > 0 { | ||
prepared += nl | ||
} | ||
prepared += strings.Join(flags, nl) | ||
if len(flags) > 0 { | ||
prepared += nl | ||
} | ||
|
||
coms = append(coms, prepared) | ||
|
||
// recursevly iterate subcommands | ||
if len(command.Subcommands) > 0 { | ||
coms = append( | ||
coms, | ||
prepareCommands(command.Subcommands, level+1)..., | ||
) | ||
} | ||
} | ||
|
||
return coms | ||
} | ||
|
||
func prepareArgsWithValues(flags []Flag) []string { | ||
return prepareFlags(flags, ", ", "**", "**", `""`, true) | ||
} | ||
|
||
func prepareArgsSynopsis(flags []Flag) []string { | ||
return prepareFlags(flags, "|", "[", "]", "[value]", false) | ||
} | ||
|
||
func prepareFlags( | ||
flags []Flag, | ||
sep, opener, closer, value string, | ||
addDetails bool, | ||
) []string { | ||
args := []string{} | ||
for _, flag := range flags { | ||
modifiedArg := opener | ||
for _, s := range strings.Split(flag.GetName(), ",") { | ||
trimmed := strings.TrimSpace(s) | ||
if len(modifiedArg) > len(opener) { | ||
modifiedArg += sep | ||
} | ||
if len(trimmed) > 1 { | ||
modifiedArg += "--" + trimmed | ||
} else { | ||
modifiedArg += "-" + trimmed | ||
} | ||
} | ||
modifiedArg += closer | ||
if flag.TakesValue() { | ||
modifiedArg += "=" + value | ||
} | ||
|
||
if addDetails { | ||
modifiedArg += flagDetails(flag) | ||
} | ||
|
||
args = append(args, modifiedArg+nl) | ||
|
||
} | ||
sort.Strings(args) | ||
return args | ||
} | ||
|
||
// flagDetails returns a string containing the flags metadata | ||
func flagDetails(flag Flag) string { | ||
description := flag.GetUsage() | ||
if flag.GetUsage() == "" { | ||
description = noDescription | ||
} | ||
value := flag.GetValue() | ||
if value != "" { | ||
description += " (default: " + value + ")" | ||
} | ||
return ": " + description | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
package cli | ||
|
||
import ( | ||
"testing" | ||
) | ||
|
||
func testApp() *App { | ||
app := NewApp() | ||
app.Name = "greet" | ||
app.Flags = []Flag{ | ||
StringFlag{ | ||
Name: "socket, s", | ||
Usage: "some usage text", | ||
Value: "value", | ||
}, | ||
StringFlag{Name: "flag, fl, f"}, | ||
BoolFlag{ | ||
Name: "another-flag, b", | ||
Usage: "another usage text", | ||
}, | ||
} | ||
app.Commands = []Command{{ | ||
Aliases: []string{"c"}, | ||
Flags: []Flag{ | ||
StringFlag{Name: "flag, fl, f"}, | ||
BoolFlag{ | ||
Name: "another-flag, b", | ||
Usage: "another usage text", | ||
}, | ||
}, | ||
Name: "config", | ||
Usage: "another usage test", | ||
Subcommands: []Command{{ | ||
Aliases: []string{"s", "ss"}, | ||
Flags: []Flag{ | ||
StringFlag{Name: "sub-flag, sub-fl, s"}, | ||
BoolFlag{ | ||
Name: "sub-command-flag, s", | ||
Usage: "some usage text", | ||
}, | ||
}, | ||
Name: "sub-config", | ||
Usage: "another usage test", | ||
}}, | ||
}, { | ||
Aliases: []string{"i", "in"}, | ||
Name: "info", | ||
Usage: "retrieve generic information", | ||
}, { | ||
Name: "some-command", | ||
}} | ||
app.UsageText = "app [first_arg] [second_arg]" | ||
app.Usage = "Some app" | ||
app.Author = "Harrison" | ||
app.Email = "harrison@lolwut.com" | ||
app.Authors = []Author{{Name: "Oliver Allen", Email: "oliver@toyshop.com"}} | ||
return app | ||
} | ||
|
||
func TestToMarkdown(t *testing.T) { | ||
// Given | ||
app := testApp() | ||
|
||
// When | ||
_, err := app.ToMarkdown() | ||
|
||
// Then | ||
// TODO: extend test case | ||
expect(t, err, nil) | ||
} | ||
|
||
func TestToMan(t *testing.T) { | ||
// Given | ||
app := testApp() | ||
|
||
// When | ||
_, err := app.ToMan() | ||
|
||
// Then | ||
// TODO: extend test case | ||
expect(t, err, nil) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.