Skip to content

Commit 7cf5cd6

Browse files
authored
Merge pull request #3034 from thaJeztah/yamldocs_updates
yamldocs: various improvements
2 parents 13e4a09 + b86f513 commit 7cf5cd6

5 files changed

Lines changed: 324 additions & 43 deletions

File tree

docs/yaml/generate.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,22 +17,26 @@ import (
1717
const descriptionSourcePath = "docs/reference/commandline/"
1818

1919
func generateCliYaml(opts *options) error {
20-
dockerCli, err := command.NewDockerCli()
20+
dockerCLI, err := command.NewDockerCli()
2121
if err != nil {
2222
return err
2323
}
2424
cmd := &cobra.Command{
2525
Use: "docker [OPTIONS] COMMAND [ARG...]",
2626
Short: "The base command for the Docker CLI.",
2727
}
28-
commands.AddCommands(cmd, dockerCli)
28+
commands.AddCommands(cmd, dockerCLI)
2929
disableFlagsInUseLine(cmd)
3030
source := filepath.Join(opts.source, descriptionSourcePath)
3131
fmt.Println("Markdown source:", source)
3232
if err := loadLongDescription(cmd, source); err != nil {
3333
return err
3434
}
3535

36+
if err := os.MkdirAll(opts.target, 0755); err != nil {
37+
return err
38+
}
39+
3640
cmd.DisableAutoGenTag = true
3741
return GenYamlTree(cmd, opts.target)
3842
}
@@ -80,9 +84,7 @@ func loadLongDescription(parentCmd *cobra.Command, path string) error {
8084
if err != nil {
8185
return err
8286
}
83-
description, examples := parseMDContent(string(content))
84-
cmd.Long = description
85-
cmd.Example = examples
87+
applyDescriptionAndExamples(cmd, string(content))
8688
}
8789
return nil
8890
}

docs/yaml/markdown.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package main
2+
3+
import (
4+
"regexp"
5+
"strings"
6+
"unicode"
7+
)
8+
9+
var (
10+
// mdHeading matches MarkDown H1..h6 headings. Note that this regex may produce
11+
// false positives for (e.g.) comments in code-blocks (# this is a comment),
12+
// so should not be used as a generic regex for other purposes.
13+
mdHeading = regexp.MustCompile(`^([#]{1,6})\s(.*)$`)
14+
// htmlAnchor matches inline HTML anchors. This is intended to only match anchors
15+
// for our use-case; DO NOT consider using this as a generic regex, or at least
16+
// not before reading https://stackoverflow.com/a/1732454/1811501.
17+
htmlAnchor = regexp.MustCompile(`<a\s+(?:name|id)="?([^"]+)"?\s*></a>\s*`)
18+
)
19+
20+
// getSections returns all H2 sections by title (lowercase)
21+
func getSections(mdString string) map[string]string {
22+
parsedContent := strings.Split("\n"+mdString, "\n## ")
23+
sections := make(map[string]string, len(parsedContent))
24+
for _, s := range parsedContent {
25+
if strings.HasPrefix(s, "#") {
26+
// not a H2 Section
27+
continue
28+
}
29+
parts := strings.SplitN(s, "\n", 2)
30+
if len(parts) == 2 {
31+
sections[strings.ToLower(parts[0])] = parts[1]
32+
}
33+
}
34+
return sections
35+
}
36+
37+
// cleanupMarkDown cleans up the MarkDown passed in mdString for inclusion in
38+
// YAML. It removes trailing whitespace and substitutes tabs for four spaces
39+
// to prevent YAML switching to use "compact" form; ("line1 \nline\t2\n")
40+
// which, although equivalent, is hard to read.
41+
func cleanupMarkDown(mdString string) (md string, anchors []string) {
42+
// remove leading/trailing whitespace, and replace tabs in the whole content
43+
mdString = strings.TrimSpace(mdString)
44+
mdString = strings.ReplaceAll(mdString, "\t", " ")
45+
mdString = strings.ReplaceAll(mdString, "https://docs.docker.com", "")
46+
47+
var id string
48+
// replace trailing whitespace per line, and handle custom anchors
49+
lines := strings.Split(mdString, "\n")
50+
for i := 0; i < len(lines); i++ {
51+
lines[i] = strings.TrimRightFunc(lines[i], unicode.IsSpace)
52+
lines[i], id = convertHTMLAnchor(lines[i])
53+
if id != "" {
54+
anchors = append(anchors, id)
55+
}
56+
}
57+
return strings.Join(lines, "\n"), anchors
58+
}
59+
60+
// convertHTMLAnchor converts inline anchor-tags in headings (<a name=myanchor></a>)
61+
// to an extended-markdown property ({#myanchor}). Extended Markdown properties
62+
// are not supported in GitHub Flavored Markdown, but are supported by Jekyll,
63+
// and lead to cleaner HTML in our docs, and prevents duplicate anchors.
64+
// It returns the converted MarkDown heading and the custom ID (if present)
65+
func convertHTMLAnchor(mdLine string) (md string, customID string) {
66+
if m := mdHeading.FindStringSubmatch(mdLine); len(m) > 0 {
67+
if a := htmlAnchor.FindStringSubmatch(m[2]); len(a) > 0 {
68+
customID = a[1]
69+
mdLine = m[1] + " " + htmlAnchor.ReplaceAllString(m[2], "") + " {#" + customID + "}"
70+
}
71+
}
72+
return mdLine, customID
73+
}

docs/yaml/markdown_test.go

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
package main
2+
3+
import "testing"
4+
5+
func TestCleanupMarkDown(t *testing.T) {
6+
tests := []struct {
7+
doc, in, expected string
8+
}{
9+
{
10+
doc: "whitespace around sections",
11+
in: `
12+
13+
## Section start
14+
15+
Some lines.
16+
And more lines.
17+
18+
`,
19+
expected: `## Section start
20+
21+
Some lines.
22+
And more lines.`,
23+
},
24+
{
25+
doc: "lines with inline tabs",
26+
in: `## Some Heading
27+
28+
A line with tabs in it.
29+
Tabs should be replaced by spaces`,
30+
expected: `## Some Heading
31+
32+
A line with tabs in it.
33+
Tabs should be replaced by spaces`,
34+
},
35+
{
36+
doc: "lines with trailing spaces",
37+
in: `## Some Heading with spaces
38+
39+
This is a line.
40+
This is an indented line
41+
42+
### Some other heading
43+
44+
Last line.`,
45+
expected: `## Some Heading with spaces
46+
47+
This is a line.
48+
This is an indented line
49+
50+
### Some other heading
51+
52+
Last line.`,
53+
},
54+
{
55+
doc: "lines with trailing tabs",
56+
in: `## Some Heading with tabs
57+
58+
This is a line.
59+
This is an indented line
60+
61+
### Some other heading
62+
63+
Last line.`,
64+
expected: `## Some Heading with tabs
65+
66+
This is a line.
67+
This is an indented line
68+
69+
### Some other heading
70+
71+
Last line.`,
72+
},
73+
}
74+
for _, tc := range tests {
75+
tc := tc
76+
t.Run(tc.doc, func(t *testing.T) {
77+
out, _ := cleanupMarkDown(tc.in)
78+
if out != tc.expected {
79+
t.Fatalf("\nexpected:\n%q\nactual:\n%q\n", tc.expected, out)
80+
}
81+
})
82+
}
83+
}
84+
85+
func TestConvertHTMLAnchor(t *testing.T) {
86+
tests := []struct {
87+
in, id, expected string
88+
}{
89+
{
90+
in: `# <a name=heading1></a> Heading 1`,
91+
id: "heading1",
92+
expected: `# Heading 1 {#heading1}`,
93+
},
94+
{
95+
in: `## Heading 2<a name=heading2></a> `,
96+
id: "heading2",
97+
expected: `## Heading 2 {#heading2}`,
98+
},
99+
{
100+
in: `### <a id=heading3></a>Heading 3`,
101+
id: "heading3",
102+
expected: `### Heading 3 {#heading3}`,
103+
},
104+
{
105+
in: `#### <a id="heading4"></a> Heading 4`,
106+
id: "heading4",
107+
expected: `#### Heading 4 {#heading4}`,
108+
},
109+
{
110+
in: `##### <a id="heading5" ></a> Heading 5`,
111+
id: "heading5",
112+
expected: `##### Heading 5 {#heading5}`,
113+
},
114+
{
115+
in: `###### <a id=hello href=foo>hello!</a>Heading 6`,
116+
id: "",
117+
expected: `###### <a id=hello href=foo>hello!</a>Heading 6`,
118+
},
119+
}
120+
for _, tc := range tests {
121+
tc := tc
122+
t.Run(tc.in, func(t *testing.T) {
123+
out, id := convertHTMLAnchor(tc.in)
124+
if id != tc.id {
125+
t.Fatalf("expected: %s, actual: %s\n", tc.id, id)
126+
}
127+
if out != tc.expected {
128+
t.Fatalf("\nexpected: %s\nactual: %s\n", tc.expected, out)
129+
}
130+
})
131+
}
132+
}

0 commit comments

Comments
 (0)