110 lines
2.8 KiB
Go
110 lines
2.8 KiB
Go
//go:build ignore
|
|
|
|
// Command gen writes docs/config.md from the config structs: a heading per table and option, named by its toml key, with its doc comment and default value.
|
|
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding"
|
|
"fmt"
|
|
"go/ast"
|
|
"go/parser"
|
|
"go/token"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"strings"
|
|
|
|
"github.com/BurntSushi/toml"
|
|
"github.com/ayn2op/discordo/internal/config"
|
|
)
|
|
|
|
const header = "<!-- GENERATED BY go generate; DO NOT EDIT. -->\n\n"
|
|
|
|
// docs maps "Type.Field" to the doc comment of that struct field.
|
|
var docs = map[string]string{}
|
|
|
|
type field struct {
|
|
key, doc string
|
|
value reflect.Value
|
|
}
|
|
|
|
// fields returns the fields of the struct v, with those of embedded structs in place.
|
|
func fields(v reflect.Value) []field {
|
|
var fs []field
|
|
t := v.Type()
|
|
for i := range t.NumField() {
|
|
f := t.Field(i)
|
|
if f.Anonymous {
|
|
fs = append(fs, fields(v.Field(i))...)
|
|
continue
|
|
}
|
|
fs = append(fs, field{f.Tag.Get("toml"), docs[t.Name()+"."+f.Name], v.Field(i)})
|
|
}
|
|
return fs
|
|
}
|
|
|
|
// isTable reports whether v is written as a table rather than as a value.
|
|
func isTable(v reflect.Value) bool {
|
|
switch v.Interface().(type) {
|
|
case toml.Marshaler, encoding.TextMarshaler:
|
|
return false
|
|
}
|
|
return v.Kind() == reflect.Struct
|
|
}
|
|
|
|
// write writes a section for each option of the table v at path: its doc comment, then its default value or, for a table, its own options.
|
|
func write(w *bytes.Buffer, path string, v reflect.Value) {
|
|
if path != "" {
|
|
path += "."
|
|
}
|
|
for _, f := range fields(v) {
|
|
name := path + f.key
|
|
fmt.Fprintf(w, "%s %s\n\n", strings.Repeat("#", strings.Count(name, ".")+1), name)
|
|
if f.doc != "" {
|
|
fmt.Fprintf(w, "%s\n", f.doc)
|
|
}
|
|
if isTable(f.value) {
|
|
write(w, name, f.value)
|
|
continue
|
|
}
|
|
// The encoder only writes whole documents, so the value is what follows the key.
|
|
var line bytes.Buffer
|
|
if err := toml.NewEncoder(&line).Encode(map[string]any{f.key: f.value.Interface()}); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
_, value, _ := strings.Cut(line.String(), " = ")
|
|
fmt.Fprintf(w, "Default: `%s`\n\n", strings.TrimSpace(value))
|
|
}
|
|
}
|
|
|
|
func main() {
|
|
names, _ := filepath.Glob("*.go")
|
|
for _, name := range names {
|
|
file, err := parser.ParseFile(token.NewFileSet(), name, nil, parser.ParseComments)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
ast.Inspect(file, func(n ast.Node) bool {
|
|
spec, ok := n.(*ast.TypeSpec)
|
|
if !ok {
|
|
return true
|
|
}
|
|
if s, ok := spec.Type.(*ast.StructType); ok {
|
|
for _, f := range s.Fields.List {
|
|
for _, name := range f.Names {
|
|
docs[spec.Name.Name+"."+name.Name] = f.Doc.Text()
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
})
|
|
}
|
|
|
|
w := bytes.NewBufferString(header)
|
|
write(w, "", reflect.ValueOf(config.Default()))
|
|
if err := os.WriteFile("../../docs/config.md", append(bytes.TrimSpace(w.Bytes()), '\n'), 0o644); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|