Merge pull request #20 from hercules-ci/refactor

Refactor rules.nix
This commit is contained in:
Robert Hensing 2019-09-12 11:10:00 +02:00 committed by GitHub
commit 31860d428b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 71 additions and 34 deletions

View file

@ -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
#####

View file

@ -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;
}