aboutsummaryrefslogtreecommitdiff
path: root/internal/config
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-07-24 15:01:03 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-07-24 15:01:03 +0200
commit2453b598def94649cc7ce9af7155bfaad40cce12 (patch)
tree0267ac67c2e9d108bcf4b3006d55aaafc5deb8d8 /internal/config
parent35f98e0a8a0573d51a4412ec7f60c7de068acfe6 (diff)
downloadlectio-2453b598def94649cc7ce9af7155bfaad40cce12.tar.gz
lectio-2453b598def94649cc7ce9af7155bfaad40cce12.zip
web: /settings page to edit config + books.toml, persisted and applied live; v0.14.0
Diffstat (limited to 'internal/config')
-rw-r--r--internal/config/config.go74
-rw-r--r--internal/config/config_test.go25
2 files changed, 98 insertions, 1 deletions
diff --git a/internal/config/config.go b/internal/config/config.go
index 3428ae8..a4d2c3e 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -11,6 +11,7 @@ import (
"fmt"
"os"
"path/filepath"
+ "sort"
"strings"
"github.com/pelletier/go-toml/v2"
@@ -24,7 +25,7 @@ var seedTOML []byte
// Version is lectio's release version, shared by every binary's
// -v/--version output (lectio, lectio-ui, lectio-web).
-const Version = "0.13.1"
+const Version = "0.14.0"
// validVersions are the five scripture versions lectio understands.
var validVersions = map[string]bool{
@@ -300,3 +301,74 @@ func validate(cfg Config) error {
}
return nil
}
+
+// Validate exposes the semantic checks Load applies, so callers that build a
+// Config (the web /settings form) can reject an invalid one before saving.
+func Validate(cfg Config) error { return validate(cfg) }
+
+// Save writes cfg to the config file (configPath, honoring LECTIO_CONFIG),
+// creating the directory if needed, as commented TOML (regenerated -- the
+// explanatory comments are kept, the values are cfg's).
+func Save(cfg Config) error {
+ path, _, err := configPath()
+ if err != nil {
+ return err
+ }
+ if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
+ return err
+ }
+ return os.WriteFile(path, renderConfigTOML(cfg), 0o644)
+}
+
+func tomlStr(s string) string { return `"` + strings.ReplaceAll(s, `"`, `\"`) + `"` }
+
+func tomlStrSlice(ss []string) string {
+ q := make([]string, len(ss))
+ for i, s := range ss {
+ q[i] = tomlStr(s)
+ }
+ return "[" + strings.Join(q, ", ") + "]"
+}
+
+// renderConfigTOML regenerates config.toml from cfg, preserving the seed's
+// field comments. Parts (the per-lectionary hide map) is written back verbatim
+// so a config that had [parts.*] sections keeps them across a Save.
+func renderConfigTOML(cfg Config) []byte {
+ var b strings.Builder
+ fmt.Fprintf(&b, "schema_version = %d\n", cfg.SchemaVersion)
+ fmt.Fprintf(&b, "lectionary = %s # \"new\" (niedziela.pl) or \"traditional\" (missalemeum, 1962)\n", tomlStr(cfg.Lectionary))
+ fmt.Fprintf(&b, "traditional_lang = %s # vernacular for traditional propers: \"pl\" or \"en\"\n", tomlStr(cfg.TraditionalLang))
+ fmt.Fprintf(&b, "versions = %s # compare set + TUI cycle order (bt = Biblia TysiÄ…clecia)\n", tomlStrSlice(cfg.Versions))
+ fmt.Fprintf(&b, "default_version = %s # TUI start / `lectio -b` default\n", tomlStr(cfg.DefaultVersion))
+ fmt.Fprintf(&b, "width = %d # CLI wrap width; 0 = detect terminal\n", cfg.Width)
+ fmt.Fprintf(&b, "all = %t # all parts (true) or just the gospel (false)\n", cfg.All)
+ fmt.Fprintf(&b, "offline = %t # true = never fetch; read only cache + harvested sigla\n", cfg.Offline)
+ fmt.Fprintf(&b, "ui_language = %s # interface language: \"en\" or \"pl\" (readings stay source-language)\n", tomlStr(cfg.UILanguage))
+ fmt.Fprintf(&b, "sigla_style = %s # citation dialect for --ref/--list/--citation: \"auto\", \"polish\", \"english\"\n", tomlStr(cfg.SiglaStyle))
+ fmt.Fprintf(&b, "web_theme = %s # default lectio-web theme\n", tomlStr(cfg.WebTheme))
+ fmt.Fprintf(&b, "web_port = %d # lectio-web port; 0 = try 1099, then any free port\n", cfg.WebPort)
+ fmt.Fprintf(&b, "web_display = %s # lectio-web layout: \"horizontal\", \"vertical\", \"interlinear\"\n", tomlStr(cfg.WebDisplay))
+ fmt.Fprintf(&b, "web_mono = %t # lectio-web: monospace reading face\n", cfg.WebMono)
+ fmt.Fprintf(&b, "web_versions = %s # which versions start toggled on; empty = just default_version\n", tomlStrSlice(cfg.WebVersions))
+ fmt.Fprintf(&b, "pager = %s # pager for `lectio` output; \"\" = off\n", tomlStr(cfg.Pager))
+
+ // Preserve the parts map (not edited by the form) so [parts.*] survives Save.
+ lects := make([]string, 0, len(cfg.Parts))
+ for l := range cfg.Parts {
+ lects = append(lects, l)
+ }
+ sort.Strings(lects)
+ for _, l := range lects {
+ parts := cfg.Parts[l]
+ keys := make([]string, 0, len(parts))
+ for k := range parts {
+ keys = append(keys, k)
+ }
+ sort.Strings(keys)
+ fmt.Fprintf(&b, "\n[parts.%s]\n", l)
+ for _, k := range keys {
+ fmt.Fprintf(&b, "%s = %t\n", k, parts[k])
+ }
+ }
+ return []byte(b.String())
+}
diff --git a/internal/config/config_test.go b/internal/config/config_test.go
index cc0c941..53a3980 100644
--- a/internal/config/config_test.go
+++ b/internal/config/config_test.go
@@ -199,6 +199,31 @@ func TestSiglaStyleDefaultsAndDialect(t *testing.T) {
}
}
+func TestSaveRoundTrip(t *testing.T) {
+ dir := t.TempDir()
+ path := filepath.Join(dir, "config.toml")
+ t.Setenv("LECTIO_CONFIG", path)
+
+ cfg := Default()
+ cfg.UILanguage = "pl"
+ cfg.SiglaStyle = "english"
+ cfg.WebMono = true
+ cfg.WebVersions = []string{"wuj", "vul"}
+ cfg.Pager = "less -R"
+ cfg.Parts = map[string]map[string]bool{"new": {"psalm": false}}
+ if err := Save(cfg); err != nil {
+ t.Fatal(err)
+ }
+ got, err := Load()
+ if err != nil {
+ t.Fatalf("reload: %v", err)
+ }
+ if got.UILanguage != "pl" || got.SiglaStyle != "english" || !got.WebMono ||
+ len(got.WebVersions) != 2 || got.Pager != "less -R" || got.PartShown("new", "psalm") {
+ t.Errorf("round-trip mismatch: %+v", got)
+ }
+}
+
func TestPartShown(t *testing.T) {
var empty Config
if !empty.PartShown("new", "psalm") {