Merge pull request #247825 from tweag/lib.path-md

Minor `lib.path` documentation consistency improvements
This commit is contained in:
Silvan Mosberger 2023-08-12 08:10:15 +02:00 committed by GitHub
commit c096e03491
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -121,17 +121,18 @@ let
in /* No rec! Add dependencies on this file at the top. */ { in /* No rec! Add dependencies on this file at the top. */ {
/* Append a subpath string to a path. /*
Append a subpath string to a path.
Like `path + ("/" + string)` but safer, because it errors instead of returning potentially surprising results. 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"), 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`). and that the second argument is a [valid subpath string](#function-library-lib.path.subpath.isValid).
Laws: Laws:
- Not influenced by subpath normalisation - Not influenced by subpath [normalisation](#function-library-lib.path.subpath.normalise):
append p s == append p (subpath.normalise s) append p s == append p (subpath.normalise s)
Type: Type:
append :: Path -> String -> Path append :: Path -> String -> Path
@ -175,26 +176,26 @@ in /* No rec! Add dependencies on this file at the top. */ {
path + ("/" + subpath); path + ("/" + subpath);
/* /*
Whether the first path is a component-wise prefix of the second path. Whether the first path is a component-wise prefix of the second path.
Laws: Laws:
- `hasPrefix p q` is only true if `q == append p s` for some subpath `s`. - `hasPrefix p q` is only true if [`q == append p s`](#function-library-lib.path.append) for some [subpath](#function-library-lib.path.subpath.isValid) `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 - `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: Type:
hasPrefix :: Path -> Path -> Bool hasPrefix :: Path -> Path -> Bool
Example: Example:
hasPrefix /foo /foo/bar hasPrefix /foo /foo/bar
=> true => true
hasPrefix /foo /foo hasPrefix /foo /foo
=> true => true
hasPrefix /foo/bar /foo hasPrefix /foo/bar /foo
=> false => false
hasPrefix /. /foo hasPrefix /. /foo
=> true => true
*/ */
hasPrefix = hasPrefix =
path1: path1:
@ -219,27 +220,27 @@ in /* No rec! Add dependencies on this file at the top. */ {
take (length path1Deconstructed.components) path2Deconstructed.components == path1Deconstructed.components; take (length path1Deconstructed.components) path2Deconstructed.components == path1Deconstructed.components;
/* /*
Remove the first path as a component-wise prefix from the second path. Remove the first path as a component-wise prefix from the second path.
The result is a normalised subpath string, see `lib.path.subpath.normalise`. The result is a [normalised subpath string](#function-library-lib.path.subpath.normalise).
Laws: Laws:
- Inverts `append` for normalised subpaths: - Inverts [`append`](#function-library-lib.path.append) for [normalised subpath string](#function-library-lib.path.subpath.normalise):
removePrefix p (append p s) == subpath.normalise s removePrefix p (append p s) == subpath.normalise s
Type: Type:
removePrefix :: Path -> Path -> String removePrefix :: Path -> Path -> String
Example: Example:
removePrefix /foo /foo/bar/baz removePrefix /foo /foo/bar/baz
=> "./bar/baz" => "./bar/baz"
removePrefix /foo /foo removePrefix /foo /foo
=> "./." => "./."
removePrefix /foo/bar /foo removePrefix /foo/bar /foo
=> <error> => <error>
removePrefix /. /foo removePrefix /. /foo
=> "./foo" => "./foo"
*/ */
removePrefix = removePrefix =
path1: path1:
@ -272,41 +273,43 @@ in /* No rec! Add dependencies on this file at the top. */ {
joinRelPath components; joinRelPath components;
/* /*
Split the filesystem root from a [path](https://nixos.org/manual/nix/stable/language/values.html#type-path). Split the filesystem root from a [path](https://nixos.org/manual/nix/stable/language/values.html#type-path).
The result is an attribute set with these attributes: The result is an attribute set with these attributes:
- `root`: The filesystem root of the path, meaning that this directory has no parent directory. - `root`: The filesystem root of the path, meaning that this directory has no parent directory.
- `subpath`: The [normalised subpath string](#function-library-lib.path.subpath.normalise) that when [appended](#function-library-lib.path.append) to `root` returns the original path. - `subpath`: The [normalised subpath string](#function-library-lib.path.subpath.normalise) that when [appended](#function-library-lib.path.append) to `root` returns the original path.
Laws: Laws:
- [Appending](#function-library-lib.path.append) the `root` and `subpath` gives the original path: - [Appending](#function-library-lib.path.append) the `root` and `subpath` gives the original path:
p == p ==
append append
(splitRoot p).root (splitRoot p).root
(splitRoot p).subpath (splitRoot p).subpath
- Trying to get the parent directory of `root` using [`readDir`](https://nixos.org/manual/nix/stable/language/builtins.html#builtins-readDir) returns `root` itself: - Trying to get the parent directory of `root` using [`readDir`](https://nixos.org/manual/nix/stable/language/builtins.html#builtins-readDir) returns `root` itself:
dirOf (splitRoot p).root == (splitRoot p).root dirOf (splitRoot p).root == (splitRoot p).root
Type: Type:
splitRoot :: Path -> { root :: Path, subpath :: String } splitRoot :: Path -> { root :: Path, subpath :: String }
Example: Example:
splitRoot /foo/bar splitRoot /foo/bar
=> { root = /.; subpath = "./foo/bar"; } => { root = /.; subpath = "./foo/bar"; }
splitRoot /. splitRoot /.
=> { root = /.; subpath = "./."; } => { root = /.; subpath = "./."; }
# Nix neutralises `..` path components for all path values automatically # Nix neutralises `..` path components for all path values automatically
splitRoot /foo/../bar splitRoot /foo/../bar
=> { root = /.; subpath = "./bar"; } => { root = /.; subpath = "./bar"; }
splitRoot "/foo/bar" splitRoot "/foo/bar"
=> <error> => <error>
*/ */
splitRoot = path: splitRoot =
# The path to split the root off of
path:
assert assertMsg assert assertMsg
(isPath path) (isPath path)
"lib.path.splitRoot: Argument is of type ${typeOf path}, but a path was expected"; "lib.path.splitRoot: Argument is of type ${typeOf path}, but a path was expected";
@ -317,46 +320,47 @@ in /* No rec! Add dependencies on this file at the top. */ {
subpath = joinRelPath deconstructed.components; subpath = joinRelPath deconstructed.components;
}; };
/* Whether a value is a valid subpath string. /*
Whether a value is a valid subpath string.
A subpath string points to a specific file or directory within an absolute base directory. A subpath string points to a specific file or directory within an absolute base directory.
It is a stricter form of a relative path that excludes `..` components, since those could escape the base directory. It is a stricter form of a relative path that excludes `..` components, since those could escape the base directory.
- The value is a string - The value is a string.
- The string is not empty - The string is not empty.
- The string doesn't start with a `/` - The string doesn't start with a `/`.
- The string doesn't contain any `..` path components - The string doesn't contain any `..` path components.
Type: Type:
subpath.isValid :: String -> Bool subpath.isValid :: String -> Bool
Example: Example:
# Not a string # Not a string
subpath.isValid null subpath.isValid null
=> false => false
# Empty string # Empty string
subpath.isValid "" subpath.isValid ""
=> false => false
# Absolute path # Absolute path
subpath.isValid "/foo" subpath.isValid "/foo"
=> false => false
# Contains a `..` path component # Contains a `..` path component
subpath.isValid "../foo" subpath.isValid "../foo"
=> false => false
# Valid subpath # Valid subpath
subpath.isValid "foo/bar" subpath.isValid "foo/bar"
=> true => true
# Doesn't need to be normalised # Doesn't need to be normalised
subpath.isValid "./foo//bar/" subpath.isValid "./foo//bar/"
=> true => true
*/ */
subpath.isValid = subpath.isValid =
# The value to check # The value to check
@ -364,15 +368,16 @@ in /* No rec! Add dependencies on this file at the top. */ {
subpathInvalidReason value == null; subpathInvalidReason value == null;
/* Join subpath strings together using `/`, returning a normalised subpath string. /*
Join subpath strings together using `/`, returning a normalised subpath string.
Like `concatStringsSep "/"` but safer, specifically: Like `concatStringsSep "/"` but safer, specifically:
- All elements must be valid subpath strings, see `lib.path.subpath.isValid` - All elements must be [valid subpath strings](#function-library-lib.path.subpath.isValid).
- The result gets normalised, see `lib.path.subpath.normalise` - The result gets [normalised](#function-library-lib.path.subpath.normalise).
- The edge case of an empty list gets properly handled by returning the neutral subpath `"./."` - The edge case of an empty list gets properly handled by returning the neutral subpath `"./."`.
Laws: Laws:
@ -386,12 +391,12 @@ in /* No rec! Add dependencies on this file at the top. */ {
subpath.join [ (subpath.normalise p) "./." ] == subpath.normalise p subpath.join [ (subpath.normalise p) "./." ] == subpath.normalise p
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`: - Normalisation - the result is [normalised](#function-library-lib.path.subpath.normalise):
subpath.join ps == subpath.normalise (subpath.join ps) subpath.join ps == subpath.normalise (subpath.join ps)
- For non-empty lists, the implementation is equivalent to normalising the result of `concatStringsSep "/"`. - For non-empty lists, the implementation is equivalent to [normalising](#function-library-lib.path.subpath.normalise) the result of `concatStringsSep "/"`.
Note that the above laws can be derived from this one. Note that the above laws can be derived from this one:
ps != [] -> subpath.join ps == subpath.normalise (concatStringsSep "/" ps) ps != [] -> subpath.join ps == subpath.normalise (concatStringsSep "/" ps)
@ -439,108 +444,109 @@ in /* No rec! Add dependencies on this file at the top. */ {
) 0 subpaths; ) 0 subpaths;
/* /*
Split [a subpath](#function-library-lib.path.subpath.isValid) into its path component strings. Split [a subpath](#function-library-lib.path.subpath.isValid) into its path component strings.
Throw an error if the subpath isn't valid. Throw an error if the subpath isn't valid.
Note that the returned path components are also valid subpath strings, though they are intentionally not [normalised](#function-library-lib.path.subpath.normalise). Note that the returned path components are also [valid subpath strings](#function-library-lib.path.subpath.isValid), though they are intentionally not [normalised](#function-library-lib.path.subpath.normalise).
Laws: Laws:
- Splitting a subpath into components and [joining](#function-library-lib.path.subpath.join) the components gives the same subpath but [normalised](#function-library-lib.path.subpath.normalise): - Splitting a subpath into components and [joining](#function-library-lib.path.subpath.join) the components gives the same subpath but [normalised](#function-library-lib.path.subpath.normalise):
subpath.join (subpath.components s) == subpath.normalise s subpath.join (subpath.components s) == subpath.normalise s
Type: Type:
subpath.components :: String -> [ String ] subpath.components :: String -> [ String ]
Example: Example:
subpath.components "." subpath.components "."
=> [ ] => [ ]
subpath.components "./foo//bar/./baz/" subpath.components "./foo//bar/./baz/"
=> [ "foo" "bar" "baz" ] => [ "foo" "bar" "baz" ]
subpath.components "/foo" subpath.components "/foo"
=> <error> => <error>
*/ */
subpath.components = subpath.components =
# The subpath string to split into components
subpath: subpath:
assert assertMsg (isValid subpath) '' assert assertMsg (isValid subpath) ''
lib.path.subpath.components: Argument is not a valid subpath string: lib.path.subpath.components: Argument is not a valid subpath string:
${subpathInvalidReason subpath}''; ${subpathInvalidReason subpath}'';
splitRelPath subpath; splitRelPath subpath;
/* Normalise a subpath. Throw an error if the subpath isn't valid, see /*
`lib.path.subpath.isValid` Normalise a subpath. Throw an error if the subpath isn't [valid](#function-library-lib.path.subpath.isValid).
- Limit repeating `/` to a single one - Limit repeating `/` to a single one.
- Remove redundant `.` components - Remove redundant `.` components.
- Remove trailing `/` and `/.` - Remove trailing `/` and `/.`.
- Add leading `./` - Add leading `./`.
Laws: 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 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}) subpath.normalise p != subpath.normalise q -> $(realpath ${p}) != $(realpath ${q})
- Don't change the result when appended to a Nix path value: - Don't change the result when [appended](#function-library-lib.path.append) to a Nix path value:
base + ("/" + p) == base + ("/" + subpath.normalise p) append base p == append base (subpath.normalise p)
- Don't change the path according to `realpath`: - Don't change the path according to `realpath`:
$(realpath ${p}) == $(realpath ${subpath.normalise p}) $(realpath ${p}) == $(realpath ${subpath.normalise p})
- Only error on invalid subpaths: - Only error on [invalid subpaths](#function-library-lib.path.subpath.isValid):
builtins.tryEval (subpath.normalise p)).success == subpath.isValid p builtins.tryEval (subpath.normalise p)).success == subpath.isValid p
Type: Type:
subpath.normalise :: String -> String subpath.normalise :: String -> String
Example: Example:
# limit repeating `/` to a single one # limit repeating `/` to a single one
subpath.normalise "foo//bar" subpath.normalise "foo//bar"
=> "./foo/bar" => "./foo/bar"
# remove redundant `.` components # remove redundant `.` components
subpath.normalise "foo/./bar" subpath.normalise "foo/./bar"
=> "./foo/bar" => "./foo/bar"
# add leading `./` # add leading `./`
subpath.normalise "foo/bar" subpath.normalise "foo/bar"
=> "./foo/bar" => "./foo/bar"
# remove trailing `/` # remove trailing `/`
subpath.normalise "foo/bar/" subpath.normalise "foo/bar/"
=> "./foo/bar" => "./foo/bar"
# remove trailing `/.` # remove trailing `/.`
subpath.normalise "foo/bar/." subpath.normalise "foo/bar/."
=> "./foo/bar" => "./foo/bar"
# Return the current directory as `./.` # Return the current directory as `./.`
subpath.normalise "." subpath.normalise "."
=> "./." => "./."
# error on `..` path components # error on `..` path components
subpath.normalise "foo/../bar" subpath.normalise "foo/../bar"
=> <error> => <error>
# error on empty string # error on empty string
subpath.normalise "" subpath.normalise ""
=> <error> => <error>
# error on absolute path # error on absolute path
subpath.normalise "/foo" subpath.normalise "/foo"
=> <error> => <error>
*/ */
subpath.normalise = subpath.normalise =
# The subpath string to normalise # The subpath string to normalise