From 1d3f2d1e4c59867024470d3444e12698b7ebb22e Mon Sep 17 00:00:00 2001 From: Lukasz Kasprzak Date: Mon, 14 Sep 2026 14:08:36 +0200 Subject: 0.0.4: max-size, exclude forms, --min-age --- internal/engine/engine.go | 47 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 4 deletions(-) (limited to 'internal/engine/engine.go') diff --git a/internal/engine/engine.go b/internal/engine/engine.go index b5f2106..8cffbd1 100644 --- a/internal/engine/engine.go +++ b/internal/engine/engine.go @@ -46,6 +46,11 @@ type Dir struct { // stay memoised, as before. ContentVariants []cond.Options + // Excludes are the (exclude ...) forms that apply here, compiled with the + // directory's settings: krino.conf's first, then the directory's own. A + // file any of them matches is set aside before any rule runs. + Excludes []*Exclude + // DupScopes is every distinct directory list the duplicate tests of // Rules use, in first-seen order, the plain (duplicate) as an empty // list. Spec §5.5 rule 2 looks a file up under each of them before any @@ -53,6 +58,12 @@ type Dir struct { DupScopes [][]string } +// Exclude is one compiled (exclude ...) form. +type Exclude struct { + Text string // the form as written, for check, explain and the plan + Cond *cond.Cond +} + // Rule is one directory's rule, with its condition compiled. type Rule struct { Name string @@ -71,6 +82,9 @@ func Load(mainFile string, names ...string) (*Engine, []*config.Diag) { } var dirs []*Dir + // A mistake inside a krino.conf exclude is compiled once per directory, + // but reported once. + reported := map[string]bool{} for _, d := range cfg.Dirs { dir := &Dir{ Name: d.Name, @@ -103,7 +117,25 @@ func Load(mainFile string, names ...string) (*Engine, []*config.Diag) { } dir.Rules = append(dir.Rules, &Rule{Name: r.Name, Conf: r, Settings: rs, Cond: c}) } - dir.ContentVariants = contentVariants(dir.Rules) + dirOpt := cond.Options{IgnoreCase: dir.Settings.Case == config.CaseIgnore, Fold: dir.Settings.Fold} + for _, src := range []struct { + file string + excludes []*config.Exclude + }{{cfg.Main.File, cfg.Main.Excludes}, {d.File, d.Excludes}} { + for _, x := range src.excludes { + c, cerrs := cond.Compile(src.file, x.When, dirOpt) + for _, ce := range cerrs { + if key := ce.Error(); !reported[key] { + reported[key] = true + errs = append(errs, ce) + } + } + if len(cerrs) == 0 { + dir.Excludes = append(dir.Excludes, &Exclude{Text: x.Text, Cond: c}) + } + } + } + dir.ContentVariants = contentVariants(dir.Rules, dir.Excludes, dirOpt) dir.DupScopes = dupScopes(dir.Rules) dirs = append(dirs, dir) } @@ -184,14 +216,21 @@ func dedupeNames(names []string) []string { return out } -// contentVariants returns the distinct (IgnoreCase, Fold) pairs any of -// rules' content tests evaluate under, in first-seen order — B2's per-Dir +// contentVariants returns the distinct (IgnoreCase, Fold) pairs any content +// test evaluates under - each exclude's (under the directory's settings, +// dirOpt) and each rule's - in first-seen order — B2's per-Dir // ContentVariants. A rule whose condition has no content test at all // (Cond.UsesContent false) never calls facts.Content, so its resolved // case/fold settings contribute no variant here. -func contentVariants(rules []*Rule) []cond.Options { +func contentVariants(rules []*Rule, excludes []*Exclude, dirOpt cond.Options) []cond.Options { var out []cond.Options seen := map[cond.Options]bool{} + for _, x := range excludes { + if x.Cond.UsesContent && !seen[dirOpt] { + seen[dirOpt] = true + out = append(out, dirOpt) + } + } for _, r := range rules { if !r.Cond.UsesContent { continue -- cgit v1.3