aboutsummaryrefslogtreecommitdiff
path: root/internal/cond
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-09-14 21:40:12 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-09-14 21:40:12 +0200
commit360591d6e18d8676a2f86185ed42f46852387f85 (patch)
tree31043c351f6cb2c95c6b6a26d0c69dcb47ab1e3f /internal/cond
parente0dddff176a01d410904b6750b3395de4f7e54db (diff)
downloadkrino-360591d6e18d8676a2f86185ed42f46852387f85.tar.gz
krino-360591d6e18d8676a2f86185ed42f46852387f85.zip
plan 9: content excludes fail closed; krino.conf excludes checked without directories
Diffstat (limited to 'internal/cond')
-rw-r--r--internal/cond/eval.go17
-rw-r--r--internal/cond/eval_test.go14
2 files changed, 27 insertions, 4 deletions
diff --git a/internal/cond/eval.go b/internal/cond/eval.go
index 3cec110..b0ee828 100644
--- a/internal/cond/eval.go
+++ b/internal/cond/eval.go
@@ -33,6 +33,11 @@ type Result struct {
Captures []string // submatches of the first true, non-negated name test: [0] whole match, [1:] groups
Reasons []string // what made it true, e.g. `type pdf`, `content "acme ltd"`, `name "\bacme\b"`
Warnings []string // e.g. `content unreadable: needs pdftotext, not installed`
+
+ // Unreadable is true when evaluation reached a content test that could
+ // not read the file. The test counts as false; an exclude uses this to
+ // hold anyway (review M11).
+ Unreadable bool
}
// Trace is the full evaluation of every node, for krino explain.
@@ -47,9 +52,10 @@ type Trace struct {
// first true, non-negated name test, and warnings de-duplicated in
// first-seen order.
type evalCtx struct {
- captures []string
- warned map[string]bool
- warnings []string
+ captures []string
+ warned map[string]bool
+ warnings []string
+ unreadable bool
}
// warn records msg unless it has already been recorded.
@@ -75,7 +81,7 @@ func (c *Cond) Eval(f Facts) Result {
}
ctx := &evalCtx{}
match, reasons := c.eval(c.root, f, ctx, false)
- return Result{Match: match, Captures: ctx.captures, Reasons: reasons, Warnings: ctx.warnings}
+ return Result{Match: match, Captures: ctx.captures, Reasons: reasons, Warnings: ctx.warnings, Unreadable: ctx.unreadable}
}
// eval evaluates one node against f, short-circuiting and/or in child
@@ -122,6 +128,9 @@ func (c *Cond) eval(n *node, f Facts, ctx *evalCtx, negated bool) (bool, []strin
ok, reason, warn, caps := c.evalLeaf(n, f)
if warn != "" {
ctx.warn(warn)
+ if n.kind == kContent {
+ ctx.unreadable = true
+ }
}
if !ok {
return false, nil
diff --git a/internal/cond/eval_test.go b/internal/cond/eval_test.go
index 8891de7..88612ac 100644
--- a/internal/cond/eval_test.go
+++ b/internal/cond/eval_test.go
@@ -226,3 +226,17 @@ func TestNegatedCombinatorReason(t *testing.T) {
t.Errorf("negated leaf = %+v, want its own label unchanged: %q", r, "not matched")
}
}
+
+// TestEvalReportsUnreadableContent: Result.Unreadable says a content test
+// was reached and could not read the file - what lets an exclude fail
+// closed (review M11) - and stays false when evaluation never reached the
+// content test.
+func TestEvalReportsUnreadableContent(t *testing.T) {
+ f := &fake{name: "a.pdf", rawErr: errors.New("larger than max-read")}
+ if r := eval(t, `(and (type pdf) (content "x"))`, Options{}, f); r.Match || !r.Unreadable {
+ t.Errorf("reached: Match %v Unreadable %v; want false, true", r.Match, r.Unreadable)
+ }
+ if r := eval(t, `(or (name "^a") (content "x"))`, Options{}, f); !r.Match || r.Unreadable {
+ t.Errorf("not reached: Match %v Unreadable %v; want true, false", r.Match, r.Unreadable)
+ }
+}