source: trunk/abcl/src/org/armedbear/lisp/pathnames.lisp

Last change on this file was 15422, checked in by Mark Evenson, 4 years ago

pathname: remove references to %invalidate-namestring

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 19.7 KB
Line 
1;;; pathnames.lisp
2;;;
3;;; Copyright (C) 2003-2007 Peter Graves
4;;; $Id: pathnames.lisp 15422 2020-10-20 06:45:21Z mevenson $
5;;;
6;;; This program is free software; you can redistribute it and/or
7;;; modify it under the terms of the GNU General Public License
8;;; as published by the Free Software Foundation; either version 2
9;;; of the License, or (at your option) any later version.
10;;;
11;;; This program is distributed in the hope that it will be useful,
12;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
13;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14;;; GNU General Public License for more details.
15;;;
16;;; You should have received a copy of the GNU General Public License
17;;; along with this program; if not, write to the Free Software
18;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19;;;
20;;; As a special exception, the copyright holders of this library give you
21;;; permission to link this library with independent modules to produce an
22;;; executable, regardless of the license terms of these independent
23;;; modules, and to copy and distribute the resulting executable under
24;;; terms of your choice, provided that you also meet, for each linked
25;;; independent module, the terms and conditions of the license of that
26;;; module.  An independent module is a module which is not derived from
27;;; or based on this library.  If you modify this library, you may extend
28;;; this exception to your version of the library, but you are not
29;;; obligated to do so.  If you do not wish to do so, delete this
30;;; exception statement from your version.
31
32(in-package "SYSTEM")
33
34(export '(logical-host-p))
35
36(defun pathname-host (pathname &key (case :local))
37  (%pathname-host pathname case))
38
39(defun pathname-device (pathname &key (case :local))
40  (%pathname-device pathname case))
41
42(defun pathname-directory (pathname &key (case :local))
43  (%pathname-directory pathname case))
44
45(defun pathname-name (pathname &key (case :local))
46  (%pathname-name pathname case))
47
48(defun pathname-type (pathname &key (case :local))
49  (%pathname-type pathname case))
50
51(defun wild-pathname-p (pathname &optional field-key)
52  (%wild-pathname-p pathname field-key))
53
54(defun component-match-wild-p (thing wild ignore-case)
55  (let ((testfunc (if ignore-case #'equalp #'equal)))
56    (labels ((split-string (delim str)
57               (flet ((finder (char) (find char delim)))
58                 (loop
59                   :for x = (position-if-not #'finder str)
60                     :then (position-if-not #'finder str :start (or y (length str)))
61                   :for y = (position-if #'finder str :start (or x (length str)))
62                     :then (position-if #'finder str :start (or x (length str)))
63                   :while x 
64                    :collect (subseq str x y))))
65             (positions-larger (thing substrings previous-pos)
66               (let ((new-pos (search (car substrings) 
67                                      thing 
68                                      :start2 previous-pos
69                                      :test testfunc)))
70                 (or 
71                  (not substrings)
72                  (and new-pos
73                       (>= new-pos previous-pos)
74                       (positions-larger thing 
75                                         (cdr substrings) 
76                                         new-pos))))))
77      (let ((split-result (split-string "*" wild)))
78        (and (positions-larger thing split-result 0)
79             (if (eql (elt wild 0) #\*)
80                 t
81                 (eql (search (first split-result) thing :test testfunc) 0))
82             (if (eql (elt wild (1- (length wild))) #\*)
83                 t
84                 (let ((last-split-result (first (last split-result))))
85                   (eql (search last-split-result thing :from-end t 
86                                :test testfunc)
87                        (- (length thing) (length last-split-result))))))))))
88
89(defun component-match-p (thing wild ignore-case)
90  (cond ((eq wild :wild)
91         t)
92        ((null wild)
93         t)
94        ((and (stringp wild) (position #\* wild))
95         (component-match-wild-p thing wild ignore-case))
96        (ignore-case
97         (equalp thing wild))
98        (t
99         (equal thing wild))))
100
101(defun directory-match-components (thing wild ignore-case)
102  (loop
103    (cond ((endp thing)
104           (return (or (endp wild) (equal wild '(:wild-inferiors)))))
105          ((endp wild)
106           (return nil)))
107    (let ((x (car thing))
108          (y (car wild)))
109      (when (eq y :wild-inferiors)
110        (return t))
111      (unless (component-match-p x y ignore-case)
112        (return nil))
113      (setf thing (cdr thing)
114            wild  (cdr wild)))))
115
116(defun directory-match-p (thing wild ignore-case)
117  (cond ((eq wild :wild)
118         t)
119        ((null wild)
120         t)
121        ((and ignore-case (equalp thing wild))
122         t)
123        ((equal thing wild)
124         t)
125        ((and (null thing) (equal wild '(:absolute :wild-inferiors)))
126         t)
127        ((and (consp thing) (consp wild))
128         (if (eq (%car thing) (%car wild))
129             (directory-match-components (%cdr thing) (%cdr wild) ignore-case)
130             nil))
131        (t
132         nil)))
133
134(defun pathname-match-p (pathname wildcard)
135  (setf pathname (pathname pathname)
136        wildcard (pathname wildcard))
137  (unless (component-match-p (pathname-host pathname) (pathname-host wildcard) nil)
138    (return-from pathname-match-p nil))
139  (when (and (pathname-jar-p pathname) 
140             (pathname-jar-p wildcard))
141    (unless 
142        (every (lambda (value) (not (null value)))
143               (mapcar #'pathname-match-p 
144                       (pathname-device pathname) 
145                       (pathname-device wildcard)))
146      (return-from pathname-match-p nil)))
147  (when (or (and (pathname-jar-p pathname)
148                 (not (pathname-jar-p wildcard)))
149            (and (not (pathname-jar-p pathname))
150                 (pathname-jar-p wildcard)))
151    (return-from pathname-match-p nil))
152  (let* ((windows-p (featurep :windows))
153         (ignore-case (or windows-p (typep pathname 'logical-pathname))))
154    (cond ((and windows-p
155                (not (pathname-jar-p pathname))
156                (not (pathname-jar-p wildcard))
157                (not (component-match-p (pathname-device pathname)
158                                        (pathname-device wildcard)
159                                        ignore-case)))
160           nil)
161          ((not (directory-match-p (pathname-directory pathname)
162                                   (pathname-directory wildcard)
163                                   ignore-case))
164           nil)
165          ((not (component-match-p (pathname-name pathname)
166                                   (pathname-name wildcard)
167                                   ignore-case))
168           nil)
169          ((not (component-match-p (pathname-type pathname)
170                                   (pathname-type wildcard)
171                                   ignore-case))
172           nil)
173          (t
174           t))))
175
176(defun wild-p (component)
177  (or (eq component :wild)
178      (and (stringp component)
179           (position #\* component))))
180
181(defun casify (thing case)
182  (typecase thing
183    (string
184     (case case
185       (:upcase (string-upcase thing))
186       (:downcase (string-downcase thing))
187       (t thing)))
188    (list
189     (let (result)
190       (dolist (component thing (nreverse result))
191         (push (casify component case) result))))
192    (t
193     thing)))
194
195(defun translate-component (source from to &optional case)
196  (declare (ignore from))
197  (cond ((or (eq to :wild) (null to))
198         ;; "If the piece in TO-WILDCARD is :WILD or NIL, the piece in source
199         ;; is copied into the result."
200         (casify source case))
201        ((and to (not (wild-p to)))
202        ;; "If the piece in TO-WILDCARD is present and not wild, it is copied
203        ;; into the result."
204         to)
205        (t
206         ;; "Otherwise, the piece in TO-WILDCARD might be a complex wildcard
207         ;; such as "foo*bar" and the piece in FROM-WILDCARD should be wild;
208         ;; the portion of the piece in SOURCE that matches the wildcard
209         ;; portion of the piece in FROM-WILDCARD replaces the wildcard portion
210         ;; of the piece in TO-WILDCARD and the value produced is used in the
211         ;; result."
212         ;; FIXME
213         (error "Unsupported wildcard pattern: ~S" to))))
214
215(defun translate-jar-device (source from to &optional case)
216  (declare (ignore case)) ; FIXME
217  (unless to
218    (return-from translate-jar-device nil))
219  (when (not (= (length source) 
220                (length from)
221                (length to)))
222    (error "Unsupported pathname translation for unequal jar ~
223  references: ~S != ~S != ~S" source from to))
224  (mapcar #'translate-pathname source from to))
225
226(defun translate-directory-components-aux (src from to case)
227  (cond
228    ((and (null src) (null from) (null to))
229     NIL)
230    ((and to
231          (not (member (car to) '(:wild :wild-inferiors))))
232     (cons (casify (car to) case)
233           (translate-directory-components-aux 
234            src from (cdr to) case)))
235    ((and (not src) 
236          (eq (car from) :wild-inferiors) 
237          (eq (car to) :wild-inferiors))
238     (translate-directory-components-aux src (cdr from) (cdr to) case))
239    ((not (and src from))
240     ;; both are NIL --> TO is a wildcard which can't be matched
241     ;; either is NIL --> SRC can't be fully matched against FROM, vice versa
242     (throw 'failed-match nil))
243    ((not (member (car from) '(:wild :wild-inferiors)))
244     (unless (string= (casify (car src) case) (casify (car from) case))
245       (throw 'failed-match nil)) ;; FROM doesn't match SRC
246     (translate-directory-components-aux (cdr src) (cdr from) to case))
247    ((not (eq (car from) (car to))) ;; TO is NIL while FROM is not, or
248     (throw 'failed-match nil))     ;; FROM wildcard doesn't match TO wildcard
249    ((eq (car to) :wild)  ;; FROM and TO wildcards are :WILD
250     (cons (casify (car src) case)
251       (translate-directory-components-aux (cdr src) (cdr from) (cdr to) case)))
252    ((eq (car to) :wild-inferiors) ;; FROM and TO wildcards are :WILD-INFERIORS
253     (do ((src (cdr src) (cdr src))
254          (match (list (casify (car src) case))
255                 (cons (casify (car src) case) match)))
256         (NIL) ;; we'll exit the loop in different ways
257       (catch 'failed-match
258         (return-from translate-directory-components-aux
259           (append (reverse match) 
260                   (translate-directory-components-aux
261                    src (cdr from) (cdr to) case))))
262       (when (and (null src) 
263                  (eq (car from) :wild-inferiors)
264                  (eq (car to) :wild-inferiors))
265         (return-from translate-directory-components-aux nil))
266       (when (null src) ;; SRC is NIL and we're still here: error exit
267         (throw 'failed-match nil))))))
268
269(defun translate-directory-components (src from to case)
270  (catch 'failed-match
271    (return-from translate-directory-components
272      (translate-directory-components-aux src from to case)))
273  (error "Unsupported case in TRANSLATE-DIRECTORY-COMPONENTS."))
274
275
276(defun translate-directory (source from to case)
277  ;; FIXME The IGNORE-CASE argument to DIRECTORY-MATCH-P should not be nil on
278  ;; Windows or if the source pathname is a logical pathname.
279  ;; FIXME We can canonicalize logical pathnames to upper case, so we only need
280  ;; IGNORE-CASE for Windows.
281  (cond ((null source)
282         to)
283        ((equal source '(:absolute))
284         (remove :wild-inferiors to))
285        (t
286         (translate-directory-components source from to case))))
287
288;; "The resulting pathname is TO-WILDCARD with each wildcard or missing field
289;; replaced by a portion of SOURCE."
290(defun translate-pathname (source from-wildcard to-wildcard &key)
291  (unless (pathname-match-p source from-wildcard)
292    (error "~S and ~S do not match." source from-wildcard))
293  (let* ((source (pathname source))
294         (from   (pathname from-wildcard))
295         (to     (pathname to-wildcard))
296         (device (if (typep 'to 'logical-pathname)
297                     :unspecific
298                     (if (pathname-jar-p source)
299                         (translate-jar-device (pathname-device source)
300                                               (pathname-device from)
301                                               (pathname-device to))
302                         (translate-component (pathname-device source)
303                                              (pathname-device from)
304                                              (pathname-device to)))))
305         (case   (and (typep source 'logical-pathname)
306                      (or (featurep :unix) (featurep :windows))
307                      :downcase)))
308    (make-pathname :host      (pathname-host to)
309                   :device    (cond ((typep to 'logical-pathname)
310                                     :unspecific)
311                                    ((eq device :unspecific)
312                                     nil)
313                                    (t
314                                     device))
315                   :directory (translate-directory (pathname-directory source)
316                                                   (pathname-directory from)
317                                                   (pathname-directory to)
318                                                   case)
319                   :name      (translate-component (pathname-name source)
320                                                   (pathname-name from)
321                                                   (pathname-name to)
322                                                   case)
323                   :type      (translate-component (pathname-type source)
324                                                   (pathname-type from)
325                                                   (pathname-type to)
326                                                   case)
327                   :version   (if (null (pathname-host from))
328                                  (if (or (eq (pathname-version to) :wild)
329                                          (eq (pathname-version to) nil))
330                                      (pathname-version source)
331                                      (pathname-version to))
332                                  (translate-component (pathname-version source)
333                                                       (pathname-version from)
334                                                       (pathname-version to))))))
335
336(defun logical-host-p (canonical-host)
337  (multiple-value-bind (translations present)
338      (gethash canonical-host *logical-pathname-translations*)
339    (declare (ignore translations))
340    present))
341
342(defun logical-pathname-translations (host)
343  (multiple-value-bind (translations present)
344      (gethash (canonicalize-logical-host host) *logical-pathname-translations*)
345    (unless present
346      (error 'type-error
347             :datum host
348             :expected-type '(and string (satisfies logical-host-p))))
349    translations))
350
351(defun canonicalize-logical-pathname-translations (translations host)
352  (let (result)
353    (dolist (translation translations (nreverse result))
354      (let ((from (car translation))
355            (to (cadr translation)))
356        (push (list (if (typep from 'logical-pathname)
357                        from
358                        (parse-namestring from host))
359                    (pathname to))
360              result)))))
361
362(defun %set-logical-pathname-translations (host translations)
363  (setf host (canonicalize-logical-host host))
364  ;; Avoid undefined host error in CANONICALIZE-LOGICAL-PATHNAME-TRANSLATIONS.
365  (unless (logical-host-p host)
366    (setf (gethash host *logical-pathname-translations*) nil))
367  (setf (gethash host *logical-pathname-translations*)
368        (canonicalize-logical-pathname-translations translations host)))
369
370(defsetf logical-pathname-translations %set-logical-pathname-translations)
371
372(defun translate-logical-pathname (pathname &key)
373  (typecase pathname
374    (logical-pathname
375     (let* ((host (pathname-host pathname))
376            (translations (logical-pathname-translations host)))
377       (dolist (translation translations
378                            (error 'file-error
379                                   :pathname pathname
380                                   :format-control "No translation for ~S"
381                                   :format-arguments (list pathname)))
382         (let ((from-wildcard (car translation))
383               (to-wildcard (cadr translation)))
384           (when (pathname-match-p pathname from-wildcard)
385             (return (translate-logical-pathname
386                      (translate-pathname pathname from-wildcard to-wildcard))))))))
387    (pathname pathname)
388    (t
389     (translate-logical-pathname (pathname pathname)))))
390
391(defun load-logical-pathname-translations (host)
392  (declare (type string host))
393  (multiple-value-bind (ignore found)
394      (gethash (canonicalize-logical-host host)
395               *logical-pathname-translations*)
396    (declare (ignore ignore))
397    (unless found
398      (error "The logical host ~S was not found." host))))
399
400(defun logical-pathname (pathspec)
401  (typecase pathspec
402    (logical-pathname pathspec)
403    (string
404     (%make-logical-pathname pathspec))
405    (stream
406     (let ((result (pathname pathspec)))
407       (if (typep result 'logical-pathname)
408           result
409           (error 'simple-type-error
410                  :datum result
411                  :expected-type 'logical-pathname))))
412    (t
413     (error 'type-error
414            :datum pathspec
415            :expected-type '(or logical-pathname string stream)))))
416
417(defun parse-namestring (thing
418                         &optional host (default-pathname *default-pathname-defaults*)
419                         &key (start 0) end junk-allowed)
420  (declare (ignore junk-allowed)) ; FIXME
421  (cond ((eq host :unspecific)
422         (setf host nil))
423        ((consp host)) ;; A URL
424        (host
425         (setf host (canonicalize-logical-host host))))
426  (typecase thing
427    (stream
428     (values (pathname thing) start))
429    (pathname
430     (values thing start))
431    (string
432     (unless end
433       (setf end (length thing)))
434     (%parse-namestring (subseq thing start end) host default-pathname))
435    (t
436     (error 'type-error
437            :format-control "~S cannot be converted to a pathname."
438            :format-arguments (list thing)))))
439
440
441;;; Functions for dealing with URL Pathnames
442
443(in-package :extensions)
444
445(defun url-pathname-scheme (p)
446  (unless (pathname-url-p p)
447    (error "~A is not a URL pathname." p))
448  (getf (pathname-host p) :scheme))
449
450(defun set-url-pathname-scheme (p v)
451  (unless (pathname-url-p p)
452    (error "~A is not a URL pathname." p))
453  (let ((host (pathname-host p)))
454    (setf (getf host :scheme) v)))
455
456(defsetf url-pathname-scheme set-url-pathname-scheme)
457
458(defun url-pathname-authority (p)
459  (unless (pathname-url-p p)
460    (error "~A is not a URL pathname." p))
461  (getf (pathname-host p) :authority))
462
463(defun set-url-pathname-authority (p v)
464  (unless (pathname-url-p p)
465    (error "~A is not a URL pathname." p))
466  (let ((host (pathname-host p)))
467    (setf (getf host :authority) v)))
468
469
470(defsetf url-pathname-authority set-url-pathname-authority)
471
472(defun url-pathname-query (p)
473  (unless (pathname-url-p p)
474    (error "~A is not a URL pathname." p))
475  (getf (pathname-host p) :query))
476
477(defun set-url-pathname-query (p v)
478  (unless (pathname-url-p p)
479    (error "~A is not a URL pathname." p))
480  (let ((host (pathname-host p)))
481    (setf (getf host :query) v)))
482
483(defsetf url-pathname-query set-url-pathname-query)
484
485(defun url-pathname-fragment (p)
486  (unless (pathname-url-p p)
487    (error "~A is not a URL pathname." p))
488  (getf (pathname-host p) :fragment))
489
490(defun set-url-pathname-fragment (p v)
491  (unless (pathname-url-p p)
492    (error "~A is not a URL pathname." p))
493  (let ((host (pathname-host p)))
494    (setf (getf host :fragment) v)))
495
496(defsetf url-pathname-fragment set-url-pathname-fragment)
497
498(export '(url-pathname-scheme
499          url-pathname-authority
500          url-pathname-query
501          url-pathname-fragment) 
502        'ext)
Note: See TracBrowser for help on using the repository browser.