Created
March 6, 2019 18:56
-
-
Save nh2/c5e9b81f6d90fb55589fa824232caf67 to your computer and use it in GitHub Desktop.
A convenient function for filtering nix src input
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Example usage: | |
# | |
# src = filterSourceConvenience.filter ./. { | |
# srcDirs = [ | |
# ./src | |
# ./app | |
# ./images | |
# ]; | |
# srcFiles = [ | |
# ./mypackage.cabal | |
# ./Setup.hs | |
# ]; | |
# pathComponentExcludes = ["gen"]; | |
# }; | |
{ | |
lib, | |
}: | |
let | |
# Turns a list into a "set" (map where all values are {}). | |
keySet = list: builtins.listToAttrs (map (name: lib.nameValuePair name {}) list); | |
# Turns a list of path components into a tree, e.g. | |
# [a b c1] | |
# [a b c2] | |
# [a b c3] | |
# [a x ] | |
# becomes | |
# { a = { b = { c1 = null; c2 = null; c3 = null }; x = null; }; } | |
pathComponentsToTree = paths: with lib; | |
foldl (tree: path: recursiveUpdate tree (setAttrByPath path null)) {} paths; | |
# Returns true iff any prefix of the given `path` leads to a leaf (`null`) | |
# in the given `tree`. | |
# That is: "If we go down the tree by the given path, do we hit a leaf?" | |
# | |
# Example: | |
# For a tree | |
# a | |
# b-leaf | |
# c-leaf | |
# d | |
# e-leaf | |
# f-leaf | |
# we have | |
# isPrefixOfLeafPath [a] == true | |
# isPrefixOfLeafPath [x] == false | |
# isPrefixOfLeafPath [a b] == true | |
# isPrefixOfLeafPath [a b c] == true | |
# isPrefixOfLeafPath [a b c x] == true | |
# isPrefixOfLeafPath [a d] == false | |
isPrefixOfLeafPath = path: tree: | |
if tree == null | |
then true | |
else | |
if path == [] | |
then false | |
else | |
let | |
component = builtins.head path; | |
restPath = builtins.tail path; | |
in | |
if !(builtins.hasAttr component tree) | |
then false | |
else | |
let | |
subtree = builtins.getAttr component tree; | |
in | |
isPrefixOfLeafPath restPath subtree; | |
# Splits a filesystem path into its components. | |
splitPath = path: lib.splitString "/" (toString path); | |
mkPredicate = with lib; | |
{ | |
# List of dirs under which all recursively contained files are taken in | |
# (unless a file is filtered by other arguments). | |
# Dirs that match explicitly are immediately taken in. | |
srcDirs ? [], | |
# Explicit list of files that should be taken in. | |
srcFiles ? [], | |
# Exclude dotfiles/dirs by default (unless they are matched explicitly)? | |
excludeHidden ? true, | |
# If any of the path components given here appears anywhere in the path, | |
# (e.g. X in `.../X/...`), the path is excluded (unless matched explicitly). | |
# Example: `pathComponentExcludes = ["gen", "build"]`. | |
pathComponentExcludes ? [], | |
# Debugging | |
# Enable this to enable a `builtins.trace` output that prints which files | |
# were matched as source inputs. | |
debugEnableTracing ? false, | |
# Set this to prefix the trace output with some arbitrary string. | |
# Useful if you enable `debugEnableTracing` in multiple places and want | |
# to distinguish them. | |
debugTracePrefix ? "", | |
}: | |
let | |
# Pre-processing across all files passed in by `builtins.filterSource`. | |
# For fast non-O(n) lookup, we turn `srcDirs` and `srcFiles` into | |
# string-keyed attrsets first. | |
srcDirsSet = keySet (map toString srcDirs); | |
srcFilesSet = keySet (map toString srcFiles); | |
# We also turn `srcDirs` into a directory-prefix-tree so that we can | |
# check whether a given path is under one of the `srcDirs` in sub-O(n). | |
srcDirsTree = pathComponentsToTree (map splitPath srcDirs); | |
in | |
fullPath: type: | |
let | |
fileName = baseNameOf (toString fullPath); | |
components = splitPath fullPath; | |
isExplicitSrcFile = builtins.hasAttr fullPath srcFilesSet; | |
isExplicitSrcDir = type == "directory" && builtins.hasAttr fullPath srcDirsSet; | |
# The below is equivalent to | |
# any (srcDir: hasPrefix (toString srcDir + "/") fullPath) srcDirs; | |
# but faster than O(n) where n is the number of `srcDirs` entries. | |
isUnderSomeSrcDir = isPrefixOfLeafPath components srcDirsTree; | |
isHidden = excludeHidden && lib.hasPrefix "." fileName; | |
hasExcludedComponentInPath = any (c: elem c pathComponentExcludes) components; | |
isSourceInput = | |
isExplicitSrcFile || | |
isExplicitSrcDir || | |
(isUnderSomeSrcDir && !isHidden && !hasExcludedComponentInPath); | |
tracing = | |
let | |
prefix = if debugTracePrefix == "" then "" else debugTracePrefix + ": "; | |
action = if isSourceInput then "include" else "skip "; | |
# Pad "regular" to be as wide as "directory" for aligned output. | |
formattedType = if type == "regular" then "regular " else type; | |
in | |
if debugEnableTracing | |
then builtins.trace "${prefix}${action} ${formattedType} ${fullPath}" | |
else id; | |
in | |
tracing isSourceInput; | |
filter = topPath: args: | |
builtins.filterSource (mkPredicate args) topPath; | |
in | |
{ | |
inherit mkPredicate; | |
inherit filter; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment