ILLOGICAL PATHNAMES

                           By Robert Smith


The purpose of this library is to allow one to specify pathnames in
source files whose syntax mostly doesn't depend on a physical path
location. To put it simply, one can write

    #P(:HOME (".emacs.d") "foo.el")

instead of


Somewhere, :HOME---a so-called "illogical host"---has to be
specified. This is done by associating it with a directory via the
macro DEFINE-ILLOGICAL-HOST. They can be redefined, which won't affect
evaluated uses of #P(...) syntax.

The former syntax isn't actually an "illogical pathname"; it evaluates
to a physical pathname. (See the example below and the FAQ.) However,
it does involve objects of the type ILLOGICAL-PATHNAME under the hood.

Using illogical pathnames allows one to easily write code whose
pathnames are relative to a few known base directories. When the
program is moved, or perhaps an executable is created, one only has to
redefine the set of illogical hosts. Using the power of
*LOAD-TRUENAME* and others, one can make mostly portable applications
that don't depend on physical filesystem location at all.


Before this library, I attempted to solve the same problem by defining
logical hosts with wildcard translations. This worked relatively well
with Clozure CL, due to their more flexible implementation of logical
pathnames. However, more ANSI compliant systems (with respect to
logical pathnames) didn't work with the same code. As such, in my
mind, I made "logical pathnames that solve the 95% case" and called
them "illogical pathnames", a play on the fact that they were supposed
to be "logical logical pathnames". (A double positive makes a
negative, right?)

Despite the name and the reasoning behind the name, these aren't a
replacement for logical pathnames and are not "better" than logical
pathnames; they just solve a different problem than that which logical
hosts solve on modern machines.


Note that normal pathname syntax isn't changed.

    > #P"/foo/bar"

Let's define an illogical host that points to my home directory.

    > (ipath:define-illogical-host :home "/home/me/")

Let's use the extended pathname syntax to refer to a file in my home

    > #P(:home ("Scratch") "new.txt")

We see that #P(...) isn't truly a literal for an illogical
pathname. It returned a physical pathname. What does #P(...) really
expand into then?

    > '#P(:home ("Scratch") "new.txt")
          :HOST :HOME
          :DIRECTORY ("Scratch")
          :NAME "new"
          :TYPE "txt"))

Just an unevaluated translation of an illogical pathname object.

Let's define another illogical host referring to my scratch space
directory in my home directory.

    > (ipath:define-illogical-host :scratch #P(:home ("Scratch")))

Let's open a new file and write to it in my scratch space.

    > (with-open-file (s #P(:scratch nil "test.txt") :direction ':output
                                                     :if-does-not-exist ':create)
        (write-string "testing, 1 2 3" s))
    "testing, 1 2 3"

And finally, let's read it back.

    > (with-open-file (s #P(:scratch nil "test.txt") :direction ':input)
        (read-line s))
    "testing, 1 2 3"

That is basically it.

Frequently Asked Questions

Q. Why doesn't #P(...) specify a literal illogical pathname object?

This was a pragmatic choice. If Common Lisp had a generic function
called, say, TRANSLATE-TO-PATHNAME which all relevant Common Lisp
functions knew about, we could indeed use illogical pathname objects
by creating a method of that generic function. However, since we don't
have that functionality, yet we want to relatively transparently be
able to specify illogical pathnames in the places they're used, we do
the translation within the expansion of #P(...).

Q. My system needs to be bootstrapped, and I can't rely on Quicklisp
   or ASDF, but I want to use illogical pathnames.

No problem. The file "illogical-pathnames.lisp" is self-contained. You
can load it as-is.

Q. ANSI logical pathnames can solve this problem. I tried it and
   it works fine!
The ANSI spec requires that strings in logical pathnames must be uppercase,
and the system should convert them to uppercase when pathnames are created:

"Logical pathname words are restricted to non-case-sensitive letters,
digits, and hyphens to avoid creating problems with real file systems
that support limited character sets for file naming. (If logical pathnames
were case-sensitive, it would be very difficult to map them into a file
system that is not sensitive to case in its file names.)"

The phrase in parentheses above is precisely the opposite of the truth.
A fully ANSI-conforming logical pathname implementation cannot generally
be used on a case-sensitive file system such as that typically used in Linux
or Unix.

If you are not seeing this problem with your CL implementation, it is either because
you are using a case-insensitive file system like MacOSX, or because
your implementation (e.g., Clozure CL or Franz) sanely nixed the ANSI idea
of logical pathnames and made them case-preserving.
Unfortunately, that is not ANSI conforming.

Q. Why didn't you just make a DEFINE-* macro and a function?

Pathnames need to be convenient, and specifying functions in full is
not syntactically convenient. If one doesn't like the #P syntax, they
may opt to create ILLOGICAL-PATHNAME objects and call

       > (ipath:define-illogical-host :home "/Users/me/")
       > (ipath:make-illogical-pathname
          :host ':home
          :directory '("foo" "bar")
          :name "test"
          :type "txt")
       > (ipath:translate-illogical-pathname *)