config.go 2.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. package main
  2. import (
  3. "encoding/json"
  4. "errors"
  5. "fmt"
  6. "io/fs"
  7. "log"
  8. "os"
  9. "strings"
  10. "sync"
  11. )
  12. // siteConfig holds the settings in site.json. Every field is optional.
  13. type siteConfig struct {
  14. HomeText string `json:"homeText"` // text of the header link to the index
  15. DefaultAuthor string `json:"defaultAuthor"` // author of posts that don't name one
  16. RepoURL string `json:"repoURL"` // web address of the source repo, for commit links
  17. }
  18. // withDefaults fills in any setting that is missing or blank.
  19. func (c siteConfig) withDefaults() siteConfig {
  20. if strings.TrimSpace(c.HomeText) == "" {
  21. c.HomeText = "Home"
  22. }
  23. if strings.TrimSpace(c.RepoURL) == "" {
  24. c.RepoURL = "https://git.monoexp.com/bajdev/posts"
  25. }
  26. if strings.TrimSpace(c.DefaultAuthor) == "" {
  27. c.DefaultAuthor = "Claude"
  28. }
  29. return c
  30. }
  31. // configFile reads site.json and re-reads it when its size or modification
  32. // time changes. A missing file means all defaults; a broken file is logged
  33. // and the last good settings stay in use.
  34. type configFile struct {
  35. path string
  36. mu sync.Mutex
  37. sig string
  38. loaded bool
  39. cfg siteConfig
  40. }
  41. func newConfigFile(path string) *configFile {
  42. return &configFile{path: path, cfg: siteConfig{}.withDefaults()}
  43. }
  44. func (c *configFile) Get() siteConfig {
  45. c.mu.Lock()
  46. defer c.mu.Unlock()
  47. sig := "missing"
  48. info, err := os.Stat(c.path)
  49. switch {
  50. case err == nil:
  51. sig = fmt.Sprintf("%d %d", info.Size(), info.ModTime().UnixNano())
  52. case !errors.Is(err, fs.ErrNotExist):
  53. log.Printf("%s: %v; keeping previous settings", c.path, err)
  54. return c.cfg
  55. }
  56. if c.loaded && sig == c.sig {
  57. return c.cfg
  58. }
  59. c.sig, c.loaded = sig, true
  60. if sig == "missing" {
  61. c.cfg = siteConfig{}.withDefaults()
  62. return c.cfg
  63. }
  64. cfg, err := readConfig(c.path)
  65. if err != nil {
  66. log.Printf("%s: %v; keeping previous settings", c.path, err)
  67. return c.cfg
  68. }
  69. c.cfg = cfg
  70. return c.cfg
  71. }
  72. func readConfig(path string) (siteConfig, error) {
  73. f, err := os.Open(path)
  74. if err != nil {
  75. return siteConfig{}, err
  76. }
  77. defer f.Close()
  78. dec := json.NewDecoder(f)
  79. dec.DisallowUnknownFields() // catch typos in setting names
  80. var cfg siteConfig
  81. if err := dec.Decode(&cfg); err != nil {
  82. return siteConfig{}, err
  83. }
  84. return cfg.withDefaults(), nil
  85. }