diff --git a/docs/docsgen/generate.go b/docs/docsgen/generate.go new file mode 100644 index 0000000..eb09c07 --- /dev/null +++ b/docs/docsgen/generate.go @@ -0,0 +1,192 @@ +package main + +import ( + "fmt" + "io/ioutil" + "log" + "os" + "path/filepath" + "strings" + + "github.com/docker/buildx/commands" + "github.com/docker/cli/cli/command" + "github.com/pkg/errors" + "github.com/spf13/cobra" + "github.com/spf13/pflag" +) + +const descriptionSourcePath = "docs/reference/" + +func generateDocs(opts *options) error { + dockerCLI, err := command.NewDockerCli() + if err != nil { + return err + } + cmd := &cobra.Command{ + Use: "docker [OPTIONS] COMMAND [ARG...]", + Short: "The base command for the Docker CLI.", + } + cmd.AddCommand(commands.NewRootCmd("buildx", true, dockerCLI)) + return genCmd(cmd, opts.target) +} + +func getMDFilename(cmd *cobra.Command) string { + name := cmd.CommandPath() + if i := strings.Index(name, " "); i >= 0 { + name = name[i+1:] + } + return strings.ReplaceAll(name, " ", "_") + ".md" +} + +func genCmd(cmd *cobra.Command, dir string) error { + for _, c := range cmd.Commands() { + if err := genCmd(c, dir); err != nil { + return err + } + } + if !cmd.HasParent() { + return nil + } + + mdFile := getMDFilename(cmd) + fullPath := filepath.Join(dir, mdFile) + + content, err := ioutil.ReadFile(fullPath) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return errors.Wrapf(err, "%s does not exist", mdFile) + } + } + + cs := string(content) + + markerStart := "" + markerEnd := "" + + start := strings.Index(cs, markerStart) + end := strings.Index(cs, markerEnd) + + if start == -1 { + return errors.Errorf("no start marker in %s", mdFile) + } + if end == -1 { + return errors.Errorf("no end marker in %s", mdFile) + } + + out, err := cmdOutput(cmd, cs) + if err != nil { + return err + } + cont := cs[:start] + markerStart + "\n" + out + "\n" + cs[end:] + + fi, err := os.Stat(fullPath) + if err != nil { + return err + } + if err := ioutil.WriteFile(fullPath, []byte(cont), fi.Mode()); err != nil { + return errors.Wrapf(err, "failed to write %s", fullPath) + } + log.Printf("updated %s", fullPath) + return nil +} + +func makeLink(txt, link string) string { + return "[" + txt + "](#" + link + ")" +} + +func cmdOutput(cmd *cobra.Command, old string) (string, error) { + b := &strings.Builder{} + + desc := cmd.Short + if cmd.Long != "" { + desc = cmd.Long + } + if desc != "" { + fmt.Fprintf(b, "%s\n\n", desc) + } + + if len(cmd.Aliases) != 0 { + fmt.Fprintf(b, "### Aliases\n\n`%s`", cmd.Name()) + for _, a := range cmd.Aliases { + fmt.Fprintf(b, ", `%s`", a) + } + fmt.Fprint(b, "\n\n") + } + + if len(cmd.Commands()) != 0 { + fmt.Fprint(b, "### Subcommands\n\n") + fmt.Fprint(b, "| Name | Description |\n") + fmt.Fprint(b, "| --- | --- |\n") + for _, c := range cmd.Commands() { + fmt.Fprintf(b, "| [`%s`](%s) | %s |\n", c.Name(), getMDFilename(c), c.Short) + } + fmt.Fprint(b, "\n\n") + } + + hasFlags := cmd.Flags().HasAvailableFlags() + + cmd.Flags().AddFlagSet(cmd.InheritedFlags()) + + if hasFlags { + fmt.Fprint(b, "### Options\n\n") + fmt.Fprint(b, "| Name | Description |\n") + fmt.Fprint(b, "| --- | --- |\n") + + cmd.Flags().VisitAll(func(f *pflag.Flag) { + if f.Hidden { + return + } + isLink := strings.Contains(old, "") + fmt.Fprint(b, "| ") + if f.Shorthand != "" { + name := "`-" + f.Shorthand + "`" + if isLink { + name = makeLink(name, f.Name) + } + fmt.Fprintf(b, "%s, ", name) + } + name := "`--" + f.Name + if f.Value.Type() != "bool" { + name += " " + f.Value.Type() + } + name += "`" + if isLink { + name = makeLink(name, f.Name) + } + fmt.Fprintf(b, "%s | %s |\n", name, f.Usage) + }) + fmt.Fprintln(b, "") + } + + return b.String(), nil +} + +type options struct { + target string +} + +func parseArgs() (*options, error) { + opts := &options{} + flags := pflag.NewFlagSet(os.Args[0], pflag.ContinueOnError) + flags.StringVar(&opts.target, "target", descriptionSourcePath, "Docs directory") + err := flags.Parse(os.Args[1:]) + return opts, err +} + +func main() { + if err := run(); err != nil { + log.Printf("error: %+v", err) + os.Exit(1) + } +} + +func run() error { + opts, err := parseArgs() + if err != nil { + return err + } + if err := generateDocs(opts); err != nil { + return err + } + return nil +}