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 (not (member (car results)
429 '(:back :wild-inferiors :relative :absolute))))
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 (%make-maybe-logical-pathname
458 (or pathname-host default-host)
459 (or (%pathname-device pathname)
460 (maybe-diddle-case (%pathname-device defaults)
462 (merge-directories (%pathname-directory pathname)
463 (%pathname-directory defaults)
465 (or (%pathname-name pathname)
466 (maybe-diddle-case (%pathname-name defaults)
468 (or (%pathname-type pathname)
469 (maybe-diddle-case (%pathname-type defaults)
471 (or (%pathname-version pathname)
472 (and (not (%pathname-name pathname)) (%pathname-version defaults))
473 default-version))))))
475 (defun import-directory (directory diddle-case)
478 ((member :wild) '(:absolute :wild-inferiors))
479 ((member :unspecific) '(:relative))
482 (let ((root (pop directory)))
483 (if (member root '(:relative :absolute))
485 (error "List of directory components must start with ~S or ~S."
486 :absolute :relative)))
488 (let ((next (pop directory)))
489 (if (or (eq :home next)
490 (typep next '(cons (eql :home) (cons string null))))
492 (push next directory)))
493 (dolist (piece directory)
494 (cond ((member piece '(:wild :wild-inferiors :up :back))
496 ((or (simple-string-p piece) (pattern-p piece))
497 (results (maybe-diddle-case piece diddle-case)))
499 (results (maybe-diddle-case (coerce piece 'simple-string)
502 (error "~S is not allowed as a directory component." piece)))))
505 `(:absolute ,(maybe-diddle-case directory diddle-case)))
508 ,(maybe-diddle-case (coerce directory 'simple-string) diddle-case)))))
510 (defun make-pathname (&key host
515 (version nil versionp)
519 "Makes a new pathname from the component arguments. Note that host is
520 a host-structure or string."
521 (declare (type (or string host pathname-component-tokens) host)
522 (type (or string pathname-component-tokens) device)
523 (type (or list string pattern pathname-component-tokens) directory)
524 (type (or string pattern pathname-component-tokens) name type)
525 (type (or integer pathname-component-tokens (member :newest))
527 (type (or pathname-designator null) defaults)
528 (type (member :common :local) case))
529 (let* ((defaults (when defaults
530 (with-pathname (defaults defaults) defaults)))
531 (default-host (if defaults
532 (%pathname-host defaults)
533 (pathname-host *default-pathname-defaults*)))
534 ;; Raymond Toy writes: CLHS says make-pathname can take a
535 ;; string (as a logical-host) for the host part. We map that
536 ;; string into the corresponding logical host structure.
538 ;; Paul Werkowski writes:
539 ;; HyperSpec says for the arg to MAKE-PATHNAME;
540 ;; "host---a valid physical pathname host. ..."
541 ;; where it probably means -- a valid pathname host.
542 ;; "valid pathname host n. a valid physical pathname host or
543 ;; a valid logical pathname host."
545 ;; "valid physical pathname host n. any of a string,
546 ;; a list of strings, or the symbol :unspecific,
547 ;; that is recognized by the implementation as the name of a host."
548 ;; "valid logical pathname host n. a string that has been defined
549 ;; as the name of a logical host. ..."
550 ;; HS is silent on what happens if the :HOST arg is NOT one of these.
551 ;; It seems an error message is appropriate.
552 (host (or (find-host host nil) default-host))
553 (diddle-args (and (eq (host-customary-case host) :lower)
556 (not (eq (host-customary-case host)
557 (host-customary-case default-host))))
558 (dev (if devp device (if defaults (%pathname-device defaults))))
559 (dir (import-directory directory diddle-args))
562 (defaults (%pathname-version defaults))
564 (when (and defaults (not dirp))
566 (merge-directories dir
567 (%pathname-directory defaults)
570 (macrolet ((pick (var varp field)
571 `(cond ((or (simple-string-p ,var)
573 (maybe-diddle-case ,var diddle-args))
575 (maybe-diddle-case (coerce ,var 'simple-string)
578 (maybe-diddle-case ,var diddle-args))
580 (maybe-diddle-case (,field defaults)
584 (%make-maybe-logical-pathname host
585 dev ; forced to :UNSPECIFIC when logical
587 (pick name namep %pathname-name)
588 (pick type typep %pathname-type)
591 (defun pathname-host (pathname &key (case :local))
593 "Return PATHNAME's host."
594 (declare (type pathname-designator pathname)
595 (type (member :local :common) case)
598 (with-pathname (pathname pathname)
599 (%pathname-host pathname)))
601 (defun pathname-device (pathname &key (case :local))
603 "Return PATHNAME's device."
604 (declare (type pathname-designator pathname)
605 (type (member :local :common) case))
606 (with-pathname (pathname pathname)
607 (maybe-diddle-case (%pathname-device pathname)
608 (and (eq case :common)
609 (eq (host-customary-case
610 (%pathname-host pathname))
613 (defun pathname-directory (pathname &key (case :local))
615 "Return PATHNAME's directory."
616 (declare (type pathname-designator pathname)
617 (type (member :local :common) case))
618 (with-pathname (pathname pathname)
619 (maybe-diddle-case (%pathname-directory pathname)
620 (and (eq case :common)
621 (eq (host-customary-case
622 (%pathname-host pathname))
624 (defun pathname-name (pathname &key (case :local))
626 "Return PATHNAME's name."
627 (declare (type pathname-designator pathname)
628 (type (member :local :common) case))
629 (with-pathname (pathname pathname)
630 (maybe-diddle-case (%pathname-name pathname)
631 (and (eq case :common)
632 (eq (host-customary-case
633 (%pathname-host pathname))
636 (defun pathname-type (pathname &key (case :local))
638 "Return PATHNAME's type."
639 (declare (type pathname-designator pathname)
640 (type (member :local :common) case))
641 (with-pathname (pathname pathname)
642 (maybe-diddle-case (%pathname-type pathname)
643 (and (eq case :common)
644 (eq (host-customary-case
645 (%pathname-host pathname))
648 (defun pathname-version (pathname)
650 "Return PATHNAME's version."
651 (declare (type pathname-designator pathname))
652 (with-pathname (pathname pathname)
653 (%pathname-version pathname)))
657 ;;; Handle the case for PARSE-NAMESTRING parsing a potentially
658 ;;; syntactically valid logical namestring with an explicit host.
660 ;;; This then isn't fully general -- we are relying on the fact that
661 ;;; we will only pass to parse-namestring namestring with an explicit
662 ;;; logical host, so that we can pass the host return from
663 ;;; parse-logical-namestring through to %PARSE-NAMESTRING as a truth
664 ;;; value. Yeah, this is probably a KLUDGE - CSR, 2002-04-18
665 (defun parseable-logical-namestring-p (namestr start end)
668 ((namestring-parse-error (lambda (c)
671 (let ((colon (position #\: namestr :start start :end end)))
673 (let ((potential-host
674 (logical-word-or-lose (subseq namestr start colon))))
675 ;; depending on the outcome of CSR comp.lang.lisp post
676 ;; "can PARSE-NAMESTRING create logical hosts", we may need
677 ;; to do things with potential-host (create it
678 ;; temporarily, parse the namestring and unintern the
679 ;; logical host potential-host on failure.
680 (declare (ignore potential-host))
683 ((simple-type-error (lambda (c)
686 (parse-logical-namestring namestr start end))))
687 ;; if we got this far, we should have an explicit host
688 ;; (first return value of parse-logical-namestring)
692 ;;; Handle the case where PARSE-NAMESTRING is actually parsing a
693 ;;; namestring. We pick off the :JUNK-ALLOWED case then find a host to
694 ;;; use for parsing, call the parser, then check whether the host matches.
695 (defun %parse-namestring (namestr host defaults start end junk-allowed)
696 (declare (type (or host null) host)
697 (type string namestr)
699 (type (or index null) end))
703 (%parse-namestring namestr host defaults start end nil)
704 (namestring-parse-error (condition)
705 (values nil (namestring-parse-error-offset condition)))))
707 (let* ((end (%check-vector-sequence-bounds namestr start end)))
708 (multiple-value-bind (new-host device directory file type version)
709 ;; Comments below are quotes from the HyperSpec
710 ;; PARSE-NAMESTRING entry, reproduced here to demonstrate
711 ;; that we actually have to do things this way rather than
712 ;; some possibly more logical way. - CSR, 2002-04-18
714 ;; "If host is a logical host then thing is parsed as a
715 ;; logical pathname namestring on the host."
716 (host (funcall (host-parse host) namestr start end))
717 ;; "If host is nil and thing is a syntactically valid
718 ;; logical pathname namestring containing an explicit
719 ;; host, then it is parsed as a logical pathname
721 ((parseable-logical-namestring-p namestr start end)
722 (parse-logical-namestring namestr start end))
723 ;; "If host is nil, default-pathname is a logical
724 ;; pathname, and thing is a syntactically valid logical
725 ;; pathname namestring without an explicit host, then it
726 ;; is parsed as a logical pathname namestring on the
727 ;; host that is the host component of default-pathname."
729 ;; "Otherwise, the parsing of thing is
730 ;; implementation-defined."
732 ;; Both clauses are handled here, as the default
733 ;; *DEFAULT-PATHNAME-DEFAULTS* has a SB-IMPL::UNIX-HOST
735 ((pathname-host defaults)
736 (funcall (host-parse (pathname-host defaults))
740 ;; I don't think we should ever get here, as the default
741 ;; host will always have a non-null HOST, given that we
742 ;; can't create a new pathname without going through
743 ;; *DEFAULT-PATHNAME-DEFAULTS*, which has a non-null
745 (t (bug "Fallen through COND in %PARSE-NAMESTRING")))
746 (when (and host new-host (not (eq new-host host)))
747 (error 'simple-type-error
749 ;; Note: ANSI requires that this be a TYPE-ERROR,
750 ;; but there seems to be no completely correct
751 ;; value to use for TYPE-ERROR-EXPECTED-TYPE.
752 ;; Instead, we return a sort of "type error allowed
753 ;; type", trying to say "it would be OK if you
754 ;; passed NIL as the host value" but not mentioning
755 ;; that a matching string would be OK too.
758 "The host in the namestring, ~S,~@
759 does not match the explicit HOST argument, ~S."
760 :format-arguments (list new-host host)))
761 (let ((pn-host (or new-host host (pathname-host defaults))))
762 (values (%make-maybe-logical-pathname
763 pn-host device directory file type version)
766 ;;; If NAMESTR begins with a colon-terminated, defined, logical host,
767 ;;; then return that host, otherwise return NIL.
768 (defun extract-logical-host-prefix (namestr start end)
769 (declare (type simple-string namestr)
770 (type index start end)
771 (values (or logical-host null)))
772 (let ((colon-pos (position #\: namestr :start start :end end)))
774 (values (gethash (nstring-upcase (subseq namestr start colon-pos))
778 (defun parse-namestring (thing
781 (defaults *default-pathname-defaults*)
782 &key (start 0) end junk-allowed)
783 (declare (type pathname-designator thing defaults)
784 (type (or list host string (member :unspecific)) host)
786 (type (or index null) end)
787 (type (or t null) junk-allowed)
788 (values (or null pathname) (or null index)))
789 (with-host (found-host host)
790 (let (;; According to ANSI defaults may be any valid pathname designator
791 (defaults (etypecase defaults
795 (aver (pathnamep *default-pathname-defaults*))
796 (parse-namestring defaults))
798 (truename defaults)))))
799 (declare (type pathname defaults))
802 (%parse-namestring thing found-host defaults start end junk-allowed))
804 (%parse-namestring (coerce thing 'simple-string)
805 found-host defaults start end junk-allowed))
807 (let ((defaulted-host (or found-host (%pathname-host defaults))))
808 (declare (type host defaulted-host))
809 (unless (eq defaulted-host (%pathname-host thing))
810 (error "The HOST argument doesn't match the pathname host:~% ~
812 defaulted-host (%pathname-host thing))))
813 (values thing start))
815 (let ((name (file-name thing)))
817 (error "can't figure out the file associated with stream:~% ~S"
819 (values name nil)))))))
821 (defun %parse-native-namestring (namestr host defaults start end junk-allowed
823 (declare (type (or host null) host)
824 (type string namestr)
826 (type (or index null) end))
830 (%parse-native-namestring namestr host defaults start end nil as-directory)
831 (namestring-parse-error (condition)
832 (values nil (namestring-parse-error-offset condition)))))
834 (let* ((end (%check-vector-sequence-bounds namestr start end)))
835 (multiple-value-bind (new-host device directory file type version)
838 (funcall (host-parse-native host) namestr start end as-directory))
839 ((pathname-host defaults)
840 (funcall (host-parse-native (pathname-host defaults))
845 ;; I don't think we should ever get here, as the default
846 ;; host will always have a non-null HOST, given that we
847 ;; can't create a new pathname without going through
848 ;; *DEFAULT-PATHNAME-DEFAULTS*, which has a non-null
850 (t (bug "Fallen through COND in %PARSE-NAMESTRING")))
851 (when (and host new-host (not (eq new-host host)))
852 (error 'simple-type-error
854 :expected-type `(or null (eql ,host))
856 "The host in the namestring, ~S,~@
857 does not match the explicit HOST argument, ~S."
858 :format-arguments (list new-host host)))
859 (let ((pn-host (or new-host host (pathname-host defaults))))
860 (values (%make-pathname
861 pn-host device directory file type version)
864 (defun parse-native-namestring (thing
867 (defaults *default-pathname-defaults*)
868 &key (start 0) end junk-allowed
871 "Convert THING into a pathname, using the native conventions
872 appropriate for the pathname host HOST, or if not specified the
873 host of DEFAULTS. If THING is a string, the parse is bounded by
874 START and END, and error behaviour is controlled by JUNK-ALLOWED,
875 as with PARSE-NAMESTRING. For file systems whose native
876 conventions allow directories to be indicated as files, if
877 AS-DIRECTORY is true, return a pathname denoting THING as a
879 (declare (type pathname-designator thing defaults)
880 (type (or list host string (member :unspecific)) host)
882 (type (or index null) end)
883 (type (or t null) junk-allowed)
884 (values (or null pathname) (or null index)))
885 (with-host (found-host host)
886 (let ((defaults (etypecase defaults
890 (aver (pathnamep *default-pathname-defaults*))
891 (parse-native-namestring defaults))
893 (truename defaults)))))
894 (declare (type pathname defaults))
897 (%parse-native-namestring
898 thing found-host defaults start end junk-allowed as-directory))
900 (%parse-native-namestring (coerce thing 'simple-string)
901 found-host defaults start end junk-allowed
904 (let ((defaulted-host (or found-host (%pathname-host defaults))))
905 (declare (type host defaulted-host))
906 (unless (eq defaulted-host (%pathname-host thing))
907 (error "The HOST argument doesn't match the pathname host:~% ~
909 defaulted-host (%pathname-host thing))))
910 (values thing start))
913 (let ((name (file-name thing)))
915 (error "can't figure out the file associated with stream:~% ~S"
917 (values name nil)))))))
919 (defun namestring (pathname)
921 "Construct the full (name)string form of the pathname."
922 (declare (type pathname-designator pathname))
923 (with-pathname (pathname pathname)
925 (let ((host (%pathname-host pathname)))
927 (error "can't determine the namestring for pathnames with no ~
928 host:~% ~S" pathname))
929 (funcall (host-unparse host) pathname)))))
931 (defun native-namestring (pathname &key as-file)
933 "Construct the full native (name)string form of PATHNAME. For
934 file systems whose native conventions allow directories to be
935 indicated as files, if AS-FILE is true and the name, type, and
936 version components of PATHNAME are all NIL or :UNSPECIFIC,
937 construct a string that names the directory according to the file
938 system's syntax for files."
939 (declare (type pathname-designator pathname))
940 (with-native-pathname (pathname pathname)
942 (let ((host (%pathname-host pathname)))
944 (error "can't determine the native namestring for pathnames with no ~
945 host:~% ~S" pathname))
946 (funcall (host-unparse-native host) pathname as-file)))))
948 (defun host-namestring (pathname)
950 "Return a string representation of the name of the host in the pathname."
951 (declare (type pathname-designator pathname))
952 (with-pathname (pathname pathname)
953 (let ((host (%pathname-host pathname)))
955 (funcall (host-unparse-host host) pathname)
957 "can't determine the namestring for pathnames with no host:~% ~S"
960 (defun directory-namestring (pathname)
962 "Return a string representation of the directories used in the pathname."
963 (declare (type pathname-designator pathname))
964 (with-pathname (pathname pathname)
965 (let ((host (%pathname-host pathname)))
967 (funcall (host-unparse-directory host) pathname)
969 "can't determine the namestring for pathnames with no host:~% ~S"
972 (defun file-namestring (pathname)
974 "Return a string representation of the name 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-file host) pathname)
981 "can't determine the namestring for pathnames with no host:~% ~S"
984 (defun enough-namestring (pathname
986 (defaults *default-pathname-defaults*))
988 "Return an abbreviated pathname sufficent to identify the pathname relative
990 (declare (type pathname-designator pathname))
991 (with-pathname (pathname pathname)
992 (let ((host (%pathname-host pathname)))
994 (with-pathname (defaults defaults)
995 (funcall (host-unparse-enough host) pathname defaults))
997 "can't determine the namestring for pathnames with no host:~% ~S"
1002 (defun wild-pathname-p (pathname &optional field-key)
1004 "Predicate for determining whether pathname contains any wildcards."
1005 (declare (type pathname-designator pathname)
1006 (type (member nil :host :device :directory :name :type :version)
1008 (with-pathname (pathname pathname)
1010 (or (pattern-p x) (member x '(:wild :wild-inferiors)))))
1013 (or (wild-pathname-p pathname :host)
1014 (wild-pathname-p pathname :device)
1015 (wild-pathname-p pathname :directory)
1016 (wild-pathname-p pathname :name)
1017 (wild-pathname-p pathname :type)
1018 (wild-pathname-p pathname :version)))
1019 (:host (frob (%pathname-host pathname)))
1020 (:device (frob (%pathname-host pathname)))
1021 (:directory (some #'frob (%pathname-directory pathname)))
1022 (:name (frob (%pathname-name pathname)))
1023 (:type (frob (%pathname-type pathname)))
1024 (:version (frob (%pathname-version pathname)))))))
1026 (defun pathname-match-p (in-pathname in-wildname)
1028 "Pathname matches the wildname template?"
1029 (declare (type pathname-designator in-pathname))
1030 (with-pathname (pathname in-pathname)
1031 (with-pathname (wildname in-wildname)
1032 (macrolet ((frob (field &optional (op 'components-match))
1033 `(or (null (,field wildname))
1034 (,op (,field pathname) (,field wildname)))))
1035 (and (or (null (%pathname-host wildname))
1036 (eq (%pathname-host wildname) (%pathname-host pathname)))
1037 (frob %pathname-device)
1038 (frob %pathname-directory directory-components-match)
1039 (frob %pathname-name)
1040 (frob %pathname-type)
1041 (or (eq (%pathname-host wildname) *physical-host*)
1042 (frob %pathname-version)))))))
1044 ;;; Place the substitutions into the pattern and return the string or pattern
1045 ;;; that results. If DIDDLE-CASE is true, we diddle the result case as well,
1046 ;;; in case we are translating between hosts with difference conventional case.
1047 ;;; The second value is the tail of subs with all of the values that we used up
1048 ;;; stripped off. Note that PATTERN-MATCHES matches all consecutive wildcards
1049 ;;; as a single string, so we ignore subsequent contiguous wildcards.
1050 (defun substitute-into (pattern subs diddle-case)
1051 (declare (type pattern pattern)
1053 (values (or simple-string pattern) list))
1054 (let ((in-wildcard nil)
1057 (dolist (piece (pattern-pieces pattern))
1058 (cond ((simple-string-p piece)
1059 (push piece strings)
1060 (setf in-wildcard nil))
1063 (setf in-wildcard t)
1065 (error "not enough wildcards in FROM pattern to match ~
1068 (let ((sub (pop subs)))
1072 (push (apply #'concatenate 'simple-string
1075 (dolist (piece (pattern-pieces sub))
1076 (push piece pieces)))
1080 (error "can't substitute this into the middle of a word:~
1085 (push (apply #'concatenate 'simple-string (nreverse strings))
1089 (if (and pieces (simple-string-p (car pieces)) (null (cdr pieces)))
1091 (make-pattern (nreverse pieces)))
1095 ;;; Called when we can't see how source and from matched.
1096 (defun didnt-match-error (source from)
1097 (error "Pathname components from SOURCE and FROM args to TRANSLATE-PATHNAME~@
1098 did not match:~% ~S ~S"
1101 ;;; Do TRANSLATE-COMPONENT for all components except host, directory
1103 (defun translate-component (source from to diddle-case)
1110 (if (pattern= from source)
1112 (didnt-match-error source from)))
1114 (multiple-value-bind (won subs) (pattern-matches from source)
1116 (values (substitute-into to subs diddle-case))
1117 (didnt-match-error source from))))
1119 (maybe-diddle-case source diddle-case))))
1121 (values (substitute-into to (list source) diddle-case)))
1123 (if (components-match source from)
1124 (maybe-diddle-case source diddle-case)
1125 (didnt-match-error source from)))))
1127 (maybe-diddle-case source diddle-case))
1129 (if (components-match source from)
1131 (didnt-match-error source from)))))
1133 ;;; Return a list of all the things that we want to substitute into the TO
1134 ;;; pattern (the things matched by from on source.) When From contains
1135 ;;; :WILD-INFERIORS, the result contains a sublist of the matched source
1137 (defun compute-directory-substitutions (orig-source orig-from)
1138 (let ((source orig-source)
1143 (unless (every (lambda (x) (eq x :wild-inferiors)) from)
1144 (didnt-match-error orig-source orig-from))
1147 (unless from (didnt-match-error orig-source orig-from))
1148 (let ((from-part (pop from))
1149 (source-part (pop source)))
1152 (typecase source-part
1154 (if (pattern= from-part source-part)
1156 (didnt-match-error orig-source orig-from)))
1158 (multiple-value-bind (won new-subs)
1159 (pattern-matches from-part source-part)
1161 (dolist (sub new-subs)
1163 (didnt-match-error orig-source orig-from))))
1165 (didnt-match-error orig-source orig-from))))
1168 ((member :wild-inferiors)
1169 (let ((remaining-source (cons source-part source)))
1172 (when (directory-components-match remaining-source from)
1174 (unless remaining-source
1175 (didnt-match-error orig-source orig-from))
1176 (res (pop remaining-source)))
1178 (setq source remaining-source))))
1180 (unless (and (simple-string-p source-part)
1181 (string= from-part source-part))
1182 (didnt-match-error orig-source orig-from)))
1184 (didnt-match-error orig-source orig-from)))))
1187 ;;; This is called by TRANSLATE-PATHNAME on the directory components
1188 ;;; of its argument pathnames to produce the result directory
1189 ;;; component. If this leaves the directory NIL, we return the source
1190 ;;; directory. The :RELATIVE or :ABSOLUTE is taken from the source
1191 ;;; directory, except if TO is :ABSOLUTE, in which case the result
1192 ;;; will be :ABSOLUTE.
1193 (defun translate-directories (source from to diddle-case)
1194 (if (not (and source to from))
1195 (or (and to (null source) (remove :wild-inferiors to))
1196 (mapcar (lambda (x) (maybe-diddle-case x diddle-case)) source))
1198 ;; If TO is :ABSOLUTE, the result should still be :ABSOLUTE.
1199 (res (if (eq (first to) :absolute)
1202 (let ((subs-left (compute-directory-substitutions (rest source)
1204 (dolist (to-part (rest to))
1208 (let ((match (pop subs-left)))
1210 (error ":WILD-INFERIORS is not paired in from and to ~
1211 patterns:~% ~S ~S" from to))
1212 (res (maybe-diddle-case match diddle-case))))
1213 ((member :wild-inferiors)
1215 (let ((match (pop subs-left)))
1216 (unless (listp match)
1217 (error ":WILD-INFERIORS not paired in from and to ~
1218 patterns:~% ~S ~S" from to))
1220 (res (maybe-diddle-case x diddle-case)))))
1222 (multiple-value-bind
1224 (substitute-into to-part subs-left diddle-case)
1225 (setf subs-left new-subs-left)
1227 (t (res to-part)))))
1230 (defun translate-pathname (source from-wildname to-wildname &key)
1232 "Use the source pathname to translate the from-wildname's wild and
1233 unspecified elements into a completed to-pathname based on the to-wildname."
1234 (declare (type pathname-designator source from-wildname to-wildname))
1235 (with-pathname (source source)
1236 (with-pathname (from from-wildname)
1237 (with-pathname (to to-wildname)
1238 (let* ((source-host (%pathname-host source))
1239 (from-host (%pathname-host from))
1240 (to-host (%pathname-host to))
1242 (and source-host to-host
1243 (not (eq (host-customary-case source-host)
1244 (host-customary-case to-host))))))
1245 (macrolet ((frob (field &optional (op 'translate-component))
1246 `(let ((result (,op (,field source)
1250 (if (eq result :error)
1251 (error "~S doesn't match ~S." source from)
1253 (%make-maybe-logical-pathname
1254 (or to-host source-host)
1255 (frob %pathname-device)
1256 (frob %pathname-directory translate-directories)
1257 (frob %pathname-name)
1258 (frob %pathname-type)
1259 (if (eq from-host *physical-host*)
1260 (if (or (eq (%pathname-version to) :wild)
1261 (eq (%pathname-version to) nil))
1262 (%pathname-version source)
1263 (%pathname-version to))
1264 (frob %pathname-version)))))))))
1266 ;;;; logical pathname support. ANSI 92-102 specification.
1268 ;;;; As logical-pathname translations are loaded they are
1269 ;;;; canonicalized as patterns to enable rapid efficient translation
1270 ;;;; into physical pathnames.
1274 (defun simplify-namestring (namestring &optional host)
1275 (funcall (host-simplify-namestring
1277 (pathname-host (sane-default-pathname-defaults))))
1280 ;;; Canonicalize a logical pathname word by uppercasing it checking that it
1281 ;;; contains only legal characters.
1282 (defun logical-word-or-lose (word)
1283 (declare (string word))
1284 (when (string= word "")
1285 (error 'namestring-parse-error
1286 :complaint "Attempted to treat invalid logical hostname ~
1287 as a logical host:~% ~S"
1289 :namestring word :offset 0))
1290 (let ((word (string-upcase word)))
1291 (dotimes (i (length word))
1292 (let ((ch (schar word i)))
1293 (unless (and (typep ch 'standard-char)
1294 (or (alpha-char-p ch) (digit-char-p ch) (char= ch #\-)))
1295 (error 'namestring-parse-error
1296 :complaint "logical namestring character which ~
1297 is not alphanumeric or hyphen:~% ~S"
1299 :namestring word :offset i))))
1300 (coerce word 'string))) ; why not simple-string?
1302 ;;; Given a logical host or string, return a logical host. If ERROR-P
1303 ;;; is NIL, then return NIL when no such host exists.
1304 (defun find-logical-host (thing &optional (errorp t))
1307 (let ((found (gethash (logical-word-or-lose thing)
1309 (if (or found (not errorp))
1311 ;; This is the error signalled from e.g.
1312 ;; LOGICAL-PATHNAME-TRANSLATIONS when host is not a defined
1313 ;; host, and ANSI specifies that that's a TYPE-ERROR.
1314 (error 'simple-type-error
1316 ;; God only knows what ANSI expects us to use for
1317 ;; the EXPECTED-TYPE here. Maybe this will be OK..
1319 '(and string (satisfies logical-pathname-translations))
1320 :format-control "logical host not yet defined: ~S"
1321 :format-arguments (list thing)))))
1322 (logical-host thing)))
1324 ;;; Given a logical host name or host, return a logical host, creating
1325 ;;; a new one if necessary.
1326 (defun intern-logical-host (thing)
1327 (declare (values logical-host))
1328 (with-locked-system-table (*logical-hosts*)
1329 (or (find-logical-host thing nil)
1330 (let* ((name (logical-word-or-lose thing))
1331 (new (make-logical-host :name name)))
1332 (setf (gethash name *logical-hosts*) new)
1335 ;;;; logical pathname parsing
1337 ;;; Deal with multi-char wildcards in a logical pathname token.
1338 (defun maybe-make-logical-pattern (namestring chunks)
1339 (let ((chunk (caar chunks)))
1340 (collect ((pattern))
1342 (len (length chunk)))
1343 (declare (fixnum last-pos))
1345 (when (= last-pos len) (return))
1346 (let ((pos (or (position #\* chunk :start last-pos) len)))
1347 (if (= pos last-pos)
1349 (error 'namestring-parse-error
1350 :complaint "double asterisk inside of logical ~
1353 :namestring namestring
1354 :offset (+ (cdar chunks) pos)))
1355 (pattern (subseq chunk last-pos pos)))
1358 (pattern :multi-char-wild))
1359 (setq last-pos (1+ pos)))))
1362 (make-pattern (pattern))
1363 (let ((x (car (pattern))))
1364 (if (eq x :multi-char-wild)
1368 ;;; Return a list of conses where the CDR is the start position and
1369 ;;; the CAR is a string (token) or character (punctuation.)
1370 (defun logical-chunkify (namestr start end)
1372 (do ((i start (1+ i))
1376 (chunks (cons (nstring-upcase (subseq namestr prev end)) prev))))
1377 (let ((ch (schar namestr i)))
1378 (unless (or (alpha-char-p ch) (digit-char-p ch)
1379 (member ch '(#\- #\*)))
1381 (chunks (cons (nstring-upcase (subseq namestr prev i)) prev)))
1383 (unless (member ch '(#\; #\: #\.))
1384 (error 'namestring-parse-error
1385 :complaint "illegal character for logical pathname:~% ~S"
1389 (chunks (cons ch i)))))
1392 ;;; Break up a logical-namestring, always a string, into its
1393 ;;; constituent parts.
1394 (defun parse-logical-namestring (namestr start end)
1395 (declare (type simple-string namestr)
1396 (type index start end))
1397 (collect ((directory))
1402 (labels ((expecting (what chunks)
1403 (unless (and chunks (simple-string-p (caar chunks)))
1404 (error 'namestring-parse-error
1405 :complaint "expecting ~A, got ~:[nothing~;~S~]."
1406 :args (list what (caar chunks) (caar chunks))
1408 :offset (if chunks (cdar chunks) end)))
1410 (parse-host (chunks)
1411 (case (caadr chunks)
1414 (find-logical-host (expecting "a host name" chunks)))
1415 (parse-relative (cddr chunks)))
1417 (parse-relative chunks))))
1418 (parse-relative (chunks)
1421 (directory :relative)
1422 (parse-directory (cdr chunks)))
1424 (directory :absolute) ; Assumption! Maybe revoked later.
1425 (parse-directory chunks))))
1426 (parse-directory (chunks)
1427 (case (caadr chunks)
1430 (let ((res (expecting "a directory name" chunks)))
1431 (cond ((string= res "..") :up)
1432 ((string= res "**") :wild-inferiors)
1434 (maybe-make-logical-pattern namestr chunks)))))
1435 (parse-directory (cddr chunks)))
1437 (parse-name chunks))))
1438 (parse-name (chunks)
1440 (expecting "a file name" chunks)
1441 (setq name (maybe-make-logical-pattern namestr chunks))
1442 (expecting-dot (cdr chunks))))
1443 (expecting-dot (chunks)
1445 (unless (eql (caar chunks) #\.)
1446 (error 'namestring-parse-error
1447 :complaint "expecting a dot, got ~S."
1448 :args (list (caar chunks))
1450 :offset (cdar chunks)))
1452 (parse-version (cdr chunks))
1453 (parse-type (cdr chunks)))))
1454 (parse-type (chunks)
1455 (expecting "a file type" chunks)
1456 (setq type (maybe-make-logical-pattern namestr chunks))
1457 (expecting-dot (cdr chunks)))
1458 (parse-version (chunks)
1459 (let ((str (expecting "a positive integer, * or NEWEST"
1462 ((string= str "*") (setq version :wild))
1463 ((string= str "NEWEST") (setq version :newest))
1465 (multiple-value-bind (res pos)
1466 (parse-integer str :junk-allowed t)
1467 (unless (and res (plusp res))
1468 (error 'namestring-parse-error
1469 :complaint "expected a positive integer, ~
1473 :offset (+ pos (cdar chunks))))
1474 (setq version res)))))
1476 (error 'namestring-parse-error
1477 :complaint "extra stuff after end of file name"
1479 :offset (cdadr chunks)))))
1480 (parse-host (logical-chunkify namestr start end)))
1481 (values host :unspecific (directory) name type version))))
1483 ;;; We can't initialize this yet because not all host methods are
1485 (defvar *logical-pathname-defaults*)
1487 (defun logical-namestring-p (x)
1490 (typep (pathname x) 'logical-pathname))))
1492 (deftype logical-namestring ()
1493 `(satisfies logical-namestring-p))
1495 (defun logical-pathname (pathspec)
1497 "Converts the pathspec argument to a logical-pathname and returns it."
1498 (declare (type (or logical-pathname string stream) pathspec)
1499 (values logical-pathname))
1500 (if (typep pathspec 'logical-pathname)
1502 (flet ((oops (problem)
1503 (error 'simple-type-error
1505 :expected-type 'logical-namestring
1506 :format-control "~S is not a valid logical namestring:~% ~A"
1507 :format-arguments (list pathspec problem))))
1508 (let ((res (handler-case
1509 (parse-namestring pathspec nil *logical-pathname-defaults*)
1510 (error (e) (oops e)))))
1511 (when (eq (%pathname-host res)
1512 (%pathname-host *logical-pathname-defaults*))
1513 (oops "no host specified"))
1516 ;;;; logical pathname unparsing
1518 (defun unparse-logical-directory (pathname)
1519 (declare (type pathname pathname))
1521 (let ((directory (%pathname-directory pathname)))
1523 (ecase (pop directory)
1524 (:absolute) ; nothing special
1525 (:relative (pieces ";")))
1526 (dolist (dir directory)
1527 (cond ((or (stringp dir) (pattern-p dir))
1528 (pieces (unparse-logical-piece dir))
1532 ((eq dir :wild-inferiors)
1535 (error "invalid directory component: ~S" dir))))))
1536 (apply #'concatenate 'simple-string (pieces))))
1538 (defun unparse-logical-piece (thing)
1540 ((member :wild) "*")
1541 (simple-string thing)
1543 (collect ((strings))
1544 (dolist (piece (pattern-pieces thing))
1546 (simple-string (strings piece))
1548 (cond ((eq piece :wild-inferiors)
1550 ((eq piece :multi-char-wild)
1552 (t (error "invalid keyword: ~S" piece))))))
1553 (apply #'concatenate 'simple-string (strings))))))
1555 (defun unparse-logical-file (pathname)
1556 (declare (type pathname pathname))
1557 (collect ((strings))
1558 (let* ((name (%pathname-name pathname))
1559 (type (%pathname-type pathname))
1560 (version (%pathname-version pathname))
1561 (type-supplied (not (or (null type) (eq type :unspecific))))
1562 (version-supplied (not (or (null version)
1563 (eq version :unspecific)))))
1565 (when (and (null type)
1566 (typep name 'string)
1567 (position #\. name :start 1))
1568 (error "too many dots in the name: ~S" pathname))
1569 (strings (unparse-logical-piece name)))
1572 (error "cannot specify the type without a file: ~S" pathname))
1573 (when (typep type 'string)
1574 (when (position #\. type)
1575 (error "type component can't have a #\. inside: ~S" pathname)))
1577 (strings (unparse-logical-piece type)))
1578 (when version-supplied
1579 (unless type-supplied
1580 (error "cannot specify the version without a type: ~S" pathname))
1582 ((member :newest) (strings ".NEWEST"))
1583 ((member :wild) (strings ".*"))
1584 (fixnum (strings ".") (strings (format nil "~D" version))))))
1585 (apply #'concatenate 'simple-string (strings))))
1587 ;;; Unparse a logical pathname string.
1588 (defun unparse-enough-namestring (pathname defaults)
1589 (let* ((path-directory (pathname-directory pathname))
1590 (def-directory (pathname-directory defaults))
1592 ;; Go down the directory lists to see what matches. What's
1593 ;; left is what we want, more or less.
1594 (cond ((and (eq (first path-directory) (first def-directory))
1595 (eq (first path-directory) :absolute))
1596 ;; Both paths are :ABSOLUTE, so find where the
1597 ;; common parts end and return what's left
1598 (do* ((p (rest path-directory) (rest p))
1599 (d (rest def-directory) (rest d)))
1600 ((or (endp p) (endp d)
1601 (not (equal (first p) (first d))))
1604 ;; At least one path is :RELATIVE, so just return the
1605 ;; original path. If the original path is :RELATIVE,
1606 ;; then that's the right one. If PATH-DIRECTORY is
1607 ;; :ABSOLUTE, we want to return that except when
1608 ;; DEF-DIRECTORY is :ABSOLUTE, as handled above. so return
1609 ;; the original directory.
1611 (unparse-logical-namestring
1612 (make-pathname :host (pathname-host pathname)
1613 :directory enough-directory
1614 :name (pathname-name pathname)
1615 :type (pathname-type pathname)
1616 :version (pathname-version pathname)))))
1618 (defun unparse-logical-namestring (pathname)
1619 (declare (type logical-pathname pathname))
1620 (concatenate 'simple-string
1621 (logical-host-name (%pathname-host pathname)) ":"
1622 (unparse-logical-directory pathname)
1623 (unparse-logical-file pathname)))
1625 ;;;; logical pathname translations
1627 ;;; Verify that the list of translations consists of lists and prepare
1628 ;;; canonical translations. (Parse pathnames and expand out wildcards
1630 (defun canonicalize-logical-pathname-translations (translation-list host)
1631 (declare (type list translation-list) (type host host)
1633 (mapcar (lambda (translation)
1634 (destructuring-bind (from to) translation
1635 (list (if (typep from 'logical-pathname)
1637 (parse-namestring from host))
1641 (defun logical-pathname-translations (host)
1643 "Return the (logical) host object argument's list of translations."
1644 (declare (type (or string logical-host) host)
1646 (logical-host-translations (find-logical-host host)))
1648 (defun (setf logical-pathname-translations) (translations host)
1650 "Set the translations list for the logical host argument."
1651 (declare (type (or string logical-host) host)
1652 (type list translations)
1654 (let ((host (intern-logical-host host)))
1655 (setf (logical-host-canon-transls host)
1656 (canonicalize-logical-pathname-translations translations host))
1657 (setf (logical-host-translations host) translations)))
1659 (defun translate-logical-pathname (pathname &key)
1661 "Translate PATHNAME to a physical pathname, which is returned."
1662 (declare (type pathname-designator pathname)
1663 (values (or null pathname)))
1666 (dolist (x (logical-host-canon-transls (%pathname-host pathname))
1667 (error 'simple-file-error
1669 :format-control "no translation for ~S"
1670 :format-arguments (list pathname)))
1671 (destructuring-bind (from to) x
1672 (when (pathname-match-p pathname from)
1673 (return (translate-logical-pathname
1674 (translate-pathname pathname from to)))))))
1676 (t (translate-logical-pathname (pathname pathname)))))
1678 (defvar *logical-pathname-defaults*
1679 (%make-logical-pathname
1680 (make-logical-host :name (logical-word-or-lose "BOGUS"))
1681 :unspecific nil nil nil nil))
1683 (defun load-logical-pathname-translations (host)
1685 "Reads logical pathname translations from SYS:SITE;HOST.TRANSLATIONS.NEWEST,
1686 with HOST replaced by the supplied parameter. Returns T on success.
1688 If HOST is already defined as logical pathname host, no file is loaded and NIL
1691 The file should contain a single form, suitable for use with
1692 \(SETF LOGICAL-PATHNAME-TRANSLATIONS).
1694 Note: behaviour of this function is higly implementation dependent, and
1695 historically it used to be a no-op in SBcL -- the current approach is somewhat
1696 experimental and subject to change."
1697 (declare (type string host)
1698 (values (member t nil)))
1699 (if (find-logical-host host nil)
1700 ;; This host is already defined, all is well and good.
1702 ;; ANSI: "The specific nature of the search is
1703 ;; implementation-defined."
1705 (setf (logical-pathname-translations host)
1706 (with-open-file (lpt (make-pathname :host "SYS"
1707 :directory '(:absolute "SITE")
1709 :type "TRANSLATIONS"
1713 (defun !pathname-cold-init ()
1714 (let* ((sys *default-pathname-defaults*)
1717 (make-pathname :directory '(:relative "src" :wild-inferiors)
1718 :name :wild :type :wild)
1722 (make-pathname :directory '(:relative "contrib" :wild-inferiors)
1723 :name :wild :type :wild)
1727 (make-pathname :directory '(:relative "output" :wild-inferiors)
1728 :name :wild :type :wild)
1730 (setf (logical-pathname-translations "SYS")
1731 `(("SYS:SRC;**;*.*.*" ,src)
1732 ("SYS:CONTRIB;**;*.*.*" ,contrib)
1733 ("SYS:OUTPUT;**;*.*.*" ,output)))))
1735 (defun set-sbcl-source-location (pathname)
1736 "Initialize the SYS logical host based on PATHNAME, which should be
1737 the top-level directory of the SBCL sources. This will replace any
1738 existing translations for \"SYS:SRC;\", \"SYS:CONTRIB;\", and
1739 \"SYS:OUTPUT;\". Other \"SYS:\" translations are preserved."
1740 (let ((truename (truename pathname))
1741 (current-translations
1742 (remove-if (lambda (translation)
1743 (or (pathname-match-p "SYS:SRC;" translation)
1744 (pathname-match-p "SYS:CONTRIB;" translation)
1745 (pathname-match-p "SYS:OUTPUT;" translation)))
1746 (logical-pathname-translations "SYS")
1748 (flet ((physical-target (component)
1750 (make-pathname :directory (list :relative component
1755 (setf (logical-pathname-translations "SYS")
1756 `(("SYS:SRC;**;*.*.*" ,(physical-target "src"))
1757 ("SYS:CONTRIB;**;*.*.*" ,(physical-target "contrib"))
1758 ("SYS:OUTPUT;**;*.*.*" ,(physical-target "output"))
1759 ,@current-translations)))))