aboutsummaryrefslogtreecommitdiff
path: root/internal/config/config.go
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-07-23 13:18:34 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-07-23 13:18:34 +0200
commitfbb3345518872346a629e7ccbec81f09532765f9 (patch)
tree0f07371550d3dc1885d92d99c8aa847223b875ae /internal/config/config.go
parent8a401a84af52c94f991c74443b7f4397e30cad4a (diff)
downloadlectio-fbb3345518872346a629e7ccbec81f09532765f9.tar.gz
lectio-fbb3345518872346a629e7ccbec81f09532765f9.zip
config: TOML load + seed
Diffstat (limited to 'internal/config/config.go')
-rw-r--r--internal/config/config.go159
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
+}