This is something that always leaves me a bit stumped, the equivalent of Ruby’s Pathname#join
It resolves ./ and ../, when joining with an absolute path it uses that, and it respects the OS path name separator (less of interest to me but nice to have).
Clojure itself has nothing like it AFAIK. I’m ok with falling back to Java, but the closest I could find, java.nio.file.Paths/get is a variadic method, meaning interop gets awkward. (you have to give it a java array). It also doesn’t resolve ./ or ../ unless you normalize, so this is what I ended up using.
Thanks for all the suggestions, however none of these are general enough or have the semantics that I’m after.
Here are some test cases to make it more clear what I’m after
(is (= (join "/foo/bar" "baz") "/foo/baz")) ;; most already get this wrong and turn it into "/foo/bar/baz"
(is (= (join "/foo/bar/" "baz") "/foo/bar/baz"))
(is (= (join "/foo/bar/" "/baz") "/baz"))
(is (= (join "/foo/bar/" "./baz") "/foo/bar/baz")) ;; I would accept "/foo/bar/./baz"
(is (= (join "/foo/bar/qux" "../baz") "/foo/baz")) ;; I would accept "/foo/bar/../baz"
To give you a concrete example that I came across today (which is different from the thing I came across yesterday where I needed this): I’ve been working on code that “fingerprints” web assets, i.e. it adds a content-based hash to the file name so that files can safely be cached.
When a JS file has a source map, then that JS file needs to be rewritten so it points to the source map with hash in the filename. This sourceMappingURL is usually just a file name like my_code.js.map, but it could be an absolute path like /js/my_code.js.map, or a path relative to the original like ./source-maps/my_code.js.map or source_maps/my_code.js.map. I need to do path “arithmetic” on this find which file on my filesystem this corresponds with.
Seriously though that you are asking for is not standard join behaviour and you are probably going to need to do some (.isDirectory x) checks yourself. io/file accepts a file instance as its first argument so you’ll typically use that as a root. Benefit of this is that it works cross platform.