Clone of https://github.com/NixOS/nixpkgs.git (to stress-test knotserver)
at devShellTools-shell 258 lines 7.1 kB view raw
1# https://github.com/siers/nix-gitignore/ 2 3{ lib, runCommand }: 4 5# An interesting bit from the gitignore(5): 6# - A slash followed by two consecutive asterisks then a slash matches 7# - zero or more directories. For example, "a/**/b" matches "a/b", 8# - "a/x/b", "a/x/y/b" and so on. 9 10let 11 inherit (builtins) filterSource; 12 13 inherit (lib) 14 concatStringsSep 15 elemAt 16 filter 17 head 18 isList 19 length 20 optionals 21 optionalString 22 pathExists 23 readFile 24 removePrefix 25 replaceStrings 26 stringLength 27 sub 28 substring 29 toList 30 trace 31 ; 32 33 inherit (lib.strings) match split typeOf; 34 35 debug = a: trace a a; 36 last = l: elemAt l ((length l) - 1); 37in 38rec { 39 # [["good/relative/source/file" true] ["bad.tmpfile" false]] -> root -> path 40 filterPattern = 41 patterns: root: 42 ( 43 name: _type: 44 let 45 relPath = removePrefix ((toString root) + "/") name; 46 matches = pair: (match (head pair) relPath) != null; 47 matched = map (pair: [ 48 (matches pair) 49 (last pair) 50 ]) patterns; 51 in 52 last ( 53 last ( 54 [ 55 [ 56 true 57 true 58 ] 59 ] 60 ++ (filter head matched) 61 ) 62 ) 63 ); 64 65 # string -> [[regex bool]] 66 gitignoreToPatterns = 67 gitignore: 68 let 69 # ignore -> bool 70 isComment = i: (match "^(#.*|$)" i) != null; 71 72 # ignore -> [ignore bool] 73 computeNegation = 74 l: 75 let 76 split = match "^(!?)(.*)" l; 77 in 78 [ 79 (elemAt split 1) 80 (head split == "!") 81 ]; 82 83 # regex -> regex 84 handleHashesBangs = replaceStrings [ "\\#" "\\!" ] [ "#" "!" ]; 85 86 # ignore -> regex 87 substWildcards = 88 let 89 special = "^$.+{}()"; 90 escs = "\\*?"; 91 splitString = 92 let 93 recurse = 94 str: 95 [ (substring 0 1 str) ] ++ (optionals (str != "") (recurse (substring 1 (stringLength (str)) str))); 96 in 97 str: recurse str; 98 chars = s: filter (c: c != "" && !isList c) (splitString s); 99 escape = s: map (c: "\\" + c) (chars s); 100 in 101 replaceStrings 102 ( 103 (chars special) 104 ++ (escape escs) 105 ++ [ 106 "**/" 107 "**" 108 "*" 109 "?" 110 ] 111 ) 112 ( 113 (escape special) 114 ++ (escape escs) 115 ++ [ 116 "(.*/)?" 117 ".*" 118 "[^/]*" 119 "[^/]" 120 ] 121 ); 122 123 # (regex -> regex) -> regex -> regex 124 mapAroundCharclass = 125 f: r: # rl = regex or list 126 let 127 slightFix = replaceStrings [ "\\]" ] [ "]" ]; 128 in 129 concatStringsSep "" ( 130 map (rl: if isList rl then slightFix (elemAt rl 0) else f rl) (split "(\\[([^\\\\]|\\\\.)+])" r) 131 ); 132 133 # regex -> regex 134 handleSlashPrefix = 135 l: 136 let 137 split = (match "^(/?)(.*)" l); 138 findSlash = l: optionalString ((match ".+/.+" l) == null) l; 139 hasSlash = mapAroundCharclass findSlash l != l; 140 in 141 (if (elemAt split 0) == "/" || hasSlash then "^" else "(^|.*/)") + (elemAt split 1); 142 143 # regex -> regex 144 handleSlashSuffix = 145 l: 146 let 147 split = (match "^(.*)/$" l); 148 in 149 if split != null then (elemAt split 0) + "($|/.*)" else l; 150 151 # (regex -> regex) -> [regex, bool] -> [regex, bool] 152 mapPat = f: l: [ 153 (f (head l)) 154 (last l) 155 ]; 156 in 157 map ( 158 l: # `l' for "line" 159 mapPat ( 160 l: handleSlashSuffix (handleSlashPrefix (handleHashesBangs (mapAroundCharclass substWildcards l))) 161 ) (computeNegation l) 162 ) (filter (l: !isList l && !isComment l) (split "\n" gitignore)); 163 164 gitignoreFilter = ign: root: filterPattern (gitignoreToPatterns ign) root; 165 166 # string|[string|file] (→ [string|file] → [string]) -> string 167 gitignoreCompileIgnore = 168 file_str_patterns: root: 169 let 170 onPath = f: a: if typeOf a == "path" then f a else a; 171 str_patterns = map (onPath readFile) (toList file_str_patterns); 172 in 173 concatStringsSep "\n" str_patterns; 174 175 gitignoreFilterPure = 176 predicate: patterns: root: name: type: 177 gitignoreFilter (gitignoreCompileIgnore patterns root) root name type && predicate name type; 178 179 # This is a very hacky way of programming this! 180 # A better way would be to reuse existing filtering by making multiple gitignore functions per each root. 181 # Then for each file find the set of roots with gitignores (and functions). 182 # This would make gitignoreFilterSource very different from gitignoreFilterPure. 183 # rootPath → gitignoresConcatenated 184 compileRecursiveGitignore = 185 root: 186 let 187 dirOrIgnore = file: type: baseNameOf file == ".gitignore" || type == "directory"; 188 ignores = builtins.filterSource dirOrIgnore root; 189 in 190 readFile ( 191 runCommand "${baseNameOf root}-recursive-gitignore" { } '' 192 cd ${ignores} 193 194 find -type f -exec sh -c ' 195 rel="$(realpath --relative-to=. "$(dirname "$1")")/" 196 if [ "$rel" = "./" ]; then rel=""; fi 197 198 awk -v prefix="$rel" -v root="$1" -v top="$(test -z "$rel" && echo 1)" " 199 BEGIN { print \"# \"root } 200 201 /^!?[^\\/]+\/?$/ { 202 match(\$0, /^!?/, negation) 203 sub(/^!?/, \"\") 204 205 if (top) { middle = \"\" } else { middle = \"**/\" } 206 207 print negation[0] prefix middle \$0 208 } 209 210 /^!?(\\/|.*\\/.+$)/ { 211 match(\$0, /^!?/, negation) 212 sub(/^!?/, \"\") 213 214 if (!top) sub(/^\//, \"\") 215 216 print negation[0] prefix \$0 217 } 218 219 END { print \"\" } 220 " "$1" 221 ' sh {} \; > $out 222 '' 223 ); 224 225 withGitignoreFile = patterns: root: toList patterns ++ [ ".git" ] ++ [ (root + "/.gitignore") ]; 226 227 withRecursiveGitignoreFile = 228 patterns: root: toList patterns ++ [ ".git" ] ++ [ (compileRecursiveGitignore root) ]; 229 230 # filterSource derivatives 231 232 gitignoreFilterSourcePure = 233 predicate: patterns: root: 234 filterSource (gitignoreFilterPure predicate patterns root) root; 235 236 gitignoreFilterSource = 237 predicate: patterns: root: 238 gitignoreFilterSourcePure predicate (withGitignoreFile patterns root) root; 239 240 gitignoreFilterRecursiveSource = 241 predicate: patterns: root: 242 gitignoreFilterSourcePure predicate (withRecursiveGitignoreFile patterns root) root; 243 244 # "Predicate"-less alternatives 245 246 gitignoreSourcePure = gitignoreFilterSourcePure (_: _: true); 247 gitignoreSource = 248 patterns: 249 let 250 type = typeOf patterns; 251 in 252 if (type == "string" && pathExists patterns) || type == "path" then 253 throw "type error in gitignoreSource(patterns -> source -> path), " "use [] or \"\" if there are no additional patterns" 254 else 255 gitignoreFilterSource (_: _: true) patterns; 256 257 gitignoreRecursiveSource = gitignoreFilterSourcePure (_: _: true); 258}