Get relative path in Common Lisp

Viewed 423

Given an arbitrary path path and some other path base to a directory, how can I get a new relative path from base to the same object in file system as path?

For example (relpath #p"~/foo" #p"~/bar/") must give me #p"../foo". There is such a function in Julia, for example, which is also called relpath. Is there anything like that in Common Lisp (either in the standard or as a third-party library)?

3 Answers

The closest to what you are looking for is called enough-namestring:

(enough-namestring "~/foo/bar/baz" "~/foo/")
==> "bar/baz"

If both paths exists, you can normalize them to an absolute pathname by calling truename; then, since pathname directories are lists, you can easily find the longest common path and build a relative pathname with as many :up elements as necessary to go from the second pathname to the first:

(defun rp (p1 p2)
  (loop
     for d1 on (pathname-directory (truename p1))
     for d2 on (pathname-directory (truename p2))
     while (string= (first d1) (first d2))
     finally
       (return
         (make-pathname
          :directory (append (list :relative)
                     (substitute :up t d2 :test (constantly t))
                     d1)
          :defaults p1))))

For example, assuming "/tmp/foo" exists and "~" is "/home/user/":

> (rp "/tmp/foo" "~/")
#P"../../tmp/foo"

This should cover a lot of common use cases, but this has its limitations (no wildcard names, files must exist, and probably other corner cases)

I know the question is related to common lisp but as a hint there is a solution within emacs lisp. The emacs lisp function to return the relative path is file-relative-name.

Related