From 05026b6bf552981b374fa99f4a4df3487c437702 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Fri, 6 Sep 2019 21:15:14 +0200 Subject: [PATCH] Refactor rules.nix Make it centered around type patternFunction = path -> type -> nullOr bool instead of path -> type -> [(bool, bool)] of which only the last match (fst?) was used (snd?). --- find-files.nix | 15 ++++----- rules.nix | 90 +++++++++++++++++++++++++++++++++++--------------- 2 files changed, 71 insertions(+), 34 deletions(-) diff --git a/find-files.nix b/find-files.nix index 191a0b6..9336ffa 100644 --- a/find-files.nix +++ b/find-files.nix @@ -16,7 +16,6 @@ rec { # - readLines function with CRLF support # TODO: check assumption that a relative core.excludesFile is relative to HOME # TODO: write test for trailing slash (matches dir only) - # TODO: rename Pattern' gitignoreFilter = basePath: let @@ -26,7 +25,7 @@ rec { path: type: let localDirPath = removePrefix basePathStr (toString (dirOf path)); localDirPathElements = splitString "/" localDirPath; - in parse-gitignore.runFilterPattern' (getPatterns patternsBelowP localDirPathElements)."/patterns" path type; + in parse-gitignore.runFilterPattern (getPatterns patternsBelowP localDirPathElements)."/patterns" path type; getPatterns = patternTree: pathElems: @@ -58,10 +57,10 @@ rec { findPatternsTree = dir: let listOfStartingPatterns = map ({contextDir, file, ...}: - parse-gitignore.gitignoreFilter' (readFile file) contextDir + parse-gitignore.gitignoreFilter (readFile file) contextDir ) (findAncestryGitignores dir); startingPatterns = builtins.foldl' - parse-gitignore.mergePattern' + parse-gitignore.mergePattern (defaultPatterns dir) # not the unit of merge but a set of defaults listOfStartingPatterns; in @@ -79,21 +78,21 @@ rec { let nodes = readDir dir; dirs = filterAttrs (name: type: type == nodeTypes.directory && - (parse-gitignore.runFilterPattern' currentPatterns (dir + "/${name}") type) + (parse-gitignore.runFilterPattern currentPatterns (dir + "/${name}") type) ) nodes; in mapAttrs (name: _t: let subdir = dir + "/${name}"; ignore = subdir + "/.gitignore"; newPatterns = map (file: - parse-gitignore.mergePattern' + parse-gitignore.mergePattern currentPatterns # Performance: this is where you could potentially filter out patterns irrelevant to subdir - (parse-gitignore.gitignoreFilter' (readFile file) subdir) + (parse-gitignore.gitignoreFilter (readFile file) subdir) ) (guardFile ignore); subdirPatterns = headOr currentPatterns newPatterns; in findDescendantPatternsTree subdirPatterns subdir ) dirs // { "/patterns" = currentPatterns; }; - defaultPatterns = root: parse-gitignore.gitignoreFilter' ".git" root; # no trailing slash, because of worktree references + defaultPatterns = root: parse-gitignore.gitignoreFilter ".git" root; # no trailing slash, because of worktree references ##### diff --git a/rules.nix b/rules.nix index 75f5703..57746dd 100644 --- a/rules.nix +++ b/rules.nix @@ -3,7 +3,7 @@ */ let inherit (builtins) compareVersions nixVersion split match; - inherit (lib) elemAt length head filter isList; + inherit (lib) elemAt length head filter isList reverseList foldl'; inherit (lib.strings) substring stringLength replaceStrings concatStringsSep; debug = a: builtins.trace a a; @@ -16,35 +16,74 @@ let in rec { - # [["good/relative/source/file" true] ["bad.tmpfile" false]] -> root -> path - filterPattern = patterns: root: - (name: _type: + + # type patternFunction = path -> type -> nullOr bool + # + # As used here + + # type filterFunction = path -> type -> bool + # + # As used by cleanSourceWith, builtins.path, filterSource + + # patternFunction -> filterFunction + # + # Make a patternFunction usable for cleanSourceWith etc. + # + # null values (unmatched) are converted to true (included). + runFilterPattern = + r: path: type: let - relPath = lib.removePrefix ((toString root) + "/") name; - matches = pair: (match (head pair) relPath) != null; - matched = map (pair: [(matches pair) (last pair)]) patterns; + result = r (toString path) type; in - last (last ([[true true]] ++ (filter head matched))) + if result == null + then true + else result; + + # [["good/relative/source/file" true] ["bad.tmpfile" false]] -> root -> path -> nullOr bool + filterPattern = patterns: root: + let + # Last item has the last say; might as well start there + reversed = reverseList patterns; + + matchers = map (pair: let regex = match (head pair); + in relPath: + if regex relPath == null + then null + else last pair + ) reversed; + + in + name: _type: + let + relPath = lib.removePrefix ((toString root) + "/") name; + in + # Ideally we'd use foldr, but that crashes on big lists. At least we don't + # have to actually match any patterns after we encounter a match. + foldl' + (result: matcher: + if result == null + then matcher relPath + else result) + null + matchers + ; + + # Combine the result of two pattern functions such that the later functions + # may override the result of preceding ones. + mergePattern = pa: pb: (name: type: + let ra = pa name type; + rb = pb name type; + in if rb != null + then rb + else ra ); - # TODO: we only care about the last match, so it seems we can do a reverse - # scan per file and represent the outcome as true, false, and null for - # nothing said => default to true after all rules are processed. - runFilterPattern' = r: path: type: last (last ([[true true]] ++ r (toString path) type)); - filterPattern' = patterns: root: - (name: _type: - let - relPath = lib.removePrefix ((toString root) + "/") name; - matches = pair: (match (head pair) relPath) != null; - matched = map (pair: [(matches pair) (last pair)]) patterns; - in - filter head matched - ); - mergePattern' = pa: pb: (name: type: pa name type ++ pb name type); - unitPattern' = name: type: []; + # mergePattern unitPattern x == x + # mergePattern x unitPattern == x + unitPattern = name: type: null; # string -> [[regex bool]] - gitignoreToPatterns = gitignore: + gitignoreToRegexes = gitignore: assert throwIfOldNix; let # ignore -> bool @@ -105,6 +144,5 @@ rec { (filter (l: !isList l && !isComment l) (split "\n" gitignore)); - gitignoreFilter = ign: root: filterPattern (gitignoreToPatterns ign) root; - gitignoreFilter' = ign: root: filterPattern' (gitignoreToPatterns ign) root; + gitignoreFilter = ign: root: filterPattern (gitignoreToRegexes ign) root; }