summaryrefslogtreecommitdiff
path: root/lib/path
diff options
context:
space:
mode:
authorSilvan Mosberger <silvan.mosberger@tweag.io>2023-04-05 20:31:50 +0200
committerSilvan Mosberger <silvan.mosberger@tweag.io>2023-06-15 22:29:46 +0200
commit592213ad3f4de2cf3a0f13ab8271122e5e9f9822 (patch)
tree37b96530c9c7ca6eb3e862511043d1b9455a2b76 /lib/path
parentlib.path.append: Add a law (diff)
downloadnixpkgs-592213ad3f4de2cf3a0f13ab8271122e5e9f9822.tar.gz
lib.path.hasPrefix: init
Diffstat (limited to 'lib/path')
-rw-r--r--lib/path/default.nix63
-rw-r--r--lib/path/tests/unit.nix19
2 files changed, 81 insertions, 1 deletions
diff --git a/lib/path/default.nix b/lib/path/default.nix
index 442060c18643..936e9b030253 100644
--- a/lib/path/default.nix
+++ b/lib/path/default.nix
@@ -7,6 +7,7 @@ let
isPath
split
match
+ typeOf
;
inherit (lib.lists)
@@ -18,6 +19,7 @@ let
all
concatMap
foldl'
+ take
;
inherit (lib.strings)
@@ -100,6 +102,22 @@ let
# An empty string is not a valid relative path, so we need to return a `.` when we have no components
(if components == [] then "." else concatStringsSep "/" components);
+ # Type: Path -> { root :: Path, components :: [ String ] }
+ #
+ # Deconstruct a path value type into:
+ # - root: The filesystem root of the path, generally `/`
+ # - components: All the path's components
+ #
+ # This is similar to `splitString "/" (toString path)` but safer
+ # because it can distinguish different filesystem roots
+ deconstructPath =
+ let
+ recurse = components: base:
+ # If the parent of a path is the path itself, then it's a filesystem root
+ if base == dirOf base then { root = base; inherit components; }
+ else recurse ([ (baseNameOf base) ] ++ components) (dirOf base);
+ in recurse [];
+
in /* No rec! Add dependencies on this file at the top. */ {
/* Append a subpath string to a path.
@@ -155,6 +173,51 @@ in /* No rec! Add dependencies on this file at the top. */ {
${subpathInvalidReason subpath}'';
path + ("/" + subpath);
+ /*
+ Whether the first path is a component-wise prefix of the second path.
+
+ Laws:
+
+ - `hasPrefix p q` is only true if `q == append p s` for some subpath `s`.
+
+ - `hasPrefix` is a [non-strict partial order](https://en.wikipedia.org/wiki/Partially_ordered_set#Non-strict_partial_order) over the set of all path values
+
+ Type:
+ hasPrefix :: Path -> Path -> Bool
+
+ Example:
+ hasPrefix /foo /foo/bar
+ => true
+ hasPrefix /foo /foo
+ => true
+ hasPrefix /foo/bar /foo
+ => false
+ hasPrefix /. /foo
+ => true
+ */
+ hasPrefix =
+ path1:
+ assert assertMsg
+ (isPath path1)
+ "lib.path.hasPrefix: First argument is of type ${typeOf path1}, but a path was expected";
+ let
+ path1Deconstructed = deconstructPath path1;
+ in
+ path2:
+ assert assertMsg
+ (isPath path2)
+ "lib.path.hasPrefix: Second argument is of type ${typeOf path2}, but a path was expected";
+ let
+ path2Deconstructed = deconstructPath path2;
+ in
+ assert assertMsg
+ (path1Deconstructed.root == path2Deconstructed.root) ''
+ lib.path.hasPrefix: Filesystem roots must be the same for both paths, but paths with different roots were given:
+ first argument: "${toString path1}" with root "${toString path1Deconstructed.root}"
+ second argument: "${toString path2}" with root "${toString path2Deconstructed.root}"'';
+ take (length path1Deconstructed.components) path2Deconstructed.components == path1Deconstructed.components;
+
+
/* Whether a value is a valid subpath string.
- The value is a string
diff --git a/lib/path/tests/unit.nix b/lib/path/tests/unit.nix
index 61c4ab4d6f2e..9c5b752cf64a 100644
--- a/lib/path/tests/unit.nix
+++ b/lib/path/tests/unit.nix
@@ -3,7 +3,7 @@
{ libpath }:
let
lib = import libpath;
- inherit (lib.path) append subpath;
+ inherit (lib.path) hasPrefix append subpath;
cases = lib.runTests {
# Test examples from the lib.path.append documentation
@@ -40,6 +40,23 @@ let
expected = false;
};
+ testHasPrefixExample1 = {
+ expr = hasPrefix /foo /foo/bar;
+ expected = true;
+ };
+ testHasPrefixExample2 = {
+ expr = hasPrefix /foo /foo;
+ expected = true;
+ };
+ testHasPrefixExample3 = {
+ expr = hasPrefix /foo/bar /foo;
+ expected = false;
+ };
+ testHasPrefixExample4 = {
+ expr = hasPrefix /. /foo;
+ expected = true;
+ };
+
# Test examples from the lib.path.subpath.isValid documentation
testSubpathIsValidExample1 = {
expr = subpath.isValid null;