aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-09-16 00:53:52 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-09-16 00:53:52 +0200
commit9a77de31dc9759708ce9c2d14ea7c0876c214e71 (patch)
tree9bd8bab97206468bdd1eed3d77481e37d3fe75e7
parent1b39b2683eb820560ba882936d4818af0dcfcec7 (diff)
downloadkrino-9a77de31dc9759708ce9c2d14ea7c0876c214e71.tar.gz
krino-9a77de31dc9759708ce9c2d14ea7c0876c214e71.zip
config and engine can load with unsaved text
-rw-r--r--internal/config/load.go22
-rw-r--r--internal/config/load_test.go27
-rw-r--r--internal/engine/engine.go9
-rw-r--r--internal/engine/engine_test.go18
4 files changed, 73 insertions, 3 deletions
diff --git a/internal/config/load.go b/internal/config/load.go
index cd4d97c..3811802 100644
--- a/internal/config/load.go
+++ b/internal/config/load.go
@@ -20,6 +20,15 @@ type Config struct {
Dirs []*Dir
}
+// readSource is the text of file: the caller's override when it has one,
+// else the file's own bytes.
+func readSource(overrides map[string][]byte, file string) ([]byte, error) {
+ if src, ok := overrides[file]; ok {
+ return src, nil
+ }
+ return os.ReadFile(file)
+}
+
// DefaultFile is krino.conf in the XDG config directory.
func DefaultFile() string {
return filepath.Join(xdg.ConfigHome(), "krino", "krino.conf")
@@ -34,7 +43,16 @@ func DirFile(mainFile, name string) string {
// With names, only those directories are read, and each must be included.
// The Config is nil only when the main file itself cannot be read.
func Load(mainFile string, names ...string) (*Config, []*Diag) {
- src, err := os.ReadFile(mainFile)
+ return LoadWith(mainFile, nil, names...)
+}
+
+// LoadWith is Load with some files' text supplied by the caller: overrides
+// maps a file path - mainFile, or DirFile(mainFile, name) - to the text to
+// read instead of that file's own, so an editor can have unsaved text
+// checked exactly as a run would read it (GUI design §1.3). A nil map is
+// Load.
+func LoadWith(mainFile string, overrides map[string][]byte, names ...string) (*Config, []*Diag) {
+ src, err := readSource(overrides, mainFile)
if errors.Is(err, fs.ErrNotExist) {
return nil, []*Diag{{File: mainFile, Msg: "not found; create it with: krino init"}}
}
@@ -62,7 +80,7 @@ func Load(mainFile string, names ...string) (*Config, []*Diag) {
}
for _, name := range want {
file := DirFile(mainFile, name)
- src, err := os.ReadFile(file)
+ src, err := readSource(overrides, file)
if err != nil {
msg := err.Error()
if errors.Is(err, fs.ErrNotExist) {
diff --git a/internal/config/load_test.go b/internal/config/load_test.go
index 1cc3ac9..13ef9d8 100644
--- a/internal/config/load_test.go
+++ b/internal/config/load_test.go
@@ -115,3 +115,30 @@ func TestLockFile(t *testing.T) {
t.Errorf("LockFile = %q, want %q", got, want)
}
}
+
+// TestLoadWithOverriddenText: LoadWith reads the text the caller supplies
+// instead of a file's own, so an editor can check what it has not saved yet
+// (GUI design §1.3); the files on disk are neither read differently nor
+// changed.
+func TestLoadWithOverriddenText(t *testing.T) {
+ h := t.TempDir()
+ t.Setenv("HOME", h)
+ main := filepath.Join(h, "krino.conf")
+ if err := os.MkdirAll(filepath.Join(h, "dirs"), 0o755); err != nil {
+ t.Fatal(err)
+ }
+ os.WriteFile(main, []byte("(include \"dl\")\n"), 0o644)
+ os.WriteFile(filepath.Join(h, "dirs", "dl.conf"), []byte("(path \"/tmp\")\n"), 0o644)
+
+ over := map[string][]byte{filepath.Join(h, "dirs", "dl.conf"): []byte("(path \"/tmp\")\n(rule \"r\" (move \"Out\"))\n")}
+ cfg, errs := LoadWith(main, over)
+ if len(errs) > 0 || len(cfg.Dirs) != 1 || len(cfg.Dirs[0].Rules) != 1 {
+ t.Fatalf("overridden text not used: %v %+v", errs, cfg.Dirs)
+ }
+ if _, errs := LoadWith(main, map[string][]byte{main: []byte("(include \"dl\"")}); len(errs) == 0 {
+ t.Error("a mistake in the overridden main file was not reported")
+ }
+ if cfg, errs := Load(main); len(errs) > 0 || len(cfg.Dirs[0].Rules) != 0 {
+ t.Errorf("the files on disk were read differently: %v %+v", errs, cfg.Dirs)
+ }
+}
diff --git a/internal/engine/engine.go b/internal/engine/engine.go
index 958e4f9..cc776e6 100644
--- a/internal/engine/engine.go
+++ b/internal/engine/engine.go
@@ -80,7 +80,14 @@ type Rule struct {
// Engine and every diagnostic: krino never acts on a configuration it only
// partly understood. Duplicate names are ignored after their first use.
func Load(mainFile string, names ...string) (*Engine, []*config.Diag) {
- cfg, errs := config.Load(mainFile, dedupeNames(names)...)
+ return LoadWith(mainFile, nil, names...)
+}
+
+// LoadWith is Load with some configuration files' text supplied by the
+// caller (config.LoadWith): the GUI checks unsaved editor text with it,
+// compiled exactly as a run would compile it (GUI design §1.3).
+func LoadWith(mainFile string, overrides map[string][]byte, names ...string) (*Engine, []*config.Diag) {
+ cfg, errs := config.LoadWith(mainFile, overrides, dedupeNames(names)...)
if cfg == nil {
return nil, errs
}
diff --git a/internal/engine/engine_test.go b/internal/engine/engine_test.go
index 7de2f76..b6b50fc 100644
--- a/internal/engine/engine_test.go
+++ b/internal/engine/engine_test.go
@@ -11,6 +11,7 @@ import (
"time"
"krino/internal/cond"
+ "krino/internal/config"
)
// sandbox gives a test its own HOME with no XDG overrides and returns it.
@@ -296,3 +297,20 @@ func TestLoadRefusesBadPlaceholders(t *testing.T) {
t.Errorf("valid placeholders refused: %v", errs)
}
}
+
+// TestEngineLoadWith: the engine compiles overridden text too, so unsaved
+// rules are checked exactly as a run would read them (GUI design §1.3).
+func TestEngineLoadWith(t *testing.T) {
+ h := sandbox(t)
+ os.MkdirAll(filepath.Join(h, "dl"), 0o755)
+ main := writeConfig(t, h, `(include "dl")`, map[string]string{"dl": "(path \"~/dl\")\n"})
+ over := map[string][]byte{config.DirFile(main, "dl"): []byte("(path \"~/dl\")\n(rule \"r\" (when (bogus)) (move \"Out\"))\n")}
+ if _, errs := LoadWith(main, over); len(errs) == 0 {
+ t.Error("a mistake in the overridden directory file was not reported")
+ }
+ good := map[string][]byte{config.DirFile(main, "dl"): []byte("(path \"~/dl\")\n(rule \"r\" (move \"Out\"))\n")}
+ e, errs := LoadWith(main, good)
+ if len(errs) > 0 || len(e.Dirs) != 1 || len(e.Dirs[0].Rules) != 1 {
+ t.Errorf("overridden rules not compiled: %v", errs)
+ }
+}