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