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.append: init #208887

Merged
merged 2 commits into from
Feb 7, 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
70 changes: 63 additions & 7 deletions lib/path/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ let

inherit (builtins)
isString
isPath
split
match
;
Expand All @@ -25,6 +26,10 @@ let
assertMsg
;

inherit (lib.path.subpath)
isValid
;

# Return the reason why a subpath is invalid, or `null` if it's valid
subpathInvalidReason = value:
if ! isString value then
Expand Down Expand Up @@ -94,6 +99,52 @@ let

in /* No rec! Add dependencies on this file at the top. */ {

/* Append a subpath string to a path.

Like `path + ("/" + string)` but safer, because it errors instead of returning potentially surprising results.
More specifically, it checks that the first argument is a [path value type](https://nixos.org/manual/nix/stable/language/values.html#type-path"),
and that the second argument is a valid subpath string (see `lib.path.subpath.isValid`).

Type:
append :: Path -> String -> Path

Example:
append /foo "bar/baz"
=> /foo/bar/baz

# subpaths don't need to be normalised
append /foo "./bar//baz/./"
=> /foo/bar/baz

# can append to root directory
append /. "foo/bar"
=> /foo/bar

# first argument needs to be a path value type
append "/foo" "bar"
=> <error>

# second argument needs to be a valid subpath string
append /foo /bar
=> <error>
append /foo ""
=> <error>
append /foo "/bar"
=> <error>
append /foo "../bar"
=> <error>
*/
append =
# The absolute path to append to
path:
# The subpath string to append
subpath:
assert assertMsg (isPath path) ''
lib.path.append: The first argument is of type ${builtins.typeOf path}, but a path was expected'';
assert assertMsg (isValid subpath) ''
lib.path.append: Second argument is not a valid subpath string:
${subpathInvalidReason subpath}'';
path + ("/" + subpath);

/* Whether a value is a valid subpath string.

Expand Down Expand Up @@ -133,7 +184,9 @@ in /* No rec! Add dependencies on this file at the top. */ {
subpath.isValid "./foo//bar/"
=> true
*/
subpath.isValid = value:
subpath.isValid =
# The value to check
value:
subpathInvalidReason value == null;


Expand All @@ -150,11 +203,11 @@ in /* No rec! Add dependencies on this file at the top. */ {

Laws:

- (Idempotency) Normalising multiple times gives the same result:
- Idempotency - normalising multiple times gives the same result:

subpath.normalise (subpath.normalise p) == subpath.normalise p

- (Uniqueness) There's only a single normalisation for the paths that lead to the same file system node:
- Uniqueness - there's only a single normalisation for the paths that lead to the same file system node:

subpath.normalise p != subpath.normalise q -> $(realpath ${p}) != $(realpath ${q})

Expand Down Expand Up @@ -210,9 +263,12 @@ in /* No rec! Add dependencies on this file at the top. */ {
subpath.normalise "/foo"
=> <error>
*/
subpath.normalise = path:
assert assertMsg (subpathInvalidReason path == null)
"lib.path.subpath.normalise: Argument is not a valid subpath string: ${subpathInvalidReason path}";
joinRelPath (splitRelPath path);
subpath.normalise =
# The subpath string to normalise
subpath:
assert assertMsg (isValid subpath) ''
lib.path.subpath.normalise: Argument is not a valid subpath string:
${subpathInvalidReason subpath}'';
joinRelPath (splitRelPath subpath);

}
40 changes: 39 additions & 1 deletion lib/path/tests/unit.nix
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,44 @@
{ libpath }:
let
lib = import libpath;
inherit (lib.path) subpath;
inherit (lib.path) append subpath;

cases = lib.runTests {
# Test examples from the lib.path.append documentation
testAppendExample1 = {
expr = append /foo "bar/baz";
expected = /foo/bar/baz;
};
testAppendExample2 = {
expr = append /foo "./bar//baz/./";
expected = /foo/bar/baz;
};
testAppendExample3 = {
expr = append /. "foo/bar";
expected = /foo/bar;
};
testAppendExample4 = {
expr = (builtins.tryEval (append "/foo" "bar")).success;
expected = false;
};
testAppendExample5 = {
expr = (builtins.tryEval (append /foo /bar)).success;
infinisil marked this conversation as resolved.
Show resolved Hide resolved
expected = false;
};
testAppendExample6 = {
expr = (builtins.tryEval (append /foo "")).success;
expected = false;
};
testAppendExample7 = {
expr = (builtins.tryEval (append /foo "/bar")).success;
expected = false;
};
testAppendExample8 = {
expr = (builtins.tryEval (append /foo "../bar")).success;
expected = false;
};

# Test examples from the lib.path.subpath.isValid documentation
testSubpathIsValidExample1 = {
expr = subpath.isValid null;
expected = false;
Expand All @@ -30,6 +65,7 @@ let
expr = subpath.isValid "./foo//bar/";
expected = true;
};
# Some extra tests
testSubpathIsValidTwoDotsEnd = {
expr = subpath.isValid "foo/..";
expected = false;
Expand Down Expand Up @@ -71,6 +107,7 @@ let
expected = true;
};

# Test examples from the lib.path.subpath.normalise documentation
testSubpathNormaliseExample1 = {
expr = subpath.normalise "foo//bar";
expected = "./foo/bar";
Expand Down Expand Up @@ -107,6 +144,7 @@ let
expr = (builtins.tryEval (subpath.normalise "/foo")).success;
expected = false;
};
# Some extra tests
testSubpathNormaliseIsValidDots = {
expr = subpath.normalise "./foo/.bar/.../baz...qux";
expected = "./foo/.bar/.../baz...qux";
Expand Down