From f0ca8f9c1ace130ea93c5d0fe053db33aa27b07d Mon Sep 17 00:00:00 2001 From: Phillip Wittrock Date: Fri, 22 Nov 2019 11:40:22 -0800 Subject: [PATCH] Add utility for generating cobra documentation from .md files --- cmd/mdtogo/go.mod | 3 + cmd/mdtogo/main.go | 202 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 205 insertions(+) create mode 100644 cmd/mdtogo/go.mod create mode 100644 cmd/mdtogo/main.go diff --git a/cmd/mdtogo/go.mod b/cmd/mdtogo/go.mod new file mode 100644 index 000000000..7618d1289 --- /dev/null +++ b/cmd/mdtogo/go.mod @@ -0,0 +1,3 @@ +module sigs.k8s.io/kustomize/cmd/mdtogo + +go 1.13 diff --git a/cmd/mdtogo/main.go b/cmd/mdtogo/main.go new file mode 100644 index 000000000..6d7609ef3 --- /dev/null +++ b/cmd/mdtogo/main.go @@ -0,0 +1,202 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +// Package main generates cobra.Command go variables containing documentation read from .md files. +// Usage: mdtogo SOURCE_MD_DIR/ DEST_GO_DIR/ [--full=true] +// +// The command will create a docs.go file under DEST_GO_DIR/ containing string variables to be +// used by cobra commands for documentation.The variable names are generated from the SOURCE_MD_DIR/ +// file names, replacing '-' with '', title casing the filename, and dropping the extension. +// All *.md will be read from DEST_GO_DIR/, and a single DEST_GO_DIR/docs.go file is generated. +// +// Each .md document will be parsed as follows if no flags are provided: +// +// ## cmd +// +// This section will be parsed into a string variable for `Short` +// +// ### Synopsis +// +// This section will be parsed into a string variable for `Long` +// +// ### Examples +// +// This section will be parsed into a string variable for `Example` +// +// If --full=true is provided, the document will be parsed as follows: +// +// ## cmd +// +// All sections will be parsed into a Long string. +package main + +import ( + "bufio" + "bytes" + "fmt" + "io/ioutil" + "log" + "os" + "path/filepath" + "strings" +) + +var full bool + +func main() { + for _, a := range os.Args { + if a == "--full=true" { + full = true + } + } + + if len(os.Args) < 3 { + fmt.Fprintf(os.Stderr, "Usage: mdtogo SOURCE_MD_DIR/ DEST_GO_DIR/\n") + os.Exit(1) + } + source := os.Args[1] + dest := os.Args[2] + + files, err := ioutil.ReadDir(source) + if err != nil { + fmt.Fprintf(os.Stderr, "%v\n", err) + os.Exit(1) + } + + var docs []doc + for _, f := range files { + if filepath.Ext(f.Name()) != ".md" { + continue + } + b, err := ioutil.ReadFile(filepath.Join(source, f.Name())) + if err != nil { + fmt.Fprintf(os.Stderr, "%v\n", err) + os.Exit(1) + } + + docs = append(docs, parse(f.Name(), string(b))) + } + + out := []string{`// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by "mdtogo"; DO NOT EDIT. +package ` + filepath.Base(dest) + "\n"} + + for i := range docs { + out = append(out, docs[i].String()) + } + + if _, err := os.Stat(dest); err != nil { + _ = os.Mkdir(dest, 0700) + } + + o := strings.Join(out, "\n") + err = ioutil.WriteFile(filepath.Join(dest, "docs.go"), []byte(o), 0600) + if err != nil { + fmt.Fprintf(os.Stderr, "%v\n", err) + os.Exit(1) + } +} + +func parse(name, value string) doc { + name = strings.ReplaceAll(name, filepath.Ext(name), "") + name = strings.Title(name) + name = strings.ReplaceAll(name, "-", "") + + scanner := bufio.NewScanner(bytes.NewBufferString(value)) + + var long, examples []string + var short string + var isLong, isExample, isIndent bool + var doc doc + + for scanner.Scan() { + line := scanner.Text() + + if strings.HasPrefix(line, "## ") && short == "" { + for scanner.Scan() { + if strings.TrimSpace(scanner.Text()) == "" { + continue + } + short = scanner.Text() + break + } + continue + } + + if !full { + if strings.HasPrefix(line, "### Synopsis") { + isLong = true + isExample = false + continue + } + + if strings.HasPrefix(line, "### Examples") { + isLong = false + isExample = true + continue + } + + if strings.HasPrefix(line, "### ") { + isLong = false + isExample = false + continue + } + } + + if strings.HasPrefix(line, "```") { + isIndent = !isIndent + continue + } + line = strings.ReplaceAll(line, "`", "` + \"`\" + `") + if isIndent { + line = "\t" + line + } + + if isLong || full { + long = append(long, line) + continue + } + if isExample { + examples = append(examples, line) + } + } + + doc.Name = name + doc.Short = short + doc.Long = strings.Join(long, "\n") + doc.Examples = strings.Join(examples, "\n") + + if err := scanner.Err(); err != nil { + log.Fatal(err) + } + + return doc +} + +type doc struct { + Name string + Short string + Long string + Examples string +} + +func (d doc) String() string { + var parts []string + + if d.Short != "" { + parts = append(parts, + fmt.Sprintf("var %sShort=`%s`", d.Name, d.Short)) + } + if d.Long != "" { + parts = append(parts, + fmt.Sprintf("var %sLong=`%s`", d.Name, d.Long)) + } + if d.Examples != "" { + parts = append(parts, + fmt.Sprintf("var %sExamples=`%s`", d.Name, d.Examples)) + } + + return strings.Join(parts, "\n") + "\n" +}