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

lib.path.subpath.join: init #209099

Merged
merged 1 commit into from
Mar 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
77 changes: 77 additions & 0 deletions lib/path/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ let
last
genList
elemAt
all
concatMap
foldl'
;

inherit (lib.strings)
Expand Down Expand Up @@ -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`
fricklerhandwerk marked this conversation as resolved.
Show resolved Hide resolved

- 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`

Expand Down
30 changes: 30 additions & 0 deletions lib/path/tests/unit.nix
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down