import { assertEquals } from "jsr:@std/assert"
const
cwd = "/x/y/z",
getCwd = () => (cwd),
// we also define a custom absolute segment path tester function that will identify "file://" and "http://" segments as absolute path,
// in addition to the standard filesystem local path absoluteness tester `isAbsolutePath`.
custom_absolute_path_segment_tester = (segment: string) => {
if (isAbsolutePath(segment)) { return true }
if (segment.startsWith("file://")) { return true }
if (segment.startsWith("http://")) { return true }
return false
},
resolvePosixPath = resolvePosixPathFactory(getCwd, custom_absolute_path_segment_tester)
// aliasing our functions for brevity
const eq = assertEquals, fn = resolvePosixPath
// relative path resolution
eq(fn("a", "b", "c.zip"), "/x/y/z/a/b/c.zip")
eq(fn("./a", "b", "c/"), "/x/y/z/a/b/c/")
eq(fn("a/", "b/", "c/"), "/x/y/z/a/b/c/")
eq(fn("../a", "../b", "c/"), "/x/b/c/")
eq(fn("../a/", "../b", "c/"), "/x/y/b/c/")
eq(fn("a", "b", "c.zip", "./"), "/x/y/z/a/b/")
eq(fn("a", "b", "c/", "./"), "/x/y/z/a/b/c/")
eq(fn("a", "b", "c", "../"), "/x/y/z/a/")
eq(fn("a", "b", "c/d.txt", "./e.txt"), "/x/y/z/a/b/c/e.txt")
eq(fn("a", "b", "c/d.txt", "../e.txt"), "/x/y/z/a/b/e.txt")
eq(fn("a/b", "c/d.txt", "..", "e.txt"), "/x/y/z/a/b/e.txt") // notice that you can use "." instead of "./"
eq(fn("a/", "b/", "c/", "../", "./","d.txt"), "/x/y/z/a/b/d.txt")
eq(fn("a/b", "c/", "..", ".","d.txt"), "/x/y/z/a/b/d.txt") // notice that you can use ".." instead of "../"
eq(fn("a/b", "c/", ".d.txt"), "/x/y/z/a/b/c/.d.txt")
eq(fn("a/b", "c/", "..d.txt"), "/x/y/z/a/b/c/..d.txt")
eq(fn("a/b", "c/", ".d"), "/x/y/z/a/b/c/.d")
eq(fn("a/b", "c/", ".d."), "/x/y/z/a/b/c/.d.")
eq(fn("a/b", "c/", "..d"), "/x/y/z/a/b/c/..d")
eq(fn("a/b", "c/", "..d."), "/x/y/z/a/b/c/..d.")
eq(fn("a/b", "c/", "..d.."), "/x/y/z/a/b/c/..d..")
eq(fn("a/b", "c/", "..d.", "e.txt"), "/x/y/z/a/b/c/..d./e.txt")
eq(fn("a/b", "c/", "..d..", "e.txt"), "/x/y/z/a/b/c/..d../e.txt")
eq(fn("./a", "./b", "./c"), "/x/y/z/c")
// pre-existing absolute path resolution
eq(fn("./a", "/b", "/c"), "/c") // both "/b" and "/c" are absolute paths, and so they purge all segments behind them.
eq(fn("./a/", "/b/", "./c"), "/b/c") // "/b/" is an absolute path, hence it negates all segments prior to it.
eq(fn("file:///", "a/b/c", "./d"), "file:///a/b/d") // the first "file:///" segment is an absolute path according to our `custom_absolute_path_segment_tester`
eq(fn("file:/", "a/b/c", "./d"), "/x/y/z/file:/a/b/d") // "file:/" is not considered as an absolute path by `custom_absolute_path_segment_tester`, thus it the cwd will be prepended to the final path
eq(fn("file:", "a/b/c", "./d"), "/x/y/z/file:/a/b/d") // same as before, but we're putting emphasis on the mandatory "/" separator that gets added after the "file:" segment
eq(fn("a/b/c", "http://d/e/f.txt"), "http://d/e/f.txt") // the "http://" segment is identified as an absolute path by our `custom_absolute_path_segment_tester`
this is a factory function for creating customizable posix path-resolving functions. a path-resolving function is one that takes in a list of path-segments, and then computes the absolute normalized path of all the segments combined.
since it is not possible for this submodule to know the context under which you are computing/resolving paths, it becomes impossible to give a meaning to a list of path segmensts that begin with a relative path. which is why you would need to feed this factory function with the (static or dynamic) location of your current working path (directory), and it will spit out a path-resolver function that is tailored to your specific working-directory's path.
if you want to preserve the starting relative segments, (i.e. you don't want an absolute path), then you're looking for the joinPosixPaths (or joinPaths) function, not this one.
an important detail to note is that whenever an absolute path segment is encountered, all segments prior to it are discarded (i.e. not joined with a "/" separator, like the way joinPaths does). this behavior is enforced to remain consistent with popular implementations of path-resolvers, like:
Path.resolve
:resolve
, fromjsr:@std/path
this path resolver function works best when only absolute and relative path segments are provided. when root (but not necessarily absolute) path segments are encountered, such as
/sys/bin
, our function will typically assume that it is referring to an absolute local-filesystem path/sys/bin
.but as you may be aware, the "correct" interpretation of the root path depends on the context of the preceding absolute path segment. for example:
C:/a/b/c/d.txt
, and the current path segment is/sys/bin
, then our result will be/sys/bin
, but the result in most implementations (including deno-std's path module) would beC:/sys/bin
.http://test.com/a/b/c/d.txt
, and the current path segment is/sys/bin
, then our result will be/sys/bin
, but the "correct" url path resolution (vianew URL(...)
) should have beenhttp://test.com/sys.bin
.this is why, if you're dealing with ambiguous root paths that are not necessarily tied down to your posix-filesystem's root, you should use the resolveAsUrl function instead, as it is aware of most common types of base contexts/domains for path evaluation. but on the other hand, you will be unable to use your custom absolute_segment_test_fn, nor be able to resolve multiple segments all at oncce.
TODO: contemplate if it would be a good idea to add a configuration interface to this factory function, where one will be able to set their custom join rule when a root path segment is discovered, in addition to containing the absolute_segment_test_fn. the signature of the root-join function would look like:
(abs_path_evaluated_up_till_now: string, current_root_path_segment: string) => string
.