package main import ( "errors" "fmt" "io/fs" "log/slog" "os" "reflect" "strings" "github.com/tinyauthapp/tinyauth/internal/model" ) type ConfigOptions struct { Env string Flag string YAML string } type MarkdownEntry struct { Options ConfigOptions Description string Default any } func generateMarkdown() { cfg := model.NewDefaultConfiguration(model.RuntimeEnvUnknown) 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") gen := child.Tag.Get("gen") if tag == "-" && gen != "include" { return } tag = strings.TrimSuffix(tag, ",omitempty") value := childValue.Interface() configOptions := ConfigOptions{ Env: strings.ToUpper(strings.ReplaceAll(parentPath, ".", "_")) + strings.ToUpper(child.Name), Flag: fmt.Sprintf("--%s%s", strings.TrimPrefix(parentPath, "tinyauth."), strings.ToLower(child.Name)), } if tag != "-" && tag != "" { configOptions.YAML = strings.TrimPrefix(parentPath, "tinyauth.") + tag } entry := MarkdownEntry{ Options: configOptions, 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") gen := child.Tag.Get("gen") if tag == "-" && gen != "include" { return } tag = strings.TrimSuffix(tag, ",omitempty") 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 := strings.Builder{} buffer.WriteString("{/* This part is automatically generated by gen/gen_md.go in the main repository. DO NOT EDIT. */}\n\n") buffer.WriteString("import { Tabs, TabItem } from '@astrojs/starlight/components';\n\n") buffer.WriteString("\n") renderTabItem(&buffer, entries, "Environment") renderTabItem(&buffer, entries, "Flags") renderTabItem(&buffer, entries, "YAML") buffer.WriteString("\n") return []byte(buffer.String()) } func renderTabItem(buffer *strings.Builder, entries []MarkdownEntry, section string) { buffer.WriteString(fmt.Sprintf(" \n", section)) buffer.WriteString(" ### main\n\n") buffer.WriteString(" | Option | Description | Default |\n") buffer.WriteString(" | - | - | - |\n") configType := strings.ToLower(section) previousSection := "" for _, entry := range entries { section := strings.Split(entry.Options.YAML, ".")[0] var option string switch configType { case "yaml": option = entry.Options.YAML case "flags": option = entry.Options.Flag case "environment": option = entry.Options.Env } if option == "" { continue } if strings.Count(entry.Options.YAML, ".") >= 1 { if section != previousSection { buffer.WriteString("\n ### " + strings.ToLower(section) + "\n\n") buffer.WriteString(" | Option | Description | Default |\n") buffer.WriteString(" | - | - | - |\n") previousSection = section } } fmt.Fprintf(buffer, " | `%s` | %s | %s |\n", option, entry.Description, entry.Default) } buffer.WriteString(" \n") }