/
main.go
132 lines (125 loc) · 3.18 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
package main
import (
"flag"
"fmt"
"go/ast"
"go/parser"
"go/token"
"log"
"os"
"sort"
"strings"
)
var configPath = flag.String("config", "internal/config/config.go", "The path to internal/config/config.go")
type VarDoc struct {
Name string
Description string
Default string
Type string
}
func NewVarDoc(docstring string) (vd VarDoc) {
lines := strings.Split(docstring, "\n")
isDescription := false
for _, l := range lines {
if strings.HasPrefix(l, "Name:") {
isDescription = false
vd.Name = strings.TrimSpace(strings.TrimPrefix(l, "Name:"))
}
if strings.HasPrefix(l, "Default:") {
isDescription = false
vd.Default = strings.TrimSpace(strings.TrimPrefix(l, "Default:"))
}
if strings.HasPrefix(l, "Description:") {
l = strings.TrimPrefix(l, "Description:")
isDescription = true
}
if isDescription {
vd.Description += strings.TrimSpace(l) + " "
}
}
return
}
func findComplementStruct(path string) *ast.StructType {
fset := token.NewFileSet()
node, err := parser.ParseFile(fset, path, nil, parser.ParseComments)
if err != nil {
log.Fatal(err)
}
var complementConfigType *ast.TypeSpec
FindStruct:
for _, d := range node.Decls {
typeNode, ok := d.(*ast.GenDecl)
if !ok || typeNode.Tok != token.TYPE { // we want `type` keywords
continue
}
for _, s := range typeNode.Specs {
typeSpec, ok := s.(*ast.TypeSpec)
if !ok {
continue
}
if typeSpec.Name.Name == "Complement" {
complementConfigType = typeSpec
break FindStruct
}
}
}
sType, ok := complementConfigType.Type.(*ast.StructType)
if !ok {
return nil
}
return sType
}
func typeForExpr(ex ast.Expr) string {
switch typeDecl := ex.(type) {
case *ast.Ident:
return typeDecl.Name
case *ast.SelectorExpr:
return typeDecl.Sel.Name
case *ast.ArrayType:
return "[]" + typeForExpr(typeDecl.Elt)
case *ast.MapType:
return "map[" + typeForExpr(typeDecl.Key) + "]" + typeForExpr(typeDecl.Value)
default:
return "-"
}
}
func main() {
flag.Parse()
if *configPath == "" {
flag.Usage()
os.Exit(1)
}
complement := findComplementStruct(*configPath)
if complement == nil {
log.Fatal("file does not contain type Complement struct {...}")
}
var varDocs []VarDoc
// loop each field looking for valid comments
for _, f := range complement.Fields.List {
fieldComment := f.Doc.Text()
vd := NewVarDoc(fieldComment)
if vd.Name == "" {
continue // not valid comment
}
vd.Type = typeForExpr(f.Type)
varDocs = append(varDocs, vd)
}
sort.Slice(varDocs, func(i, j int) bool {
return varDocs[i].Name < varDocs[j].Name
})
mdFileLines := []string{
"*This file is automatically generated via ./cmd/gendoc*",
"",
"## Complement Configuration",
"Complement is configured exclusively through the use of environment variables. These variables are described below.",
}
for _, vd := range varDocs {
mdFileLines = append(mdFileLines, fmt.Sprintf("\n#### `%v`", vd.Name))
mdFileLines = append(mdFileLines, vd.Description)
mdFileLines = append(mdFileLines, fmt.Sprintf("- Type: `%v`", vd.Type))
if vd.Default != "" {
mdFileLines = append(mdFileLines, fmt.Sprintf("- Default: %v", vd.Default))
}
}
fmt.Println(strings.Join(mdFileLines, "\n"))
}