aboutsummaryrefslogtreecommitdiff
path: root/cmd/krino
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-09-14 23:47:15 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-09-14 23:47:15 +0200
commit89c1fcd8fae93a64d304251a35a65763818aa8b1 (patch)
treec695ff9c729a12eeab63d8a7743a6d789f64de27 /cmd/krino
parent97c8164bc81e0a438dab92d7244485005dac4ff2 (diff)
downloadkrino-89c1fcd8fae93a64d304251a35a65763818aa8b1.tar.gz
krino-89c1fcd8fae93a64d304251a35a65763818aa8b1.zip
--json carries exclusions and matching warnings
Diffstat (limited to 'cmd/krino')
-rw-r--r--cmd/krino/matching_test.go49
-rw-r--r--cmd/krino/sort.go16
2 files changed, 64 insertions, 1 deletions
diff --git a/cmd/krino/matching_test.go b/cmd/krino/matching_test.go
index 6986e99..754e577 100644
--- a/cmd/krino/matching_test.go
+++ b/cmd/krino/matching_test.go
@@ -499,3 +499,52 @@ func TestLongNameGetsItsOwnLine(t *testing.T) {
t.Errorf("short name block:\n%s\nwant substring:\n%s", out, want)
}
}
+
+// TestDryRunJSONCarriesExclusionsAndWarnings: the JSON plan says which
+// exclude set a file aside and carries the warnings matching raised, for
+// matched and unmatched files alike, as the text plan does (triage 28h).
+func TestDryRunJSONCarriesExclusionsAndWarnings(t *testing.T) {
+ h := home(t)
+ dl := filepath.Join(h, "dl")
+ os.MkdirAll(dl, 0o755)
+ old := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
+ for name, body := range map[string]string{"keep.pdf": "x", "big.txt": strings.Repeat("y", 4096)} {
+ p := filepath.Join(dl, name)
+ os.WriteFile(p, []byte(body), 0o644)
+ os.Chtimes(p, old, old)
+ }
+ if code, _, errOut := runCLI(t, "init"); code != 0 {
+ t.Fatal(errOut)
+ }
+ if code, _, errOut := runCLI(t, "new", "dl", dl); code != 0 {
+ t.Fatal(errOut)
+ }
+ rules := "(path \"~/dl\")\n(max-read 1K)\n(exclude (name \"^keep\"))\n(rule \"acme\" (when (content \"acme\")) (move \"Acme\"))\n"
+ os.WriteFile(filepath.Join(h, ".config", "krino", "dirs", "dl.conf"), []byte(rules), 0o644)
+ code, out, errOut := runCLI(t, "-n", "--json")
+ if code != 0 {
+ t.Fatalf("exit %d: %s", code, errOut)
+ }
+ var doc struct {
+ Dirs []struct {
+ Files []struct {
+ Rel string `json:"rel"`
+ Excluded string `json:"excluded"`
+ } `json:"files"`
+ Unmatched []struct {
+ Rel string `json:"rel"`
+ Warnings []string `json:"warnings"`
+ } `json:"unmatched"`
+ } `json:"dirs"`
+ }
+ if err := json.Unmarshal([]byte(out), &doc); err != nil {
+ t.Fatalf("%v\n%s", err, out)
+ }
+ d := doc.Dirs[0]
+ if len(d.Files) != 1 || d.Files[0].Rel != "keep.pdf" || d.Files[0].Excluded != `(exclude (name "^keep"))` {
+ t.Errorf("files = %+v; want keep.pdf excluded by its form\n%s", d.Files, out)
+ }
+ if len(d.Unmatched) != 1 || d.Unmatched[0].Rel != "big.txt" || len(d.Unmatched[0].Warnings) == 0 || !strings.Contains(d.Unmatched[0].Warnings[0], "content unreadable") {
+ t.Errorf("unmatched = %+v; want big.txt with its content warning\n%s", d.Unmatched, out)
+ }
+}
diff --git a/cmd/krino/sort.go b/cmd/krino/sort.go
index f600475..83ede11 100644
--- a/cmd/krino/sort.go
+++ b/cmd/krino/sort.go
@@ -183,7 +183,7 @@ func cmdSort(g *globals, names []string, stdout, stderr io.Writer) int {
// --json is only ever reached with -n (checked above), and
// Ruling 7 is explicit that JSON must never be paged, so
// this returns before any of the paging/review code below.
- jsonDirs = append(jsonDirs, plan.NewJSONDir(d.Name, d.Root, dp.Chains, dp.Result.Warnings))
+ jsonDirs = append(jsonDirs, plan.NewJSONDir(d.Name, d.Root, dp.Chains, fileNotes(dp.Result), dp.Result.Warnings))
return false
}
@@ -427,6 +427,20 @@ type warnLine struct {
// the chain-level warnings (e.g. "moved more than once"), keyed by
// Chain.File.Rel; every chain's file is necessarily also in r.Matched (only
// matched files ever reach plan.Build), so it is visited exactly once here.
+// fileNotes is r's per-file findings - the exclude that set a file aside,
+// the warnings matching raised - for the JSON plan.
+func fileNotes(r *engine.Result) map[string]plan.FileNotes {
+ notes := map[string]plan.FileNotes{}
+ for _, fms := range [][]engine.FileMatch{r.Matched, r.Unmatched} {
+ for _, fm := range fms {
+ if fm.Excluded != "" || len(fm.Warnings) > 0 {
+ notes[fm.File.Rel] = plan.FileNotes{File: fm.File, Excluded: fm.Excluded, Warnings: fm.Warnings}
+ }
+ }
+ }
+ return notes
+}
+
func collectWarnings(r *engine.Result, chains []plan.Chain) []warnLine {
files := make([]engine.FileMatch, 0, len(r.Matched)+len(r.Unmatched))
files = append(files, r.Matched...)