Common Lisp the Language, 2nd Edition
Some
file systems provide more complex conventions for wildcards than
simple component-wise wildcards representable by :wild.
For example, the namestring "F*O" might mean a normal three-character
name; a three-character name with the middle character wild;
a name with at least two characters, beginning with F and ending with O;
or perhaps a wild match spanning multiple directories. Similarly, the
namestring ">foo>**>bar>" might imply that the middle directory is
named "**"; the middle directory is :wild;
there are zero or more middle directories that are :wild;
or perhaps that the middle directory name matches any two-letter name.
Some file systems support even more complex wildcards, such as
regular expressions.
X3J13 voted in June 1989 (PATHNAME-WILD) to provide some facilities for dealing with more general wildcard pathnames in a fairly portable manner.
[Function]
wild-pathname-p pathname &optional field-key
Tests a pathname for the presence of wildcard components. If the first argument is not a pathname, string, or file stream, an error of type type-error is signaled.
If no field-key is provided, or the field-key is nil, the result is true if and only if pathname has any wildcard components.
If a non-null field-key is provided, it must be one of :host, :device, :directory, :name, :type, or :version. In this case, the result is true if and only if the indicated component of pathname is a wildcard.
Note that X3J13 voted in June 1989 (PATHNAME-COMPONENT-VALUE) to specify that an implementation need not support wildcards in all fields; the only requirement is that the name, type, or version may be :wild. However, portable programs should be prepared to encounter either :wild or implementation-dependent wildcards in any pathname component. The function wild-pathname-p provides a portable way for testing the presence of wildcards.
[Function]
pathname-match-p pathname wildname
This predicate is true if and only if the pathname matches the wildname. The matching rules are implementation-defined but should be consistent with the behavior of the directory function. Missing components of wildname default to :wild.
If either argument is not a pathname, string, or file stream, an error of type type-error is signaled. It is valid for pathname to be a wild pathname; a wildcard field in pathname will match only a wildcard field in wildname; that is, pathname-match-p is not commutative. It is valid for wildname to be a non-wild pathname; I believe that in this case pathname-match-p will have the same behavior as equal, though the X3J13 specification did not say so.
[Function]
translate-pathname source from-wildname to-wildname &key
Translates the pathname source, which must match from-wildname, into a corresponding pathname (call it result), which is constructed so as to match to-wildname, and returns result.
The pathname result is a copy of to-wildname with each missing or wildcard field replaced by a portion of source; for this purpose a wildcard field is a pathname component with a value of :wild, a :wild element of a list-valued directory component, or an implementation-defined portion of a component, such as the * in the complex wildcard string "foo*bar" that some implementations support. An implementation that adds other wildcard features, such as regular expressions, must define how translate-pathname extends to those features. A missing field is a pathname component that is nil.
The portion of source that is copied into result is implementation-defined. Typically it is determined by the user interface conventions of the file systems involved. Usually it is the portion of source that matches a wildcard field of from-wildname that is in the same position as the missing or wildcard field of to-wildname. If there is no wildcard field in from-wildname at that position, then usually it is the entire corresponding pathname component of source or, in the case of a list-valued directory component, the entire corresponding list element. For example, if the name components of source, from-wildname, and to-wildname are "gazonk", "gaz*", and "h*" respectively, then in most file systems the wildcard fields of the name component of from-wildname and to-wildname are each "*", the matching portion of source is "onk", and the name component of result is "honk"; however, the exact behavior of translate-pathname is not dictated by the Common Lisp language and may vary according to the user interface conventions of the file systems involved.
During the copying of a portion of source into result, additional implementation-defined translations of alphabetic case or file naming conventions may occur, especially when from-wildname and to-wildname are for different hosts.
If any of the first three arguments is not a pathname, string, or file stream, an error of type type-error is signaled. It is valid for source to be a wild pathname; in general this will produce a wild result pathname. It is valid for from-wildname or to-wildname or both to be non-wild. An error is signaled if the source pathname does not match the from-wildname, that is, if (pathname-match-p source from-wildname) would not be true.
There are no specified keyword arguments for translate-pathname, but implementations are permitted to extend it by adding keyword arguments. There is one specified return value from translate-pathname; implementations are permitted to extend it by returning additional values.
Here is an implementation suggestion. One file system performs this operation by examining corresponding pieces of the three pathnames in turn, where a piece is a pathname component or a list element of a structured component such as a hierarchical directory. Hierarchical directory elements in from-wildname and to-wildname are matched by whether they are wildcards, not by depth in the directory hierarchy. If the piece in to-wildname is present and not wild, it is copied into the result. If the piece in to-wildname is :wild or nil, the corresponding piece in source is copied into the result. Otherwise, the piece in to-wildname might be a complex wildcard such as "foo*bar"; the portion of the piece in source that matches the wildcard portion of the corresponding piece in from-wildname (or the entire source piece, if the from-wildname piece is not wild and therefore equals the source piece) replaces the wildcard portion of the piece in to-wildname and the value produced is used in the result.
X3J13 voted in June 1989 (PATHNAME-COMPONENT-CASE) to require translate-pathname to map customary case in argument pathnames to the customary case in returned pathnames (see section 23.1.2).
Here are some examples of the use of the new wildcard pathname facilities. These examples are not portable. They are written to run with particular file systems and particular wildcard conventions and are intended to be illustrative, not prescriptive. Other implementations may behave differently.
(wild-pathname-p (make-pathname :name :wild)) => t (wild-pathname-p (make-pathname :name :wild) :name) => t (wild-pathname-p (make-pathname :name :wild) :type) => nil (wild-pathname-p (pathname "S:>foo>**>")) => t ;Maybe (wild-pathname-p (make-pathname :name "F*O")) => t ;Probably
One cannot rely on rename-file to handle wild pathnames in a predictable manner. However, one can use translate-pathname explicitly to control the process.
(defun rename-files (from to) "Rename all files that match the first argument by translating their names to the form of the second argument. Both arguments may be wild pathnames." (dolist (file (directory from)) ;; DIRECTORY produces only pathnames that match from-wildname. (rename-file file (translate-pathname file from to))))
Assuming one particular set of popular wildcard conventions, this function might exhibit the following behavior. Not all file systems will run this example exactly as written.
(rename-files "/usr/me/*.lisp" "/dev/her/*.l") renames /usr/me/init.lisp to /dev/her/init.l (rename-files "/usr/me/pcl*/*" "/sys/pcl/*/") renames /usr/me/pcl-5-may/low.lisp to /sys/pcl/pcl-5-may/low.lisp (in some file systems the result might be /sys/pcl/5-may/low.lisp) (rename-files "/usr/me/pcl*/*" "/sys/library/*/") renames /usr/me/pcl-5-may/low.lisp to /sys/library/pcl-5-may/low.lisp (in some file systems the result might be /sys/library/5-may/low.lisp) (rename-files "/usr/me/foo.bar" "/usr/me2/") renames /usr/me/foo.bar to /usr/me2/foo.bar (rename-files "/usr/joe/*-recipes.text" "/usr/jim/personal/cookbook/joe's-*-rec.text") renames /usr/joe/lamb-recipes.text to /usr/jim/personal/cookbook/joe's-lamb-rec.text renames /usr/joe/veg-recipes.text to /usr/jim/personal/cookbook/joe's-veg-rec.text renames /usr/joe/cajun-recipes.text to /usr/jim/personal/cookbook/joe's-cajun-rec.text renames /usr/joe/szechuan-recipes.text to /usr/jim/personal/cookbook/joe's-szechuan-rec.text
The following examples use UNIX syntax and the wildcard conventions of one particular version of UNIX.
(namestring (translate-pathname "/usr/dmr/hacks/frob.l" "/usr/d*/hacks/*.l" "/usr/d*/backup/hacks/backup-*.*")) => "/usr/dmr/backup/hacks/backup-frob.l"
(namestring (translate-pathname "/usr/dmr/hacks/frob.l" "/usr/d*/hacks/fr*.l" "/usr/d*/backup/hacks/backup-*.*")) => "/usr/dmr/backup/hacks/backup-ob.l"
The following examples are similar to the preceding examples but use two different hosts; host U supports a UNIX file system and host V supports a VAX/VMS file system. Note the translation of file type (from l to LSP) and the change of alphabetic case conventions.
(namestring (translate-pathname "U:/usr/dmr/hacks/frob.l" "U:/usr/d*/hacks/*.l" "V:SYS$DISK:[D*.BACKUP.HACKS]BACKUP-*.*")) => "V:SYS$DISK:[DMR.BACKUP.HACKS]BACKUP-FROB.LSP"
(namestring (translate-pathname "U:/usr/dmr/hacks/frob.l" "U:/usr/d*/hacks/fr*.l" "V:SYS$DISK:[D*.BACKUP.HACKS]BACKUP-*.*")) => "V:SYS$DISK:[DMR.BACKUP.HACKS]BACKUP-OB.LSP"
The next example is a version of the function translate-logical-pathname (simplified a bit) for a logical host named FOO. The points of interest are the use of pathname-match-p as a :test argument for assoc and the use of translate-pathname as a substrate for translate-logical-pathname.
(define-condition logical-translation-error (file-error)) (defun my-translate-logical-pathname (pathname &key rules) (let ((rule (assoc pathname rules :test #'pathname-match-p))) (unless rule (error 'logical-translation-error :pathname pathname)) (translate-pathname pathname (first rule) (second rule)))) (my-translate-logical-pathname "FOO:CODE;BASIC.LISP" :rules '(("FOO:DOCUMENTATION;" "U:/doc/foo/") ("FOO:CODE;" "U:/lib/foo/") ("FOO:PATCHES;*;" "U:/lib/foo/patch/*/"))) => #P"U:/lib/foo/basic.l"