You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
276 lines
9.0 KiB
276 lines
9.0 KiB
/** |
|
|
|
`Path` is a library for creating and operating on file paths consistently on |
|
all platforms. |
|
|
|
`Path` works exactly the same on Windows, Linux, and OSX, instead of adjusting |
|
behavior based on your current OS |
|
|
|
The `Path` API uses the following conventions: |
|
|
|
- Accepts/returns only `t(absolute))` for values that must be absolute paths. |
|
- Accepts/returns only `t(relative))` for values that must be absolute paths. |
|
- Accepts `t('any)` for values that may be either absolute or relative paths. |
|
- Returns `firstClass = Absolute(t(absolute)) | Relative(t(relative)` for |
|
return values that could be either. Consumers must pattern match on it. |
|
- Wraps return values in `Some(..)` / `None` when it is possible that no |
|
value may be computed even when the caller supplies valid data. |
|
- Wraps return values in `Ok(..)` / `Error(exn)` when it is possible that no |
|
value may be computed due to an error occuring in either user input or system |
|
failure. |
|
- For every `functionName` that wraps return values in `Ok`/`Error`, an |
|
alternative form `functionNameExn` is also supplied which does not wrap |
|
the return value, and instead raises an exception. |
|
|
|
TODO: Consider the following universal convention instead: |
|
|
|
type specificUsageError = UserNameInvalid | LoggedOut; |
|
type blame('usage) = | Caller('usage) | Implementation(exn); |
|
// Returns Error only for system blame: |
|
result(x, exn) |
|
// Returns Error for caller/system blame |
|
result(x, blame(usage)) |
|
// Returns Error only for caller blame |
|
result(x, usage) |
|
// Returns Error for caller/system blame, but no value expected. |
|
result(option(x), blame(usage)) |
|
// Returns Error for system blame, but no value expected. |
|
result(option(x), exn) |
|
*/ |
|
|
|
type relative; |
|
type absolute; |
|
/** |
|
A file system path, parameterized on the kind of file system path, |
|
`Path.t(relative)` or `Path.t(absolute)`. |
|
*/ |
|
type t('kind); |
|
|
|
/** |
|
Used to allow dynamically checking whether or not a path is absolute or |
|
relative. Use seldomly. |
|
*/ |
|
type firstClass = |
|
| Absolute(t(absolute)) |
|
| Relative(t(relative)); |
|
|
|
let drive: string => t(absolute); |
|
let root: t(absolute); |
|
let home: t(relative); |
|
let dot: t(relative); |
|
|
|
/** |
|
Queries whether a path is absolute or relative. Use seldomly, and typically |
|
only on end-user input. Once queried, use the wrapped `t(absolute)/t(relative)` |
|
as the primary path passed around. |
|
*/ |
|
let testForPath: string => option(firstClass); |
|
|
|
/** |
|
Same as `testForPath`, but raises `Invalid_argument` if no path could be |
|
detected. |
|
*/ |
|
let testForPathExn: string => firstClass; |
|
|
|
/** |
|
Creates a "first class" path could be _either_ a relative path or an absolute |
|
one. |
|
|
|
This allows you to return values from functions that might be absolute or might |
|
be relative. It also allows relative and absolute paths to coexist inside of a |
|
list together. |
|
For example, if you create a polymorphic function that accepts any kind of |
|
path, and then you want to do something differently based on whether or not the |
|
path is relative or absolute, you would first use `firstClass(path)` and then |
|
pattern match on the result `Absolute(p) => .. | Relative(p) => ...`. |
|
*/ |
|
let firstClass: t('any) => firstClass; |
|
|
|
/** |
|
Prints absolute `Path.t` as strings, always removes the final `/` separator. |
|
*/ |
|
let toString: t(absolute) => string; |
|
|
|
/** |
|
Prints any `Path.t` for debugging, always removes the final `/` separator |
|
except in the case of the empty relative paths `./`, `~/`. |
|
*/ |
|
let toDebugString: t('kind) => string; |
|
|
|
/** |
|
Parses an absolute path into a `Path.t(absolute)` or returns `None` if the path |
|
is not a absolute, yet still valid. Raises Invalid_argument if the path is |
|
invalid. |
|
*/ |
|
let absolute: string => option(t(absolute)); |
|
/** |
|
Parses a relative path into a `Path.t(relative)` or returns `None` if the path |
|
is not a valid. |
|
*/ |
|
let relative: string => option(t(relative)); |
|
|
|
/** |
|
Same as `Path.absolute` but raises a Invalid_argument if argument is not a |
|
valid absolute path. |
|
*/ |
|
let absoluteExn: string => t(absolute); |
|
|
|
/** |
|
Same as `Path.relative` but raises a Invalid_argument if argument is not a |
|
valid relative path. |
|
*/ |
|
let relativeExn: string => t(relative); |
|
|
|
/** |
|
Creates a relative path from two paths, which is the relative path that is |
|
required to arive at the `dest`, if starting from `source` directory. The |
|
`source` and `dest` must both be `t(absolute)` or `t(relative)`, but the |
|
returned path is always of type `t(relative)`. |
|
|
|
If `source` and `dest` are relative, it is assumed that the two relative paths |
|
are relative to the same yet-to-be-specified absolute path. |
|
|
|
relativize(~source=/a, ~dest=/a) == ./ |
|
relativize(~source=/a/b/c/d /a/b/qqq == ../c/d |
|
relativize(~source=/a/b/c/d, ~dest=/f/f/zzz) == ../../../../f/f/zz |
|
relativize(~source=/a/b/c/d, ~dest=/a/b/c/d/q) == ../q |
|
relativize(~source=./x/y/z, ~dest=./a/b/c) == ../../a/b/c |
|
relativize(~source=./x/y/z, ~dest=../a/b/c) == ../../../a/b/c |
|
|
|
Unsupported: |
|
`relativize` only accepts `source` and `dest` of the same kind of path because |
|
the following are meaningless: |
|
|
|
relativize(~source=/x/y/z, ~dest=./a/b/c) == ??? |
|
relativize(~source=./x/y/z, ~dest=/a/b/c) == ??? |
|
|
|
Exceptions: |
|
If it is impossible to create a relative path from `soure` to `dest` an |
|
exception is raised. |
|
If `source`/`dest` are absolute paths, the drive must match or an exception is |
|
thrown. If `source`/`dest` are relative paths, they both must be relative to |
|
`"~"` vs. `"."`. If both are relative, but the source has more `..` than the |
|
dest, then it is also impossible to create a relative path and an exception is |
|
raised. |
|
|
|
relativize(~source=./foo/bar, ~dest=~/foo/bar) == raise(Invalid_argument) |
|
relativize(~source=~/foo/bar, ~dest=./foo/) == raise(Invalid_argument) |
|
relativize(~source=C:/foo/bar, ~dest=/foo/bar) == raise(Invalid_argument) |
|
relativize(~source=C:/foo/bar, ~dest=F:/foo/bar) == raise(Invalid_argument) |
|
relativize(~source=/foo/bar, ~dest=C:/foo/) == raise(Invalid_argument) |
|
relativize(~source=../x/y/z, ~dest=./a/b/c) == raise(Invalid_argument) |
|
relativize(~source=../x/y/z, ~dest=../foo/../a/b/c) == raise(Invalid_argument) |
|
*/ |
|
let relativizeExn: (~source: t('kind), ~dest: t('kind)) => t(relative); |
|
/** |
|
Same as `relativizeExn` but returns `result(Path.t(Path.absolute), exn)` |
|
instead of throwing an exception. |
|
*/ |
|
let relativize: |
|
(~source: t('kind), ~dest: t('kind)) => result(t(relative), exn); |
|
|
|
/** |
|
Accepts any `Path.t` and returns a `Path.t` of the same kind. Relative path |
|
inputs return relative path outputs, and absolute path inputs return absolute |
|
path outputs. |
|
*/ |
|
let dirName: t('kind) => t('kind); |
|
|
|
/** |
|
Accepts any `Path.t` and returns the final segment in its path string, or |
|
`None` if there are no segments in its path string. |
|
|
|
Path.baseName(Path.At(Path.dot /../ "")) |
|
None |
|
|
|
Path.baseName(Path.At(Path.dot /../ "foo")) |
|
Some("foo") |
|
|
|
Path.baseName(Path.At(Path.dot /../ "foo" /../ "")) |
|
None |
|
|
|
Path.baseName(Path.At(Path.dot /../ "foo" / "bar" /../ "")) |
|
Some("foo") |
|
*/ |
|
let baseName: t('kind) => option(string); |
|
|
|
/** |
|
Appends one segment to a path. Preserves the relative/absoluteness of the first |
|
arguments. |
|
*/ |
|
let append: (t('kind), string) => t('kind); |
|
|
|
/** |
|
Appends one path to another. Preserves the relative/absoluteness of the first |
|
arguments. |
|
*/ |
|
let join: (t('kind1), t('kind2)) => t('kind1); |
|
|
|
let eq: (t('kind1), t('kind2)) => bool; |
|
|
|
/** |
|
Tests for path equality of two absolute paths. |
|
*/ |
|
let absoluteEq: (t(absolute), t(absolute)) => bool; |
|
|
|
/** |
|
Tests for path equality of two absolute paths. |
|
*/ |
|
let relativeEq: (t(relative), t(relative)) => bool; |
|
|
|
/** |
|
Tests whether or not an absolute path has a parent path. Absolute paths such as |
|
"C:/" and "/" have no parent dir. |
|
*/ |
|
let hasParentDir: t(absolute) => bool; |
|
|
|
/** |
|
Returns `true` if a path exists inside another path `~ofPath` or is equal to |
|
`~ofPath`. |
|
*/ |
|
let isDescendent: (~ofPath: t('kind), t('kind)) => bool; |
|
|
|
/** |
|
Syntactic forms for utilities provided above. These are included in a separate |
|
module so that it can be opened safely without causing collisions with other |
|
identifiers in scope such as "root"/"home". |
|
|
|
Use like this: |
|
|
|
Path.At(Path.root / "foo" / "bar"); |
|
Path.At(Path.dot /../ "bar"); |
|
*/ |
|
module At: { |
|
/** |
|
Performs `append` with infix syntax. |
|
*/ |
|
let (/): (t('kind), string) => t('kind); |
|
/** |
|
`dir /../ s` is equivalent to `append(dirName(dir), s)` |
|
*/ |
|
let (/../): (t('kind), string) => t('kind); |
|
/** |
|
`dir /../../ s` is equivalent to `append(dirName(dirName(dir)), s)` |
|
*/ |
|
let (/../../): (t('kind), string) => t('kind); |
|
/** |
|
`dir /../../../ s` is equivalent to |
|
`append(dirName(dirName(dirName(dir))), s)` |
|
*/ |
|
let (/../../../): (t('kind), string) => t('kind); |
|
/** |
|
`dir /../../../../ s` is equivalent to |
|
`append(dirName(dirName(dirName(dirName(dir)))), s)` |
|
*/ |
|
let (/../../../../): (t('kind), string) => t('kind); |
|
/** |
|
`dir /../../../../../ s` is equivalent to |
|
`append(dirName(dirName(dirName(dirName(dirName(dir))))), s)` |
|
*/ |
|
let (/../../../../../): (t('kind), string) => t('kind); |
|
/** |
|
`dir /../../../../../../ s` is equivalent to |
|
`append(dirName(dirName(dirName(dirName(dirName(dirName(dir)))))), s)` |
|
*/ |
|
let (/../../../../../../): (t('kind), string) => t('kind); |
|
}; |