1 ;;;; machine/filesystem-independent pathname functions
3 ;;;; This software is part of the SBCL system. See the README file for
6 ;;;; This software is derived from the CMU CL system, which was
7 ;;;; written at Carnegie Mellon University and released into the
8 ;;;; public domain. The software is in the public domain and is
9 ;;;; provided with absolutely no warranty. See the COPYING and CREDITS
10 ;;;; files for more information.
12 (in-package "SB!IMPL")
14 #!-sb-fluid
(declaim (freeze-type logical-pathname logical-host
))
16 ;;; To be initialized in unix/win32-pathname.lisp
17 (defvar *physical-host
*)
19 (defun make-host-load-form (host)
20 (declare (ignore host
))
23 ;;; Return a value suitable, e.g., for preinitializing
24 ;;; *DEFAULT-PATHNAME-DEFAULTS* before *DEFAULT-PATHNAME-DEFAULTS* is
25 ;;; initialized (at which time we can't safely call e.g. #'PATHNAME).
26 (defun make-trivial-default-pathname ()
27 (%make-pathname
*physical-host
* nil nil nil nil
:newest
))
31 (def!method print-object
((pathname pathname
) stream
)
32 (let ((namestring (handler-case (namestring pathname
)
36 (if (or *print-readably
* *print-escape
*)
39 (coerce namestring
'(simple-array character
(*))))
40 (print-unreadable-object (pathname stream
:type t
)
42 "~@<(with no namestring) ~_:HOST ~S ~_:DEVICE ~S ~_:DIRECTORY ~S ~
43 ~_:NAME ~S ~_:TYPE ~S ~_:VERSION ~S~:>"
44 (%pathname-host pathname
)
45 (%pathname-device pathname
)
46 (%pathname-directory pathname
)
47 (%pathname-name pathname
)
48 (%pathname-type pathname
)
49 (%pathname-version pathname
))))))
51 (def!method make-load-form
((pathname pathname
) &optional environment
)
52 (make-load-form-saving-slots pathname
:environment environment
))
54 ;;; A pathname is logical if the host component is a logical host.
55 ;;; This constructor is used to make an instance of the correct type
56 ;;; from parsed arguments.
57 (defun %make-maybe-logical-pathname
(host device directory name type version
)
58 ;; We canonicalize logical pathname components to uppercase. ANSI
59 ;; doesn't strictly require this, leaving it up to the implementor;
60 ;; but the arguments given in the X3J13 cleanup issue
61 ;; PATHNAME-LOGICAL:ADD seem compelling: we should canonicalize the
62 ;; case, and uppercase is the ordinary way to do that.
63 (flet ((upcase-maybe (x) (typecase x
(string (logical-word-or-lose x
)) (t x
))))
64 (if (typep host
'logical-host
)
65 (%make-logical-pathname host
67 (mapcar #'upcase-maybe directory
)
72 (aver (eq host
*physical-host
*))
73 (%make-pathname host device directory name type version
)))))
75 ;;; Hash table searching maps a logical pathname's host to its
76 ;;; physical pathname translation.
77 (defvar *logical-hosts
* (make-hash-table :test
'equal
:synchronized t
))
81 (def!method make-load-form
((pattern pattern
) &optional environment
)
82 (make-load-form-saving-slots pattern
:environment environment
))
84 (def!method print-object
((pattern pattern
) stream
)
85 (print-unreadable-object (pattern stream
:type t
)
87 (let ((*print-escape
* t
))
88 (pprint-fill stream
(pattern-pieces pattern
) nil
))
89 (prin1 (pattern-pieces pattern
) stream
))))
91 (defun pattern= (pattern1 pattern2
)
92 (declare (type pattern pattern1 pattern2
))
93 (let ((pieces1 (pattern-pieces pattern1
))
94 (pieces2 (pattern-pieces pattern2
)))
95 (and (= (length pieces1
) (length pieces2
))
96 (every (lambda (piece1 piece2
)
99 (and (simple-string-p piece2
)
100 (string= piece1 piece2
)))
103 (eq (car piece1
) (car piece2
))
104 (string= (cdr piece1
) (cdr piece2
))))
106 (eq piece1 piece2
))))
110 ;;; If the string matches the pattern returns the multiple values T
111 ;;; and a list of the matched strings.
112 (defun pattern-matches (pattern string
)
113 (declare (type pattern pattern
)
114 (type simple-string string
))
115 (let ((len (length string
)))
116 (labels ((maybe-prepend (subs cur-sub chars
)
118 (let* ((len (length chars
))
119 (new (make-string len
))
122 (setf (schar new
(decf index
)) char
))
125 (matches (pieces start subs cur-sub chars
)
128 (values t
(maybe-prepend subs cur-sub chars
))
130 (let ((piece (car pieces
)))
133 (let ((end (+ start
(length piece
))))
135 (string= piece string
136 :start2 start
:end2 end
)
137 (matches (cdr pieces
) end
138 (maybe-prepend subs cur-sub chars
)
144 (let ((char (schar string start
)))
145 (if (find char
(cdr piece
) :test
#'char
=)
146 (matches (cdr pieces
) (1+ start
) subs t
147 (cons char chars
))))))))
148 ((member :single-char-wild
)
150 (matches (cdr pieces
) (1+ start
) subs t
151 (cons (schar string start
) chars
))))
152 ((member :multi-char-wild
)
153 (multiple-value-bind (won new-subs
)
154 (matches (cdr pieces
) start subs t chars
)
158 (matches pieces
(1+ start
) subs t
159 (cons (schar string start
)
161 (multiple-value-bind (won subs
)
162 (matches (pattern-pieces pattern
) 0 nil nil nil
)
163 (values won
(reverse subs
))))))
165 ;;; PATHNAME-MATCH-P for directory components
166 (defun directory-components-match (thing wild
)
169 ;; If THING has a null directory, assume that it matches
170 ;; (:ABSOLUTE :WILD-INFERIORS) or (:RELATIVE :WILD-INFERIORS).
173 (member (first wild
) '(:absolute
:relative
))
174 (eq (second wild
) :wild-inferiors
))
176 (let ((wild1 (first wild
)))
177 (if (eq wild1
:wild-inferiors
)
178 (let ((wild-subdirs (rest wild
)))
179 (or (null wild-subdirs
)
181 (when (directory-components-match thing wild-subdirs
)
184 (unless thing
(return nil
)))))
186 (components-match (first thing
) wild1
)
187 (directory-components-match (rest thing
)
190 ;;; Return true if pathname component THING is matched by WILD. (not
192 (defun components-match (thing wild
)
193 (declare (type (or pattern symbol simple-string integer
) thing wild
))
198 ;; String is matched by itself, a matching pattern or :WILD.
201 (values (pattern-matches wild thing
)))
203 (string= thing wild
))))
205 ;; A pattern is only matched by an identical pattern.
206 (and (pattern-p wild
) (pattern= thing wild
)))
208 ;; An integer (version number) is matched by :WILD or the
209 ;; same integer. This branch will actually always be NIL as
210 ;; long as the version is a fixnum.
213 ;;; a predicate for comparing two pathname slot component sub-entries
214 (defun compare-component (this that
)
218 (and (simple-string-p that
)
219 (string= this that
)))
221 (and (pattern-p that
)
222 (pattern= this that
)))
225 (compare-component (car this
) (car that
))
226 (compare-component (cdr this
) (cdr that
)))))))
228 ;;;; pathname functions
230 (defun pathname= (pathname1 pathname2
)
231 (declare (type pathname pathname1
)
232 (type pathname pathname2
))
233 (or (eq pathname1 pathname2
)
234 (and (eq (%pathname-host pathname1
)
235 (%pathname-host pathname2
))
236 (compare-component (%pathname-device pathname1
)
237 (%pathname-device pathname2
))
238 (compare-component (%pathname-directory pathname1
)
239 (%pathname-directory pathname2
))
240 (compare-component (%pathname-name pathname1
)
241 (%pathname-name pathname2
))
242 (compare-component (%pathname-type pathname1
)
243 (%pathname-type pathname2
))
244 (or (eq (%pathname-host pathname1
) *physical-host
*)
245 (compare-component (%pathname-version pathname1
)
246 (%pathname-version pathname2
))))))
248 ;;; Convert PATHNAME-DESIGNATOR (a pathname, or string, or
249 ;;; stream), into a pathname in pathname.
251 ;;; FIXME: was rewritten, should be tested (or rewritten again, this
252 ;;; time using ONCE-ONLY, *then* tested)
253 (eval-when (:compile-toplevel
:execute
)
254 (sb!xc
:defmacro with-pathname
((pathname pathname-designator
) &body body
)
255 (let ((pd0 (gensym)))
256 `(let* ((,pd0
,pathname-designator
)
257 (,pathname
(etypecase ,pd0
259 (string (parse-namestring ,pd0
))
260 (file-stream (file-name ,pd0
)))))
263 (sb!xc
:defmacro with-native-pathname
((pathname pathname-designator
) &body body
)
264 (let ((pd0 (gensym)))
265 `(let* ((,pd0
,pathname-designator
)
266 (,pathname
(etypecase ,pd0
268 (string (parse-native-namestring ,pd0
))
271 (file-stream (file-name ,pd0
)))))
274 (sb!xc
:defmacro with-host
((host host-designator
) &body body
)
275 ;; Generally, redundant specification of information in software,
276 ;; whether in code or in comments, is bad. However, the ANSI spec
277 ;; for this is messy enough that it's hard to hold in short-term
278 ;; memory, so I've recorded these redundant notes on the
279 ;; implications of the ANSI spec.
281 ;; According to the ANSI spec, HOST can be a valid pathname host, or
282 ;; a logical host, or NIL.
284 ;; A valid pathname host can be a valid physical pathname host or a
285 ;; valid logical pathname host.
287 ;; A valid physical pathname host is "any of a string, a list of
288 ;; strings, or the symbol :UNSPECIFIC, that is recognized by the
289 ;; implementation as the name of a host". In SBCL as of 0.6.9.8,
290 ;; that means :UNSPECIFIC: though someday we might want to
291 ;; generalize it to allow strings like "RTFM.MIT.EDU" or lists like
292 ;; '("RTFM" "MIT" "EDU"), that's not supported now.
294 ;; A valid logical pathname host is a string which has been defined as
295 ;; the name of a logical host, as with LOAD-LOGICAL-PATHNAME-TRANSLATIONS.
297 ;; A logical host is an object of implementation-dependent nature. In
298 ;; SBCL, it's a member of the HOST class (a subclass of STRUCTURE-OBJECT).
299 (let ((hd0 (gensym)))
300 `(let* ((,hd0
,host-designator
)
301 (,host
(etypecase ,hd0
303 ;; This is a special host. It's not valid as a
304 ;; logical host, so it is a sensible thing to
305 ;; designate the physical host object. So we do
309 ;; In general ANSI-compliant Common Lisps, a
310 ;; string might also be a physical pathname
311 ;; host, but ANSI leaves this up to the
312 ;; implementor, and in SBCL we don't do it, so
313 ;; it must be a logical host.
314 (find-logical-host ,hd0
))
315 ((or null
(member :unspecific
))
316 ;; CLHS says that HOST=:UNSPECIFIC has
317 ;; implementation-defined behavior. We
318 ;; just turn it into NIL.
321 ;; ANSI also allows LISTs to designate hosts,
322 ;; but leaves its interpretation
323 ;; implementation-defined. Our interpretation
324 ;; is that it's unsupported.:-|
325 (error "A LIST representing a pathname host is not ~
326 supported in this implementation:~% ~S"
332 (defun find-host (host-designator &optional
(errorp t
))
333 (with-host (host host-designator
)
334 (when (and errorp
(not host
))
335 (error "Couldn't find host: ~S" host-designator
))
338 (defun pathname (pathspec)
340 "Convert PATHSPEC (a pathname designator) into a pathname."
341 (declare (type pathname-designator pathspec
))
342 (with-pathname (pathname pathspec
)
345 (defun native-pathname (pathspec)
347 "Convert PATHSPEC (a pathname designator) into a pathname, assuming
348 the operating system native pathname conventions."
349 (with-native-pathname (pathname pathspec
)
352 ;;; Change the case of thing if DIDDLE-P.
353 (defun maybe-diddle-case (thing diddle-p
)
354 (if (and diddle-p
(not (or (symbolp thing
) (integerp thing
))))
355 (labels ((check-for (pred in
)
358 (dolist (piece (pattern-pieces in
))
359 (when (typecase piece
361 (check-for pred piece
))
365 (check-for pred
(cdr piece
))))))
369 (when (check-for pred x
)
372 (dotimes (i (length in
))
373 (when (funcall pred
(schar in i
))
376 (diddle-with (fun thing
)
380 (mapcar (lambda (piece)
388 (funcall fun
(cdr piece
))))
393 (pattern-pieces thing
))))
400 (let ((any-uppers (check-for #'upper-case-p thing
))
401 (any-lowers (check-for #'lower-case-p thing
)))
402 (cond ((and any-uppers any-lowers
)
403 ;; mixed case, stays the same
406 ;; all uppercase, becomes all lower case
407 (diddle-with (lambda (x) (if (stringp x
)
411 ;; all lowercase, becomes all upper case
412 (diddle-with (lambda (x) (if (stringp x
)
416 ;; no letters? I guess just leave it.
420 (defun merge-directories (dir1 dir2 diddle-case
)
421 (if (or (eq (car dir1
) :absolute
)
426 (if (and (eq dir
:back
)
428 (typep (car results
) '(or string pattern
429 (member :wild
:wild-inferiors
))))
431 (push dir results
))))
432 (dolist (dir (maybe-diddle-case dir2 diddle-case
))
434 (dolist (dir (cdr dir1
))
438 (defun merge-pathnames (pathname
440 (defaults *default-pathname-defaults
*)
441 (default-version :newest
))
443 "Construct a filled in pathname by completing the unspecified components
445 (declare (type pathname-designator pathname
)
446 (type pathname-designator defaults
)
448 (with-pathname (defaults defaults
)
449 (let ((pathname (let ((*default-pathname-defaults
* defaults
))
450 (pathname pathname
))))
451 (let* ((default-host (%pathname-host defaults
))
452 (pathname-host (%pathname-host pathname
))
454 (and default-host pathname-host
455 (not (eq (host-customary-case default-host
)
456 (host-customary-case pathname-host
)))))
457 (directory (merge-directories (%pathname-directory pathname
)
458 (%pathname-directory defaults
)
460 (%make-maybe-logical-pathname
461 (or pathname-host default-host
)
462 (and ;; The device of ~/ shouldn't be merged,
463 ;; because the expansion may have a different device
464 (not (and (>= (length directory
) 2)
465 (eql (car directory
) :absolute
)
466 (eql (cadr directory
) :home
)))
467 (or (%pathname-device pathname
)
468 (maybe-diddle-case (%pathname-device defaults
)
471 (or (%pathname-name pathname
)
472 (maybe-diddle-case (%pathname-name defaults
)
474 (or (%pathname-type pathname
)
475 (maybe-diddle-case (%pathname-type defaults
)
477 (or (%pathname-version pathname
)
478 (and (not (%pathname-name pathname
)) (%pathname-version defaults
))
479 default-version
))))))
481 (defun import-directory (directory diddle-case
)
484 ((member :wild
) '(:absolute
:wild-inferiors
))
485 ((member :unspecific
) '(:relative
))
487 (let ((root (pop directory
))
489 (if (member root
'(:relative
:absolute
))
491 (error "List of directory components must start with ~S or ~S."
492 :absolute
:relative
))
494 (let ((next (car directory
)))
495 (when (or (eq :home next
)
496 (typep next
'(cons (eql :home
) (cons string null
))))
497 (push (pop directory
) results
)))
498 (dolist (piece directory
)
500 ((member :wild
:wild-inferiors
:up
)
501 (push piece results
))
503 (if (typep (car results
) '(or string pattern
504 (member :wild
:wild-inferiors
)))
506 (push piece results
)))
507 ((or simple-string pattern
)
508 (push (maybe-diddle-case piece diddle-case
) results
))
510 (push (maybe-diddle-case (coerce piece
'simple-string
)
511 diddle-case
) results
))
514 (error "~S is not allowed as a directory component." piece
)))))
517 `(:absolute
,(maybe-diddle-case directory diddle-case
)))
520 ,(maybe-diddle-case (coerce directory
'simple-string
) diddle-case
)))))
522 (defun make-pathname (&key host
527 (version nil versionp
)
531 "Makes a new pathname from the component arguments. Note that host is
532 a host-structure or string."
533 (declare (type (or string host pathname-component-tokens
) host
)
534 (type (or string pathname-component-tokens
) device
)
535 (type (or list string pattern pathname-component-tokens
) directory
)
536 (type (or string pattern pathname-component-tokens
) name type
)
537 (type (or integer pathname-component-tokens
(member :newest
))
539 (type (or pathname-designator null
) defaults
)
540 (type (member :common
:local
) case
))
541 (let* ((defaults (when defaults
542 (with-pathname (defaults defaults
) defaults
)))
543 (default-host (if defaults
544 (%pathname-host defaults
)
545 (pathname-host *default-pathname-defaults
*)))
546 ;; Raymond Toy writes: CLHS says make-pathname can take a
547 ;; string (as a logical-host) for the host part. We map that
548 ;; string into the corresponding logical host structure.
550 ;; Paul Werkowski writes:
551 ;; HyperSpec says for the arg to MAKE-PATHNAME;
552 ;; "host---a valid physical pathname host. ..."
553 ;; where it probably means -- a valid pathname host.
554 ;; "valid pathname host n. a valid physical pathname host or
555 ;; a valid logical pathname host."
557 ;; "valid physical pathname host n. any of a string,
558 ;; a list of strings, or the symbol :unspecific,
559 ;; that is recognized by the implementation as the name of a host."
560 ;; "valid logical pathname host n. a string that has been defined
561 ;; as the name of a logical host. ..."
562 ;; HS is silent on what happens if the :HOST arg is NOT one of these.
563 ;; It seems an error message is appropriate.
564 (host (or (find-host host nil
) default-host
))
565 (diddle-args (and (eq (host-customary-case host
) :lower
)
568 (not (eq (host-customary-case host
)
569 (host-customary-case default-host
))))
570 (dev (if devp device
(if defaults
(%pathname-device defaults
))))
571 (dir (import-directory directory diddle-args
))
574 (defaults (%pathname-version defaults
))
576 (when (and defaults
(not dirp
))
578 (merge-directories dir
579 (%pathname-directory defaults
)
582 (macrolet ((pick (var varp field
)
583 `(cond ((or (simple-string-p ,var
)
585 (maybe-diddle-case ,var diddle-args
))
587 (maybe-diddle-case (coerce ,var
'simple-string
)
590 (maybe-diddle-case ,var diddle-args
))
592 (maybe-diddle-case (,field defaults
)
596 (%make-maybe-logical-pathname host
597 dev
; forced to :UNSPECIFIC when logical
599 (pick name namep %pathname-name
)
600 (pick type typep %pathname-type
)
603 (defun pathname-host (pathname &key
(case :local
))
605 "Return PATHNAME's host."
606 (declare (type pathname-designator pathname
)
607 (type (member :local
:common
) case
)
610 (with-pathname (pathname pathname
)
611 (%pathname-host pathname
)))
613 (defun pathname-device (pathname &key
(case :local
))
615 "Return PATHNAME's device."
616 (declare (type pathname-designator pathname
)
617 (type (member :local
:common
) case
))
618 (with-pathname (pathname pathname
)
619 (maybe-diddle-case (%pathname-device pathname
)
620 (and (eq case
:common
)
621 (eq (host-customary-case
622 (%pathname-host pathname
))
625 (defun pathname-directory (pathname &key
(case :local
))
627 "Return PATHNAME's directory."
628 (declare (type pathname-designator pathname
)
629 (type (member :local
:common
) case
))
630 (with-pathname (pathname pathname
)
631 (maybe-diddle-case (%pathname-directory pathname
)
632 (and (eq case
:common
)
633 (eq (host-customary-case
634 (%pathname-host pathname
))
636 (defun pathname-name (pathname &key
(case :local
))
638 "Return PATHNAME's name."
639 (declare (type pathname-designator pathname
)
640 (type (member :local
:common
) case
))
641 (with-pathname (pathname pathname
)
642 (maybe-diddle-case (%pathname-name pathname
)
643 (and (eq case
:common
)
644 (eq (host-customary-case
645 (%pathname-host pathname
))
648 (defun pathname-type (pathname &key
(case :local
))
650 "Return PATHNAME's type."
651 (declare (type pathname-designator pathname
)
652 (type (member :local
:common
) case
))
653 (with-pathname (pathname pathname
)
654 (maybe-diddle-case (%pathname-type pathname
)
655 (and (eq case
:common
)
656 (eq (host-customary-case
657 (%pathname-host pathname
))
660 (defun pathname-version (pathname)
662 "Return PATHNAME's version."
663 (declare (type pathname-designator pathname
))
664 (with-pathname (pathname pathname
)
665 (%pathname-version pathname
)))
669 ;;; Handle the case for PARSE-NAMESTRING parsing a potentially
670 ;;; syntactically valid logical namestring with an explicit host.
672 ;;; This then isn't fully general -- we are relying on the fact that
673 ;;; we will only pass to parse-namestring namestring with an explicit
674 ;;; logical host, so that we can pass the host return from
675 ;;; parse-logical-namestring through to %PARSE-NAMESTRING as a truth
676 ;;; value. Yeah, this is probably a KLUDGE - CSR, 2002-04-18
677 (defun parseable-logical-namestring-p (namestr start end
)
680 ((namestring-parse-error (lambda (c)
683 (let ((colon (position #\
: namestr
:start start
:end end
)))
685 (let ((potential-host
686 (logical-word-or-lose (subseq namestr start colon
))))
687 ;; depending on the outcome of CSR comp.lang.lisp post
688 ;; "can PARSE-NAMESTRING create logical hosts", we may need
689 ;; to do things with potential-host (create it
690 ;; temporarily, parse the namestring and unintern the
691 ;; logical host potential-host on failure.
692 (declare (ignore potential-host
))
695 ((simple-type-error (lambda (c)
698 (parse-logical-namestring namestr start end
))))
699 ;; if we got this far, we should have an explicit host
700 ;; (first return value of parse-logical-namestring)
704 ;;; Handle the case where PARSE-NAMESTRING is actually parsing a
705 ;;; namestring. We pick off the :JUNK-ALLOWED case then find a host to
706 ;;; use for parsing, call the parser, then check whether the host matches.
707 (defun %parse-namestring
(namestr host defaults start end junk-allowed
)
708 (declare (type (or host null
) host
)
709 (type string namestr
)
711 (type (or index null
) end
))
715 (%parse-namestring namestr host defaults start end nil
)
716 (namestring-parse-error (condition)
717 (values nil
(namestring-parse-error-offset condition
)))))
719 (let* ((end (%check-vector-sequence-bounds namestr start end
)))
720 (multiple-value-bind (new-host device directory file type version
)
721 ;; Comments below are quotes from the HyperSpec
722 ;; PARSE-NAMESTRING entry, reproduced here to demonstrate
723 ;; that we actually have to do things this way rather than
724 ;; some possibly more logical way. - CSR, 2002-04-18
726 ;; "If host is a logical host then thing is parsed as a
727 ;; logical pathname namestring on the host."
728 (host (funcall (host-parse host
) namestr start end
))
729 ;; "If host is nil and thing is a syntactically valid
730 ;; logical pathname namestring containing an explicit
731 ;; host, then it is parsed as a logical pathname
733 ((parseable-logical-namestring-p namestr start end
)
734 (parse-logical-namestring namestr start end
))
735 ;; "If host is nil, default-pathname is a logical
736 ;; pathname, and thing is a syntactically valid logical
737 ;; pathname namestring without an explicit host, then it
738 ;; is parsed as a logical pathname namestring on the
739 ;; host that is the host component of default-pathname."
741 ;; "Otherwise, the parsing of thing is
742 ;; implementation-defined."
744 ;; Both clauses are handled here, as the default
745 ;; *DEFAULT-PATHNAME-DEFAULTS* has a SB-IMPL::UNIX-HOST
747 ((pathname-host defaults
)
748 (funcall (host-parse (pathname-host defaults
))
752 ;; I don't think we should ever get here, as the default
753 ;; host will always have a non-null HOST, given that we
754 ;; can't create a new pathname without going through
755 ;; *DEFAULT-PATHNAME-DEFAULTS*, which has a non-null
757 (t (bug "Fallen through COND in %PARSE-NAMESTRING")))
758 (when (and host new-host
(not (eq new-host host
)))
759 (error 'simple-type-error
761 ;; Note: ANSI requires that this be a TYPE-ERROR,
762 ;; but there seems to be no completely correct
763 ;; value to use for TYPE-ERROR-EXPECTED-TYPE.
764 ;; Instead, we return a sort of "type error allowed
765 ;; type", trying to say "it would be OK if you
766 ;; passed NIL as the host value" but not mentioning
767 ;; that a matching string would be OK too.
770 "The host in the namestring, ~S,~@
771 does not match the explicit HOST argument, ~S."
772 :format-arguments
(list new-host host
)))
773 (let ((pn-host (or new-host host
(pathname-host defaults
))))
774 (values (%make-maybe-logical-pathname
775 pn-host device directory file type version
)
778 ;;; If NAMESTR begins with a colon-terminated, defined, logical host,
779 ;;; then return that host, otherwise return NIL.
780 (defun extract-logical-host-prefix (namestr start end
)
781 (declare (type simple-string namestr
)
782 (type index start end
)
783 (values (or logical-host null
)))
784 (let ((colon-pos (position #\
: namestr
:start start
:end end
)))
786 (values (gethash (nstring-upcase (subseq namestr start colon-pos
))
790 (defun parse-namestring (thing
793 (defaults *default-pathname-defaults
*)
794 &key
(start 0) end junk-allowed
)
795 (declare (type pathname-designator thing defaults
)
796 (type (or list host string
(member :unspecific
)) host
)
798 (type (or index null
) end
)
799 (type (or t null
) junk-allowed
)
800 (values (or null pathname
) (or null index
)))
801 (with-host (found-host host
)
802 (let (;; According to ANSI defaults may be any valid pathname designator
803 (defaults (etypecase defaults
807 (aver (pathnamep *default-pathname-defaults
*))
808 (parse-namestring defaults
))
810 (truename defaults
)))))
811 (declare (type pathname defaults
))
814 (%parse-namestring thing found-host defaults start end junk-allowed
))
816 (%parse-namestring
(coerce thing
'simple-string
)
817 found-host defaults start end junk-allowed
))
819 (let ((defaulted-host (or found-host
(%pathname-host defaults
))))
820 (declare (type host defaulted-host
))
821 (unless (eq defaulted-host
(%pathname-host thing
))
822 (error "The HOST argument doesn't match the pathname host:~% ~
824 defaulted-host
(%pathname-host thing
))))
825 (values thing start
))
827 (let ((name (file-name thing
)))
829 (error "can't figure out the file associated with stream:~% ~S"
831 (values name nil
)))))))
833 (defun %parse-native-namestring
(namestr host defaults start end junk-allowed
835 (declare (type (or host null
) host
)
836 (type string namestr
)
838 (type (or index null
) end
))
842 (%parse-native-namestring namestr host defaults start end nil as-directory
)
843 (namestring-parse-error (condition)
844 (values nil
(namestring-parse-error-offset condition
)))))
846 (let* ((end (%check-vector-sequence-bounds namestr start end
)))
847 (multiple-value-bind (new-host device directory file type version
)
850 (funcall (host-parse-native host
) namestr start end as-directory
))
851 ((pathname-host defaults
)
852 (funcall (host-parse-native (pathname-host defaults
))
857 ;; I don't think we should ever get here, as the default
858 ;; host will always have a non-null HOST, given that we
859 ;; can't create a new pathname without going through
860 ;; *DEFAULT-PATHNAME-DEFAULTS*, which has a non-null
862 (t (bug "Fallen through COND in %PARSE-NAMESTRING")))
863 (when (and host new-host
(not (eq new-host host
)))
864 (error 'simple-type-error
866 :expected-type
`(or null
(eql ,host
))
868 "The host in the namestring, ~S,~@
869 does not match the explicit HOST argument, ~S."
870 :format-arguments
(list new-host host
)))
871 (let ((pn-host (or new-host host
(pathname-host defaults
))))
872 (values (%make-pathname
873 pn-host device directory file type version
)
876 (defun parse-native-namestring (thing
879 (defaults *default-pathname-defaults
*)
880 &key
(start 0) end junk-allowed
883 "Convert THING into a pathname, using the native conventions
884 appropriate for the pathname host HOST, or if not specified the
885 host of DEFAULTS. If THING is a string, the parse is bounded by
886 START and END, and error behaviour is controlled by JUNK-ALLOWED,
887 as with PARSE-NAMESTRING. For file systems whose native
888 conventions allow directories to be indicated as files, if
889 AS-DIRECTORY is true, return a pathname denoting THING as a
891 (declare (type pathname-designator thing defaults
)
892 (type (or list host string
(member :unspecific
)) host
)
894 (type (or index null
) end
)
895 (type (or t null
) junk-allowed
)
896 (values (or null pathname
) (or null index
)))
897 (with-host (found-host host
)
898 (let ((defaults (etypecase defaults
902 (aver (pathnamep *default-pathname-defaults
*))
903 (parse-native-namestring defaults
))
905 (truename defaults
)))))
906 (declare (type pathname defaults
))
909 (%parse-native-namestring
910 thing found-host defaults start end junk-allowed as-directory
))
912 (%parse-native-namestring
(coerce thing
'simple-string
)
913 found-host defaults start end junk-allowed
916 (let ((defaulted-host (or found-host
(%pathname-host defaults
))))
917 (declare (type host defaulted-host
))
918 (unless (eq defaulted-host
(%pathname-host thing
))
919 (error "The HOST argument doesn't match the pathname host:~% ~
921 defaulted-host
(%pathname-host thing
))))
922 (values thing start
))
925 (let ((name (file-name thing
)))
927 (error "can't figure out the file associated with stream:~% ~S"
929 (values name nil
)))))))
931 (defun namestring (pathname)
933 "Construct the full (name)string form of the pathname."
934 (declare (type pathname-designator pathname
))
935 (with-pathname (pathname pathname
)
937 (let ((host (%pathname-host pathname
)))
939 (error "can't determine the namestring for pathnames with no ~
940 host:~% ~S" pathname
))
941 (funcall (host-unparse host
) pathname
)))))
943 (defun native-namestring (pathname &key as-file
)
945 "Construct the full native (name)string form of PATHNAME. For
946 file systems whose native conventions allow directories to be
947 indicated as files, if AS-FILE is true and the name, type, and
948 version components of PATHNAME are all NIL or :UNSPECIFIC,
949 construct a string that names the directory according to the file
950 system's syntax for files."
951 (declare (type pathname-designator pathname
))
952 (with-native-pathname (pathname pathname
)
954 (let ((host (%pathname-host pathname
)))
956 (error "can't determine the native namestring for pathnames with no ~
957 host:~% ~S" pathname
))
958 (funcall (host-unparse-native host
) pathname as-file
)))))
960 (defun host-namestring (pathname)
962 "Return a string representation of the name of the host in the pathname."
963 (declare (type pathname-designator pathname
))
964 (with-pathname (pathname pathname
)
965 (let ((host (%pathname-host pathname
)))
967 (funcall (host-unparse-host host
) pathname
)
969 "can't determine the namestring for pathnames with no host:~% ~S"
972 (defun directory-namestring (pathname)
974 "Return a string representation of the directories used in the pathname."
975 (declare (type pathname-designator pathname
))
976 (with-pathname (pathname pathname
)
977 (let ((host (%pathname-host pathname
)))
979 (funcall (host-unparse-directory host
) pathname
)
981 "can't determine the namestring for pathnames with no host:~% ~S"
984 (defun file-namestring (pathname)
986 "Return a string representation of the name used in the pathname."
987 (declare (type pathname-designator pathname
))
988 (with-pathname (pathname pathname
)
989 (let ((host (%pathname-host pathname
)))
991 (funcall (host-unparse-file host
) pathname
)
993 "can't determine the namestring for pathnames with no host:~% ~S"
996 (defun enough-namestring (pathname
998 (defaults *default-pathname-defaults
*))
1000 "Return an abbreviated pathname sufficient to identify the pathname relative
1002 (declare (type pathname-designator pathname
))
1003 (with-pathname (pathname pathname
)
1004 (let ((host (%pathname-host pathname
)))
1006 (with-pathname (defaults defaults
)
1007 (funcall (host-unparse-enough host
) pathname defaults
))
1009 "can't determine the namestring for pathnames with no host:~% ~S"
1014 (defun wild-pathname-p (pathname &optional field-key
)
1016 "Predicate for determining whether pathname contains any wildcards."
1017 (declare (type pathname-designator pathname
)
1018 (type (member nil
:host
:device
:directory
:name
:type
:version
)
1020 (with-pathname (pathname pathname
)
1022 (or (pattern-p x
) (member x
'(:wild
:wild-inferiors
)))))
1025 (or (wild-pathname-p pathname
:host
)
1026 (wild-pathname-p pathname
:device
)
1027 (wild-pathname-p pathname
:directory
)
1028 (wild-pathname-p pathname
:name
)
1029 (wild-pathname-p pathname
:type
)
1030 (wild-pathname-p pathname
:version
)))
1031 (:host
(frob (%pathname-host pathname
)))
1032 (:device
(frob (%pathname-host pathname
)))
1033 (:directory
(some #'frob
(%pathname-directory pathname
)))
1034 (:name
(frob (%pathname-name pathname
)))
1035 (:type
(frob (%pathname-type pathname
)))
1036 (:version
(frob (%pathname-version pathname
)))))))
1038 (defun pathname-match-p (in-pathname in-wildname
)
1040 "Pathname matches the wildname template?"
1041 (declare (type pathname-designator in-pathname
))
1042 (with-pathname (pathname in-pathname
)
1043 (with-pathname (wildname in-wildname
)
1044 (macrolet ((frob (field &optional
(op 'components-match
))
1045 `(or (null (,field wildname
))
1046 (,op
(,field pathname
) (,field wildname
)))))
1047 (and (or (null (%pathname-host wildname
))
1048 (eq (%pathname-host wildname
) (%pathname-host pathname
)))
1049 (frob %pathname-device
)
1050 (frob %pathname-directory directory-components-match
)
1051 (frob %pathname-name
)
1052 (frob %pathname-type
)
1053 (or (eq (%pathname-host wildname
) *physical-host
*)
1054 (frob %pathname-version
)))))))
1056 ;;; Place the substitutions into the pattern and return the string or pattern
1057 ;;; that results. If DIDDLE-CASE is true, we diddle the result case as well,
1058 ;;; in case we are translating between hosts with difference conventional case.
1059 ;;; The second value is the tail of subs with all of the values that we used up
1060 ;;; stripped off. Note that PATTERN-MATCHES matches all consecutive wildcards
1061 ;;; as a single string, so we ignore subsequent contiguous wildcards.
1062 (defun substitute-into (pattern subs diddle-case
)
1063 (declare (type pattern pattern
)
1065 (values (or simple-string pattern
) list
))
1066 (let ((in-wildcard nil
)
1069 (dolist (piece (pattern-pieces pattern
))
1070 (cond ((simple-string-p piece
)
1071 (push piece strings
)
1072 (setf in-wildcard nil
))
1075 (setf in-wildcard t
)
1077 (error "not enough wildcards in FROM pattern to match ~
1080 (let ((sub (pop subs
)))
1084 (push (apply #'concatenate
'simple-string
1087 (dolist (piece (pattern-pieces sub
))
1088 (push piece pieces
)))
1092 (error "can't substitute this into the middle of a word:~
1097 (push (apply #'concatenate
'simple-string
(nreverse strings
))
1101 (if (and pieces
(simple-string-p (car pieces
)) (null (cdr pieces
)))
1103 (make-pattern (nreverse pieces
)))
1107 ;;; Called when we can't see how source and from matched.
1108 (defun didnt-match-error (source from
)
1109 (error "Pathname components from SOURCE and FROM args to TRANSLATE-PATHNAME~@
1110 did not match:~% ~S ~S"
1113 ;;; Do TRANSLATE-COMPONENT for all components except host, directory
1115 (defun translate-component (source from to diddle-case
)
1122 (if (pattern= from source
)
1124 (didnt-match-error source from
)))
1126 (multiple-value-bind (won subs
) (pattern-matches from source
)
1128 (values (substitute-into to subs diddle-case
))
1129 (didnt-match-error source from
))))
1131 (maybe-diddle-case source diddle-case
))))
1133 (values (substitute-into to
(list source
) diddle-case
)))
1135 (if (components-match source from
)
1136 (maybe-diddle-case source diddle-case
)
1137 (didnt-match-error source from
)))))
1139 (maybe-diddle-case source diddle-case
))
1141 (if (components-match source from
)
1143 (didnt-match-error source from
)))))
1145 ;;; Return a list of all the things that we want to substitute into the TO
1146 ;;; pattern (the things matched by from on source.) When From contains
1147 ;;; :WILD-INFERIORS, the result contains a sublist of the matched source
1149 (defun compute-directory-substitutions (orig-source orig-from
)
1150 (let ((source orig-source
)
1155 (unless (every (lambda (x) (eq x
:wild-inferiors
)) from
)
1156 (didnt-match-error orig-source orig-from
))
1159 (unless from
(didnt-match-error orig-source orig-from
))
1160 (let ((from-part (pop from
))
1161 (source-part (pop source
)))
1164 (typecase source-part
1166 (if (pattern= from-part source-part
)
1168 (didnt-match-error orig-source orig-from
)))
1170 (multiple-value-bind (won new-subs
)
1171 (pattern-matches from-part source-part
)
1173 (dolist (sub new-subs
)
1175 (didnt-match-error orig-source orig-from
))))
1177 (didnt-match-error orig-source orig-from
))))
1180 ((member :wild-inferiors
)
1181 (let ((remaining-source (cons source-part source
)))
1184 (when (directory-components-match remaining-source from
)
1186 (unless remaining-source
1187 (didnt-match-error orig-source orig-from
))
1188 (res (pop remaining-source
)))
1190 (setq source remaining-source
))))
1192 (unless (and (simple-string-p source-part
)
1193 (string= from-part source-part
))
1194 (didnt-match-error orig-source orig-from
)))
1196 (didnt-match-error orig-source orig-from
)))))
1199 ;;; This is called by TRANSLATE-PATHNAME on the directory components
1200 ;;; of its argument pathnames to produce the result directory
1201 ;;; component. If this leaves the directory NIL, we return the source
1202 ;;; directory. The :RELATIVE or :ABSOLUTE is taken from the source
1203 ;;; directory, except if TO is :ABSOLUTE, in which case the result
1204 ;;; will be :ABSOLUTE.
1205 (defun translate-directories (source from to diddle-case
)
1206 (if (not (and source to from
))
1207 (or (and to
(null source
) (remove :wild-inferiors to
))
1208 (mapcar (lambda (x) (maybe-diddle-case x diddle-case
)) source
))
1210 ;; If TO is :ABSOLUTE, the result should still be :ABSOLUTE.
1211 (res (if (eq (first to
) :absolute
)
1214 (let ((subs-left (compute-directory-substitutions (rest source
)
1216 (dolist (to-part (rest to
))
1220 (let ((match (pop subs-left
)))
1222 (error ":WILD-INFERIORS is not paired in from and to ~
1223 patterns:~% ~S ~S" from to
))
1224 (res (maybe-diddle-case match diddle-case
))))
1225 ((member :wild-inferiors
)
1227 (let ((match (pop subs-left
)))
1228 (unless (listp match
)
1229 (error ":WILD-INFERIORS not paired in from and to ~
1230 patterns:~% ~S ~S" from to
))
1232 (res (maybe-diddle-case x diddle-case
)))))
1234 (multiple-value-bind
1236 (substitute-into to-part subs-left diddle-case
)
1237 (setf subs-left new-subs-left
)
1239 (t (res to-part
)))))
1242 (defun translate-pathname (source from-wildname to-wildname
&key
)
1244 "Use the source pathname to translate the from-wildname's wild and
1245 unspecified elements into a completed to-pathname based on the to-wildname."
1246 (declare (type pathname-designator source from-wildname to-wildname
))
1247 (with-pathname (source source
)
1248 (with-pathname (from from-wildname
)
1249 (with-pathname (to to-wildname
)
1250 (let* ((source-host (%pathname-host source
))
1251 (from-host (%pathname-host from
))
1252 (to-host (%pathname-host to
))
1254 (and source-host to-host
1255 (not (eq (host-customary-case source-host
)
1256 (host-customary-case to-host
))))))
1257 (macrolet ((frob (field &optional
(op 'translate-component
))
1258 `(let ((result (,op
(,field source
)
1262 (if (eq result
:error
)
1263 (error "~S doesn't match ~S." source from
)
1265 (%make-maybe-logical-pathname
1266 (or to-host source-host
)
1267 (frob %pathname-device
)
1268 (frob %pathname-directory translate-directories
)
1269 (frob %pathname-name
)
1270 (frob %pathname-type
)
1271 (if (eq from-host
*physical-host
*)
1272 (if (or (eq (%pathname-version to
) :wild
)
1273 (eq (%pathname-version to
) nil
))
1274 (%pathname-version source
)
1275 (%pathname-version to
))
1276 (frob %pathname-version
)))))))))
1278 ;;;; logical pathname support. ANSI 92-102 specification.
1280 ;;;; As logical-pathname translations are loaded they are
1281 ;;;; canonicalized as patterns to enable rapid efficient translation
1282 ;;;; into physical pathnames.
1286 (defun simplify-namestring (namestring &optional host
)
1287 (funcall (host-simplify-namestring
1289 (pathname-host (sane-default-pathname-defaults))))
1292 ;;; Canonicalize a logical pathname word by uppercasing it checking that it
1293 ;;; contains only legal characters.
1294 (defun logical-word-or-lose (word)
1295 (declare (string word
))
1296 (when (string= word
"")
1297 (error 'namestring-parse-error
1298 :complaint
"Attempted to treat invalid logical hostname ~
1299 as a logical host:~% ~S"
1301 :namestring word
:offset
0))
1302 (let ((word (string-upcase word
)))
1303 (dotimes (i (length word
))
1304 (let ((ch (schar word i
)))
1305 (unless (and (typep ch
'standard-char
)
1306 (or (alpha-char-p ch
) (digit-char-p ch
) (char= ch
#\-
)))
1307 (error 'namestring-parse-error
1308 :complaint
"logical namestring character which ~
1309 is not alphanumeric or hyphen:~% ~S"
1311 :namestring word
:offset i
))))
1312 (coerce word
'string
))) ; why not simple-string?
1314 ;;; Given a logical host or string, return a logical host. If ERROR-P
1315 ;;; is NIL, then return NIL when no such host exists.
1316 (defun find-logical-host (thing &optional
(errorp t
))
1319 (let ((found (gethash (logical-word-or-lose thing
)
1321 (if (or found
(not errorp
))
1323 ;; This is the error signalled from e.g.
1324 ;; LOGICAL-PATHNAME-TRANSLATIONS when host is not a defined
1325 ;; host, and ANSI specifies that that's a TYPE-ERROR.
1326 (error 'simple-type-error
1328 ;; God only knows what ANSI expects us to use for
1329 ;; the EXPECTED-TYPE here. Maybe this will be OK..
1331 '(and string
(satisfies logical-pathname-translations
))
1332 :format-control
"logical host not yet defined: ~S"
1333 :format-arguments
(list thing
)))))
1334 (logical-host thing
)))
1336 ;;; Given a logical host name or host, return a logical host, creating
1337 ;;; a new one if necessary.
1338 (defun intern-logical-host (thing)
1339 (declare (values logical-host
))
1340 (with-locked-system-table (*logical-hosts
*)
1341 (or (find-logical-host thing nil
)
1342 (let* ((name (logical-word-or-lose thing
))
1343 (new (make-logical-host :name name
)))
1344 (setf (gethash name
*logical-hosts
*) new
)
1347 ;;;; logical pathname parsing
1349 ;;; Deal with multi-char wildcards in a logical pathname token.
1350 (defun maybe-make-logical-pattern (namestring chunks
)
1351 (let ((chunk (caar chunks
)))
1352 (collect ((pattern))
1354 (len (length chunk
)))
1355 (declare (fixnum last-pos
))
1357 (when (= last-pos len
) (return))
1358 (let ((pos (or (position #\
* chunk
:start last-pos
) len
)))
1359 (if (= pos last-pos
)
1361 (error 'namestring-parse-error
1362 :complaint
"double asterisk inside of logical ~
1365 :namestring namestring
1366 :offset
(+ (cdar chunks
) pos
)))
1367 (pattern (subseq chunk last-pos pos
)))
1370 (pattern :multi-char-wild
))
1371 (setq last-pos
(1+ pos
)))))
1374 (make-pattern (pattern))
1375 (let ((x (car (pattern))))
1376 (if (eq x
:multi-char-wild
)
1380 ;;; Return a list of conses where the CDR is the start position and
1381 ;;; the CAR is a string (token) or character (punctuation.)
1382 (defun logical-chunkify (namestr start end
)
1384 (do ((i start
(1+ i
))
1388 (chunks (cons (nstring-upcase (subseq namestr prev end
)) prev
))))
1389 (let ((ch (schar namestr i
)))
1390 (unless (or (alpha-char-p ch
) (digit-char-p ch
)
1391 (member ch
'(#\-
#\
*)))
1393 (chunks (cons (nstring-upcase (subseq namestr prev i
)) prev
)))
1395 (unless (member ch
'(#\
; #\: #\.))
1396 (error 'namestring-parse-error
1397 :complaint
"illegal character for logical pathname:~% ~S"
1401 (chunks (cons ch i
)))))
1404 ;;; Break up a logical-namestring, always a string, into its
1405 ;;; constituent parts.
1406 (defun parse-logical-namestring (namestr start end
)
1407 (declare (type simple-string namestr
)
1408 (type index start end
))
1409 (collect ((directory))
1414 (labels ((expecting (what chunks
)
1415 (unless (and chunks
(simple-string-p (caar chunks
)))
1416 (error 'namestring-parse-error
1417 :complaint
"expecting ~A, got ~:[nothing~;~S~]."
1418 :args
(list what
(caar chunks
) (caar chunks
))
1420 :offset
(if chunks
(cdar chunks
) end
)))
1422 (parse-host (chunks)
1423 (case (caadr chunks
)
1426 (find-logical-host (expecting "a host name" chunks
)))
1427 (parse-relative (cddr chunks
)))
1429 (parse-relative chunks
))))
1430 (parse-relative (chunks)
1433 (directory :relative
)
1434 (parse-directory (cdr chunks
)))
1436 (directory :absolute
) ; Assumption! Maybe revoked later.
1437 (parse-directory chunks
))))
1438 (parse-directory (chunks)
1439 (case (caadr chunks
)
1442 (let ((res (expecting "a directory name" chunks
)))
1443 (cond ((string= res
"..") :up
)
1444 ((string= res
"**") :wild-inferiors
)
1446 (maybe-make-logical-pattern namestr chunks
)))))
1447 (parse-directory (cddr chunks
)))
1449 (parse-name chunks
))))
1450 (parse-name (chunks)
1452 (expecting "a file name" chunks
)
1453 (setq name
(maybe-make-logical-pattern namestr chunks
))
1454 (expecting-dot (cdr chunks
))))
1455 (expecting-dot (chunks)
1457 (unless (eql (caar chunks
) #\.
)
1458 (error 'namestring-parse-error
1459 :complaint
"expecting a dot, got ~S."
1460 :args
(list (caar chunks
))
1462 :offset
(cdar chunks
)))
1464 (parse-version (cdr chunks
))
1465 (parse-type (cdr chunks
)))))
1466 (parse-type (chunks)
1467 (expecting "a file type" chunks
)
1468 (setq type
(maybe-make-logical-pattern namestr chunks
))
1469 (expecting-dot (cdr chunks
)))
1470 (parse-version (chunks)
1471 (let ((str (expecting "a positive integer, * or NEWEST"
1474 ((string= str
"*") (setq version
:wild
))
1475 ((string= str
"NEWEST") (setq version
:newest
))
1477 (multiple-value-bind (res pos
)
1478 (parse-integer str
:junk-allowed t
)
1479 (unless (and res
(plusp res
))
1480 (error 'namestring-parse-error
1481 :complaint
"expected a positive integer, ~
1485 :offset
(+ pos
(cdar chunks
))))
1486 (setq version res
)))))
1488 (error 'namestring-parse-error
1489 :complaint
"extra stuff after end of file name"
1491 :offset
(cdadr chunks
)))))
1492 (parse-host (logical-chunkify namestr start end
)))
1493 (values host
:unspecific
(directory) name type version
))))
1495 ;;; We can't initialize this yet because not all host methods are
1497 (defvar *logical-pathname-defaults
*)
1499 (defun logical-namestring-p (x)
1502 (typep (pathname x
) 'logical-pathname
))))
1504 (deftype logical-namestring
()
1505 `(satisfies logical-namestring-p
))
1507 (defun logical-pathname (pathspec)
1509 "Converts the pathspec argument to a logical-pathname and returns it."
1510 (declare (type (or logical-pathname string stream
) pathspec
)
1511 (values logical-pathname
))
1512 (if (typep pathspec
'logical-pathname
)
1514 (flet ((oops (problem)
1515 (error 'simple-type-error
1517 :expected-type
'logical-namestring
1518 :format-control
"~S is not a valid logical namestring:~% ~A"
1519 :format-arguments
(list pathspec problem
))))
1520 (let ((res (handler-case
1521 (parse-namestring pathspec nil
*logical-pathname-defaults
*)
1522 (error (e) (oops e
)))))
1523 (when (eq (%pathname-host res
)
1524 (%pathname-host
*logical-pathname-defaults
*))
1525 (oops "no host specified"))
1528 ;;;; logical pathname unparsing
1530 (defun unparse-logical-directory (pathname)
1531 (declare (type pathname pathname
))
1533 (let ((directory (%pathname-directory pathname
)))
1535 (ecase (pop directory
)
1536 (:absolute
) ; nothing special
1537 (:relative
(pieces ";")))
1538 (dolist (dir directory
)
1539 (cond ((or (stringp dir
) (pattern-p dir
))
1540 (pieces (unparse-logical-piece dir
))
1544 ((eq dir
:wild-inferiors
)
1547 (error "invalid directory component: ~S" dir
))))))
1548 (apply #'concatenate
'simple-string
(pieces))))
1550 (defun unparse-logical-piece (thing)
1552 ((member :wild
) "*")
1553 (simple-string thing
)
1555 (collect ((strings))
1556 (dolist (piece (pattern-pieces thing
))
1558 (simple-string (strings piece
))
1560 (cond ((eq piece
:wild-inferiors
)
1562 ((eq piece
:multi-char-wild
)
1564 (t (error "invalid keyword: ~S" piece
))))))
1565 (apply #'concatenate
'simple-string
(strings))))))
1567 (defun unparse-logical-file (pathname)
1568 (declare (type pathname pathname
))
1569 (collect ((strings))
1570 (let* ((name (%pathname-name pathname
))
1571 (type (%pathname-type pathname
))
1572 (version (%pathname-version pathname
))
1573 (type-supplied (not (or (null type
) (eq type
:unspecific
))))
1574 (version-supplied (not (or (null version
)
1575 (eq version
:unspecific
)))))
1577 (when (and (null type
)
1578 (typep name
'string
)
1579 (position #\. name
:start
1))
1580 (error "too many dots in the name: ~S" pathname
))
1581 (strings (unparse-logical-piece name
)))
1584 (error "cannot specify the type without a file: ~S" pathname
))
1585 (when (typep type
'string
)
1586 (when (position #\. type
)
1587 (error "type component can't have a #\. inside: ~S" pathname
)))
1589 (strings (unparse-logical-piece type
)))
1590 (when version-supplied
1591 (unless type-supplied
1592 (error "cannot specify the version without a type: ~S" pathname
))
1594 ((member :newest
) (strings ".NEWEST"))
1595 ((member :wild
) (strings ".*"))
1596 (fixnum (strings ".") (strings (format nil
"~D" version
))))))
1597 (apply #'concatenate
'simple-string
(strings))))
1599 ;;; Unparse a logical pathname string.
1600 (defun unparse-enough-namestring (pathname defaults
)
1601 (let* ((path-directory (pathname-directory pathname
))
1602 (def-directory (pathname-directory defaults
))
1604 ;; Go down the directory lists to see what matches. What's
1605 ;; left is what we want, more or less.
1606 (cond ((and (eq (first path-directory
) (first def-directory
))
1607 (eq (first path-directory
) :absolute
))
1608 ;; Both paths are :ABSOLUTE, so find where the
1609 ;; common parts end and return what's left
1610 (do* ((p (rest path-directory
) (rest p
))
1611 (d (rest def-directory
) (rest d
)))
1612 ((or (endp p
) (endp d
)
1613 (not (equal (first p
) (first d
))))
1616 ;; At least one path is :RELATIVE, so just return the
1617 ;; original path. If the original path is :RELATIVE,
1618 ;; then that's the right one. If PATH-DIRECTORY is
1619 ;; :ABSOLUTE, we want to return that except when
1620 ;; DEF-DIRECTORY is :ABSOLUTE, as handled above. so return
1621 ;; the original directory.
1623 (unparse-logical-namestring
1624 (make-pathname :host
(pathname-host pathname
)
1625 :directory enough-directory
1626 :name
(pathname-name pathname
)
1627 :type
(pathname-type pathname
)
1628 :version
(pathname-version pathname
)))))
1630 (defun unparse-logical-namestring (pathname)
1631 (declare (type logical-pathname pathname
))
1632 (concatenate 'simple-string
1633 (logical-host-name (%pathname-host pathname
)) ":"
1634 (unparse-logical-directory pathname
)
1635 (unparse-logical-file pathname
)))
1637 ;;;; logical pathname translations
1639 ;;; Verify that the list of translations consists of lists and prepare
1640 ;;; canonical translations. (Parse pathnames and expand out wildcards
1642 (defun canonicalize-logical-pathname-translations (translation-list host
)
1643 (declare (type list translation-list
) (type host host
)
1645 (mapcar (lambda (translation)
1646 (destructuring-bind (from to
) translation
1647 (list (if (typep from
'logical-pathname
)
1649 (parse-namestring from host
))
1653 (defun logical-pathname-translations (host)
1655 "Return the (logical) host object argument's list of translations."
1656 (declare (type (or string logical-host
) host
)
1658 (logical-host-translations (find-logical-host host
)))
1660 (defun (setf logical-pathname-translations
) (translations host
)
1662 "Set the translations list for the logical host argument."
1663 (declare (type (or string logical-host
) host
)
1664 (type list translations
)
1666 (let ((host (intern-logical-host host
)))
1667 (setf (logical-host-canon-transls host
)
1668 (canonicalize-logical-pathname-translations translations host
))
1669 (setf (logical-host-translations host
) translations
)))
1671 (defun translate-logical-pathname (pathname &key
)
1673 "Translate PATHNAME to a physical pathname, which is returned."
1674 (declare (type pathname-designator pathname
)
1675 (values (or null pathname
)))
1678 (dolist (x (logical-host-canon-transls (%pathname-host pathname
))
1679 (error 'simple-file-error
1681 :format-control
"no translation for ~S"
1682 :format-arguments
(list pathname
)))
1683 (destructuring-bind (from to
) x
1684 (when (pathname-match-p pathname from
)
1685 (return (translate-logical-pathname
1686 (translate-pathname pathname from to
)))))))
1688 (t (translate-logical-pathname (pathname pathname
)))))
1690 (defvar *logical-pathname-defaults
*
1691 (%make-logical-pathname
1692 (make-logical-host :name
(logical-word-or-lose "BOGUS"))
1693 :unspecific nil nil nil nil
))
1695 (defun load-logical-pathname-translations (host)
1697 "Reads logical pathname translations from SYS:SITE;HOST.TRANSLATIONS.NEWEST,
1698 with HOST replaced by the supplied parameter. Returns T on success.
1700 If HOST is already defined as logical pathname host, no file is loaded and NIL
1703 The file should contain a single form, suitable for use with
1704 \(SETF LOGICAL-PATHNAME-TRANSLATIONS).
1706 Note: behaviour of this function is highly implementation dependent, and
1707 historically it used to be a no-op in SBCL -- the current approach is somewhat
1708 experimental and subject to change."
1709 (declare (type string host
)
1710 (values (member t nil
)))
1711 (if (find-logical-host host nil
)
1712 ;; This host is already defined, all is well and good.
1714 ;; ANSI: "The specific nature of the search is
1715 ;; implementation-defined."
1717 (setf (logical-pathname-translations host
)
1718 (with-open-file (lpt (make-pathname :host
"SYS"
1719 :directory
'(:absolute
"SITE")
1721 :type
"TRANSLATIONS"
1725 (defun !pathname-cold-init
()
1726 (let* ((sys *default-pathname-defaults
*)
1729 (make-pathname :directory
'(:relative
"src" :wild-inferiors
)
1730 :name
:wild
:type
:wild
)
1734 (make-pathname :directory
'(:relative
"contrib" :wild-inferiors
)
1735 :name
:wild
:type
:wild
)
1739 (make-pathname :directory
'(:relative
"output" :wild-inferiors
)
1740 :name
:wild
:type
:wild
)
1742 (setf (logical-pathname-translations "SYS")
1743 `(("SYS:SRC;**;*.*.*" ,src
)
1744 ("SYS:CONTRIB;**;*.*.*" ,contrib
)
1745 ("SYS:OUTPUT;**;*.*.*" ,output
)))))
1747 (defun set-sbcl-source-location (pathname)
1749 "Initialize the SYS logical host based on PATHNAME, which should be
1750 the top-level directory of the SBCL sources. This will replace any
1751 existing translations for \"SYS:SRC;\", \"SYS:CONTRIB;\", and
1752 \"SYS:OUTPUT;\". Other \"SYS:\" translations are preserved."
1753 (let ((truename (truename pathname
))
1754 (current-translations
1755 (remove-if (lambda (translation)
1756 (or (pathname-match-p "SYS:SRC;" translation
)
1757 (pathname-match-p "SYS:CONTRIB;" translation
)
1758 (pathname-match-p "SYS:OUTPUT;" translation
)))
1759 (logical-pathname-translations "SYS")
1761 (flet ((physical-target (component)
1763 (make-pathname :directory
(list :relative component
1768 (setf (logical-pathname-translations "SYS")
1769 `(("SYS:SRC;**;*.*.*" ,(physical-target "src"))
1770 ("SYS:CONTRIB;**;*.*.*" ,(physical-target "contrib"))
1771 ("SYS:OUTPUT;**;*.*.*" ,(physical-target "output"))
1772 ,@current-translations
)))))