Reputation: 14258
I need a function that when given a base directory and another path, I've done a simplified version that just matches the absolute path but was hoping to also be able to intelligently deal with '..' and '.' in the path. I'm not sure what the best approach is
some examples:
(relative-path "example" "example/hello") => "hello"
(relative-path "example" "../example/hello") => "hello"
(relative-path "example" "/usr/local") => "../../../usr/local"
Upvotes: 2
Views: 548
Reputation: 4700
I adapted the Java solution to Clojure:
(defn relative-path
[a b]
(let [path-a (.toPath (io/file a))
path-b (.toPath (io/file b))
can-relativize? (if (.getRoot path-a)
(some? (.getRoot path-b))
(not (.getRoot path-b)))]
(when can-relativize?
(str (.relativize path-a path-b)))))
(relative-path "example" "example/hello") => "hello"
(relative-path "example" "../example/hello") => "../../example/hello"
(relative-path "example" "/usr/local") => nil
Note this only works if either both paths have a root or neither do since that's a limitation of Path#relativize.
Upvotes: 0
Reputation: 14258
I figured it out after a bit of trial and error:
(require '[clojure.java.io :as io]
'[clojure.string :as string])
(defn interpret-dots
([v] (interpret-dots v []))
([v output]
(if-let [s (first v)]
(condp = s
"." (recur (next v) output)
".." (recur (next v) (pop output))
(recur (next v) (conj output s)))
output)))
(defn drop-while-matching [u v]
(cond (or (empty? u) (empty? v)) [u v]
(= (first u) (first v))
(recur (rest u) (rest v))
:else [u v]))
(defn path-vector [path]
(string/split (.getAbsolutePath (io/file path))
(re-pattern (System/getProperty "file.separator"))))
(defn relative-path [root other]
(let [[base rel] (drop-while-matching (interpret-dots (path-vector root))
(interpret-dots (path-vector other)))]
(if (and (empty? base) (empty? rel))
"."
(->> (-> (count base)
(repeat "..")
(concat rel))
(string/join (System/getProperty "file.separator")))))
Usage:
(relative-path "example/repack.advance/resources"
"example/repack.advance/resources/eueueeueu/oeuoeu")
;;=> "eueueeueu/oeuoeu"
(relative-path "example/repack.advance/resources"
"/usr/local")
;;=> "../../../../../../../../usr/local"
Upvotes: 1