João Fé
João Fé

Reputation: 385

Pathname independent of operating system in Common Lisp

I want to load a lisp script located in a subdirectory in current working folder. The relative path is ./crossover-operators/ER.lisp.

In Linux this is done by:

(load "./crossover-operators/ER.lisp")

In Windows is done by:

(load ".\\crossover-operators\\ER.lisp")

How can I make a function which loads ER.lisp script independently of the operating system in which my Common Lisp script is running?

Upvotes: 4

Views: 670

Answers (2)

João Fé
João Fé

Reputation: 385

Based on @coredump answer and other research, one fast answer is:

(load (make-pathname :name "ER"
                     :type "lisp"
                     :defaults (make-pathname :directory '(:relative "crossover-operators"))))

Upvotes: 3

coredump
coredump

Reputation: 38789

First of all, 19.1.1 Namestrings as Filenames says that indeed, namestring (strings as pathnames) are not portable.

A conforming program must never unconditionally use a literal namestring other than a logical pathname namestring because Common Lisp does not define any namestring syntax other than that for logical pathnames that would be guaranteed to be portable.

Note also that if you ask the user for filenames, you can use them portably:

However, a conforming program can, if it is careful, successfully manipulate user-supplied data which contains or refers to non-portable namestrings.

You have two options, which are not exclusive one to the other: using pathname constructors, and/or using logical pathnames.

Pathnames constructors

Build pathnames with make-pathname, merge-pathnames. A pathname is a structure with different components (directory, name, type, etc.) which can be combined together. They are build with a prototypal inheritance approach, where you create new pathnames by copying an existing one and changing some of its components.

make-pathname is just like a struct constructor, except it has a :defaults argument that gives a pathname to use as a prototype.

merge-pathnames is a bit different, since it also performs semantic operations.

For example, if *default-pathname-defaults*, the special variable that holds the default pathname, is set as follows:

USER> (setf *default-pathname-defaults*
            (make-pathname :directory '(:relative "crossover-operator")))
#P"crossover-operator/"

Then you have two different behaviours.

USER> (make-pathname :directory '(:relative "tmp")  
                     :defaults *default-pathname-defaults*)
#P"tmp/"

make-pathname replaces the directory component of the original pathname.

USER> (merge-pathnames *)
#P"crossover-operator/tmp/"

merge-pathnames takes a pathname (here, the one we just built, denoted by *), and merge the directory relatively to the one in *default-pathname-defaults*.

Logical pathnames

Logical pathnames are a bit like URLs (Uniform Resource Locator) and only indirectly represents files. The programmer must define translation functions from logical pathnames to actual, physical pathnames, based on the HOST part of the address.

The printed representation of pathnames (namestrings), are not portable, except for logical pathnames. See 19.3.1 Syntax of Logical Pathname Namestrings.

Other than having a defined syntax and translation functions (from logical to physical pathnames), they behave as other pathnames, so you can call merge-pathnames as seen above.

Pathnames translations can map to non-portable namestrings (but you can setup the translations differently on different hosts), but also logical or physical pathnames. The hyperspec for LOGICAL-PATHNAME-TRANSLATIONS has some examples on how to use logical pathnames, like this one:

 ;;;A more complex example, dividing the files among two file servers
 ;;;and several different directories.  This Unix doesn't support
 ;;;:WILD-INFERIORS in the directory, so each directory level must
 ;;;be translated individually.  No file name or type translations
 ;;;are required except for .MAIL to .MBX.
 ;;;The namestring syntax on the right-hand side is implementation-dependent.
 (setf (logical-pathname-translations "prog")
       '(("RELEASED;*.*.*"        "MY-UNIX:/sys/bin/my-prog/")
         ("RELEASED;*;*.*.*"      "MY-UNIX:/sys/bin/my-prog/*/")
         ("EXPERIMENTAL;*.*.*"    "MY-UNIX:/usr/Joe/development/prog/")
         ("EXPERIMENTAL;DOCUMENTATION;*.*.*"
                                  "MY-VAX:SYS$DISK:[JOE.DOC]")
         ("EXPERIMENTAL;*;*.*.*"  "MY-UNIX:/usr/Joe/development/prog/*/")
         ("MAIL;**;*.MAIL"        "MY-VAX:SYS$DISK:[JOE.MAIL.PROG...]*.MBX")))

 ;;;Sample use of that logical pathname.  The return value
 ;;;is implementation-dependent.          
 (translate-logical-pathname "prog:mail;save;ideas.mail.3")
=>  #P"MY-VAX:SYS$DISK:[JOE.MAIL.PROG.SAVE]IDEAS.MBX.3"

Upvotes: 5

Related Questions