package main import ( "bytes" "errors" "fmt" "io/fs" "log/slog" "os" "reflect" "strings" "github.com/steveiliop56/tinyauth/internal/config" ) type MarkdownEntry struct { Env string Flag string Description string Default any } func generateMarkdown() { cfg := config.NewDefaultConfiguration() entries := make([]MarkdownEntry, 0) root := reflect.TypeOf(cfg).Elem() rootValue := reflect.ValueOf(cfg).Elem() rootPath := "tinyauth." walkAndBuild(root, rootValue, rootPath, &entries, buildMdEntry, buildMdMapEntry, buildMdChildPath) compiled := compileMd(entries) err := os.Remove("config.gen.md") if err != nil && !errors.Is(err, fs.ErrNotExist) { slog.Error("failed to remove example env file", "error", err) os.Exit(1) } err = os.WriteFile("config.gen.md", compiled, 0644) if err != nil { slog.Error("failed to write example env file", "error", err) os.Exit(1) } } func buildMdEntry(child reflect.StructField, childValue reflect.Value, parentPath string, entries *[]MarkdownEntry) { desc := child.Tag.Get("description") tag := child.Tag.Get("yaml") if tag == "-" { return } value := childValue.Interface() entry := MarkdownEntry{ Env: strings.ToUpper(strings.ReplaceAll(parentPath, ".", "_")) + strings.ToUpper(child.Name), Flag: fmt.Sprintf("--%s%s", strings.TrimPrefix(parentPath, "tinyauth."), tag), Description: desc, } switch childValue.Kind() { case reflect.Slice: sl, ok := value.([]string) if !ok { slog.Error("invalid default value", "value", value) return } entry.Default = fmt.Sprintf("`%s`", strings.Join(sl, ",")) default: entry.Default = fmt.Sprintf("`%v`", value) } *entries = append(*entries, entry) } func buildMdMapEntry(child reflect.StructField, parentPath string, entries *[]MarkdownEntry) { fieldType := child.Type if fieldType.Key().Kind() != reflect.String { slog.Info("unsupported map key type", "type", fieldType.Key().Kind()) return } tag := child.Tag.Get("yaml") if tag == "-" { return } mapPath := parentPath + tag + ".[name]." valueType := fieldType.Elem() if valueType.Kind() == reflect.Struct { zeroValue := reflect.New(valueType).Elem() walkAndBuild(valueType, zeroValue, mapPath, entries, buildMdEntry, buildMdMapEntry, buildMdChildPath) } } func buildMdChildPath(parent string, child string) string { return parent + strings.ToLower(child) + "." } func compileMd(entries []MarkdownEntry) []byte { buffer := bytes.Buffer{} buffer.WriteString("# Tinyauth configuration reference\n\n") buffer.WriteString("| Environment | Flag | Description | Default |\n") buffer.WriteString("| - | - | - | - |\n") previousSection := "" for _, entry := range entries { if strings.Count(entry.Env, "_") > 1 { section := strings.Split(strings.TrimPrefix(entry.Env, "TINYAUTH_"), "_")[0] if section != previousSection { buffer.WriteString("\n## " + strings.ToLower(section) + "\n\n") buffer.WriteString("| Environment | Flag | Description | Default |\n") buffer.WriteString("| - | - | - | - |\n") previousSection = section } } fmt.Fprintf(&buffer, "| `%s` | `%s` | %s | %s |\n", entry.Env, entry.Flag, entry.Description, entry.Default) } return buffer.Bytes() }