Skip to content

Instantly share code, notes, and snippets.

@nh2
Created March 6, 2019 18:56
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nh2/c5e9b81f6d90fb55589fa824232caf67 to your computer and use it in GitHub Desktop.
Save nh2/c5e9b81f6d90fb55589fa824232caf67 to your computer and use it in GitHub Desktop.
A convenient function for filtering nix src input
# 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