tpathname.lisp - clic - Clic is an command line interactive client for gopher written in Common LISP
(HTM) git clone git://bitreich.org/clic/ git://hg6vgqziawt5s4dj.onion/clic/
(DIR) Log
(DIR) Files
(DIR) Refs
(DIR) Tags
(DIR) LICENSE
---
tpathname.lisp (37231B)
---
1 ;;;; -------------------------------------------------------------------------
2 ;;;; Portability layer around Common Lisp pathnames
3 ;; This layer allows for portable manipulation of pathname objects themselves,
4 ;; which all is necessary prior to any access the filesystem or environment.
5
6 (uiop/package:define-package :uiop/pathname
7 (:nicknames :asdf/pathname) ;; deprecated. Used by ceramic
8 (:use :uiop/common-lisp :uiop/package :uiop/utility :uiop/os)
9 (:export
10 ;; Making and merging pathnames, portably
11 #:normalize-pathname-directory-component #:denormalize-pathname-directory-component
12 #:merge-pathname-directory-components #:*unspecific-pathname-type* #:make-pathname*
13 #:make-pathname-component-logical #:make-pathname-logical
14 #:merge-pathnames*
15 #:nil-pathname #:*nil-pathname* #:with-pathname-defaults
16 ;; Predicates
17 #:pathname-equal #:logical-pathname-p #:physical-pathname-p #:physicalize-pathname
18 #:absolute-pathname-p #:relative-pathname-p #:hidden-pathname-p #:file-pathname-p
19 ;; Directories
20 #:pathname-directory-pathname #:pathname-parent-directory-pathname
21 #:directory-pathname-p #:ensure-directory-pathname
22 ;; Parsing filenames
23 #:split-name-type #:parse-unix-namestring #:unix-namestring
24 #:split-unix-namestring-directory-components
25 ;; Absolute and relative pathnames
26 #:subpathname #:subpathname*
27 #:ensure-absolute-pathname
28 #:pathname-root #:pathname-host-pathname
29 #:subpathp #:enough-pathname #:with-enough-pathname #:call-with-enough-pathname
30 ;; Checking constraints
31 #:ensure-pathname ;; implemented in filesystem.lisp to accommodate for existence constraints
32 ;; Wildcard pathnames
33 #:*wild* #:*wild-file* #:*wild-file-for-directory* #:*wild-directory*
34 #:*wild-inferiors* #:*wild-path* #:wilden
35 ;; Translate a pathname
36 #:relativize-directory-component #:relativize-pathname-directory
37 #:directory-separator-for-host #:directorize-pathname-host-device
38 #:translate-pathname*
39 #:*output-translation-function*))
40 (in-package :uiop/pathname)
41
42 ;;; Normalizing pathnames across implementations
43
44 (with-upgradability ()
45 (defun normalize-pathname-directory-component (directory)
46 "Convert the DIRECTORY component from a format usable by the underlying
47 implementation's MAKE-PATHNAME and other primitives to a CLHS-standard format
48 that is a list and not a string."
49 (cond
50 #-(or cmucl sbcl scl) ;; these implementations already normalize directory components.
51 ((stringp directory) `(:absolute ,directory))
52 ((or (null directory)
53 (and (consp directory) (member (first directory) '(:absolute :relative))))
54 directory)
55 #+gcl
56 ((consp directory)
57 (cons :relative directory))
58 (t
59 (parameter-error (compatfmt "~@<~S: Unrecognized pathname directory component ~S~@:>")
60 'normalize-pathname-directory-component directory))))
61
62 (defun denormalize-pathname-directory-component (directory-component)
63 "Convert the DIRECTORY-COMPONENT from a CLHS-standard format to a format usable
64 by the underlying implementation's MAKE-PATHNAME and other primitives"
65 directory-component)
66
67 (defun merge-pathname-directory-components (specified defaults)
68 "Helper for MERGE-PATHNAMES* that handles directory components"
69 (let ((directory (normalize-pathname-directory-component specified)))
70 (ecase (first directory)
71 ((nil) defaults)
72 (:absolute specified)
73 (:relative
74 (let ((defdir (normalize-pathname-directory-component defaults))
75 (reldir (cdr directory)))
76 (cond
77 ((null defdir)
78 directory)
79 ((not (eq :back (first reldir)))
80 (append defdir reldir))
81 (t
82 (loop :with defabs = (first defdir)
83 :with defrev = (reverse (rest defdir))
84 :while (and (eq :back (car reldir))
85 (or (and (eq :absolute defabs) (null defrev))
86 (stringp (car defrev))))
87 :do (pop reldir) (pop defrev)
88 :finally (return (cons defabs (append (reverse defrev) reldir)))))))))))
89
90 ;; Giving :unspecific as :type argument to make-pathname is not portable.
91 ;; See CLHS make-pathname and 19.2.2.2.3.
92 ;; This will be :unspecific if supported, or NIL if not.
93 (defparameter *unspecific-pathname-type*
94 #+(or abcl allegro clozure cmucl genera lispworks sbcl scl) :unspecific
95 #+(or clasp clisp ecl mkcl gcl xcl #|These haven't been tested:|# cormanlisp mcl mezzano) nil
96 "Unspecific type component to use with the underlying implementation's MAKE-PATHNAME")
97
98 (defun make-pathname* (&rest keys &key directory host device name type version defaults
99 #+scl &allow-other-keys)
100 "Takes arguments like CL:MAKE-PATHNAME in the CLHS, and
101 tries hard to make a pathname that will actually behave as documented,
102 despite the peculiarities of each implementation. DEPRECATED: just use MAKE-PATHNAME."
103 (declare (ignore host device directory name type version defaults))
104 (apply 'make-pathname keys))
105
106 (defun make-pathname-component-logical (x)
107 "Make a pathname component suitable for use in a logical-pathname"
108 (typecase x
109 ((eql :unspecific) nil)
110 #+clisp (string (string-upcase x))
111 #+clisp (cons (mapcar 'make-pathname-component-logical x))
112 (t x)))
113
114 (defun make-pathname-logical (pathname host)
115 "Take a PATHNAME's directory, name, type and version components,
116 and make a new pathname with corresponding components and specified logical HOST"
117 (make-pathname
118 :host host
119 :directory (make-pathname-component-logical (pathname-directory pathname))
120 :name (make-pathname-component-logical (pathname-name pathname))
121 :type (make-pathname-component-logical (pathname-type pathname))
122 :version (make-pathname-component-logical (pathname-version pathname))))
123
124 (defun merge-pathnames* (specified &optional (defaults *default-pathname-defaults*))
125 "MERGE-PATHNAMES* is like MERGE-PATHNAMES except that
126 if the SPECIFIED pathname does not have an absolute directory,
127 then the HOST and DEVICE both come from the DEFAULTS, whereas
128 if the SPECIFIED pathname does have an absolute directory,
129 then the HOST and DEVICE both come from the SPECIFIED pathname.
130 This is what users want on a modern Unix or Windows operating system,
131 unlike the MERGE-PATHNAMES behavior.
132 Also, if either argument is NIL, then the other argument is returned unmodified;
133 this is unlike MERGE-PATHNAMES which always merges with a pathname,
134 by default *DEFAULT-PATHNAME-DEFAULTS*, which cannot be NIL."
135 (when (null specified) (return-from merge-pathnames* defaults))
136 (when (null defaults) (return-from merge-pathnames* specified))
137 #+scl
138 (ext:resolve-pathname specified defaults)
139 #-scl
140 (let* ((specified (pathname specified))
141 (defaults (pathname defaults))
142 (directory (normalize-pathname-directory-component (pathname-directory specified)))
143 (name (or (pathname-name specified) (pathname-name defaults)))
144 (type (or (pathname-type specified) (pathname-type defaults)))
145 (version (or (pathname-version specified) (pathname-version defaults))))
146 (labels ((unspecific-handler (p)
147 (if (typep p 'logical-pathname) #'make-pathname-component-logical #'identity)))
148 (multiple-value-bind (host device directory unspecific-handler)
149 (ecase (first directory)
150 ((:absolute)
151 (values (pathname-host specified)
152 (pathname-device specified)
153 directory
154 (unspecific-handler specified)))
155 ((nil :relative)
156 (values (pathname-host defaults)
157 (pathname-device defaults)
158 (merge-pathname-directory-components directory (pathname-directory defaults))
159 (unspecific-handler defaults))))
160 (make-pathname :host host :device device :directory directory
161 :name (funcall unspecific-handler name)
162 :type (funcall unspecific-handler type)
163 :version (funcall unspecific-handler version))))))
164
165 (defun logical-pathname-p (x)
166 "is X a logical-pathname?"
167 (typep x 'logical-pathname))
168
169 (defun physical-pathname-p (x)
170 "is X a pathname that is not a logical-pathname?"
171 (and (pathnamep x) (not (logical-pathname-p x))))
172
173 (defun physicalize-pathname (x)
174 "if X is a logical pathname, use translate-logical-pathname on it."
175 ;; Ought to be the same as translate-logical-pathname, except the latter borks on CLISP
176 (let ((p (when x (pathname x))))
177 (if (logical-pathname-p p) (translate-logical-pathname p) p)))
178
179 (defun nil-pathname (&optional (defaults *default-pathname-defaults*))
180 "A pathname that is as neutral as possible for use as defaults
181 when merging, making or parsing pathnames"
182 ;; 19.2.2.2.1 says a NIL host can mean a default host;
183 ;; see also "valid physical pathname host" in the CLHS glossary, that suggests
184 ;; strings and lists of strings or :unspecific
185 ;; But CMUCL decides to die on NIL.
186 ;; MCL has issues with make-pathname, nil and defaulting
187 (declare (ignorable defaults))
188 #.`(make-pathname :directory nil :name nil :type nil :version nil
189 :device (or #+(and mkcl os-unix) :unspecific)
190 :host (or #+cmucl lisp::*unix-host* #+(and mkcl os-unix) "localhost")
191 #+scl ,@'(:scheme nil :scheme-specific-part nil
192 :username nil :password nil :parameters nil :query nil :fragment nil)
193 ;; the default shouldn't matter, but we really want something physical
194 #-mcl ,@'(:defaults defaults)))
195
196 (defvar *nil-pathname* (nil-pathname (physicalize-pathname (user-homedir-pathname)))
197 "A pathname that is as neutral as possible for use as defaults
198 when merging, making or parsing pathnames")
199
200 (defmacro with-pathname-defaults ((&optional defaults) &body body)
201 "Execute BODY in a context where the *DEFAULT-PATHNAME-DEFAULTS* is as specified,
202 where leaving the defaults NIL or unspecified means a (NIL-PATHNAME), except
203 on ABCL, Genera and XCL, where it remains unchanged for it doubles as current-directory."
204 `(let ((*default-pathname-defaults*
205 ,(or defaults
206 #-(or abcl genera xcl) '*nil-pathname*
207 #+(or abcl genera xcl) '*default-pathname-defaults*)))
208 ,@body)))
209
210
211 ;;; Some pathname predicates
212 (with-upgradability ()
213 (defun pathname-equal (p1 p2)
214 "Are the two pathnames P1 and P2 reasonably equal in the paths they denote?"
215 (when (stringp p1) (setf p1 (pathname p1)))
216 (when (stringp p2) (setf p2 (pathname p2)))
217 (flet ((normalize-component (x)
218 (unless (member x '(nil :unspecific :newest (:relative)) :test 'equal)
219 x)))
220 (macrolet ((=? (&rest accessors)
221 (flet ((frob (x)
222 (reduce 'list (cons 'normalize-component accessors)
223 :initial-value x :from-end t)))
224 `(equal ,(frob 'p1) ,(frob 'p2)))))
225 (or (and (null p1) (null p2))
226 (and (pathnamep p1) (pathnamep p2)
227 (and (=? pathname-host)
228 #-(and mkcl os-unix) (=? pathname-device)
229 (=? normalize-pathname-directory-component pathname-directory)
230 (=? pathname-name)
231 (=? pathname-type)
232 #-mkcl (=? pathname-version)))))))
233
234 (defun absolute-pathname-p (pathspec)
235 "If PATHSPEC is a pathname or namestring object that parses as a pathname
236 possessing an :ABSOLUTE directory component, return the (parsed) pathname.
237 Otherwise return NIL"
238 (and pathspec
239 (typep pathspec '(or null pathname string))
240 (let ((pathname (pathname pathspec)))
241 (and (eq :absolute (car (normalize-pathname-directory-component
242 (pathname-directory pathname))))
243 pathname))))
244
245 (defun relative-pathname-p (pathspec)
246 "If PATHSPEC is a pathname or namestring object that parses as a pathname
247 possessing a :RELATIVE or NIL directory component, return the (parsed) pathname.
248 Otherwise return NIL"
249 (and pathspec
250 (typep pathspec '(or null pathname string))
251 (let* ((pathname (pathname pathspec))
252 (directory (normalize-pathname-directory-component
253 (pathname-directory pathname))))
254 (when (or (null directory) (eq :relative (car directory)))
255 pathname))))
256
257 (defun hidden-pathname-p (pathname)
258 "Return a boolean that is true if the pathname is hidden as per Unix style,
259 i.e. its name starts with a dot."
260 (and pathname (equal (first-char (pathname-name pathname)) #\.)))
261
262 (defun file-pathname-p (pathname)
263 "Does PATHNAME represent a file, i.e. has a non-null NAME component?
264
265 Accepts NIL, a string (converted through PARSE-NAMESTRING) or a PATHNAME.
266
267 Note that this does _not_ check to see that PATHNAME points to an
268 actually-existing file.
269
270 Returns the (parsed) PATHNAME when true"
271 (when pathname
272 (let ((pathname (pathname pathname)))
273 (unless (and (member (pathname-name pathname) '(nil :unspecific "") :test 'equal)
274 (member (pathname-type pathname) '(nil :unspecific "") :test 'equal))
275 pathname)))))
276
277
278 ;;; Directory pathnames
279 (with-upgradability ()
280 (defun pathname-directory-pathname (pathname)
281 "Returns a new pathname with same HOST, DEVICE, DIRECTORY as PATHNAME,
282 and NIL NAME, TYPE and VERSION components"
283 (when pathname
284 (make-pathname :name nil :type nil :version nil :defaults pathname)))
285
286 (defun pathname-parent-directory-pathname (pathname)
287 "Returns a new pathname that corresponds to the parent of the current pathname's directory,
288 i.e. removing one level of depth in the DIRECTORY component. e.g. if pathname is
289 Unix pathname /foo/bar/baz/file.type then return /foo/bar/"
290 (when pathname
291 (make-pathname :name nil :type nil :version nil
292 :directory (merge-pathname-directory-components
293 '(:relative :back) (pathname-directory pathname))
294 :defaults pathname)))
295
296 (defun directory-pathname-p (pathname)
297 "Does PATHNAME represent a directory?
298
299 A directory-pathname is a pathname _without_ a filename. The three
300 ways that the filename components can be missing are for it to be NIL,
301 :UNSPECIFIC or the empty string.
302
303 Note that this does _not_ check to see that PATHNAME points to an
304 actually-existing directory."
305 (when pathname
306 ;; I tried using Allegro's excl:file-directory-p, but this cannot be done,
307 ;; because it rejects apparently legal pathnames as
308 ;; ill-formed. [2014/02/10:rpg]
309 (let ((pathname (pathname pathname)))
310 (flet ((check-one (x)
311 (member x '(nil :unspecific) :test 'equal)))
312 (and (not (wild-pathname-p pathname))
313 (check-one (pathname-name pathname))
314 (check-one (pathname-type pathname))
315 t)))))
316
317 (defun ensure-directory-pathname (pathspec &optional (on-error 'error))
318 "Converts the non-wild pathname designator PATHSPEC to directory form."
319 (cond
320 ((stringp pathspec)
321 (ensure-directory-pathname (pathname pathspec)))
322 ((not (pathnamep pathspec))
323 (call-function on-error (compatfmt "~@<Invalid pathname designator ~S~@:>") pathspec))
324 ((wild-pathname-p pathspec)
325 (call-function on-error (compatfmt "~@<Can't reliably convert wild pathname ~3i~_~S~@:>") pathspec))
326 ((directory-pathname-p pathspec)
327 pathspec)
328 (t
329 (handler-case
330 (make-pathname :directory (append (or (normalize-pathname-directory-component
331 (pathname-directory pathspec))
332 (list :relative))
333 (list (file-namestring pathspec)))
334 :name nil :type nil :version nil :defaults pathspec)
335 (error (c) (call-function on-error (compatfmt "~@<error while trying to create a directory pathname for ~S: ~A~@:>") pathspec c)))))))
336
337
338 ;;; Parsing filenames
339 (with-upgradability ()
340 (declaim (ftype function ensure-pathname)) ; forward reference
341
342 (defun split-unix-namestring-directory-components
343 (unix-namestring &key ensure-directory dot-dot)
344 "Splits the path string UNIX-NAMESTRING, returning four values:
345 A flag that is either :absolute or :relative, indicating
346 how the rest of the values are to be interpreted.
347 A directory path --- a list of strings and keywords, suitable for
348 use with MAKE-PATHNAME when prepended with the flag value.
349 Directory components with an empty name or the name . are removed.
350 Any directory named .. is read as DOT-DOT, or :BACK if it's NIL (not :UP).
351 A last-component, either a file-namestring including type extension,
352 or NIL in the case of a directory pathname.
353 A flag that is true iff the unix-style-pathname was just
354 a file-namestring without / path specification.
355 ENSURE-DIRECTORY forces the namestring to be interpreted as a directory pathname:
356 the third return value will be NIL, and final component of the namestring
357 will be treated as part of the directory path.
358
359 An empty string is thus read as meaning a pathname object with all fields nil.
360
361 Note that colon characters #\: will NOT be interpreted as host specification.
362 Absolute pathnames are only appropriate on Unix-style systems.
363
364 The intention of this function is to support structured component names,
365 e.g., \(:file \"foo/bar\"\), which will be unpacked to relative pathnames."
366 (check-type unix-namestring string)
367 (check-type dot-dot (member nil :back :up))
368 (if (and (not (find #\/ unix-namestring)) (not ensure-directory)
369 (plusp (length unix-namestring)))
370 (values :relative () unix-namestring t)
371 (let* ((components (split-string unix-namestring :separator "/"))
372 (last-comp (car (last components))))
373 (multiple-value-bind (relative components)
374 (if (equal (first components) "")
375 (if (equal (first-char unix-namestring) #\/)
376 (values :absolute (cdr components))
377 (values :relative nil))
378 (values :relative components))
379 (setf components (remove-if #'(lambda (x) (member x '("" ".") :test #'equal))
380 components))
381 (setf components (substitute (or dot-dot :back) ".." components :test #'equal))
382 (cond
383 ((equal last-comp "")
384 (values relative components nil nil)) ; "" already removed from components
385 (ensure-directory
386 (values relative components nil nil))
387 (t
388 (values relative (butlast components) last-comp nil)))))))
389
390 (defun split-name-type (filename)
391 "Split a filename into two values NAME and TYPE that are returned.
392 We assume filename has no directory component.
393 The last . if any separates name and type from from type,
394 except that if there is only one . and it is in first position,
395 the whole filename is the NAME with an empty type.
396 NAME is always a string.
397 For an empty type, *UNSPECIFIC-PATHNAME-TYPE* is returned."
398 (check-type filename string)
399 (assert (plusp (length filename)))
400 (destructuring-bind (name &optional (type *unspecific-pathname-type*))
401 (split-string filename :max 2 :separator ".")
402 (if (equal name "")
403 (values filename *unspecific-pathname-type*)
404 (values name type))))
405
406 (defun parse-unix-namestring (name &rest keys &key type defaults dot-dot ensure-directory
407 &allow-other-keys)
408 "Coerce NAME into a PATHNAME using standard Unix syntax.
409
410 Unix syntax is used whether or not the underlying system is Unix;
411 on such non-Unix systems it is reliably usable only for relative pathnames.
412 This function is especially useful to manipulate relative pathnames portably,
413 where it is of crucial to possess a portable pathname syntax independent of the underlying OS.
414 This is what PARSE-UNIX-NAMESTRING provides, and why we use it in ASDF.
415
416 When given a PATHNAME object, just return it untouched.
417 When given NIL, just return NIL.
418 When given a non-null SYMBOL, first downcase its name and treat it as a string.
419 When given a STRING, portably decompose it into a pathname as below.
420
421 #\\/ separates directory components.
422
423 The last #\\/-separated substring is interpreted as follows:
424 1- If TYPE is :DIRECTORY or ENSURE-DIRECTORY is true,
425 the string is made the last directory component, and NAME and TYPE are NIL.
426 if the string is empty, it's the empty pathname with all slots NIL.
427 2- If TYPE is NIL, the substring is a file-namestring, and its NAME and TYPE
428 are separated by SPLIT-NAME-TYPE.
429 3- If TYPE is a string, it is the given TYPE, and the whole string is the NAME.
430
431 Directory components with an empty name or the name \".\" are removed.
432 Any directory named \"..\" is read as DOT-DOT,
433 which must be one of :BACK or :UP and defaults to :BACK.
434
435 HOST, DEVICE and VERSION components are taken from DEFAULTS,
436 which itself defaults to *NIL-PATHNAME*, also used if DEFAULTS is NIL.
437 No host or device can be specified in the string itself,
438 which makes it unsuitable for absolute pathnames outside Unix.
439
440 For relative pathnames, these components (and hence the defaults) won't matter
441 if you use MERGE-PATHNAMES* but will matter if you use MERGE-PATHNAMES,
442 which is an important reason to always use MERGE-PATHNAMES*.
443
444 Arbitrary keys are accepted, and the parse result is passed to ENSURE-PATHNAME
445 with those keys, removing TYPE DEFAULTS and DOT-DOT.
446 When you're manipulating pathnames that are supposed to make sense portably
447 even though the OS may not be Unixish, we recommend you use :WANT-RELATIVE T
448 to throw an error if the pathname is absolute"
449 (block nil
450 (check-type type (or null string (eql :directory)))
451 (when ensure-directory
452 (setf type :directory))
453 (etypecase name
454 ((or null pathname) (return name))
455 (symbol
456 (setf name (string-downcase name)))
457 (string))
458 (multiple-value-bind (relative path filename file-only)
459 (split-unix-namestring-directory-components
460 name :dot-dot dot-dot :ensure-directory (eq type :directory))
461 (multiple-value-bind (name type)
462 (cond
463 ((or (eq type :directory) (null filename))
464 (values nil nil))
465 (type
466 (values filename type))
467 (t
468 (split-name-type filename)))
469 (apply 'ensure-pathname
470 (make-pathname
471 :directory (unless file-only (cons relative path))
472 :name name :type type
473 :defaults (or #-mcl defaults *nil-pathname*))
474 (remove-plist-keys '(:type :dot-dot :defaults) keys))))))
475
476 (defun unix-namestring (pathname)
477 "Given a non-wild PATHNAME, return a Unix-style namestring for it.
478 If the PATHNAME is NIL or a STRING, return it unchanged.
479
480 This only considers the DIRECTORY, NAME and TYPE components of the pathname.
481 This is a portable solution for representing relative pathnames,
482 But unless you are running on a Unix system, it is not a general solution
483 to representing native pathnames.
484
485 An error is signaled if the argument is not NULL, a STRING or a PATHNAME,
486 or if it is a PATHNAME but some of its components are not recognized."
487 (etypecase pathname
488 ((or null string) pathname)
489 (pathname
490 (with-output-to-string (s)
491 (flet ((err () (parameter-error "~S: invalid unix-namestring ~S"
492 'unix-namestring pathname)))
493 (let* ((dir (normalize-pathname-directory-component (pathname-directory pathname)))
494 (name (pathname-name pathname))
495 (name (and (not (eq name :unspecific)) name))
496 (type (pathname-type pathname))
497 (type (and (not (eq type :unspecific)) type)))
498 (cond
499 ((member dir '(nil :unspecific)))
500 ((eq dir '(:relative)) (princ "./" s))
501 ((consp dir)
502 (destructuring-bind (relabs &rest dirs) dir
503 (or (member relabs '(:relative :absolute)) (err))
504 (when (eq relabs :absolute) (princ #\/ s))
505 (loop :for x :in dirs :do
506 (cond
507 ((member x '(:back :up)) (princ "../" s))
508 ((equal x "") (err))
509 ;;((member x '("." "..") :test 'equal) (err))
510 ((stringp x) (format s "~A/" x))
511 (t (err))))))
512 (t (err)))
513 (cond
514 (name
515 (unless (and (stringp name) (or (null type) (stringp type))) (err))
516 (format s "~A~@[.~A~]" name type))
517 (t
518 (or (null type) (err)))))))))))
519
520 ;;; Absolute and relative pathnames
521 (with-upgradability ()
522 (defun subpathname (pathname subpath &key type)
523 "This function takes a PATHNAME and a SUBPATH and a TYPE.
524 If SUBPATH is already a PATHNAME object (not namestring),
525 and is an absolute pathname at that, it is returned unchanged;
526 otherwise, SUBPATH is turned into a relative pathname with given TYPE
527 as per PARSE-UNIX-NAMESTRING with :WANT-RELATIVE T :TYPE TYPE,
528 then it is merged with the PATHNAME-DIRECTORY-PATHNAME of PATHNAME."
529 (or (and (pathnamep subpath) (absolute-pathname-p subpath))
530 (merge-pathnames* (parse-unix-namestring subpath :type type :want-relative t)
531 (pathname-directory-pathname pathname))))
532
533 (defun subpathname* (pathname subpath &key type)
534 "returns NIL if the base pathname is NIL, otherwise like SUBPATHNAME."
535 (and pathname
536 (subpathname (ensure-directory-pathname pathname) subpath :type type)))
537
538 (defun pathname-root (pathname)
539 "return the root directory for the host and device of given PATHNAME"
540 (make-pathname :directory '(:absolute)
541 :name nil :type nil :version nil
542 :defaults pathname ;; host device, and on scl, *some*
543 ;; scheme-specific parts: port username password, not others:
544 . #.(or #+scl '(:parameters nil :query nil :fragment nil))))
545
546 (defun pathname-host-pathname (pathname)
547 "return a pathname with the same host as given PATHNAME, and all other fields NIL"
548 (make-pathname :directory nil
549 :name nil :type nil :version nil :device nil
550 :defaults pathname ;; host device, and on scl, *some*
551 ;; scheme-specific parts: port username password, not others:
552 . #.(or #+scl '(:parameters nil :query nil :fragment nil))))
553
554 (defun ensure-absolute-pathname (path &optional defaults (on-error 'error))
555 "Given a pathname designator PATH, return an absolute pathname as specified by PATH
556 considering the DEFAULTS, or, if not possible, use CALL-FUNCTION on the specified ON-ERROR behavior,
557 with a format control-string and other arguments as arguments"
558 (cond
559 ((absolute-pathname-p path))
560 ((stringp path) (ensure-absolute-pathname (pathname path) defaults on-error))
561 ((not (pathnamep path)) (call-function on-error "not a valid pathname designator ~S" path))
562 ((let ((default-pathname (if (pathnamep defaults) defaults (call-function defaults))))
563 (or (if (absolute-pathname-p default-pathname)
564 (absolute-pathname-p (merge-pathnames* path default-pathname))
565 (call-function on-error "Default pathname ~S is not an absolute pathname"
566 default-pathname))
567 (call-function on-error "Failed to merge ~S with ~S into an absolute pathname"
568 path default-pathname))))
569 (t (call-function on-error
570 "Cannot ensure ~S is evaluated as an absolute pathname with defaults ~S"
571 path defaults))))
572
573 (defun subpathp (maybe-subpath base-pathname)
574 "if MAYBE-SUBPATH is a pathname that is under BASE-PATHNAME, return a pathname object that
575 when used with MERGE-PATHNAMES* with defaults BASE-PATHNAME, returns MAYBE-SUBPATH."
576 (and (pathnamep maybe-subpath) (pathnamep base-pathname)
577 (absolute-pathname-p maybe-subpath) (absolute-pathname-p base-pathname)
578 (directory-pathname-p base-pathname) (not (wild-pathname-p base-pathname))
579 (pathname-equal (pathname-root maybe-subpath) (pathname-root base-pathname))
580 (with-pathname-defaults (*nil-pathname*)
581 (let ((enough (enough-namestring maybe-subpath base-pathname)))
582 (and (relative-pathname-p enough) (pathname enough))))))
583
584 (defun enough-pathname (maybe-subpath base-pathname)
585 "if MAYBE-SUBPATH is a pathname that is under BASE-PATHNAME, return a pathname object that
586 when used with MERGE-PATHNAMES* with defaults BASE-PATHNAME, returns MAYBE-SUBPATH."
587 (let ((sub (when maybe-subpath (pathname maybe-subpath)))
588 (base (when base-pathname (ensure-absolute-pathname (pathname base-pathname)))))
589 (or (and base (subpathp sub base)) sub)))
590
591 (defun call-with-enough-pathname (maybe-subpath defaults-pathname thunk)
592 "In a context where *DEFAULT-PATHNAME-DEFAULTS* is bound to DEFAULTS-PATHNAME (if not null,
593 or else to its current value), call THUNK with ENOUGH-PATHNAME for MAYBE-SUBPATH
594 given DEFAULTS-PATHNAME as a base pathname."
595 (let ((enough (enough-pathname maybe-subpath defaults-pathname))
596 (*default-pathname-defaults* (or defaults-pathname *default-pathname-defaults*)))
597 (funcall thunk enough)))
598
599 (defmacro with-enough-pathname ((pathname-var &key (pathname pathname-var)
600 (defaults *default-pathname-defaults*))
601 &body body)
602 "Shorthand syntax for CALL-WITH-ENOUGH-PATHNAME"
603 `(call-with-enough-pathname ,pathname ,defaults #'(lambda (,pathname-var) ,@body))))
604
605
606 ;;; Wildcard pathnames
607 (with-upgradability ()
608 (defparameter *wild* (or #+cormanlisp "*" :wild)
609 "Wild component for use with MAKE-PATHNAME")
610 (defparameter *wild-directory-component* (or :wild)
611 "Wild directory component for use with MAKE-PATHNAME")
612 (defparameter *wild-inferiors-component* (or :wild-inferiors)
613 "Wild-inferiors directory component for use with MAKE-PATHNAME")
614 (defparameter *wild-file*
615 (make-pathname :directory nil :name *wild* :type *wild*
616 :version (or #-(or allegro abcl xcl) *wild*))
617 "A pathname object with wildcards for matching any file with TRANSLATE-PATHNAME")
618 (defparameter *wild-file-for-directory*
619 (make-pathname :directory nil :name *wild* :type (or #-(or clisp gcl) *wild*)
620 :version (or #-(or allegro abcl clisp gcl xcl) *wild*))
621 "A pathname object with wildcards for matching any file with DIRECTORY")
622 (defparameter *wild-directory*
623 (make-pathname :directory `(:relative ,*wild-directory-component*)
624 :name nil :type nil :version nil)
625 "A pathname object with wildcards for matching any subdirectory")
626 (defparameter *wild-inferiors*
627 (make-pathname :directory `(:relative ,*wild-inferiors-component*)
628 :name nil :type nil :version nil)
629 "A pathname object with wildcards for matching any recursive subdirectory")
630 (defparameter *wild-path*
631 (merge-pathnames* *wild-file* *wild-inferiors*)
632 "A pathname object with wildcards for matching any file in any recursive subdirectory")
633
634 (defun wilden (path)
635 "From a pathname, return a wildcard pathname matching any file in any subdirectory of given pathname's directory"
636 (merge-pathnames* *wild-path* path)))
637
638
639 ;;; Translate a pathname
640 (with-upgradability ()
641 (defun relativize-directory-component (directory-component)
642 "Given the DIRECTORY-COMPONENT of a pathname, return an otherwise similar relative directory component"
643 (let ((directory (normalize-pathname-directory-component directory-component)))
644 (cond
645 ((stringp directory)
646 (list :relative directory))
647 ((eq (car directory) :absolute)
648 (cons :relative (cdr directory)))
649 (t
650 directory))))
651
652 (defun relativize-pathname-directory (pathspec)
653 "Given a PATHNAME, return a relative pathname with otherwise the same components"
654 (let ((p (pathname pathspec)))
655 (make-pathname
656 :directory (relativize-directory-component (pathname-directory p))
657 :defaults p)))
658
659 (defun directory-separator-for-host (&optional (pathname *default-pathname-defaults*))
660 "Given a PATHNAME, return the character used to delimit directory names on this host and device."
661 (let ((foo (make-pathname :directory '(:absolute "FOO") :defaults pathname)))
662 (last-char (namestring foo))))
663
664 #-scl
665 (defun directorize-pathname-host-device (pathname)
666 "Given a PATHNAME, return a pathname that has representations of its HOST and DEVICE components
667 added to its DIRECTORY component. This is useful for output translations."
668 (os-cond
669 ((os-unix-p)
670 (when (physical-pathname-p pathname)
671 (return-from directorize-pathname-host-device pathname))))
672 (let* ((root (pathname-root pathname))
673 (wild-root (wilden root))
674 (absolute-pathname (merge-pathnames* pathname root))
675 (separator (directory-separator-for-host root))
676 (root-namestring (namestring root))
677 (root-string
678 (substitute-if #\/
679 #'(lambda (x) (or (eql x #\:)
680 (eql x separator)))
681 root-namestring)))
682 (multiple-value-bind (relative path filename)
683 (split-unix-namestring-directory-components root-string :ensure-directory t)
684 (declare (ignore relative filename))
685 (let ((new-base (make-pathname :defaults root :directory `(:absolute ,@path))))
686 (translate-pathname absolute-pathname wild-root (wilden new-base))))))
687
688 #+scl
689 (defun directorize-pathname-host-device (pathname)
690 (let ((scheme (ext:pathname-scheme pathname))
691 (host (pathname-host pathname))
692 (port (ext:pathname-port pathname))
693 (directory (pathname-directory pathname)))
694 (flet ((specificp (x) (and x (not (eq x :unspecific)))))
695 (if (or (specificp port)
696 (and (specificp host) (plusp (length host)))
697 (specificp scheme))
698 (let ((prefix ""))
699 (when (specificp port)
700 (setf prefix (format nil ":~D" port)))
701 (when (and (specificp host) (plusp (length host)))
702 (setf prefix (strcat host prefix)))
703 (setf prefix (strcat ":" prefix))
704 (when (specificp scheme)
705 (setf prefix (strcat scheme prefix)))
706 (assert (and directory (eq (first directory) :absolute)))
707 (make-pathname :directory `(:absolute ,prefix ,@(rest directory))
708 :defaults pathname)))
709 pathname)))
710
711 (defun* (translate-pathname*) (path absolute-source destination &optional root source)
712 "A wrapper around TRANSLATE-PATHNAME to be used by the ASDF output-translations facility.
713 PATH is the pathname to be translated.
714 ABSOLUTE-SOURCE is an absolute pathname to use as source for translate-pathname,
715 DESTINATION is either a function, to be called with PATH and ABSOLUTE-SOURCE,
716 or a relative pathname, to be merged with ROOT and used as destination for translate-pathname
717 or an absolute pathname, to be used as destination for translate-pathname.
718 In that last case, if ROOT is non-NIL, PATH is first transformated by DIRECTORIZE-PATHNAME-HOST-DEVICE."
719 (declare (ignore source))
720 (cond
721 ((functionp destination)
722 (funcall destination path absolute-source))
723 ((eq destination t)
724 path)
725 ((not (pathnamep destination))
726 (parameter-error "~S: Invalid destination" 'translate-pathname*))
727 ((not (absolute-pathname-p destination))
728 (translate-pathname path absolute-source (merge-pathnames* destination root)))
729 (root
730 (translate-pathname (directorize-pathname-host-device path) absolute-source destination))
731 (t
732 (translate-pathname path absolute-source destination))))
733
734 (defvar *output-translation-function* 'identity
735 "Hook for output translations.
736
737 This function needs to be idempotent, so that actions can work
738 whether their inputs were translated or not,
739 which they will be if we are composing operations. e.g. if some
740 create-lisp-op creates a lisp file from some higher-level input,
741 you need to still be able to use compile-op on that lisp file."))