From 25093d4d1041ebc1fa1909eebad1e3aa0e7b4fa7 Mon Sep 17 00:00:00 2001 From: siers Date: Sun, 12 May 2019 11:51:48 +0200 Subject: [PATCH] Initialize with rules, test data and license from nix-gitignore --- LICENSE | 24 ++++++++++++ rules.nix | 93 ++++++++++++++++++++++++++++++++++++++++++++++ tests/testdata.nix | 88 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 205 insertions(+) create mode 100644 LICENSE create mode 100644 rules.nix create mode 100644 tests/testdata.nix diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..cf1ab25 --- /dev/null +++ b/LICENSE @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/rules.nix b/rules.nix new file mode 100644 index 0000000..c232512 --- /dev/null +++ b/rules.nix @@ -0,0 +1,93 @@ +{ lib ? import }: +/* The functions in this file translate gitignore files into filter functions. + */ +let + inherit (builtins) compareVersions nixVersion split match; + inherit (lib) elemAt length head filter isList; + inherit (lib.strings) substring stringLength replaceStrings concatStringsSep; + + debug = a: builtins.trace a a; + last = l: elemAt l ((length l) - 1); + + throwIfOldNix = let required = "2.0"; in + if compareVersions nixVersion required == -1 + then throw "nix (v${nixVersion} =< v${required}) is too old for nix-gitignore" + else true; + +in +rec { + # [["good/relative/source/file" true] ["bad.tmpfile" false]] -> root -> path + 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 + last (last ([[true true]] ++ (filter head matched))) + ); + + # string -> [[regex bool]] + gitignoreToPatterns = gitignore: + assert throwIfOldNix; + let + # ignore -> bool + isComment = i: (match "^(#.*|$)" i) != null; + + # ignore -> [ignore bool] + computeNegation = l: + let split = match "^(!?)(.*)" l; + in [(elemAt split 1) (head split == "!")]; + + # ignore -> regex + substWildcards = + let + special = "^$.+{}()"; + escs = "\\*?"; + splitString = + let recurse = str : [(substring 0 1 str)] ++ + (if str == "" then [] else (recurse (substring 1 (stringLength(str)) str) )); + in str : recurse str; + chars = s: filter (c: c != "" && !isList c) (splitString s); + escape = s: map (c: "\\" + c) (chars s); + in + replaceStrings + ((chars special) ++ (escape escs) ++ ["**/" "**" "*" "?"]) + ((escape special) ++ (escape escs) ++ ["(.*/)?" ".*" "[^/]*" "[^/]"]); + + # (regex -> regex) -> regex -> regex + mapAroundCharclass = f: r: # rl = regex or list + let slightFix = replaceStrings ["\\]"] ["]"]; + in + concatStringsSep "" + (map (rl: if isList rl then slightFix (elemAt rl 0) else f rl) + (split "(\\[([^\\\\]|\\\\.)+])" r)); + + # regex -> regex + handleSlashPrefix = l: + let + split = (match "^(/?)(.*)" l); + findSlash = l: if (match ".+/.+" l) != null then "" else l; + hasSlash = mapAroundCharclass findSlash l != l; + in + (if (elemAt split 0) == "/" || hasSlash + then "^" + else "(^|.*/)" + ) + (elemAt split 1); + + # regex -> regex + handleSlashSuffix = l: + let split = (match "^(.*)/$" l); + in if split != null then (elemAt split 0) + "($|/.*)" else l; + + # (regex -> regex) -> [regex, bool] -> [regex, bool] + mapPat = f: l: [(f (head l)) (last l)]; + in + map (l: # `l' for "line" + mapPat (l: handleSlashSuffix (handleSlashPrefix (mapAroundCharclass substWildcards l))) + (computeNegation l)) + (filter (l: !isList l && !isComment l) + (split "\n" gitignore)); + + gitignoreFilter = ign: root: filterPattern (gitignoreToPatterns ign) root; +} diff --git a/tests/testdata.nix b/tests/testdata.nix new file mode 100644 index 0000000..ac7c134 --- /dev/null +++ b/tests/testdata.nix @@ -0,0 +1,88 @@ +{ pkgs ? import {} }: + +/* + Programmatically specifies the test data. + */ +let + inherit (pkgs) runCommand; + + createTree = '' + touches() { ( + mkdir -p "$1"; cd "$1"; shift + touch "$@" + ); } + + create-tree() { ( + mkdir -p "$1"; cd "$1" + + touches 1-simpl {1,2,3,4,5,^,$,^$,$^,[,[[,],]],]]],ab,bb,\\,\\\\,simple-test} + touches 1-simpl/1-simpl {1,2,3,4,5,^,$,^$,$^,[,[[,],]],]]],ab,bb,\\,\\\\,simpletest} + touches 1-xxxxx/1-simpl {1,2} + touch {,1-simpl/}char-class-pathalogic + + touches 2-negation {.keep,10,20,30,40,50,60,70} + + touches 3-wildcards {foo,bar,bbar,baz}.html + touches 3-wildcards/html {foo,bar,bbar,baz}.html + + touches 4-escapes {{*,o{,_,__,?,}ther}.html,other.html{,\$,\$\$}} + + touches 5-directory {1,2,3,4,5,^,$,^$,$^,[,[[,],]],]]],ab,bb,\\,\\\\} + + touches 9-expected {unfiltered,filtered-via-aux-{filter,ignore,filepath}} + ); } + + create-tree "$1" + + cat ${builtins.toFile "nixgitignore-ignores" ignores} > "$1/.gitignore" + cat ${builtins.toFile "nixgitignore-ignores" ignoresAux} > "$1/aux.gitignore" + ''; + + createTreeRecursive = createTree + "\n" + '' + cp -r "$1" "$1" 2>&1 | grep -vq 'cannot copy a directory, .*into itself' || : + ''; + + ignores = '' + 1-simpl/1 + /1-simpl/2 + /1-simpl/[35^$[]] + /1-simpl/][\]] + /1-simpl/[^a]b + /1-simpl/[\\] + simple*test + + # [^b/]har-class-pathalogic + # this fails, but is pathalogic, so I won't cover it + + 2-*/[^.]* + !2-*/1? + !2-*/30 + !/2-*/70 + !/40 + !50 + + 3-*/*foo.html + 3-*/**/bar.html + + 4-*/\*.html + 4-*/o??ther.html + 4-*/o\?ther.html + 4-*/other.html$ + + 5-*/ + ''; + + ignoresAux = "/9-expected/*filepath\n"; + + createSourceTree = createTree: (runCommand "test-tree" {} '' + mkdir -p $out; cd $out; + bash ${builtins.toFile "create-tree" createTree} test-tree + ''); + + # source is a copy of sourceUnfiltered, which lives in the nix store + sourceUnfiltered = createSourceTree createTree; + sourceUnfilteredRecursive = createSourceTree createTreeRecursive; + +in { + inherit sourceUnfiltered sourceUnfilteredRecursive; +}