Skip to content

Commit 67f339f

Browse files
committed
New opts for setting target and plugin capabilities
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
1 parent 27b26b5 commit 67f339f

17 files changed

Lines changed: 417 additions & 235 deletions

.github/workflows/godev.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ on:
99

1010
jobs:
1111
update:
12+
if: startsWith(github.ref, 'refs/tags/')
1213
runs-on: ubuntu-latest
1314
steps:
1415
-

.github/workflows/test.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,20 @@ jobs:
4141
uses: codecov/codecov-action@v2
4242
with:
4343
file: ./coverage.txt
44+
45+
example:
46+
runs-on: ubuntu-latest
47+
steps:
48+
-
49+
name: Checkout
50+
uses: actions/checkout@v2
51+
-
52+
name: Set up Go
53+
uses: actions/setup-go@v2
54+
with:
55+
go-version: 1.16
56+
-
57+
name: Run
58+
run: |
59+
go run main.go
60+
working-directory: ./example

README.md

Lines changed: 3 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -41,61 +41,10 @@ require (
4141
)
4242
```
4343

44-
Next, create a file named `docgen.go` inside that directory containing the
45-
following Go code:
44+
Next, create a file named `main.go` inside that directory containing the
45+
following Go code from [`example/main.go`](example/main.go).
4646

47-
```go
48-
package main
49-
50-
import (
51-
"log"
52-
"os"
53-
"path/filepath"
54-
55-
"github.com/docker/buildx/commands"
56-
"github.com/docker/cli/cli/command"
57-
clidocstool "github.com/docker/cli-docs-tool"
58-
"github.com/spf13/cobra"
59-
)
60-
61-
const sourcePath = "docs/"
62-
63-
func main() {
64-
log.SetFlags(0)
65-
66-
dockerCLI, err := command.NewDockerCli()
67-
if err != nil {
68-
log.Printf("ERROR: %+v", err)
69-
}
70-
71-
cmd := &cobra.Command{
72-
Use: "docker [OPTIONS] COMMAND [ARG...]",
73-
Short: "The base command for the Docker CLI.",
74-
DisableAutoGenTag: true,
75-
}
76-
77-
cmd.AddCommand(commands.NewRootCmd("buildx", true, dockerCLI))
78-
clidocstool.DisableFlagsInUseLine(cmd)
79-
80-
cwd, _ := os.Getwd()
81-
source := filepath.Join(cwd, sourcePath)
82-
83-
// Make sure "source" folder is created first
84-
if err = os.MkdirAll(source, 0755); err != nil {
85-
log.Printf("ERROR: %+v", err)
86-
}
87-
88-
// Generate Markdown and YAML documentation to "source" folder
89-
if err = clidocstool.GenTree(cmd, source); err != nil {
90-
log.Printf("ERROR: %+v", err)
91-
}
92-
}
93-
```
94-
95-
Here we create a new instance of Docker CLI with `command.NewDockerCli` and a
96-
subcommand `commands.NewRootCmd` for `buildx`.
97-
98-
Finally, we generate Markdown and YAML documentation with `clidocstool.GenTree`.
47+
Running this example should produce the following output:
9948

10049
```console
10150
$ go run main.go

clidocstool.go

Lines changed: 86 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,38 +15,108 @@
1515
package clidocstool
1616

1717
import (
18+
"io"
19+
"os"
20+
21+
"github.com/pkg/errors"
1822
"github.com/spf13/cobra"
1923
)
2024

21-
// GenTree creates yaml and markdown structured ref files for this command
22-
// and all descendants in the directory given. This function will just
23-
// call GenMarkdownTree and GenYamlTree functions successively.
24-
func GenTree(cmd *cobra.Command, dir string) error {
25+
// Options defines options for cli-docs-tool
26+
type Options struct {
27+
Root *cobra.Command
28+
SourceDir string
29+
TargetDir string
30+
Plugin bool
31+
}
32+
33+
// Client represents an active cli-docs-tool object
34+
type Client struct {
35+
root *cobra.Command
36+
source string
37+
target string
38+
plugin bool
39+
}
40+
41+
// New initializes a new cli-docs-tool client
42+
func New(opts Options) (*Client, error) {
43+
if opts.Root == nil {
44+
return nil, errors.New("root cmd required")
45+
}
46+
if len(opts.SourceDir) == 0 {
47+
return nil, errors.New("source dir required")
48+
}
49+
c := &Client{
50+
root: opts.Root,
51+
source: opts.SourceDir,
52+
plugin: opts.Plugin,
53+
}
54+
if len(opts.TargetDir) == 0 {
55+
c.target = c.source
56+
} else {
57+
c.target = opts.TargetDir
58+
}
59+
if err := os.MkdirAll(c.target, 0755); err != nil {
60+
return nil, err
61+
}
62+
return c, nil
63+
}
64+
65+
// GenAllTree creates all structured ref files for this command and
66+
// all descendants in the directory given.
67+
func (c *Client) GenAllTree() error {
2568
var err error
26-
if err = GenMarkdownTree(cmd, dir); err != nil {
69+
if err = c.GenMarkdownTree(c.root); err != nil {
2770
return err
2871
}
29-
if err = GenYamlTree(cmd, dir); err != nil {
72+
if err = c.GenYamlTree(c.root); err != nil {
3073
return err
3174
}
3275
return nil
3376
}
3477

35-
// VisitAll will traverse all commands from the root.
78+
// DisableFlagsInUseLine sets the DisableFlagsInUseLine flag on all
79+
// commands within the tree rooted at cmd.
80+
func (c *Client) DisableFlagsInUseLine() {
81+
visitAll(c.root, func(ccmd *cobra.Command) {
82+
// do not add a `[flags]` to the end of the usage line.
83+
ccmd.DisableFlagsInUseLine = true
84+
})
85+
}
86+
87+
// visitAll traverses all commands from the root.
3688
// This is different from the VisitAll of cobra.Command where only parents
3789
// are checked.
38-
func VisitAll(root *cobra.Command, fn func(*cobra.Command)) {
90+
func visitAll(root *cobra.Command, fn func(*cobra.Command)) {
3991
for _, cmd := range root.Commands() {
40-
VisitAll(cmd, fn)
92+
visitAll(cmd, fn)
4193
}
4294
fn(root)
4395
}
4496

45-
// DisableFlagsInUseLine sets the DisableFlagsInUseLine flag on all
46-
// commands within the tree rooted at cmd.
47-
func DisableFlagsInUseLine(cmd *cobra.Command) {
48-
VisitAll(cmd, func(ccmd *cobra.Command) {
49-
// do not add a `[flags]` to the end of the usage line.
50-
ccmd.DisableFlagsInUseLine = true
51-
})
97+
func fileExists(f string) bool {
98+
info, err := os.Stat(f)
99+
if os.IsNotExist(err) {
100+
return false
101+
}
102+
return !info.IsDir()
103+
}
104+
105+
func copyFile(src string, dest string) error {
106+
srcFile, err := os.Open(src)
107+
if err != nil {
108+
return err
109+
}
110+
defer srcFile.Close()
111+
112+
destFile, err := os.Create(dest)
113+
if err != nil {
114+
return err
115+
}
116+
defer destFile.Close()
117+
118+
if _, err = io.Copy(destFile, srcFile); err != nil {
119+
return err
120+
}
121+
return destFile.Sync()
52122
}

clidocstool_md.go

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,21 +31,25 @@ import (
3131

3232
// GenMarkdownTree will generate a markdown page for this command and all
3333
// descendants in the directory given.
34-
func GenMarkdownTree(cmd *cobra.Command, dir string) error {
35-
for _, c := range cmd.Commands() {
36-
if err := GenMarkdownTree(c, dir); err != nil {
34+
func (c *Client) GenMarkdownTree(cmd *cobra.Command) error {
35+
for _, sc := range cmd.Commands() {
36+
if err := c.GenMarkdownTree(sc); err != nil {
3737
return err
3838
}
3939
}
40-
if !cmd.HasParent() {
40+
41+
// Skip the root command altogether, to prevent generating a useless
42+
// md file for plugins.
43+
if c.plugin && !cmd.HasParent() {
4144
return nil
4245
}
4346

4447
log.Printf("INFO: Generating Markdown for %q", cmd.CommandPath())
4548
mdFile := mdFilename(cmd)
46-
fullPath := filepath.Join(dir, mdFile)
49+
sourcePath := filepath.Join(c.source, mdFile)
50+
targetPath := filepath.Join(c.target, mdFile)
4751

48-
if _, err := os.Stat(fullPath); os.IsNotExist(err) {
52+
if !fileExists(sourcePath) {
4953
var icBuf bytes.Buffer
5054
icTpl, err := template.New("ic").Option("missingkey=error").Parse(`# {{ .Command }}
5155
@@ -63,12 +67,14 @@ func GenMarkdownTree(cmd *cobra.Command, dir string) error {
6367
}); err != nil {
6468
return err
6569
}
66-
if err = ioutil.WriteFile(fullPath, icBuf.Bytes(), 0644); err != nil {
70+
if err = ioutil.WriteFile(targetPath, icBuf.Bytes(), 0644); err != nil {
6771
return err
6872
}
73+
} else if err := copyFile(sourcePath, targetPath); err != nil {
74+
return err
6975
}
7076

71-
content, err := ioutil.ReadFile(fullPath)
77+
content, err := ioutil.ReadFile(targetPath)
7278
if err != nil {
7379
return err
7480
}
@@ -91,12 +97,12 @@ func GenMarkdownTree(cmd *cobra.Command, dir string) error {
9197
}
9298
cont := cs[:start] + "<!---MARKER_GEN_START-->" + "\n" + out + "\n" + cs[end:]
9399

94-
fi, err := os.Stat(fullPath)
100+
fi, err := os.Stat(targetPath)
95101
if err != nil {
96102
return err
97103
}
98-
if err := ioutil.WriteFile(fullPath, []byte(cont), fi.Mode()); err != nil {
99-
return errors.Wrapf(err, "failed to write %s", fullPath)
104+
if err = ioutil.WriteFile(targetPath, []byte(cont), fi.Mode()); err != nil {
105+
return errors.Wrapf(err, "failed to write %s", targetPath)
100106
}
101107

102108
return nil

clidocstool_md_test.go

Lines changed: 21 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -17,67 +17,39 @@ package clidocstool
1717
import (
1818
"io/ioutil"
1919
"os"
20+
"path"
2021
"path/filepath"
2122
"testing"
2223

23-
"github.com/spf13/cobra"
2424
"github.com/stretchr/testify/assert"
2525
"github.com/stretchr/testify/require"
2626
)
2727

2828
//nolint:errcheck
2929
func TestGenMarkdownTree(t *testing.T) {
30-
c := &cobra.Command{Use: "do [OPTIONS] arg1 arg2"}
31-
s := &cobra.Command{Use: "sub [OPTIONS] arg1 arg2"}
32-
33-
flags := s.Flags()
34-
_ = flags.Bool("push", false, "Shorthand for --output=type=registry")
35-
_ = flags.Bool("load", false, "Shorthand for --output=type=docker")
36-
_ = flags.StringArrayP("tag", "t", []string{}, "Name and optionally a tag in the 'name:tag' format")
37-
flags.SetAnnotation("tag", "docs.external.url", []string{"https://docs.docker.com/engine/reference/commandline/build/#tag-an-image--t"})
38-
_ = flags.StringArray("build-arg", []string{}, "Set build-time variables")
39-
flags.SetAnnotation("build-arg", "docs.external.url", []string{"https://docs.docker.com/engine/reference/commandline/build/#set-build-time-variables---build-arg"})
40-
_ = flags.StringP("file", "f", "", "Name of the Dockerfile (Default is 'PATH/Dockerfile')")
41-
flags.SetAnnotation("file", "docs.external.url", []string{"https://docs.docker.com/engine/reference/commandline/build/#specify-a-dockerfile--f"})
42-
_ = flags.StringArray("label", []string{}, "Set metadata for an image")
43-
_ = flags.StringArray("cache-from", []string{}, "External cache sources (eg. user/app:cache, type=local,src=path/to/dir)")
44-
_ = flags.StringArray("cache-to", []string{}, "Cache export destinations (eg. user/app:cache, type=local,dest=path/to/dir)")
45-
_ = flags.String("target", "", "Set the target build stage to build.")
46-
flags.SetAnnotation("target", "docs.external.url", []string{"https://docs.docker.com/engine/reference/commandline/build/#specifying-target-build-stage---target"})
47-
_ = flags.StringSlice("allow", []string{}, "Allow extra privileged entitlement, e.g. network.host, security.insecure")
48-
_ = flags.StringArray("platform", []string{}, "Set target platform for build")
49-
_ = flags.StringArray("secret", []string{}, "Secret file to expose to the build: id=mysecret,src=/local/secret")
50-
_ = flags.StringArray("ssh", []string{}, "SSH agent socket or keys to expose to the build (format: `default|<id>[=<socket>|<key>[,<key>]]`)")
51-
_ = flags.StringArrayP("output", "o", []string{}, "Output destination (format: type=local,dest=path)")
52-
// not implemented
53-
_ = flags.String("network", "default", "Set the networking mode for the RUN instructions during build")
54-
_ = flags.StringSlice("add-host", []string{}, "Add a custom host-to-IP mapping (host:ip)")
55-
_ = flags.SetAnnotation("add-host", "docs.external.url", []string{"https://docs.docker.com/engine/reference/commandline/build/#add-entries-to-container-hosts-file---add-host"})
56-
_ = flags.String("iidfile", "", "Write the image ID to the file")
57-
// hidden flags
58-
_ = flags.BoolP("quiet", "q", false, "Suppress the build output and print image ID on success")
59-
flags.MarkHidden("quiet")
60-
_ = flags.Bool("squash", false, "Squash newly built layers into a single new layer")
61-
flags.MarkHidden("squash")
62-
_ = flags.String("ulimit", "", "Ulimit options")
63-
flags.MarkHidden("ulimit")
64-
_ = flags.StringSlice("security-opt", []string{}, "Security options")
65-
flags.MarkHidden("security-opt")
66-
_ = flags.Bool("compress", false, "Compress the build context using gzip")
67-
68-
c.AddCommand(s)
69-
7030
tmpdir, err := ioutil.TempDir("", "test-gen-markdown-tree")
7131
require.NoError(t, err)
72-
7332
defer os.RemoveAll(tmpdir)
74-
require.NoError(t, GenMarkdownTree(c, tmpdir))
7533

76-
fres := filepath.Join(tmpdir, "sub.md")
77-
require.FileExists(t, fres)
78-
bres, err := ioutil.ReadFile(fres)
79-
require.NoError(t, err)
80-
bexc, err := ioutil.ReadFile("fixtures/sub.md")
34+
c, err := New(Options{
35+
Root: buildxCmd,
36+
SourceDir: tmpdir,
37+
Plugin: true,
38+
})
8139
require.NoError(t, err)
82-
assert.Equal(t, string(bres), string(bexc))
40+
require.NoError(t, c.GenMarkdownTree(buildxCmd))
41+
42+
for _, tt := range []string{"buildx.md", "buildx_build.md"} {
43+
tt := tt
44+
t.Run(tt, func(t *testing.T) {
45+
fres := filepath.Join(tmpdir, tt)
46+
require.FileExists(t, fres)
47+
bres, err := ioutil.ReadFile(fres)
48+
require.NoError(t, err)
49+
50+
bexc, err := ioutil.ReadFile(path.Join("fixtures", tt))
51+
require.NoError(t, err)
52+
assert.Equal(t, string(bexc), string(bres))
53+
})
54+
}
8355
}

0 commit comments

Comments
 (0)