Skip to content

Commit f6bfe6c

Browse files
authored
Merge pull request #118 from barrettj12/format-cmd
feat: exported PrintMarkdown function
2 parents 687aaf5 + 2dfc312 commit f6bfe6c

File tree

6 files changed

+484
-249
lines changed

6 files changed

+484
-249
lines changed

cmdtesting/cmd.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ func Context(c *gc.C) *cmd.Context {
4343
Stdout: &bytes.Buffer{},
4444
Stderr: &bytes.Buffer{},
4545
}
46-
ctx.Context = context.TODO()
46+
ctx.Context = context.Background()
4747
return ctx
4848
}
4949

documentation.go

+22-243
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package cmd
55

66
import (
77
"bufio"
8+
"bytes"
89
"errors"
910
"fmt"
1011
"io"
@@ -354,83 +355,35 @@ func (c *documentationCommand) linkForCommand(cmd string) string {
354355
// whether the command name should be a title or not. This is particularly
355356
// handy when splitting the commands in different files.
356357
func (c *documentationCommand) formatCommand(ref commandReference, title bool, commandSeq []string) string {
357-
formatted := ""
358+
var fmtedTitle string
358359
if title {
359-
formatted = "# " + strings.ToUpper(strings.Join(commandSeq[1:], " ")) + "\n"
360+
fmtedTitle = strings.ToUpper(strings.Join(commandSeq[1:], " "))
360361
}
361362

362-
var info *Info
363-
if ref.name == "documentation" {
364-
info = c.Info()
365-
} else {
366-
info = ref.command.Info()
367-
}
368-
369-
// See Also
370-
if len(info.SeeAlso) > 0 {
371-
formatted += "> See also: "
372-
prefix := "#"
373-
if c.ids != nil {
374-
prefix = "/t/"
375-
}
376-
if c.url != "" {
377-
prefix = c.url + "t/"
378-
}
363+
var buf bytes.Buffer
364+
PrintMarkdown(&buf, ref.command, MarkdownOptions{
365+
Title: fmtedTitle,
366+
UsagePrefix: strings.Join(commandSeq[:len(commandSeq)-1], " ") + " ",
367+
LinkForCommand: func(s string) string {
368+
prefix := "#"
369+
if c.ids != nil {
370+
prefix = "/t/"
371+
}
372+
if c.url != "" {
373+
prefix = c.url + "t/"
374+
}
379375

380-
for i, s := range info.SeeAlso {
381376
target, err := c.getTargetCmd(s)
382377
if err != nil {
383378
fmt.Println(err.Error())
384379
}
385-
formatted += fmt.Sprintf("[%s](%s%s)", s, prefix, target)
386-
if i < len(info.SeeAlso)-1 {
387-
formatted += ", "
388-
}
389-
}
390-
formatted += "\n"
391-
}
392-
393-
if ref.alias != "" {
394-
formatted += "**Alias:** " + ref.alias + "\n"
395-
}
396-
if ref.check != nil && ref.check.Obsolete() {
397-
formatted += "*This command is deprecated*\n"
398-
}
399-
formatted += "\n"
400-
401-
// Summary
402-
formatted += "## Summary\n" + info.Purpose + "\n\n"
403-
404-
// Usage
405-
if strings.TrimSpace(info.Args) != "" {
406-
formatted += fmt.Sprintf(`## Usage
407-
`+"```"+`%s [options] %s`+"```"+`
408-
409-
`, strings.Join(commandSeq, " "), info.Args)
410-
}
411-
412-
// Options
413-
formattedFlags := c.formatFlags(ref.command, info)
414-
if len(formattedFlags) > 0 {
415-
formatted += "### Options\n" + formattedFlags + "\n"
416-
}
417-
418-
// Examples
419-
examples := info.Examples
420-
if strings.TrimSpace(examples) != "" {
421-
formatted += "## Examples\n" + examples + "\n\n"
422-
}
423-
424-
// Details
425-
doc := EscapeMarkdown(info.Doc)
426-
if strings.TrimSpace(doc) != "" {
427-
formatted += "## Details\n" + doc + "\n\n"
428-
}
429-
430-
formatted += c.formatSubcommands(info.Subcommands, commandSeq)
431-
formatted += "---\n\n"
432-
433-
return formatted
380+
return fmt.Sprintf("%s%s", prefix, target)
381+
},
382+
LinkForSubcommand: func(s string) string {
383+
return c.linkForCommand(strings.Join(append(commandSeq[1:], s), "_"))
384+
},
385+
})
386+
return buf.String()
434387
}
435388

436389
// getTargetCmd is an auxiliary function that returns the target command or
@@ -456,177 +409,3 @@ func (d *documentationCommand) getTargetCmd(cmd string) (string, error) {
456409

457410
}
458411
}
459-
460-
// formatFlags is an internal formatting solution similar to
461-
// the gnuflag.PrintDefaults. The code is extended here
462-
// to permit additional formatting without modifying the
463-
// gnuflag package.
464-
func (d *documentationCommand) formatFlags(c Command, info *Info) string {
465-
flagsAlias := FlagAlias(c, "")
466-
if flagsAlias == "" {
467-
// For backward compatibility, the default is 'flag'.
468-
flagsAlias = "flag"
469-
}
470-
f := gnuflag.NewFlagSetWithFlagKnownAs(info.Name, gnuflag.ContinueOnError, flagsAlias)
471-
472-
// if we are working with the documentation command,
473-
// we have to set flags on a new instance, otherwise
474-
// we will overwrite the current flag values
475-
if info.Name != "documentation" {
476-
c.SetFlags(f)
477-
} else {
478-
c = newDocumentationCommand(d.super)
479-
c.SetFlags(f)
480-
}
481-
482-
// group together all flags for a given value, meaning that flag which sets the same value are
483-
// grouped together and displayed with the same description, as below:
484-
//
485-
// -s, --short, --alternate-string | default value | some description.
486-
flags := make(map[interface{}]flagsByLength)
487-
f.VisitAll(func(f *gnuflag.Flag) {
488-
flags[f.Value] = append(flags[f.Value], f)
489-
})
490-
if len(flags) == 0 {
491-
return ""
492-
}
493-
494-
// sort the output flags by shortest name for each group.
495-
// Caution: this mean that description/default value displayed in documentation will
496-
// be the one of the shortest alias. Other will be discarded. Be careful to have the same default
497-
// values between each alias, and put the description on the shortest alias.
498-
var byName flagsByName
499-
for _, fl := range flags {
500-
sort.Sort(fl)
501-
byName = append(byName, fl)
502-
}
503-
sort.Sort(byName)
504-
505-
formatted := "| Flag | Default | Usage |\n"
506-
formatted += "| --- | --- | --- |\n"
507-
for _, fs := range byName {
508-
// Collect all flag aliases (usually a short one and a plain one, like -v / --verbose)
509-
formattedFlags := ""
510-
for i, f := range fs {
511-
if i > 0 {
512-
formattedFlags += ", "
513-
}
514-
if len(f.Name) == 1 {
515-
formattedFlags += fmt.Sprintf("`-%s`", f.Name)
516-
} else {
517-
formattedFlags += fmt.Sprintf("`--%s`", f.Name)
518-
}
519-
}
520-
// display all the flags aliases and the default value and description of the shortest one.
521-
// Escape Markdown in description in order to display it cleanly in the final documentation.
522-
formatted += fmt.Sprintf("| %s | %s | %s |\n", formattedFlags,
523-
EscapeMarkdown(fs[0].DefValue),
524-
strings.ReplaceAll(EscapeMarkdown(fs[0].Usage), "\n", " "),
525-
)
526-
}
527-
return formatted
528-
}
529-
530-
// flagsByLength is a slice of flags implementing sort.Interface,
531-
// sorting primarily by the length of the flag, and secondarily
532-
// alphabetically.
533-
type flagsByLength []*gnuflag.Flag
534-
535-
func (f flagsByLength) Less(i, j int) bool {
536-
s1, s2 := f[i].Name, f[j].Name
537-
if len(s1) != len(s2) {
538-
return len(s1) < len(s2)
539-
}
540-
return s1 < s2
541-
}
542-
func (f flagsByLength) Swap(i, j int) {
543-
f[i], f[j] = f[j], f[i]
544-
}
545-
func (f flagsByLength) Len() int {
546-
return len(f)
547-
}
548-
549-
// flagsByName is a slice of slices of flags implementing sort.Interface,
550-
// alphabetically sorting by the name of the first flag in each slice.
551-
type flagsByName [][]*gnuflag.Flag
552-
553-
func (f flagsByName) Less(i, j int) bool {
554-
return f[i][0].Name < f[j][0].Name
555-
}
556-
func (f flagsByName) Swap(i, j int) {
557-
f[i], f[j] = f[j], f[i]
558-
}
559-
func (f flagsByName) Len() int {
560-
return len(f)
561-
}
562-
563-
// EscapeMarkdown returns a copy of the input string, in which any special
564-
// Markdown characters (e.g. < > |) are escaped.
565-
func EscapeMarkdown(raw string) string {
566-
escapeSeqs := map[rune]string{
567-
'<': "&lt;",
568-
'>': "&gt;",
569-
'&': "&amp;",
570-
'|': "&#x7c;",
571-
}
572-
573-
var escaped strings.Builder
574-
escaped.Grow(len(raw))
575-
576-
lines := strings.Split(raw, "\n")
577-
for i, line := range lines {
578-
if strings.HasPrefix(line, " ") {
579-
// Literal code block - don't escape anything
580-
escaped.WriteString(line)
581-
582-
} else {
583-
// Keep track of whether we are inside a code span `...`
584-
// If so, don't escape characters
585-
insideCodeSpan := false
586-
587-
for _, c := range line {
588-
if c == '`' {
589-
insideCodeSpan = !insideCodeSpan
590-
}
591-
592-
if !insideCodeSpan {
593-
if escapeSeq, ok := escapeSeqs[c]; ok {
594-
escaped.WriteString(escapeSeq)
595-
continue
596-
}
597-
}
598-
escaped.WriteRune(c)
599-
}
600-
}
601-
602-
if i < len(lines)-1 {
603-
escaped.WriteRune('\n')
604-
}
605-
}
606-
607-
return escaped.String()
608-
}
609-
610-
func (c *documentationCommand) formatSubcommands(subcommands map[string]string, commandSeq []string) string {
611-
var output string
612-
613-
sorted := []string{}
614-
for name := range subcommands {
615-
if isDefaultCommand(name) {
616-
continue
617-
}
618-
sorted = append(sorted, name)
619-
}
620-
sort.Strings(sorted)
621-
622-
if len(sorted) > 0 {
623-
output += "## Subcommands\n"
624-
for _, name := range sorted {
625-
output += fmt.Sprintf("- [%s](%s)\n", name,
626-
c.linkForCommand(strings.Join(append(commandSeq[1:], name), "_")))
627-
}
628-
output += "\n"
629-
}
630-
631-
return output
632-
}

documentation_test.go

+2-5
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ func (s *documentationSuite) TestFormatCommand(c *gc.C) {
3838
expected: (`
3939
> See also: [clouds](#clouds), [update-cloud](#update-cloud), [remove-cloud](#remove-cloud), [update-credential](#update-credential)
4040
41+
**Aliases:** cloud-add, import-cloud
42+
4143
## Summary
4244
summary for add-cloud...
4345
@@ -57,8 +59,6 @@ examples for add-cloud...
5759
## Details
5860
details for add-cloud...
5961
60-
---
61-
6262
`)[1:],
6363
}, {
6464
// no flags - don't print "Options" table
@@ -74,7 +74,6 @@ details for add-cloud...
7474
},
7575
title: false,
7676
expected: (`
77-
7877
## Summary
7978
insert summary here...
8079
@@ -87,8 +86,6 @@ insert examples here...
8786
## Details
8887
insert details here...
8988
90-
---
91-
9289
`)[1:],
9390
}}
9491

0 commit comments

Comments
 (0)