diff options
| author | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-09-16 01:44:06 +0200 |
|---|---|---|
| committer | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-09-16 01:44:06 +0200 |
| commit | 69e942371823638fbe24a94202189729f2f17ebc (patch) | |
| tree | bc491ef6d0fe62e7a89e618a2eb9337b4f3ed8fc /internal/config | |
| parent | ecfaeabf2a92e26c6a521d5fac404a0fff6263b6 (diff) | |
| download | krino-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/config')
| -rw-r--r-- | internal/config/load.go | 46 | ||||
| -rw-r--r-- | internal/config/load_test.go | 49 |
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) + } +} |
