zcaudate
zcaudate

Reputation: 14258

function that gives the relative path in clojure?

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

Answers (2)

Eva
Eva

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

zcaudate
zcaudate

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

Related Questions