* Documentation: completely rework how env vars are documented Automatically generate the markdown based on line comments in `type Complement struct {...}`. Rejig `README.md` to include a link to `ENVIRONMENT.md`. Update sytest stats. * Default to internal/config/config.go
132 lines
3.2 KiB
Go
132 lines
3.2 KiB
Go
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"))
|
|
}
|