Files
GiteaMailTemplates/tools/config/config.go
T
KenanZhuandClaude Opus 4.8 0bf982ffb9 refactor(tools): modularize CLI with urfave/cli/v2, externalise template metadata
- Split monolithic build-preview.go into modular packages:
  tools.go (entry), cli/ (list/create/delete/preview commands),
  config/ (JSON loading), preview/ (rendering engine, funcs, locale)
- Extract all template metadata into data/templates_config.json
  as single source of truth — no hardcoded template data in Go
- Replace hand-rolled CLI parsing with github.com/urfave/cli/v2
- Add sensible defaults for --folder (../themes) and --config
  (./data/templates_config.json) — most commands now run bare
- Add type-coercing gt/lt/ge/le template funcs for JSON float64
- Fix AppUrl to gitea.com matching official template convention
- rendered.js now exports __REGISTRY__ and __PARAMS__ alongside
  __RENDERED__ — preview/index.html reads all from single source
- Cross-validate all 11 template data contexts against official
  Gitea source (services/mailer/*.go + templates/mail/*.tmpl)
- Update all docs (README, AGENTS, CONTRIBUTING, 5 languages)
  to reflect new CLI workflow with create scaffolding command

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 12:26:44 +08:00

86 lines
2.2 KiB
Go

package config
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
)
// TemplateConfig holds metadata for a single mail template type.
type TemplateConfig struct {
Name string `json:"name"`
Desc string `json:"desc"`
Path []string `json:"path"`
Params map[string]any `json:"params"`
}
// TemplatesConfig is the root structure of templates_config.json.
type TemplatesConfig struct {
Templates map[string]TemplateConfig `json:"templates"`
}
// Load reads and parses a templates_config.json file.
func Load(path string) (*TemplatesConfig, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("cannot read config file %s: %w", path, err)
}
var cfg TemplatesConfig
if err := json.Unmarshal(data, &cfg); err != nil {
return nil, fmt.Errorf("cannot parse config file %s: %w", path, err)
}
if len(cfg.Templates) == 0 {
return nil, fmt.Errorf("config file %s contains no template definitions", path)
}
return &cfg, nil
}
// PathStr returns the file path of a template definition joined with OS separator.
func (t *TemplateConfig) PathStr() string {
return filepath.Join(t.Path...)
}
// Registry builds a lookup map suitable for serializing into rendered.js
// as window.__REGISTRY__: maps template ID → {name, desc, path}.
func Registry(cfg *TemplatesConfig) map[string]map[string]string {
reg := make(map[string]map[string]string, len(cfg.Templates))
for id, t := range cfg.Templates {
reg[id] = map[string]string{
"name": t.Name,
"desc": t.Desc,
"path": strings.Join(t.Path, "/"),
}
}
return reg
}
// FlattenParams converts nested template params into a flat dot-notation map
// suitable for the preview panel display in rendered.js.
func FlattenParams(params map[string]any) map[string]string {
flat := make(map[string]string)
flatten("", params, flat)
return flat
}
func flatten(prefix string, val any, out map[string]string) {
switch v := val.(type) {
case map[string]any:
for k, inner := range v {
key := k
if prefix != "" {
key = prefix + "." + k
}
flatten(key, inner, out)
}
case []any:
out[prefix] = fmt.Sprintf("[%d items]", len(v))
default:
out[prefix] = fmt.Sprintf("%v", v)
}
}