nixos-configuration/default.nix

137 lines
5.6 KiB
Nix
Raw Permalink Normal View History

2024-06-17 07:54:59 +02:00
# if evaluating outside of the store, copy the current directory to the store and import it
# filtering out .gitignore files and .git directories
# if evaluating inside the store, import the outputs.nix file
let
2025-01-19 18:16:04 +01:00
# Ideally this file should be selfcontained, but I like the utilities in nixpkgs lib
2025-01-13 00:46:20 +01:00
lib = (import "${(import ./inputs.nix {}).nixpkgs}/lib");
2024-06-17 07:54:59 +02:00
2025-01-19 18:16:04 +01:00
# function that takes gitignore file pattern and returns filter function
# true - include file
# false - exclude file
# null - no match
# string -> string -> [(string -> string -> (bool | null))]
toGitignoreMatcher = gitignorePath: pattern: lib.pipe pattern [
(v: { pattern = v; invalid = false; })
# trim whitespaces not preceded by backslash
(v: v // { pattern = let
stringLength = builtins.stringLength v.pattern;
leftPaddingLength = builtins.stringLength (lib.trimWith { start = true; end = false; } v.pattern) - stringLength;
rightPaddingLength = builtins.stringLength (lib.trimWith { start = false; end = true; } v.pattern) - stringLength;
isLastCharBackslash = if stringLength == 0 then false
else builtins.substring (stringLength - rightPaddingLength - 1) 1 v.pattern == "\\";
trimmedString = builtins.substring leftPaddingLength (stringLength - leftPaddingLength - rightPaddingLength) v.pattern;
in if isLastCharBackslash && rightPaddingLength > 0 then trimmedString + " " else trimmedString; })
# ignore empty lines
(v: if v.pattern != "" then v else v // { invalid = true; })
# ignore comments
(v: if !v.invalid && builtins.substring 0 1 v.pattern != "#" then v else v // { invalid = true; })
# mark negated patterns
(v:
if !v.invalid && builtins.substring 0 1 v.pattern == "!"
then v // {
negated = true;
pattern = builtins.substring 1 (builtins.stringLength v) v;
}
else v // { negated = false; }
)
# ignore escapes
(v: if v.invalid then v else v // { pattern = builtins.replaceStrings ["\\"] [""] v.pattern; })
# convert parsed pattern to matchers
({ pattern, negated, invalid }: {
__functor = _: path: type: let
relative = builtins.match "^/.+[^/]$" pattern == [];
directory = builtins.match "/$" pattern == [];
regexPattern = lib.pipe pattern [
(v: if relative then "${gitignorePath}/${v}" else v)
(builtins.split "/")
(builtins.filter (v: v != []))
(builtins.map (builtins.split "(\\*\\*|\\*)"))
(builtins.concatMap (v:
# v: (string | [string])[]
if v == [ "" ] then []
# TODO: check and add support for .. <directory-up> if git supports
else if v == [ "." ] then []
else [( builtins.foldl' (acc: vp:
# vp: string | [string]
if builtins.isString vp then acc + lib.escapeRegex vp
else if vp == [ "**" ] then acc + ".*"
else if vp == [ "*" ] then acc + "[^/]*"
else throw "unreachable"
) "" v )]
))
(builtins.concatStringsSep "/" )
(v: if relative then v else ".*/${v}")
];
matches = (!directory || type == "directory")
&& (builtins.match regexPattern path == []);
in if invalid then null
else if matches then negated
else null;
# for debug purposes
inherit pattern negated;
# for filtering purposes
inherit invalid;
})
];
# TODO: optimize this so if match is found in a given gitignore,
# no further checks in gitignores in parent directories are performed
parseGitignore = gitRepositoryPath: filePath: lib.pipe filePath [
(builtins.dirOf)
(builtins.split "/" )
(builtins.filter (v: v != [] && v != ""))
# ["a" "b" "c"] -> ["/" "/a/" "/a/b/" "/a/b/c/"]
(
builtins.foldl' (acc: v: acc ++ [(
(builtins.elemAt acc (builtins.length acc - 1)) + "${v}/"
)] ) ["/"]
)
(builtins.map (v: "${v}.gitignore"))
# Filter out paths that are not part of git repository and don't exist
(builtins.filter (v: lib.hasPrefix gitRepositoryPath v && builtins.pathExists v))
(builtins.map (v: {
path = v;
# Split gitignore files into lines
contents = lib.pipe v [
builtins.readFile
(builtins.split "\n")
# builtins.split uses lists for matches
(builtins.filter (v: v != []))
];
}))
# Convert gitignore patterns to matchers
(builtins.map (v:
builtins.map (toGitignoreMatcher v.path) v.contents)
)
lib.flatten
(lib.filter (v: !v.invalid))
];
runGitignoreFilter = filters: path: type: lib.pipe filters [
(builtins.map (v: v path type))
(builtins.filter (v: v != null))
# If any filter didn't match anything, include the file
(v: if v == [] then [ true ] else v)
(v: builtins.elemAt v (builtins.length v - 1))
];
2024-06-17 07:54:59 +02:00
currentFilePath = (builtins.unsafeGetAttrPos "any" { any = "any"; }).file;
storePathLength = builtins.stringLength (builtins.toString builtins.storeDir);
evaluatingInStore = (builtins.substring 0 storePathLength currentFilePath) == builtins.storeDir;
selfInStore = builtins.filterSource (path: type:
2025-01-19 18:16:04 +01:00
let
selfPath = builtins.dirOf currentFilePath;
gitIgnoreFilters = parseGitignore selfPath path;
result = type != "unknown"
&& type != "symlink"
&& builtins.baseNameOf path != ".git"
&& runGitignoreFilter gitIgnoreFilters path type;
in result
2024-06-17 07:54:59 +02:00
) ./.;
in
2024-07-07 08:34:47 +02:00
if !(evaluatingInStore) then { ... }@args: import selfInStore ({ selfPath = selfInStore; } // args )
else { ... }@args: import ./outputs.nix ({ selfPath = selfInStore; } // args)