From: Mark Wooding
Subject: File renaming misery
Date: 
Message-ID: <slrne671d7.1dq.mdw@metalzone.distorted.org.uk>
I'd like to rename a file, in Common Lisp.  For specificness: I'd like
to rename

  * `foo.new' to
  * `foo'
  * on Unixy platforms,
  * and if I have to do any system-specific weirdness, in CMU CL on
    Linux.

Unfortunately, the spec for RENAME-FILE seems deranged.  So, I try and
do the obvious thing:

* (rename-file "foo.new" "foo")
#P"/tmp/mwooding/foo/foo.new"
#P"/tmp/mwooding/foo/foo.new"
#P"/tmp/mwooding/foo/foo.new"

Hmm.  That doesn't look encouraging.  Sure enough:

* (directory "./")
(#P"/tmp/mwooding/foo/foo.new")

What's gone wrong?

: 20.2  The Files Dictionary
:
: [...]
:
: Function RENAME-FILE
:
: [...]
:
: rename-file returns three values if successful. The primary value,
: defaulted-new-name, is the resulting name which is composed of
: new-name with any missing components filled in by performing a
: merge-pathnames operation using filespec as the defaults.

Why?  This is deranged!

So:

  * I'm currently using unix:unix-rename.  I'd like to stop, because
    it's a bit grim, really.

  * It appears that CLISP can be persuaded to do the right thing if I
    say

      (rename-file (make-pathname :directory '(:relative)
                                  :name "foo.new")
                   "foo"

    but on CMU CL and SBCL this makes no difference, since they default
    new-name using (truename filespec) rather than filespec.  Is this
    allowed by the spec?  (The TRUENAME process causes a reparsing of
    the actual name into a pathname, which defeats my chicanery.)

  * Is there a sensible way of doing what I want, in Common Lisp?  

-- [mdw]

From: Steven E. Harris
Subject: Re: File renaming misery
Date: 
Message-ID: <q94hd3w2vus.fsf@chlorine.gnostech.com>
Mark Wooding <···@distorted.org.uk> writes:

> Is there a sensible way of doing what I want, in Common Lisp?  

I got the following to work in LispWorks, more through experimentation
than understanding:

> (rename-file (make-pathname :directory '(:absolute "temp")
                              :name "foo" :type "new")
               (make-pathname :type :unspecific))
#P"C:/temp/foo"
#P"C:/temp/foo.new"
#P"C:/temp/foo"


I had expected a type of nil or "" to work, but in both cases the type
of the original filespec gets merged back in.

-- 
Steven E. Harris
From: Pascal Bourguignon
Subject: Re: File renaming misery
Date: 
Message-ID: <878xp8734l.fsf@thalassa.informatimago.com>
"Steven E. Harris" <···@panix.com> writes:

> Mark Wooding <···@distorted.org.uk> writes:
>
>> Is there a sensible way of doing what I want, in Common Lisp?  
>
> I got the following to work in LispWorks, more through experimentation
> than understanding:
>
>> (rename-file (make-pathname :directory '(:absolute "temp")
>                               :name "foo" :type "new")
>                (make-pathname :type :unspecific))
> #P"C:/temp/foo"
> #P"C:/temp/foo.new"
> #P"C:/temp/foo"
>
>
> I had expected a type of nil or "" to work, but in both cases the type
> of the original filespec gets merged back in.

Which is nice, but implementation dependant it seems. clisp doesn't take it:

*** - MAKE-PATHNAME: illegal :TYPE argument :UNSPECIFIC

Indeed, CLHS MAKE-PATHNAME says:

   Portable programs should not supply :unspecific for any
   component. See Section 19.2.2.2.3 (:UNSPECIFIC as a Component
   Value).

-- 
__Pascal Bourguignon__                     http://www.informatimago.com/

CAUTION: The mass of this product contains the energy equivalent of
85 million tons of TNT per net ounce of weight.
From: Steven E. Harris
Subject: Re: File renaming misery
Date: 
Message-ID: <q947j4s2v2s.fsf@chlorine.gnostech.com>
Pascal Bourguignon <···@informatimago.com> writes:

> Indeed, CLHS MAKE-PATHNAME says:
>
>    Portable programs should not supply :unspecific for any
>    component. See Section 19.2.2.2.3 (:UNSPECIFIC as a Component
>    Value).

Ouch. I missed that part. I was determined to believe that CL had a
way.

-- 
Steven E. Harris
From: Rob Warnock
Subject: Re: File renaming misery
Date: 
Message-ID: <0eGdnUrCnaIMrPnZRVn-og@speakeasy.net>
Steven E. Harris <···@panix.com> wrote:
+---------------
| Mark Wooding <···@distorted.org.uk> writes:
| > Is there a sensible way of doing what I want, in Common Lisp?  
| 
| I got the following to work in LispWorks, more through experimentation
| than understanding:
| > (rename-file (make-pathname :directory '(:absolute "temp")
|                               :name "foo" :type "new")
|                (make-pathname :type :unspecific))
| #P"C:/temp/foo"
| #P"C:/temp/foo.new"
| #P"C:/temp/foo"
+---------------

It also works in CMUCL [on Linux or FreeBSD, at least]:

    cmu> (rename-file "foo.new"
		      (make-pathname :name "foo" :type :unspecific))

    #p"/usr/u/rpw3/foo"
    #p"/usr/u/rpw3/foo.new"
    #p"/usr/u/rpw3/foo"
    cmu> 

But as you saw, I'm sure, Pascal B. warned that this use
of :UNSPECIFIC is non-portable. However, CLHS 19.2.2.2.3
*does* say:

    19.2.2.2.3 :UNSPECIFIC as a Component Value
    ....
    However, a conforming program can, if it is careful, successfully
    manipulate user-supplied data which contains or refers to non-portable
    pathname components.
    ...
    When writing the value of any pathname component, the consequences
    are undefined if :unspecific is given for a pathname in a file system
    for which it does not make sense.

But in a file system for which it *DOES* make sense...  ;-}  ;-}


-Rob

-----
Rob Warnock			<····@rpw3.org>
627 26th Avenue			<URL:http://rpw3.org/>
San Mateo, CA 94403		(650)572-2607
From: Pascal Bourguignon
Subject: Re: File renaming misery
Date: 
Message-ID: <87d5ek738q.fsf@thalassa.informatimago.com>
Mark Wooding <···@distorted.org.uk> writes:

> I'd like to rename a file, in Common Lisp.  For specificness: I'd like
> to rename
>
>   * `foo.new' to
>   * `foo'
>   * on Unixy platforms,
>   * and if I have to do any system-specific weirdness, in CMU CL on
>     Linux.
>
> Unfortunately, the spec for RENAME-FILE seems deranged.  So, I try and
> do the obvious thing:
>
> * (rename-file "foo.new" "foo")
> #P"/tmp/mwooding/foo/foo.new"
> #P"/tmp/mwooding/foo/foo.new"
> #P"/tmp/mwooding/foo/foo.new"
>
> Hmm.  That doesn't look encouraging.  Sure enough:
>
> * (directory "./")
> (#P"/tmp/mwooding/foo/foo.new")
>
> What's gone wrong?
>
> : 20.2  The Files Dictionary
> :
> : [...]
> :
> : Function RENAME-FILE
> :
> : [...]
> :
> : rename-file returns three values if successful. The primary value,
> : defaulted-new-name, is the resulting name which is composed of
> : new-name with any missing components filled in by performing a
> : merge-pathnames operation using filespec as the defaults.
>
> Why?  This is deranged!
>
> So:
>
>   * I'm currently using unix:unix-rename.  I'd like to stop, because
>     it's a bit grim, really.

You don't have a lot of choices.

Either you limit yourself to logical pathnames  (this is portable),
and to the translations you can write, 

or you take in the implementation specific physical pathnames quircks,

or you use unix- or posix- level paths.




For example, once you've set up the logical pathname translations for
a logical host DOT, (which can be done in an implementation specifica
way), you can use them portably:

[57]> (setf (logical-pathname-translations "DOT")
            (list (list #P"DOT:*.*.*" 
                        (merge-pathnames
                         (make-pathname :name ".*" #|clisp specific|#
                                        :type :wild
                                        :version :wild)
                         (user-homedir-pathname)))
                  (list #P"DOT:*.*" 
                        (merge-pathnames
                         (make-pathname :name ".*" #|clisp specific|#
                                        :type :wild)
                         (user-homedir-pathname)))
                  (list #P"DOT:*" 
                        (merge-pathnames
                         (make-pathname :name ".*" #|clisp specific|#)
                         (user-homedir-pathname)))))
((#P"DOT:*.*.*" #P"/home/pjb/.*.*") (#P"DOT:*.*" #P"/home/pjb/.*.*")
 (#P"DOT:*" #P"/home/pjb/.*"))
[58]> (translate-logical-pathname #P"DOT:EMACS")
#P"/home/pjb/.emacs"

So now on, you can access to a whole class of unix files for which
there's no proper logical pathname syntax, portably and with a proper
logical pathname syntax: #P"DOT:EMACS", #P"DOT:BASHRC" etc.  The good
thing about it, is that it will still work with another lisp
implementation, or on another OS with different rules.  You just need
to provide a logical-pathname translations file, and use
LOAD-LOGICAL-PATHNAME-TRANSLATIONS at the beginning of the program.


In the second alternative, you can use physical pathnames. But this is
not portable either across lisp implementations or different file
systems, and this may not be too consistent as you've noticed with RENAME-FILE.



>   * It appears that CLISP can be persuaded to do the right thing if I
>     say
>
>       (rename-file (make-pathname :directory '(:relative)
>                                   :name "foo.new")
>                    "foo"

That's because clisp accepts dots in names.  This allows us to
manipulate file names starting with dots, but this is rather
ill-defined because:

(values
  (make-pathname :directory '(:relative) :name "foo.new")
  (make-pathname :directory '(:relative) :name "foo" :type "new")
  (pathname-name (make-pathname :directory '(:relative) :name "foo.new"))
  (pathname-name (make-pathname :directory '(:relative) :name "foo" 
                                                        :type "new"))
  (truename (make-pathname :directory '(:relative) :name "foo.new"))
  (truename (make-pathname :directory '(:relative) :name "foo" :type "new")))

--> #P"./foo.new" ;
    #P"./foo.new" ;
    "foo.new" ;        !!!
    "foo" ;
    #P"/tmp/foo.new" ;
    #P"/tmp/foo.new"

This builds pathnames that are externally undistinguishable from
others, different pathnames.  Granted, on a unix file system, both
would refer to the same file.

But when reading back a pathname either as #P"foo.new", it parses it
in  one way, not the other, and for rename-file you'd want the other.


>   * Is there a sensible way of doing what I want, in Common Lisp?  

There doesn't seem to be. CLHS specifies that the new name is built by
merging the second argument with the first:

   rename-file returns three values if successful. The primary value,
   defaulted-new-name, is the resulting name which is composed of
   new-name with any missing components filled in by performing a
   merge-pathnames operation using filespec as the defaults. 

So if you don't provide a type for the new name, it will get the type
from the old name.  It's designed to be used as:

  (rename-file #P"OLD.LISP" #P"NEW")

and obtain a file: #P"NEW.LISP".


File systems that managed file names and file types didn't allow not
to specify a type, and Common Lisp logical pathnames doesn't allow
types to be an empty string.  Either there is no type (in which case,
the merging will copy over the old type), or the type is at least one
character.

In clisp there's an implementation specific behavior, where you can
specify an empty string as a type:

(rename-file (make-pathname :name "foo" :type "new")
             (make-pathname :name "foo" :type "")) ; works:
--> 
#P"foo." ;
#P"/tmp/foo.new" ;
#P"/tmp/foo."

But this doesn't do what you want. 



-- 
__Pascal Bourguignon__                     http://www.informatimago.com/

"This statement is false."            In Lisp: (defun Q () (eq nil (Q)))