Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

source combinators: extend, trace, cutAt, focusAt and more #112083

Closed
wants to merge 21 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 122 additions & 0 deletions lib/filesystem.nix
Original file line number Diff line number Diff line change
@@ -1,4 +1,22 @@
{ lib }:

let
inherit (lib.attrsets)
mapAttrs
;
inherit (lib.lists)
head
tail
;
inherit (lib.strings)
hasPrefix
;
inherit (lib.filesystem)
pathHasPrefix
absolutePathComponentsBetween
;
in

{ # haskellPathsInDir : Path -> Map String Path
# A map of all haskell packages defined in the given path,
# identified by having a cabal file with the same name as the
Expand Down Expand Up @@ -54,4 +72,108 @@
dir + "/${name}"
) (builtins.readDir dir));

# pathHasPrefix : Path -> Path -> Bool
# pathHasPrefix ancestor somePath
#
# Return true iff, disregarding trailing slashes,
# - somePath is below ancestor
# - or equal to ancestor
#
# Equivalently, return true iff you can reach ancestor by starting at somePath
# and traversing the `..` node zero or more times.
pathHasPrefix = prefixPath: otherPath:
let
normalizedPathString = pathLike: toString (/. + pathLike);
pre = normalizedPathString prefixPath;
other = normalizedPathString otherPath;
in
if pre == "/" # root is the only path that already ends in "/"
then true
else
hasPrefix (pre + "/") (other + "/");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a bit obscure why + "/" is needed. From what I can tell, it's so that /some/path doesn't match as a prefix of /some/pathpath. While hasPrefix (pre + "/") other would fix that, this would then break for when pre == other. So adding the "/" to other as well makes it work when the same path is a prefix of itself.



# commonPath : PathLike -> PathLike -> Path
#
# Find the common ancestory; the longest prefix path that is common between
# the input paths.
commonPath = a: b:
let
b' = /. + b;
go = c:
if pathHasPrefix c b'
then c
else go (dirOf c);
in
go (/. + a);

# absolutePathComponentsBetween : PathOrString -> PathOrString -> [String]
# absolutePathComponentsBetween ancestor descendant
#
# Returns the path components that form the path from ancestor to descendant.
# Will not return ".." components, which is a feature. Throws when ancestor
# and descendant arguments aren't in said relation to each other.
#
# Example:
#
# absolutePathComponentsBetween /a /a/b/c == ["b" "c"]
# absolutePathComponentsBetween /a/b/c /a/b/c == []
absolutePathComponentsBetween =
ancestor: descendant:
let
a' = /. + ancestor;
go = d:
if a' == d
then []
else if d == /.
then throw "absolutePathComponentsBetween: path ${toString ancestor} is not an ancestor of ${toString descendant}"
else go (dirOf d) ++ [(baseNameOf d)];
in
go (/. + descendant);

/*
Memoize a function that takes a path argument.

Example:

analyzeTree = dir:
let g = memoizePathFunction (p: t: expensiveFunction p) (p: {}) dir;
in presentExpensiveData g;

Type:
memoizePathFunction :: (Path -> Type -> a) -> (Path -> a) -> Path -> (Path -> a)
*/
memoizePathFunction =
# Function to memoize
f:
# What to return when a path does not exist, as a function of the path
missing:
# Filesystem location below which the returned function is defined. `/.` may be acceptable, but a path closer to the data of interest is better.
root:
Comment on lines +147 to +152
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is a rather special function, 3 arguments and no clear order to them, I'd say this benefits from making it an attribute set argument

Suggested change
# Function to memoize
f:
# What to return when a path does not exist, as a function of the path
missing:
# Filesystem location below which the returned function is defined. `/.` may be acceptable, but a path closer to the data of interest is better.
root:
{
# Function to memoize
f,
# What to return when a path does not exist, as a function of the path
missing,
# Filesystem location below which the returned function is defined. `/.` may be acceptable, but a path closer to the data of interest is better.
root,
}

let
makeTree = dir: type: {
value = f dir type;
children =
if type == "directory"
then mapAttrs
(key: type: makeTree (dir + "/${key}") type)
(builtins.readDir dir)
else {};
};

# This is where the memoization happens
tree = makeTree root (lib.pathType root);

lookup = notFound: list: subtree:
if list == []
then subtree.value
else if subtree.children ? ${head list}
then lookup notFound (tail list) subtree.children.${head list}
else notFound;
in
path: lookup
(missing path)
(absolutePathComponentsBetween root path)
tree;

}
Loading