packer/cmd/struct-markdown/main.go
Megan Marsh f179f01314 Modify struct-markdown generator code to work from different projct roots
Modify makefile to call generate code properly, setting project root.
'make generate' now avoids deleting website code generated in the packer plugin sdk.
For now it will be maintainers' responsibility to regenerate this docs code from the
packer plugin sdk every release, and commit it to these folders manually.
remove boot command generator code
2020-12-10 14:35:14 -08:00

178 lines
4 KiB
Go

package main
import (
"flag"
"fmt"
"go/ast"
"go/parser"
"go/token"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/fatih/camelcase"
"github.com/fatih/structtag"
)
func main() {
args := flag.Args()
if len(args) == 0 {
// Default: process the file
args = []string{os.Getenv("GOFILE")}
}
fname := args[0]
absFilePath, err := filepath.Abs(fname)
if err != nil {
panic(err)
}
projectRoot := os.Getenv("PROJECT_ROOT")
var paths []string
if projectRoot == "" {
// fall back to the packer root.
paths = strings.SplitAfter(absFilePath, "packer"+string(os.PathSeparator))
projectRoot = paths[0]
} else {
paths = strings.SplitAfter(absFilePath, projectRoot+string(os.PathSeparator))
}
builderName, _ := filepath.Split(paths[1])
builderName = strings.Trim(builderName, string(os.PathSeparator))
b, err := ioutil.ReadFile(fname)
if err != nil {
fmt.Printf("ReadFile: %+v", err)
os.Exit(1)
}
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, fname, b, parser.ParseComments)
if err != nil {
fmt.Printf("ParseFile: %+v", err)
os.Exit(1)
}
for _, decl := range f.Decls {
typeDecl, ok := decl.(*ast.GenDecl)
if !ok {
continue
}
typeSpec, ok := typeDecl.Specs[0].(*ast.TypeSpec)
if !ok {
continue
}
structDecl, ok := typeSpec.Type.(*ast.StructType)
if !ok {
continue
}
fields := structDecl.Fields.List
sourcePath := filepath.ToSlash(paths[1])
header := Struct{
SourcePath: sourcePath,
Name: typeSpec.Name.Name,
Filename: typeSpec.Name.Name + ".mdx",
Header: strings.TrimSpace(typeDecl.Doc.Text()),
}
required := Struct{
SourcePath: sourcePath,
Name: typeSpec.Name.Name,
Filename: typeSpec.Name.Name + "-required.mdx",
}
notRequired := Struct{
SourcePath: sourcePath,
Name: typeSpec.Name.Name,
Filename: typeSpec.Name.Name + "-not-required.mdx",
}
for _, field := range fields {
if len(field.Names) == 0 || field.Tag == nil {
continue
}
tag := field.Tag.Value[1:]
tag = tag[:len(tag)-1]
tags, err := structtag.Parse(tag)
if err != nil {
fmt.Printf("structtag.Parse(%s): err: %v", field.Tag.Value, err)
os.Exit(1)
}
// Leave undocumented tags out of markdown. This is useful for
// fields which exist for backwards compatability, or internal-use
// only fields
undocumented, _ := tags.Get("undocumented")
if undocumented != nil {
if undocumented.Name == "true" {
continue
}
}
mstr, err := tags.Get("mapstructure")
if err != nil {
continue
}
name := mstr.Name
if name == "" {
continue
}
var docs string
if field.Doc != nil {
docs = field.Doc.Text()
} else {
docs = strings.Join(camelcase.Split(field.Names[0].Name), " ")
}
if strings.Contains(docs, "TODO") {
continue
}
fieldType := string(b[field.Type.Pos()-1 : field.Type.End()-1])
fieldType = strings.ReplaceAll(fieldType, "*", `\*`)
switch fieldType {
case "time.Duration":
fieldType = `duration string | ex: "1h5m2s"`
case "config.Trilean":
fieldType = `boolean`
case "config.NameValues":
fieldType = `[]{name string, value string}`
case "config.KeyValues":
fieldType = `[]{key string, value string}`
}
field := Field{
Name: name,
Type: fieldType,
Docs: docs,
}
if req, err := tags.Get("required"); err == nil && req.Value() == "true" {
required.Fields = append(required.Fields, field)
} else {
notRequired.Fields = append(notRequired.Fields, field)
}
}
dir := filepath.Join(projectRoot, "website", "pages", "partials", builderName)
os.MkdirAll(dir, 0755)
for _, str := range []Struct{header, required, notRequired} {
if len(str.Fields) == 0 && len(str.Header) == 0 {
continue
}
outputPath := filepath.Join(dir, str.Filename)
outputFile, err := os.Create(outputPath)
if err != nil {
panic(err)
}
defer outputFile.Close()
err = structDocsTemplate.Execute(outputFile, str)
if err != nil {
fmt.Printf("%v", err)
os.Exit(1)
}
}
}
}