From 6f4440bf8b84066ed0cef418bcd42b799e449ea7 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 29 Dec 2022 00:58:00 +0100 Subject: [PATCH] markdown: format generated tables to have equal-width columns This makes the tables more readable when reading the source code. Signed-off-by: Sebastiaan van Stijn --- clidocstool_md.go | 65 ++++++++++++++++++++++++++++++---------- fixtures/buildx.md | 14 ++++----- fixtures/buildx_build.md | 48 ++++++++++++++--------------- fixtures/buildx_stop.md | 6 ++-- 4 files changed, 83 insertions(+), 50 deletions(-) diff --git a/clidocstool_md.go b/clidocstool_md.go index 8c9f10b..73d05c8 100644 --- a/clidocstool_md.go +++ b/clidocstool_md.go @@ -22,6 +22,7 @@ import ( "path/filepath" "regexp" "strings" + "text/tabwriter" "text/template" "github.com/docker/cli-docs-tool/annotation" @@ -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 @@ -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{} @@ -168,12 +206,11 @@ func mdCmdOutput(cmd *cobra.Command, old string) (string, error) { if len(cmd.Commands()) != 0 { b.WriteString("### Subcommands\n\n") - b.WriteString("| Name | Description |\n") - b.WriteString("| --- | --- |\n") + table := newMdTable("Name", "Description") for _, c := range cmd.Commands() { - b.WriteString(fmt.Sprintf("| [`%s`](%s) | %s |\n", c.Name(), mdFilename(c), c.Short)) + table.AddRow(fmt.Sprintf("[`%s`](%s)", c.Name(), mdFilename(c)), c.Short) } - b.WriteString("\n\n") + b.WriteString(table.String() + "\n") } // add inherited flags before checking for flags availability @@ -181,22 +218,18 @@ func mdCmdOutput(cmd *cobra.Command, old string) (string, error) { if cmd.Flags().HasAvailableFlags() { b.WriteString("### Options\n\n") - b.WriteString("| Name | Type | Default | Description |\n") - b.WriteString("| --- | --- | --- | --- |\n") - + table := newMdTable("Name", "Type", "Default", "Description") cmd.Flags().VisitAll(func(f *pflag.Flag) { if f.Hidden { return } isLink := strings.Contains(old, "") - b.WriteString("| ") + var name string if f.Shorthand != "" { - name := "`-" + f.Shorthand + "`" - name = mdMakeLink(name, f.Name, f, isLink) - b.WriteString(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" { @@ -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, "`") } - b.WriteString(fmt.Sprintf("%s | %s | %s | %s |\n", mdEscapePipe(name), mdEscapePipe(ftype), mdEscapePipe(defval), mdReplaceNewline(mdEscapePipe(usage)))) + table.AddRow(name, ftype, defval, mdReplaceNewline(usage)) }) - b.WriteString("\n") + b.WriteString(table.String()) } return b.String(), nil diff --git a/fixtures/buildx.md b/fixtures/buildx.md index a625820..607fec9 100644 --- a/fixtures/buildx.md +++ b/fixtures/buildx.md @@ -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 | diff --git a/fixtures/buildx_build.md b/fixtures/buildx_build.md index f56c8e4..742f010 100644 --- a/fixtures/buildx_build.md +++ b/fixtures/buildx_build.md @@ -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
format: `default\|[=\|[,]]` | -| [`-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
format: `default\|[=\|[,]]` | +| [`-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 | diff --git a/fixtures/buildx_stop.md b/fixtures/buildx_stop.md index 4eb7e67..ea23375 100644 --- a/fixtures/buildx_stop.md +++ b/fixtures/buildx_stop.md @@ -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 |