package main import ( "encoding/json" "errors" "fmt" "io/fs" "log" "os" "strings" "sync" ) // siteConfig holds the settings in site.json. Every field is optional. type siteConfig struct { HomeText string `json:"homeText"` // text of the header link to the index DefaultAuthor string `json:"defaultAuthor"` // author of posts that don't name one RepoURL string `json:"repoURL"` // web address of the source repo, for commit links } // withDefaults fills in any setting that is missing or blank. func (c siteConfig) withDefaults() siteConfig { if strings.TrimSpace(c.HomeText) == "" { c.HomeText = "Home" } if strings.TrimSpace(c.RepoURL) == "" { c.RepoURL = "https://git.monoexp.com/bajdev/posts" } if strings.TrimSpace(c.DefaultAuthor) == "" { c.DefaultAuthor = "Claude" } return c } // configFile reads site.json and re-reads it when its size or modification // time changes. A missing file means all defaults; a broken file is logged // and the last good settings stay in use. type configFile struct { path string mu sync.Mutex sig string loaded bool cfg siteConfig } func newConfigFile(path string) *configFile { return &configFile{path: path, cfg: siteConfig{}.withDefaults()} } func (c *configFile) Get() siteConfig { c.mu.Lock() defer c.mu.Unlock() sig := "missing" info, err := os.Stat(c.path) switch { case err == nil: sig = fmt.Sprintf("%d %d", info.Size(), info.ModTime().UnixNano()) case !errors.Is(err, fs.ErrNotExist): log.Printf("%s: %v; keeping previous settings", c.path, err) return c.cfg } if c.loaded && sig == c.sig { return c.cfg } c.sig, c.loaded = sig, true if sig == "missing" { c.cfg = siteConfig{}.withDefaults() return c.cfg } cfg, err := readConfig(c.path) if err != nil { log.Printf("%s: %v; keeping previous settings", c.path, err) return c.cfg } c.cfg = cfg return c.cfg } func readConfig(path string) (siteConfig, error) { f, err := os.Open(path) if err != nil { return siteConfig{}, err } defer f.Close() dec := json.NewDecoder(f) dec.DisallowUnknownFields() // catch typos in setting names var cfg siteConfig if err := dec.Decode(&cfg); err != nil { return siteConfig{}, err } return cfg.withDefaults(), nil }