Files
adguardhome-sync/cmd/docs/main.go
2025-11-11 20:40:57 +01:00

175 lines
4.1 KiB
Go

// Print the available environment variables
package main
import (
"fmt"
"io"
"log/slog"
"os"
"reflect"
"strings"
"github.com/bakito/adguardhome-sync/internal/types"
)
const (
envStartMarker = "<!-- env-doc-start -->"
envEndMarker = "<!-- env-doc-end -->"
yamlStartMarker = "<!-- yaml-doc-start -->"
yamlEndMarker = "<!-- yaml-doc-end -->"
)
func main() {
slog.Info("Reading README.md")
content, err := os.ReadFile("README.md")
if err != nil {
slog.Error("Error reading README.md", "error", err)
os.Exit(1)
}
fileContent := string(content)
slog.Info("Generating environment variables")
fileContent = generateEnvDocumentation(fileContent)
slog.Info("Generating yaml configuration")
fileContent = generateYAMLDocumentation(fileContent)
slog.Info("Writing README.md")
err = os.WriteFile("README.md", []byte(fileContent), 0o644)
if err != nil {
slog.Error("Error writing README.md", "error", err)
os.Exit(1)
}
}
func generateEnvDocumentation(fileContent string) string {
var buf strings.Builder
_, _ = buf.WriteString("| Name | Type | Description |\n")
_, _ = buf.WriteString("| :--- | ---- |:----------- |\n")
writeEnvDocumentation(&buf, reflect.TypeOf(types.Config{}), "")
return updateDocumentationSection(fileContent, envStartMarker, envEndMarker, buf.String())
}
func generateYAMLDocumentation(fileContent string) string {
var buf strings.Builder
_, _ = buf.WriteString("```yaml\n")
writeYAMLDocumentation(&buf, reflect.TypeOf(types.Config{}), "", "")
_, _ = buf.WriteString("```\n")
return updateDocumentationSection(fileContent, yamlStartMarker, yamlEndMarker, buf.String())
}
func updateDocumentationSection(fileContent, startMarker, endMarker, newContent string) string {
startIdx := strings.Index(fileContent, startMarker)
endIdx := strings.Index(fileContent, endMarker)
if startIdx == -1 || endIdx == -1 {
slog.Error(fmt.Sprintf("Could not find markers %s and %s in README.md", startMarker, endMarker))
os.Exit(1)
}
return fileContent[:startIdx+len(startMarker)] + "\n" + newContent + fileContent[endIdx:]
}
func writeEnvDocumentation(w io.Writer, t reflect.Type, prefix string) {
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
if t.Kind() != reflect.Struct {
return
}
for _, field := range reflect.VisibleFields(t) {
if field.PkgPath != "" {
continue
}
envTag := field.Tag.Get("env")
if envTag == "" {
switch field.Name {
case "Origin":
envTag = "ORIGIN"
case "Replica":
envTag = "REPLICA#"
}
}
combinedTag := buildCombinedTag(prefix, envTag)
ft := field.Type
if ft.Kind() == reflect.Ptr {
ft = ft.Elem()
}
if ft.Kind() == reflect.Struct && ft.Name() != "Time" {
writeEnvDocumentation(w, ft, strings.TrimSuffix(combinedTag, "_"))
} else if envTag != "" {
envVar := strings.Trim(combinedTag, "_") + " (" + ft.Kind().String() + ")"
docs := field.Tag.Get("documentation")
_, _ = fmt.Fprintf(w, "| %s | %s | %s |\n", envVar, ft.Kind().String(), docs)
}
}
}
func writeYAMLDocumentation(w io.Writer, t reflect.Type, firstPrefix, otherPrefix string) {
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
if t.Kind() != reflect.Struct {
return
}
var i int
for _, field := range reflect.VisibleFields(t) {
if field.PkgPath != "" {
continue
}
yamlTag := field.Tag.Get("yaml")
if yamlTag == "-" {
continue
}
yamlTag = strings.TrimSuffix(yamlTag, ",omitempty")
ft := field.Type
if ft.Kind() == reflect.Ptr {
ft = ft.Elem()
}
pf := otherPrefix
if i == 0 {
pf = firstPrefix
}
newFirstPrefix := pf + " "
newOtherPrefix := otherPrefix + " "
if yamlTag == "replicas" && ft.Kind() == reflect.Slice {
ft = ft.Elem()
newFirstPrefix += "- "
newOtherPrefix += " "
}
if yamlTag != "" {
docs := field.Tag.Get("documentation")
_, _ = fmt.Fprintf(w, "%s%s: # (%s) %s\n", pf, yamlTag, ft.Kind().String(), docs)
i++
}
if ft.Kind() == reflect.Struct && ft.Name() != "Time" {
writeYAMLDocumentation(w, ft, newFirstPrefix, newOtherPrefix)
}
}
}
func buildCombinedTag(prefix, envTag string) string {
if prefix != "" && envTag != "" {
return prefix + "_" + envTag
} else if prefix != "" {
return prefix
}
return envTag
}