github.com/choria-io/go-choria@v0.28.1-0.20240416190746-b3bf9c7d5a45/config/gen.go (about) 1 // Copyright (c) 2020-2021, R.I. Pienaar and the Choria Project contributors 2 // 3 // SPDX-License-Identifier: Apache-2.0 4 5 //go:build ignore 6 // +build ignore 7 8 package main 9 10 import ( 11 "fmt" 12 "go/ast" 13 "go/parser" 14 "go/token" 15 "log" 16 "os" 17 "os/exec" 18 "path" 19 "path/filepath" 20 "strings" 21 "text/template" 22 23 "github.com/fatih/structtag" 24 ) 25 26 type keydoc struct { 27 Key string 28 Help string 29 } 30 31 var templ = ` 32 package config 33 34 var docStrings = map[string]string{ 35 {{- range . }} 36 {{ .Key | printf "%q" }}: {{ .Help | printf "%q" }}, 37 {{- end }} 38 } 39 ` 40 41 func parseDocString(d string) string { 42 d = strings.TrimSpace(d) 43 44 idx := strings.Index(d, "@doc") 45 if idx == -1 { 46 return d 47 } 48 49 d = d[idx+5:] 50 d = strings.ReplaceAll(d, "\n", " ") 51 52 return d 53 } 54 55 func parseGoFile(f string, structName string) (docs []keydoc, err error) { 56 docs = []keydoc{} 57 58 d, err := parser.ParseFile(token.NewFileSet(), f, nil, parser.ParseComments) 59 if err != nil { 60 return docs, err 61 } 62 63 var inserr error 64 65 ast.Inspect(d, func(n ast.Node) bool { 66 if inserr != nil { 67 return false 68 } 69 70 switch t := n.(type) { 71 case *ast.TypeSpec: 72 return t.Name.Name == structName 73 case *ast.StructType: 74 for _, field := range t.Fields.List { 75 tag := "" 76 doc := "" 77 78 if field.Tag != nil && field.Tag.Kind == token.STRING { 79 tag = strings.TrimRight(strings.TrimLeft(field.Tag.Value, "`"), "`") 80 } 81 82 switch { 83 case len(strings.TrimSpace(field.Doc.Text())) > 0: 84 doc = field.Doc.Text() 85 case len(strings.TrimSpace(field.Comment.Text())) > 0: 86 doc = field.Comment.Text() 87 } 88 89 if strings.Contains(tag, "confkey") && doc != "" { 90 tags, err := structtag.Parse(tag) 91 if err != nil { 92 inserr = err 93 return false 94 } 95 96 key, err := tags.Get("confkey") 97 if err != nil { 98 inserr = err 99 return false 100 } 101 102 docs = append(docs, keydoc{key.Value(), parseDocString(doc)}) 103 } 104 } 105 } 106 107 return true 108 }) 109 110 if inserr != nil { 111 return docs, inserr 112 } 113 114 return docs, nil 115 } 116 117 func goFmt(file string) error { 118 c := exec.Command("go", "fmt", file) 119 out, err := c.CombinedOutput() 120 if err != nil { 121 log.Printf("go fmt failed: %s", string(out)) 122 } 123 124 return err 125 } 126 127 func main() { 128 docs := []keydoc{} 129 130 log.Println("Generating configuration doc strings") 131 cd, err := parseGoFile(filepath.Join("config", "config.go"), "Config") 132 if err != nil { 133 panic(err) 134 } 135 docs = append(docs, cd...) 136 137 cd, err = parseGoFile(filepath.Join("config", "choria.go"), "ChoriaPluginConfig") 138 if err != nil { 139 panic(err) 140 } 141 docs = append(docs, cd...) 142 143 if len(docs) == 0 { 144 panic("no documentation strings were generated") 145 } 146 147 t, err := template.New("templates").Parse(templ) 148 if err != nil { 149 panic(err) 150 } 151 152 outfile := filepath.Join("config", "docstrings.go") 153 154 out, err := os.Create(path.Join(outfile)) 155 if err != nil { 156 panic(err) 157 } 158 159 fmt.Fprintf(out, " // generated code; DO NOT EDIT\n\n") 160 161 err = t.Execute(out, docs) 162 out.Close() 163 goFmt(outfile) 164 165 log.Println("Generated " + outfile) 166 }