diff options
| author | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-07-23 13:18:34 +0200 |
|---|---|---|
| committer | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-07-23 13:18:34 +0200 |
| commit | fbb3345518872346a629e7ccbec81f09532765f9 (patch) | |
| tree | 0f07371550d3dc1885d92d99c8aa847223b875ae /internal/config/config.go | |
| parent | 8a401a84af52c94f991c74443b7f4397e30cad4a (diff) | |
| download | lectio-fbb3345518872346a629e7ccbec81f09532765f9.tar.gz lectio-fbb3345518872346a629e7ccbec81f09532765f9.zip | |
config: TOML load + seed
Diffstat (limited to 'internal/config/config.go')
| -rw-r--r-- | internal/config/config.go | 159 |
1 files changed, 159 insertions, 0 deletions
diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 0000000..4e11c56 --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,159 @@ +// Package config loads lectio's TOML configuration file. +// +// Resolution order: LECTIO_CONFIG env var -> ~/.config/lectio/config.toml +// (auto-seeded from the embedded default on first run, honoring +// XDG_CONFIG_HOME via os.UserConfigDir()) -> built-in defaults. TOML that +// fails to parse falls back to defaults with a warning on stderr. +package config + +import ( + _ "embed" + "fmt" + "os" + "path/filepath" + + "github.com/pelletier/go-toml/v2" +) + +// seedTOML is the embedded default config.toml, written to disk on first +// run and parsed as the fallback default values. +// +//go:embed config.toml +var seedTOML []byte + +// validVersions are the five scripture versions lectio understands. +var validVersions = map[string]bool{ + "pl": true, + "wuj": true, + "vul": true, + "grb": true, + "drb": true, +} + +// Config holds lectio's user-configurable settings. +type Config struct { + SchemaVersion int `toml:"schema_version"` + Lectionary string `toml:"lectionary"` + TraditionalLang string `toml:"traditional_lang"` + Versions []string `toml:"versions"` + DefaultVersion string `toml:"default_version"` + Width int `toml:"width"` + All bool `toml:"all"` + Offline bool `toml:"offline"` + Parts map[string]map[string]bool `toml:"parts"` +} + +// PartShown reports whether a part renders: true unless explicitly set false. +func (c Config) PartShown(lectionary, partID string) bool { + if m, ok := c.Parts[lectionary]; ok { + if v, ok := m[partID]; ok { + return v + } + } + return true +} + +// Default returns lectio's built-in configuration, used when no config file +// is found and as the base that a partial config file overrides. +func Default() Config { + return Config{ + SchemaVersion: 1, + Lectionary: "new", + TraditionalLang: "pl", + Versions: []string{"pl", "wuj", "vul", "grb", "drb"}, + DefaultVersion: "pl", + Width: 0, + All: false, + Offline: false, + Parts: nil, + } +} + +// configPath resolves the config file location: LECTIO_CONFIG if set, +// otherwise the default XDG-aware path under os.UserConfigDir(). The bool +// reports whether this is the default (seedable) location. +func configPath() (path string, isDefault bool, err error) { + if p := os.Getenv("LECTIO_CONFIG"); p != "" { + return p, false, nil + } + dir, err := os.UserConfigDir() + if err != nil { + return "", false, err + } + return filepath.Join(dir, "lectio", "config.toml"), true, nil +} + +// seedIfMissing writes the embedded default config to path if nothing is +// there yet. +func seedIfMissing(path string) error { + if _, err := os.Stat(path); err == nil { + return nil + } else if !os.IsNotExist(err) { + return err + } + if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { + return err + } + return os.WriteFile(path, seedTOML, 0o644) +} + +// Load reads lectio's configuration, following the package's resolution +// order (see the package doc comment). It always returns a usable Config: +// on a missing config dir or a TOML parse error it falls back to Default() +// (warning on stderr in the parse-error case). It returns a non-nil error +// only when the loaded config contains a semantically invalid value. +func Load() (Config, error) { + def := Default() + + path, isDefault, err := configPath() + if err != nil { + fmt.Fprintf(os.Stderr, "lectio: warning: could not resolve config directory: %v; using defaults\n", err) + return def, nil + } + + if isDefault { + if err := seedIfMissing(path); err != nil { + return def, err + } + } + + data, err := os.ReadFile(path) + if err != nil { + if os.IsNotExist(err) { + return def, nil + } + return def, err + } + + cfg := def + if err := toml.Unmarshal(data, &cfg); err != nil { + fmt.Fprintf(os.Stderr, "lectio: warning: invalid config at %s: %v; using defaults\n", path, err) + return def, nil + } + + if err := validate(cfg); err != nil { + return Config{}, err + } + + return cfg, nil +} + +// validate checks the semantic constraints Load enforces beyond what TOML +// unmarshaling itself catches. +func validate(cfg Config) error { + if cfg.Lectionary != "new" && cfg.Lectionary != "traditional" { + return fmt.Errorf("config: invalid lectionary %q (must be \"new\" or \"traditional\")", cfg.Lectionary) + } + if cfg.TraditionalLang != "pl" && cfg.TraditionalLang != "en" { + return fmt.Errorf("config: invalid traditional_lang %q (must be \"pl\" or \"en\")", cfg.TraditionalLang) + } + for _, v := range cfg.Versions { + if !validVersions[v] { + return fmt.Errorf("config: invalid version %q in versions (must be one of pl, wuj, vul, grb, drb)", v) + } + } + if !validVersions[cfg.DefaultVersion] { + return fmt.Errorf("config: invalid default_version %q (must be one of pl, wuj, vul, grb, drb)", cfg.DefaultVersion) + } + return nil +} |
