aboutsummaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-09-16 01:44:06 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-09-16 01:44:06 +0200
commit69e942371823638fbe24a94202189729f2f17ebc (patch)
treebc491ef6d0fe62e7a89e618a2eb9337b4f3ed8fc /internal
parentecfaeabf2a92e26c6a521d5fac404a0fff6263b6 (diff)
downloadkrino-69e942371823638fbe24a94202189729f2f17ebc.tar.gz
krino-69e942371823638fbe24a94202189729f2f17ebc.zip
unsaved text for another included directory is not an error; two keys for one file are
Diffstat (limited to 'internal')
-rw-r--r--internal/config/load.go46
-rw-r--r--internal/config/load_test.go49
2 files changed, 76 insertions, 19 deletions
diff --git a/internal/config/load.go b/internal/config/load.go
index 14ce635..c75d000 100644
--- a/internal/config/load.go
+++ b/internal/config/load.go
@@ -6,9 +6,10 @@ import (
"errors"
"fmt"
"io/fs"
+ "maps"
"os"
"path/filepath"
- "sort"
+ "slices"
"krino/internal/sexp"
"krino/internal/xdg"
@@ -21,17 +22,22 @@ type Config struct {
Dirs []*Dir
}
-// unusedOverrides reports every override LoadWith never read: text meant
-// for a file this load does not touch, which would otherwise pass as
-// checked while the file on disk was read instead (plan 13 review F4).
-func unusedOverrides(over map[string][]byte, used map[string]bool, mainFile string) []*Diag {
+// unusedOverrides reports text for a file no part of this configuration
+// names: it would otherwise pass as checked while the file on disk was read
+// instead (plan 13 review F4). Text for an included directory this call did
+// not load - an editor holding buffers for several while checking one - is
+// not reported.
+func unusedOverrides(over map[string][]byte, used map[string]bool, mainFile string, include []string) []*Diag {
+ known := map[string]bool{filepath.Clean(mainFile): true}
+ for _, name := range include {
+ known[filepath.Clean(DirFile(mainFile, name))] = true
+ }
var out []*Diag
- for file := range over {
- if !used[file] {
+ for _, file := range slices.Sorted(maps.Keys(over)) {
+ if !used[file] && !known[file] {
out = append(out, &Diag{File: mainFile, Msg: fmt.Sprintf("unsaved text for %s, which this configuration does not read", file)})
}
}
- sort.Slice(out, func(i, j int) bool { return out[i].Msg < out[j].Msg })
return out
}
@@ -48,16 +54,23 @@ func readSource(overrides map[string][]byte, used map[string]bool, file string)
}
// cleanOverrides keys the caller's overrides by cleaned path, so a file
-// named with a "." or ".." segment is still recognised as itself.
-func cleanOverrides(overrides map[string][]byte) map[string][]byte {
+// named with a "." or ".." segment is still recognised as itself. Two keys
+// that name one file are refused rather than resolved by map order.
+func cleanOverrides(overrides map[string][]byte) (map[string][]byte, []*Diag) {
if len(overrides) == 0 {
- return nil
+ return nil, nil
}
out := make(map[string][]byte, len(overrides))
- for file, src := range overrides {
- out[filepath.Clean(file)] = src
+ var errs []*Diag
+ for _, file := range slices.Sorted(maps.Keys(overrides)) {
+ key := filepath.Clean(file)
+ if _, seen := out[key]; seen {
+ errs = append(errs, &Diag{File: key, Msg: "unsaved text given twice for this file"})
+ continue
+ }
+ out[key] = overrides[file]
}
- return out
+ return out, errs
}
// DefaultFile is krino.conf in the XDG config directory.
@@ -83,7 +96,7 @@ func Load(mainFile string, names ...string) (*Config, []*Diag) {
// 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) {
- over := cleanOverrides(overrides)
+ over, collisions := cleanOverrides(overrides)
used := map[string]bool{}
src, err := readSource(over, used, mainFile)
if errors.Is(err, fs.ErrNotExist) {
@@ -126,7 +139,8 @@ func LoadWith(mainFile string, overrides map[string][]byte, names ...string) (*C
errs = append(errs, derrs...)
cfg.Dirs = append(cfg.Dirs, dir)
}
- return cfg, append(errs, unusedOverrides(over, used, mainFile)...)
+ errs = append(errs, collisions...)
+ return cfg, append(errs, unusedOverrides(over, used, mainFile, m.Include)...)
}
// Resolved is the settings that apply in dir: built-in, then the main
diff --git a/internal/config/load_test.go b/internal/config/load_test.go
index d522316..5f848af 100644
--- a/internal/config/load_test.go
+++ b/internal/config/load_test.go
@@ -157,9 +157,9 @@ func TestLoadWithReportsAnUnusedOverride(t *testing.T) {
broken := []byte("(path \"/tmp\")\n(rule \"BROKEN\")\n")
// The same file, spelled with a "." segment: the text must still be used.
- uncleaned := filepath.Join(h, "dirs", ".", "dl.conf")
- if _, errs := LoadWith(main, map[string][]byte{uncleaned: broken}); len(errs) == 0 {
- t.Error("an override keyed by an uncleaned path was ignored")
+ uncleaned := filepath.Join(h, "dirs") + "/../dirs/dl.conf"
+ if _, errs := LoadWith(main, map[string][]byte{uncleaned: broken}); !strings.Contains(diagText(errs), "BROKEN") {
+ t.Errorf("an override keyed by an uncleaned path was ignored: %v", errs)
}
// A file this load never reads: say so rather than pass silently.
other := filepath.Join(h, "dirs", "other.conf")
@@ -176,3 +176,46 @@ func diagText(ds []*Diag) string {
}
return b.String()
}
+
+// TestLoadWithOverrideForAnotherIncludedDirectory: checking one directory
+// while holding text for another included one is normal for an editor, and
+// not an error; only text for a file no configuration file names is
+// reported (plan 13 review F4 follow-up).
+func TestLoadWithOverrideForAnotherIncludedDirectory(t *testing.T) {
+ h := t.TempDir()
+ t.Setenv("HOME", h)
+ main := filepath.Join(h, "krino.conf")
+ os.MkdirAll(filepath.Join(h, "dirs"), 0o755)
+ os.WriteFile(main, []byte("(include \"a\" \"b\")\n"), 0o644)
+ for _, n := range []string{"a", "b"} {
+ os.WriteFile(filepath.Join(h, "dirs", n+".conf"), []byte("(path \"/tmp\")\n"), 0o644)
+ }
+ bText := []byte("(path \"/tmp\")\n(rule \"r\" (move \"Out\"))\n")
+ if _, errs := LoadWith(main, map[string][]byte{filepath.Join(h, "dirs", "b.conf"): bText}, "a"); len(errs) > 0 {
+ t.Errorf("text for another included directory was reported: %v", errs)
+ }
+ strange := filepath.Join(h, "dirs", "nowhere.conf")
+ if _, errs := LoadWith(main, map[string][]byte{strange: bText}, "a"); len(errs) != 1 {
+ t.Errorf("text for a file no configuration names was not reported: %v", errs)
+ }
+}
+
+// TestLoadWithRefusesCollidingOverrides: two keys that name the same file
+// would leave which text is read to map order, so they are refused (plan 13
+// review F4 follow-up).
+func TestLoadWithRefusesCollidingOverrides(t *testing.T) {
+ h := t.TempDir()
+ t.Setenv("HOME", h)
+ main := filepath.Join(h, "krino.conf")
+ os.MkdirAll(filepath.Join(h, "dirs"), 0o755)
+ 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"),
+ filepath.Join(h, "dirs") + "/./dl.conf": []byte("(path \"/other\")\n"),
+ }
+ _, errs := LoadWith(main, over)
+ if len(errs) == 0 || !strings.Contains(diagText(errs), "twice") {
+ t.Errorf("colliding override keys were not refused: %v", errs)
+ }
+}