Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

markdown: format generated tables to have equal-width columns #34

Merged
merged 2 commits into from Dec 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
77 changes: 55 additions & 22 deletions clidocstool_md.go
Expand Up @@ -22,6 +22,7 @@ import (
"path/filepath"
"regexp"
"strings"
"text/tabwriter"
"text/template"

"github.com/docker/cli-docs-tool/annotation"
Expand All @@ -30,7 +31,8 @@ import (
)

var (
nlRegexp = regexp.MustCompile(`\r?\n`)
nlRegexp = regexp.MustCompile(`\r?\n`)
adjustSep = regexp.MustCompile(`\|:---(\s+)`)
)

// GenMarkdownTree will generate a markdown page for this command and all
Expand Down Expand Up @@ -149,6 +151,42 @@ func mdMakeLink(txt, link string, f *pflag.Flag, isAnchor bool) string {
return "[" + txt + "](" + link + ")"
}

type mdTable struct {
out *strings.Builder
tabWriter *tabwriter.Writer
}

func newMdTable(headers ...string) *mdTable {
w := &strings.Builder{}
t := &mdTable{
out: w,
// Using tabwriter.Debug, which uses "|" as separator instead of tabs,
// which is what we want. It's a bit of a hack, but does the job :)
tabWriter: tabwriter.NewWriter(w, 5, 5, 1, ' ', tabwriter.Debug),
}
t.addHeader(headers...)
return t
}

func (t *mdTable) addHeader(cols ...string) {
t.AddRow(cols...)
_, _ = t.tabWriter.Write([]byte("|" + strings.Repeat(":---\t", len(cols)) + "\n"))
}

func (t *mdTable) AddRow(cols ...string) {
for i := range cols {
cols[i] = mdEscapePipe(cols[i])
}
_, _ = t.tabWriter.Write([]byte("| " + strings.Join(cols, "\t ") + "\t\n"))
}

func (t *mdTable) String() string {
_ = t.tabWriter.Flush()
return adjustSep.ReplaceAllStringFunc(t.out.String()+"\n", func(in string) string {
return strings.ReplaceAll(in, " ", "-")
})
}

func mdCmdOutput(cmd *cobra.Command, old string) (string, error) {
b := &strings.Builder{}

Expand All @@ -157,46 +195,41 @@ func mdCmdOutput(cmd *cobra.Command, old string) (string, error) {
desc = cmd.Long
}
if desc != "" {
fmt.Fprintf(b, "%s\n\n", desc)
b.WriteString(desc + "\n\n")
}

if aliases := getAliases(cmd); len(aliases) != 0 {
fmt.Fprint(b, "### Aliases\n\n")
fmt.Fprint(b, "`"+strings.Join(aliases, "`, `")+"`")
fmt.Fprint(b, "\n\n")
b.WriteString("### Aliases\n\n")
b.WriteString("`" + strings.Join(aliases, "`, `") + "`")
b.WriteString("\n\n")
}

if len(cmd.Commands()) != 0 {
fmt.Fprint(b, "### Subcommands\n\n")
fmt.Fprint(b, "| Name | Description |\n")
fmt.Fprint(b, "| --- | --- |\n")
b.WriteString("### Subcommands\n\n")
table := newMdTable("Name", "Description")
for _, c := range cmd.Commands() {
fmt.Fprintf(b, "| [`%s`](%s) | %s |\n", c.Name(), mdFilename(c), c.Short)
table.AddRow(fmt.Sprintf("[`%s`](%s)", c.Name(), mdFilename(c)), c.Short)
}
fmt.Fprint(b, "\n\n")
b.WriteString(table.String() + "\n")
}

// add inherited flags before checking for flags availability
cmd.Flags().AddFlagSet(cmd.InheritedFlags())

if cmd.Flags().HasAvailableFlags() {
fmt.Fprint(b, "### Options\n\n")
fmt.Fprint(b, "| Name | Type | Default | Description |\n")
fmt.Fprint(b, "| --- | --- | --- | --- |\n")

b.WriteString("### Options\n\n")
table := newMdTable("Name", "Type", "Default", "Description")
cmd.Flags().VisitAll(func(f *pflag.Flag) {
if f.Hidden {
return
}
isLink := strings.Contains(old, "<a name=\""+f.Name+"\"></a>")
fmt.Fprint(b, "| ")
var name string
if f.Shorthand != "" {
name := "`-" + f.Shorthand + "`"
name = mdMakeLink(name, f.Name, f, isLink)
fmt.Fprintf(b, "%s, ", name)
name = mdMakeLink("`-"+f.Shorthand+"`", f.Name, f, isLink)
name += ", "
}
name := "`--" + f.Name + "`"
name = mdMakeLink(name, f.Name, f, isLink)
name += mdMakeLink("`--"+f.Name+"`", f.Name, f, isLink)

var ftype string
if f.Value.Type() != "bool" {
Expand All @@ -221,9 +254,9 @@ func mdCmdOutput(cmd *cobra.Command, old string) (string, error) {
} else if cd, ok := cmd.Annotations[annotation.CodeDelimiter]; ok {
usage = strings.ReplaceAll(usage, cd, "`")
}
fmt.Fprintf(b, "%s | %s | %s | %s |\n", mdEscapePipe(name), mdEscapePipe(ftype), mdEscapePipe(defval), mdReplaceNewline(mdEscapePipe(usage)))
table.AddRow(name, ftype, defval, mdReplaceNewline(usage))
})
fmt.Fprintln(b, "")
b.WriteString(table.String())
}

return b.String(), nil
Expand Down
14 changes: 7 additions & 7 deletions fixtures/buildx.md
Expand Up @@ -5,17 +5,17 @@ Extended build capabilities with BuildKit

### Subcommands

| Name | Description |
| --- | --- |
| [`build`](buildx_build.md) | Start a build |
| [`stop`](buildx_stop.md) | Stop builder instance |
| Name | Description |
|:---------------------------|:----------------------|
| [`build`](buildx_build.md) | Start a build |
| [`stop`](buildx_stop.md) | Stop builder instance |


### Options

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `--builder` | `string` | | Override the configured builder instance |
| Name | Type | Default | Description |
|:------------|:---------|:--------|:-----------------------------------------|
| `--builder` | `string` | | Override the configured builder instance |


<!---MARKER_GEN_END-->
Expand Down
48 changes: 24 additions & 24 deletions fixtures/buildx_build.md
Expand Up @@ -9,30 +9,30 @@ Start a build

### Options

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| [`--add-host`](https://docs.docker.com/engine/reference/commandline/build/#add-entries-to-container-hosts-file---add-host) | `stringSlice` | | Add a custom host-to-IP mapping (format: `host:ip`) |
| `--allow` | `stringSlice` | | Allow extra privileged entitlement (e.g., `network.host`, `security.insecure`) |
| [`--build-arg`](https://docs.docker.com/engine/reference/commandline/build/#set-build-time-variables---build-arg) | `stringArray` | | Set build-time variables |
| `--builder` | `string` | | Override the configured builder instance |
| `--cache-from` | `stringArray` | | External cache sources (e.g., `user/app:cache`, `type=local,src=path/to/dir`) |
| `--cache-to` | `stringArray` | | Cache export destinations (e.g., `user/app:cache`, `type=local,dest=path/to/dir`) |
| [`--cgroup-parent`](https://docs.docker.com/engine/reference/commandline/build/#use-a-custom-parent-cgroup---cgroup-parent) | `string` | | Optional parent cgroup for the container |
| [`-f`](https://docs.docker.com/engine/reference/commandline/build/#specify-a-dockerfile--f), [`--file`](https://docs.docker.com/engine/reference/commandline/build/#specify-a-dockerfile--f) | `string` | | Name of the Dockerfile (default: `PATH/Dockerfile`) |
| `--iidfile` | `string` | | Write the image ID to the file |
| `--label` | `stringArray` | | Set metadata for an image |
| `--load` | | | Shorthand for `--output=type=docker` |
| `--network` | `string` | `default` | Set the networking mode for the `RUN` instructions during build |
| `-o`, `--output` | `stringArray` | | Output destination (format: `type=local,dest=path`) |
| `--platform` | `stringArray` | local | Set target platform for build |
| `--push` | | | Shorthand for `--output=type=registry` |
| `-q`, `--quiet` | | | Suppress the build output and print image ID on success |
| `--secret` | `stringArray` | | Secret file to expose to the build (format: `id=mysecret,src=/local/secret`) |
| `--shm-size` | `string` | | Size of `/dev/shm` |
| `--ssh` | `stringArray` | | SSH agent socket or keys to expose to the build<br>format: `default\|<id>[=<socket>\|<key>[,<key>]]` |
| [`-t`](https://docs.docker.com/engine/reference/commandline/build/#tag-an-image--t), [`--tag`](https://docs.docker.com/engine/reference/commandline/build/#tag-an-image--t) | `stringArray` | | Name and optionally a tag (format: `name:tag`) |
| [`--target`](https://docs.docker.com/engine/reference/commandline/build/#specifying-target-build-stage---target) | `string` | | Set the target build stage to build. |
| `--ulimit` | `string` | | Ulimit options |
| Name | Type | Default | Description |
|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------------|:----------|:-----------------------------------------------------------------------------------------------------|
| [`--add-host`](https://docs.docker.com/engine/reference/commandline/build/#add-entries-to-container-hosts-file---add-host) | `stringSlice` | | Add a custom host-to-IP mapping (format: `host:ip`) |
| `--allow` | `stringSlice` | | Allow extra privileged entitlement (e.g., `network.host`, `security.insecure`) |
| [`--build-arg`](https://docs.docker.com/engine/reference/commandline/build/#set-build-time-variables---build-arg) | `stringArray` | | Set build-time variables |
| `--builder` | `string` | | Override the configured builder instance |
| `--cache-from` | `stringArray` | | External cache sources (e.g., `user/app:cache`, `type=local,src=path/to/dir`) |
| `--cache-to` | `stringArray` | | Cache export destinations (e.g., `user/app:cache`, `type=local,dest=path/to/dir`) |
| [`--cgroup-parent`](https://docs.docker.com/engine/reference/commandline/build/#use-a-custom-parent-cgroup---cgroup-parent) | `string` | | Optional parent cgroup for the container |
| [`-f`](https://docs.docker.com/engine/reference/commandline/build/#specify-a-dockerfile--f), [`--file`](https://docs.docker.com/engine/reference/commandline/build/#specify-a-dockerfile--f) | `string` | | Name of the Dockerfile (default: `PATH/Dockerfile`) |
| `--iidfile` | `string` | | Write the image ID to the file |
| `--label` | `stringArray` | | Set metadata for an image |
| `--load` | | | Shorthand for `--output=type=docker` |
| `--network` | `string` | `default` | Set the networking mode for the `RUN` instructions during build |
| `-o`, `--output` | `stringArray` | | Output destination (format: `type=local,dest=path`) |
| `--platform` | `stringArray` | local | Set target platform for build |
| `--push` | | | Shorthand for `--output=type=registry` |
| `-q`, `--quiet` | | | Suppress the build output and print image ID on success |
| `--secret` | `stringArray` | | Secret file to expose to the build (format: `id=mysecret,src=/local/secret`) |
| `--shm-size` | `string` | | Size of `/dev/shm` |
| `--ssh` | `stringArray` | | SSH agent socket or keys to expose to the build<br>format: `default\|<id>[=<socket>\|<key>[,<key>]]` |
| [`-t`](https://docs.docker.com/engine/reference/commandline/build/#tag-an-image--t), [`--tag`](https://docs.docker.com/engine/reference/commandline/build/#tag-an-image--t) | `stringArray` | | Name and optionally a tag (format: `name:tag`) |
| [`--target`](https://docs.docker.com/engine/reference/commandline/build/#specifying-target-build-stage---target) | `string` | | Set the target build stage to build. |
| `--ulimit` | `string` | | Ulimit options |


<!---MARKER_GEN_END-->
Expand Down
6 changes: 3 additions & 3 deletions fixtures/buildx_stop.md
Expand Up @@ -5,9 +5,9 @@ Stop builder instance

### Options

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `--builder` | `string` | | Override the configured builder instance |
| Name | Type | Default | Description |
|:------------|:---------|:--------|:-----------------------------------------|
| `--builder` | `string` | | Override the configured builder instance |


<!---MARKER_GEN_END-->
Expand Down