summary refs log tree commit diff
path: root/lib/path
diff options
context:
space:
mode:
authorSilvan Mosberger <silvan.mosberger@tweag.io>2023-01-18 18:15:55 +0100
committerSilvan Mosberger <silvan.mosberger@tweag.io>2023-02-13 14:01:17 +0100
commit1a2c2846b0933b596c51e964acb8a45ab9a13691 (patch)
treecccb126ed6f1c06e3cd9caecf5c45a788c474329 /lib/path
parenta770c0393cb52777794c01cdb5a412883a732c19 (diff)
downloadnixpkgs-1a2c2846b0933b596c51e964acb8a45ab9a13691.tar
nixpkgs-1a2c2846b0933b596c51e964acb8a45ab9a13691.tar.gz
nixpkgs-1a2c2846b0933b596c51e964acb8a45ab9a13691.tar.bz2
nixpkgs-1a2c2846b0933b596c51e964acb8a45ab9a13691.tar.lz
nixpkgs-1a2c2846b0933b596c51e964acb8a45ab9a13691.tar.xz
nixpkgs-1a2c2846b0933b596c51e964acb8a45ab9a13691.tar.zst
nixpkgs-1a2c2846b0933b596c51e964acb8a45ab9a13691.zip
lib.path.subpath.join: init
This function can be used to safely join subpaths together
Diffstat (limited to 'lib/path')
-rw-r--r--lib/path/default.nix77
-rw-r--r--lib/path/tests/unit.nix30
2 files changed, 107 insertions, 0 deletions
diff --git a/lib/path/default.nix b/lib/path/default.nix
index 075e2fc0d13..a4a08668ae6 100644
--- a/lib/path/default.nix
+++ b/lib/path/default.nix
@@ -15,6 +15,9 @@ let
     last
     genList
     elemAt
+    all
+    concatMap
+    foldl'
     ;
 
   inherit (lib.strings)
@@ -190,6 +193,80 @@ in /* No rec! Add dependencies on this file at the top. */ {
     subpathInvalidReason value == null;
 
 
+  /* Join subpath strings together using `/`, returning a normalised subpath string.
+
+    Like `concatStringsSep "/"` but safer, specifically:
+
+    - All elements must be valid subpath strings, see `lib.path.subpath.isValid`
+
+    - The result gets normalised, see `lib.path.subpath.normalise`
+
+    - The edge case of an empty list gets properly handled by returning the neutral subpath `"./."`
+
+    Laws:
+
+    - Associativity:
+
+          subpath.join [ x (subpath.join [ y z ]) ] == subpath.join [ (subpath.join [ x y ]) z ]
+
+    - Identity - `"./."` is the neutral element for normalised paths:
+
+          subpath.join [ ] == "./."
+          subpath.join [ (subpath.normalise p) "./." ] == subpath.normalise p
+          subpath.join [ "./." (subpath.normalise p) ] == subpath.normalise p
+
+    - Normalisation - the result is normalised according to `lib.path.subpath.normalise`:
+
+          subpath.join ps == subpath.normalise (subpath.join ps)
+
+    - For non-empty lists, the implementation is equivalent to normalising the result of `concatStringsSep "/"`.
+      Note that the above laws can be derived from this one.
+
+          ps != [] -> subpath.join ps == subpath.normalise (concatStringsSep "/" ps)
+
+    Type:
+      subpath.join :: [ String ] -> String
+
+    Example:
+      subpath.join [ "foo" "bar/baz" ]
+      => "./foo/bar/baz"
+
+      # normalise the result
+      subpath.join [ "./foo" "." "bar//./baz/" ]
+      => "./foo/bar/baz"
+
+      # passing an empty list results in the current directory
+      subpath.join [ ]
+      => "./."
+
+      # elements must be valid subpath strings
+      subpath.join [ /foo ]
+      => <error>
+      subpath.join [ "" ]
+      => <error>
+      subpath.join [ "/foo" ]
+      => <error>
+      subpath.join [ "../foo" ]
+      => <error>
+  */
+  subpath.join =
+    # The list of subpaths to join together
+    subpaths:
+    # Fast in case all paths are valid
+    if all isValid subpaths
+    then joinRelPath (concatMap splitRelPath subpaths)
+    else
+      # Otherwise we take our time to gather more info for a better error message
+      # Strictly go through each path, throwing on the first invalid one
+      # Tracks the list index in the fold accumulator
+      foldl' (i: path:
+        if isValid path
+        then i + 1
+        else throw ''
+          lib.path.subpath.join: Element at index ${toString i} is not a valid subpath string:
+              ${subpathInvalidReason path}''
+      ) 0 subpaths;
+
   /* Normalise a subpath. Throw an error if the subpath isn't valid, see
   `lib.path.subpath.isValid`
 
diff --git a/lib/path/tests/unit.nix b/lib/path/tests/unit.nix
index a1a45173a90..61c4ab4d6f2 100644
--- a/lib/path/tests/unit.nix
+++ b/lib/path/tests/unit.nix
@@ -107,6 +107,36 @@ let
       expected = true;
     };
 
+    # Test examples from the lib.path.subpath.join documentation
+    testSubpathJoinExample1 = {
+      expr = subpath.join [ "foo" "bar/baz" ];
+      expected = "./foo/bar/baz";
+    };
+    testSubpathJoinExample2 = {
+      expr = subpath.join [ "./foo" "." "bar//./baz/" ];
+      expected = "./foo/bar/baz";
+    };
+    testSubpathJoinExample3 = {
+      expr = subpath.join [ ];
+      expected = "./.";
+    };
+    testSubpathJoinExample4 = {
+      expr = (builtins.tryEval (subpath.join [ /foo ])).success;
+      expected = false;
+    };
+    testSubpathJoinExample5 = {
+      expr = (builtins.tryEval (subpath.join [ "" ])).success;
+      expected = false;
+    };
+    testSubpathJoinExample6 = {
+      expr = (builtins.tryEval (subpath.join [ "/foo" ])).success;
+      expected = false;
+    };
+    testSubpathJoinExample7 = {
+      expr = (builtins.tryEval (subpath.join [ "../foo" ])).success;
+      expected = false;
+    };
+
     # Test examples from the lib.path.subpath.normalise documentation
     testSubpathNormaliseExample1 = {
       expr = subpath.normalise "foo//bar";