pathname.lisp - clic - Clic is an command line interactive client for gopher written in Common LISP
(HTM) git clone git://bitreich.org/clic/ git://enlrupgkhuxnvlhsf6lc3fziv5h2hhfrinws65d7roiv6bfj7d652fid.onion/clic/
(DIR) Log
(DIR) Files
(DIR) Refs
(DIR) Tags
(DIR) README
(DIR) LICENSE
---
pathname.lisp (37872B)
---
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 lispworks sbcl scl) :unspecific
95 #+(or genera 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 #-genera (file-namestring pathspec)
334 ;; On Genera's native filesystem (LMFS),
335 ;; directories have a type and version
336 ;; which must be ignored when converting
337 ;; to a directory pathname
338 #+genera (if (typep pathspec 'fs:lmfs-pathname)
339 (pathname-name pathspec)
340 (file-namestring pathspec))))
341 :name nil :type nil :version nil :defaults pathspec)
342 (error (c) (call-function on-error (compatfmt "~@<error while trying to create a directory pathname for ~S: ~A~@:>") pathspec c)))))))
343
344
345 ;;; Parsing filenames
346 (with-upgradability ()
347 (declaim (ftype function ensure-pathname)) ; forward reference
348
349 (defun split-unix-namestring-directory-components
350 (unix-namestring &key ensure-directory dot-dot)
351 "Splits the path string UNIX-NAMESTRING, returning four values:
352 A flag that is either :absolute or :relative, indicating
353 how the rest of the values are to be interpreted.
354 A directory path --- a list of strings and keywords, suitable for
355 use with MAKE-PATHNAME when prepended with the flag value.
356 Directory components with an empty name or the name . are removed.
357 Any directory named .. is read as DOT-DOT, or :BACK if it's NIL (not :UP).
358 A last-component, either a file-namestring including type extension,
359 or NIL in the case of a directory pathname.
360 A flag that is true iff the unix-style-pathname was just
361 a file-namestring without / path specification.
362 ENSURE-DIRECTORY forces the namestring to be interpreted as a directory pathname:
363 the third return value will be NIL, and final component of the namestring
364 will be treated as part of the directory path.
365
366 An empty string is thus read as meaning a pathname object with all fields nil.
367
368 Note that colon characters #\: will NOT be interpreted as host specification.
369 Absolute pathnames are only appropriate on Unix-style systems.
370
371 The intention of this function is to support structured component names,
372 e.g., \(:file \"foo/bar\"\), which will be unpacked to relative pathnames."
373 (check-type unix-namestring string)
374 (check-type dot-dot (member nil :back :up))
375 (if (and (not (find #\/ unix-namestring)) (not ensure-directory)
376 (plusp (length unix-namestring)))
377 (values :relative () unix-namestring t)
378 (let* ((components (split-string unix-namestring :separator "/"))
379 (last-comp (car (last components))))
380 (multiple-value-bind (relative components)
381 (if (equal (first components) "")
382 (if (equal (first-char unix-namestring) #\/)
383 (values :absolute (cdr components))
384 (values :relative nil))
385 (values :relative components))
386 (setf components (remove-if #'(lambda (x) (member x '("" ".") :test #'equal))
387 components))
388 (setf components (substitute (or dot-dot :back) ".." components :test #'equal))
389 (cond
390 ((equal last-comp "")
391 (values relative components nil nil)) ; "" already removed from components
392 (ensure-directory
393 (values relative components nil nil))
394 (t
395 (values relative (butlast components) last-comp nil)))))))
396
397 (defun split-name-type (filename)
398 "Split a filename into two values NAME and TYPE that are returned.
399 We assume filename has no directory component.
400 The last . if any separates name and type from from type,
401 except that if there is only one . and it is in first position,
402 the whole filename is the NAME with an empty type.
403 NAME is always a string.
404 For an empty type, *UNSPECIFIC-PATHNAME-TYPE* is returned."
405 (check-type filename string)
406 (assert (plusp (length filename)))
407 (destructuring-bind (name &optional (type *unspecific-pathname-type*))
408 (split-string filename :max 2 :separator ".")
409 (if (equal name "")
410 (values filename *unspecific-pathname-type*)
411 (values name type))))
412
413 (defun parse-unix-namestring (name &rest keys &key type defaults dot-dot ensure-directory
414 &allow-other-keys)
415 "Coerce NAME into a PATHNAME using standard Unix syntax.
416
417 Unix syntax is used whether or not the underlying system is Unix;
418 on such non-Unix systems it is reliably usable only for relative pathnames.
419 This function is especially useful to manipulate relative pathnames portably,
420 where it is of crucial to possess a portable pathname syntax independent of the underlying OS.
421 This is what PARSE-UNIX-NAMESTRING provides, and why we use it in ASDF.
422
423 When given a PATHNAME object, just return it untouched.
424 When given NIL, just return NIL.
425 When given a non-null SYMBOL, first downcase its name and treat it as a string.
426 When given a STRING, portably decompose it into a pathname as below.
427
428 #\\/ separates directory components.
429
430 The last #\\/-separated substring is interpreted as follows:
431 1- If TYPE is :DIRECTORY or ENSURE-DIRECTORY is true,
432 the string is made the last directory component, and NAME and TYPE are NIL.
433 if the string is empty, it's the empty pathname with all slots NIL.
434 2- If TYPE is NIL, the substring is a file-namestring, and its NAME and TYPE
435 are separated by SPLIT-NAME-TYPE.
436 3- If TYPE is a string, it is the given TYPE, and the whole string is the NAME.
437
438 Directory components with an empty name or the name \".\" are removed.
439 Any directory named \"..\" is read as DOT-DOT,
440 which must be one of :BACK or :UP and defaults to :BACK.
441
442 HOST, DEVICE and VERSION components are taken from DEFAULTS,
443 which itself defaults to *NIL-PATHNAME*, also used if DEFAULTS is NIL.
444 No host or device can be specified in the string itself,
445 which makes it unsuitable for absolute pathnames outside Unix.
446
447 For relative pathnames, these components (and hence the defaults) won't matter
448 if you use MERGE-PATHNAMES* but will matter if you use MERGE-PATHNAMES,
449 which is an important reason to always use MERGE-PATHNAMES*.
450
451 Arbitrary keys are accepted, and the parse result is passed to ENSURE-PATHNAME
452 with those keys, removing TYPE DEFAULTS and DOT-DOT.
453 When you're manipulating pathnames that are supposed to make sense portably
454 even though the OS may not be Unixish, we recommend you use :WANT-RELATIVE T
455 to throw an error if the pathname is absolute"
456 (block nil
457 (check-type type (or null string (eql :directory)))
458 (when ensure-directory
459 (setf type :directory))
460 (etypecase name
461 ((or null pathname) (return name))
462 (symbol
463 (setf name (string-downcase name)))
464 (string))
465 (multiple-value-bind (relative path filename file-only)
466 (split-unix-namestring-directory-components
467 name :dot-dot dot-dot :ensure-directory (eq type :directory))
468 (multiple-value-bind (name type)
469 (cond
470 ((or (eq type :directory) (null filename))
471 (values nil nil))
472 (type
473 (values filename type))
474 (t
475 (split-name-type filename)))
476 (apply 'ensure-pathname
477 (make-pathname
478 :directory (unless file-only (cons relative path))
479 :name name :type type
480 :defaults (or #-mcl defaults *nil-pathname*))
481 (remove-plist-keys '(:type :dot-dot :defaults) keys))))))
482
483 (defun unix-namestring (pathname)
484 "Given a non-wild PATHNAME, return a Unix-style namestring for it.
485 If the PATHNAME is NIL or a STRING, return it unchanged.
486
487 This only considers the DIRECTORY, NAME and TYPE components of the pathname.
488 This is a portable solution for representing relative pathnames,
489 But unless you are running on a Unix system, it is not a general solution
490 to representing native pathnames.
491
492 An error is signaled if the argument is not NULL, a STRING or a PATHNAME,
493 or if it is a PATHNAME but some of its components are not recognized."
494 (etypecase pathname
495 ((or null string) pathname)
496 (pathname
497 (with-output-to-string (s)
498 (flet ((err () (parameter-error "~S: invalid unix-namestring ~S"
499 'unix-namestring pathname)))
500 (let* ((dir (normalize-pathname-directory-component (pathname-directory pathname)))
501 (name (pathname-name pathname))
502 (name (and (not (eq name :unspecific)) name))
503 (type (pathname-type pathname))
504 (type (and (not (eq type :unspecific)) type)))
505 (cond
506 ((member dir '(nil :unspecific)))
507 ((eq dir '(:relative)) (princ "./" s))
508 ((consp dir)
509 (destructuring-bind (relabs &rest dirs) dir
510 (or (member relabs '(:relative :absolute)) (err))
511 (when (eq relabs :absolute) (princ #\/ s))
512 (loop :for x :in dirs :do
513 (cond
514 ((member x '(:back :up)) (princ "../" s))
515 ((equal x "") (err))
516 ;;((member x '("." "..") :test 'equal) (err))
517 ((stringp x) (format s "~A/" x))
518 (t (err))))))
519 (t (err)))
520 (cond
521 (name
522 (unless (and (stringp name) (or (null type) (stringp type))) (err))
523 (format s "~A~@[.~A~]" name type))
524 (t
525 (or (null type) (err)))))))))))
526
527 ;;; Absolute and relative pathnames
528 (with-upgradability ()
529 (defun subpathname (pathname subpath &key type)
530 "This function takes a PATHNAME and a SUBPATH and a TYPE.
531 If SUBPATH is already a PATHNAME object (not namestring),
532 and is an absolute pathname at that, it is returned unchanged;
533 otherwise, SUBPATH is turned into a relative pathname with given TYPE
534 as per PARSE-UNIX-NAMESTRING with :WANT-RELATIVE T :TYPE TYPE,
535 then it is merged with the PATHNAME-DIRECTORY-PATHNAME of PATHNAME."
536 (or (and (pathnamep subpath) (absolute-pathname-p subpath))
537 (merge-pathnames* (parse-unix-namestring subpath :type type :want-relative t)
538 (pathname-directory-pathname pathname))))
539
540 (defun subpathname* (pathname subpath &key type)
541 "returns NIL if the base pathname is NIL, otherwise like SUBPATHNAME."
542 (and pathname
543 (subpathname (ensure-directory-pathname pathname) subpath :type type)))
544
545 (defun pathname-root (pathname)
546 "return the root directory for the host and device of given PATHNAME"
547 (make-pathname :directory '(:absolute)
548 :name nil :type nil :version nil
549 :defaults pathname ;; host device, and on scl, *some*
550 ;; scheme-specific parts: port username password, not others:
551 . #.(or #+scl '(:parameters nil :query nil :fragment nil))))
552
553 (defun pathname-host-pathname (pathname)
554 "return a pathname with the same host as given PATHNAME, and all other fields NIL"
555 (make-pathname :directory nil
556 :name nil :type nil :version nil :device nil
557 :defaults pathname ;; host device, and on scl, *some*
558 ;; scheme-specific parts: port username password, not others:
559 . #.(or #+scl '(:parameters nil :query nil :fragment nil))))
560
561 (defun ensure-absolute-pathname (path &optional defaults (on-error 'error))
562 "Given a pathname designator PATH, return an absolute pathname as specified by PATH
563 considering the DEFAULTS, or, if not possible, use CALL-FUNCTION on the specified ON-ERROR behavior,
564 with a format control-string and other arguments as arguments"
565 (cond
566 ((absolute-pathname-p path))
567 ((stringp path) (ensure-absolute-pathname (pathname path) defaults on-error))
568 ((not (pathnamep path)) (call-function on-error "not a valid pathname designator ~S" path))
569 ((let ((default-pathname (if (pathnamep defaults) defaults (call-function defaults))))
570 (or (if (absolute-pathname-p default-pathname)
571 (absolute-pathname-p (merge-pathnames* path default-pathname))
572 (call-function on-error "Default pathname ~S is not an absolute pathname"
573 default-pathname))
574 (call-function on-error "Failed to merge ~S with ~S into an absolute pathname"
575 path default-pathname))))
576 (t (call-function on-error
577 "Cannot ensure ~S is evaluated as an absolute pathname with defaults ~S"
578 path defaults))))
579
580 (defun subpathp (maybe-subpath base-pathname)
581 "if MAYBE-SUBPATH is a pathname that is under BASE-PATHNAME, return a pathname object that
582 when used with MERGE-PATHNAMES* with defaults BASE-PATHNAME, returns MAYBE-SUBPATH."
583 (and (pathnamep maybe-subpath) (pathnamep base-pathname)
584 (absolute-pathname-p maybe-subpath) (absolute-pathname-p base-pathname)
585 (directory-pathname-p base-pathname) (not (wild-pathname-p base-pathname))
586 (pathname-equal (pathname-root maybe-subpath) (pathname-root base-pathname))
587 (with-pathname-defaults (*nil-pathname*)
588 (let ((enough (enough-namestring maybe-subpath base-pathname)))
589 (and (relative-pathname-p enough) (pathname enough))))))
590
591 (defun enough-pathname (maybe-subpath base-pathname)
592 "if MAYBE-SUBPATH is a pathname that is under BASE-PATHNAME, return a pathname object that
593 when used with MERGE-PATHNAMES* with defaults BASE-PATHNAME, returns MAYBE-SUBPATH."
594 (let ((sub (when maybe-subpath (pathname maybe-subpath)))
595 (base (when base-pathname (ensure-absolute-pathname (pathname base-pathname)))))
596 (or (and base (subpathp sub base)) sub)))
597
598 (defun call-with-enough-pathname (maybe-subpath defaults-pathname thunk)
599 "In a context where *DEFAULT-PATHNAME-DEFAULTS* is bound to DEFAULTS-PATHNAME (if not null,
600 or else to its current value), call THUNK with ENOUGH-PATHNAME for MAYBE-SUBPATH
601 given DEFAULTS-PATHNAME as a base pathname."
602 (let ((enough (enough-pathname maybe-subpath defaults-pathname))
603 (*default-pathname-defaults* (or defaults-pathname *default-pathname-defaults*)))
604 (funcall thunk enough)))
605
606 (defmacro with-enough-pathname ((pathname-var &key (pathname pathname-var)
607 (defaults *default-pathname-defaults*))
608 &body body)
609 "Shorthand syntax for CALL-WITH-ENOUGH-PATHNAME"
610 `(call-with-enough-pathname ,pathname ,defaults #'(lambda (,pathname-var) ,@body))))
611
612
613 ;;; Wildcard pathnames
614 (with-upgradability ()
615 (defparameter *wild* (or #+cormanlisp "*" :wild)
616 "Wild component for use with MAKE-PATHNAME")
617 (defparameter *wild-directory-component* (or :wild)
618 "Wild directory component for use with MAKE-PATHNAME")
619 (defparameter *wild-inferiors-component* (or :wild-inferiors)
620 "Wild-inferiors directory component for use with MAKE-PATHNAME")
621 (defparameter *wild-file*
622 (make-pathname :directory nil :name *wild* :type *wild*
623 :version (or #-(or allegro abcl xcl) *wild*))
624 "A pathname object with wildcards for matching any file with TRANSLATE-PATHNAME")
625 (defparameter *wild-file-for-directory*
626 (make-pathname :directory nil :name *wild* :type (or #-(or clisp gcl) *wild*)
627 :version (or #-(or allegro abcl clisp gcl xcl) *wild*))
628 "A pathname object with wildcards for matching any file with DIRECTORY")
629 (defparameter *wild-directory*
630 (make-pathname :directory `(:relative ,*wild-directory-component*)
631 :name nil :type nil :version nil)
632 "A pathname object with wildcards for matching any subdirectory")
633 (defparameter *wild-inferiors*
634 (make-pathname :directory `(:relative ,*wild-inferiors-component*)
635 :name nil :type nil :version nil)
636 "A pathname object with wildcards for matching any recursive subdirectory")
637 (defparameter *wild-path*
638 (merge-pathnames* *wild-file* *wild-inferiors*)
639 "A pathname object with wildcards for matching any file in any recursive subdirectory")
640
641 (defun wilden (path)
642 "From a pathname, return a wildcard pathname matching any file in any subdirectory of given pathname's directory"
643 (merge-pathnames* *wild-path* path)))
644
645
646 ;;; Translate a pathname
647 (with-upgradability ()
648 (defun relativize-directory-component (directory-component)
649 "Given the DIRECTORY-COMPONENT of a pathname, return an otherwise similar relative directory component"
650 (let ((directory (normalize-pathname-directory-component directory-component)))
651 (cond
652 ((stringp directory)
653 (list :relative directory))
654 ((eq (car directory) :absolute)
655 (cons :relative (cdr directory)))
656 (t
657 directory))))
658
659 (defun relativize-pathname-directory (pathspec)
660 "Given a PATHNAME, return a relative pathname with otherwise the same components"
661 (let ((p (pathname pathspec)))
662 (make-pathname
663 :directory (relativize-directory-component (pathname-directory p))
664 :defaults p)))
665
666 (defun directory-separator-for-host (&optional (pathname *default-pathname-defaults*))
667 "Given a PATHNAME, return the character used to delimit directory names on this host and device."
668 (let ((foo (make-pathname :directory '(:absolute "FOO") :defaults pathname)))
669 (last-char (namestring foo))))
670
671 #-scl
672 (defun directorize-pathname-host-device (pathname)
673 "Given a PATHNAME, return a pathname that has representations of its HOST and DEVICE components
674 added to its DIRECTORY component. This is useful for output translations."
675 (os-cond
676 ((os-unix-p)
677 (when (physical-pathname-p pathname)
678 (return-from directorize-pathname-host-device pathname))))
679 (let* ((root (pathname-root pathname))
680 (wild-root (wilden root))
681 (absolute-pathname (merge-pathnames* pathname root))
682 (separator (directory-separator-for-host root))
683 (root-namestring (namestring root))
684 (root-string
685 (substitute-if #\/
686 #'(lambda (x) (or (eql x #\:)
687 (eql x separator)))
688 root-namestring)))
689 (multiple-value-bind (relative path filename)
690 (split-unix-namestring-directory-components root-string :ensure-directory t)
691 (declare (ignore relative filename))
692 (let ((new-base (make-pathname :defaults root :directory `(:absolute ,@path))))
693 (translate-pathname absolute-pathname wild-root (wilden new-base))))))
694
695 #+scl
696 (defun directorize-pathname-host-device (pathname)
697 (let ((scheme (ext:pathname-scheme pathname))
698 (host (pathname-host pathname))
699 (port (ext:pathname-port pathname))
700 (directory (pathname-directory pathname)))
701 (flet ((specificp (x) (and x (not (eq x :unspecific)))))
702 (if (or (specificp port)
703 (and (specificp host) (plusp (length host)))
704 (specificp scheme))
705 (let ((prefix ""))
706 (when (specificp port)
707 (setf prefix (format nil ":~D" port)))
708 (when (and (specificp host) (plusp (length host)))
709 (setf prefix (strcat host prefix)))
710 (setf prefix (strcat ":" prefix))
711 (when (specificp scheme)
712 (setf prefix (strcat scheme prefix)))
713 (assert (and directory (eq (first directory) :absolute)))
714 (make-pathname :directory `(:absolute ,prefix ,@(rest directory))
715 :defaults pathname)))
716 pathname)))
717
718 (defun* (translate-pathname*) (path absolute-source destination &optional root source)
719 "A wrapper around TRANSLATE-PATHNAME to be used by the ASDF output-translations facility.
720 PATH is the pathname to be translated.
721 ABSOLUTE-SOURCE is an absolute pathname to use as source for translate-pathname,
722 DESTINATION is either a function, to be called with PATH and ABSOLUTE-SOURCE,
723 or a relative pathname, to be merged with ROOT and used as destination for translate-pathname
724 or an absolute pathname, to be used as destination for translate-pathname.
725 In that last case, if ROOT is non-NIL, PATH is first transformated by DIRECTORIZE-PATHNAME-HOST-DEVICE."
726 (declare (ignore source))
727 (cond
728 ((functionp destination)
729 (funcall destination path absolute-source))
730 ((eq destination t)
731 path)
732 ((not (pathnamep destination))
733 (parameter-error "~S: Invalid destination" 'translate-pathname*))
734 ((not (absolute-pathname-p destination))
735 (translate-pathname path absolute-source (merge-pathnames* destination root)))
736 (root
737 (translate-pathname (directorize-pathname-host-device path) absolute-source destination))
738 (t
739 (translate-pathname path absolute-source destination))))
740
741 (defvar *output-translation-function* 'identity
742 "Hook for output translations.
743
744 This function needs to be idempotent, so that actions can work
745 whether their inputs were translated or not,
746 which they will be if we are composing operations. e.g. if some
747 create-lisp-op creates a lisp file from some higher-level input,
748 you need to still be able to use compile-op on that lisp file."))