source: branches/0.17.x/abcl/src/org/armedbear/lisp/pathnames.lisp

Last change on this file was 11577, checked in by vvoutilainen, 17 years ago

Support "partial" wildcards in DIRECTORY, like

"/path/somewh*re/foo*.txt". This also makes cl-bench

report.lisp work with either CL*.* (the form in report.lisp)
or CL* (the form which is the only one that clisp works with).

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 15.7 KB
Line 
1;;; pathnames.lisp
2;;;
3;;; Copyright (C) 2003-2007 Peter Graves
4;;; $Id: pathnames.lisp 11577 2009-01-23 19:37:18Z vvoutilainen $
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  for x = (position-if-not #'finder str) then
59          (position-if-not #'finder str :start (or y (length str)))
60        for y = (position-if #'finder str :start x) then
61          (position-if #'finder str :start (or x (length str))) while x 
62        collect (subseq str x y))))
63       (positions-larger (thing substrings previous-pos)
64         (let ((new-pos (search (car substrings) 
65              thing 
66              :start2 previous-pos
67              :test testfunc)))
68     (or 
69      (not substrings)
70      (and new-pos
71           (>= new-pos previous-pos)
72           (positions-larger thing 
73           (cdr substrings) 
74           new-pos))))))
75      (let ((split-result (split-string "*" wild)))
76  (and (positions-larger thing split-result 0)
77       (if (eql (elt wild 0) #\*)
78     t
79     (eql (search (first split-result) thing :test testfunc) 0))
80       (if (eql (elt wild (1- (length wild))) #\*)
81     t
82     (let ((last-split-result (first (last split-result))))
83       (eql (search last-split-result thing :from-end t 
84        :test testfunc)
85      (- (length thing) (length last-split-result))))))))))
86
87(defun component-match-p (thing wild ignore-case)
88  (cond ((eq wild :wild)
89         t)
90        ((null wild)
91         t)
92        ((and (stringp wild) (position #\* wild))
93   (component-match-wild-p thing wild ignore-case))
94        (ignore-case
95         (equalp thing wild))
96        (t
97         (equal thing wild))))
98
99(defun directory-match-components (thing wild ignore-case)
100  (loop
101    (cond ((endp thing)
102           (return (or (endp wild) (equal wild '(:wild-inferiors)))))
103          ((endp wild)
104           (return nil)))
105    (let ((x (car thing))
106          (y (car wild)))
107      (when (eq y :wild-inferiors)
108        (return t))
109      (unless (component-match-p x y ignore-case)
110        (return nil))
111      (setf thing (cdr thing)
112            wild  (cdr wild)))))
113
114(defun directory-match-p (thing wild ignore-case)
115  (cond ((eq wild :wild)
116         t)
117        ((null wild)
118         t)
119        ((and ignore-case (equalp thing wild))
120         t)
121        ((equal thing wild)
122         t)
123        ((and (null thing) (equal wild '(:absolute :wild-inferiors)))
124         t)
125        ((and (consp thing) (consp wild))
126         (if (eq (%car thing) (%car wild))
127             (directory-match-components (%cdr thing) (%cdr wild) ignore-case)
128             nil))
129        (t
130         nil)))
131
132(defun pathname-match-p (pathname wildcard)
133  (setf pathname (pathname pathname)
134        wildcard (pathname wildcard))
135  (unless (component-match-p (pathname-host pathname) (pathname-host wildcard) nil)
136    (return-from pathname-match-p nil))
137  (let* ((windows-p (featurep :windows))
138         (ignore-case (or windows-p (typep pathname 'logical-pathname))))
139    (cond ((and windows-p
140                (not (component-match-p (pathname-device pathname)
141                                        (pathname-device wildcard)
142                                        ignore-case)))
143           nil)
144          ((not (directory-match-p (pathname-directory pathname)
145                                   (pathname-directory wildcard)
146                                   ignore-case))
147           nil)
148          ((not (component-match-p (pathname-name pathname)
149                                   (pathname-name wildcard)
150                                   ignore-case))
151           nil)
152          ((not (component-match-p (pathname-type pathname)
153                                   (pathname-type wildcard)
154                                   ignore-case))
155           nil)
156          (t
157           t))))
158
159(defun wild-p (component)
160  (or (eq component :wild)
161      (and (stringp component)
162           (position #\* component))))
163
164(defun casify (thing case)
165  (typecase thing
166    (string
167     (case case
168       (:upcase (string-upcase thing))
169       (:downcase (string-downcase thing))
170       (t thing)))
171    (list
172     (let (result)
173       (dolist (component thing (nreverse result))
174         (push (casify component case) result))))
175    (t
176     thing)))
177
178(defun split-directory-components (directory)
179  (declare (optimize safety))
180  (declare (type list directory))
181  (unless (memq (car directory) '(:absolute :relative))
182    (error "Ill-formed directory list: ~S" directory))
183  (let (result sublist)
184    (push (car directory) result)
185    (dolist (component (cdr directory))
186      (cond ((memq component '(:wild :wild-inferiors))
187             (when sublist
188               (push (nreverse sublist) result)
189               (setf sublist nil))
190             (push component result))
191            (t
192             (push component sublist))))
193    (when sublist
194      (push (nreverse sublist) result))
195    (nreverse result)))
196
197(defun translate-component (source from to &optional case)
198  (declare (ignore from))
199  (cond ((or (eq to :wild) (null to))
200         ;; "If the piece in TO-WILDCARD is :WILD or NIL, the piece in source
201         ;; is copied into the result."
202         (casify source case))
203        ((and to (not (wild-p to)))
204        ;; "If the piece in TO-WILDCARD is present and not wild, it is copied
205        ;; into the result."
206         to)
207        (t
208         ;; "Otherwise, the piece in TO-WILDCARD might be a complex wildcard
209         ;; such as "foo*bar" and the piece in FROM-WILDCARD should be wild;
210         ;; the portion of the piece in SOURCE that matches the wildcard
211         ;; portion of the piece in FROM-WILDCARD replaces the wildcard portion
212         ;; of the piece in TO-WILDCARD and the value produced is used in the
213         ;; result."
214         ;; FIXME
215         (error "Unsupported wildcard pattern: ~S" to))))
216
217(defun translate-directory-components (source from to case)
218  (cond ((null to)
219         nil
220         )
221        ((memq (car to) '(:absolute :relative))
222         (cons (car to)
223               (translate-directory-components (cdr source) (cdr from) (cdr to) case))
224         )
225        ((eq (car to) :wild)
226         (if (eq (car from) :wild)
227             ;; Grab the next chunk from SOURCE.
228             (append (casify (car source) case)
229                     (translate-directory-components (cdr source) (cdr from) (cdr to) case))
230             (error "Unsupported case 1: ~S ~S ~S" source from to))
231         )
232        ((eq (car to) :wild-inferiors)
233         ;; Grab the next chunk from SOURCE.
234         (append (casify (car source) case)
235                 (translate-directory-components (cdr source) (cdr from) (cdr to) case))
236         )
237        (t
238         ;; "If the piece in TO-WILDCARD is present and not wild, it is copied
239         ;; into the result."
240         (append (casify (car to) case)
241                 (translate-directory-components source from (cdr to) case))
242         )
243        ))
244
245(defun translate-directory (source from to case)
246  ;; FIXME The IGNORE-CASE argument to DIRECTORY-MATCH-P should not be nil on
247  ;; Windows or if the source pathname is a logical pathname.
248  ;; FIXME We can canonicalize logical pathnames to upper case, so we only need
249  ;; IGNORE-CASE for Windows.
250  (cond ((null source)
251         to)
252        ((equal source '(:absolute))
253         (remove :wild-inferiors to))
254        (t
255         (translate-directory-components (split-directory-components source)
256                                         (split-directory-components from)
257                                         (split-directory-components to)
258                                         case))))
259
260;; "The resulting pathname is TO-WILDCARD with each wildcard or missing field
261;; replaced by a portion of SOURCE."
262(defun translate-pathname (source from-wildcard to-wildcard &key)
263  (unless (pathname-match-p source from-wildcard)
264    (error "~S and ~S do not match." source from-wildcard))
265  (let* ((source (pathname source))
266         (from   (pathname from-wildcard))
267         (to     (pathname to-wildcard))
268         (device (if (typep 'to 'logical-pathname)
269                     :unspecific
270                     (translate-component (pathname-device source)
271                                          (pathname-device from)
272                                          (pathname-device to))))
273         (case   (and (typep source 'logical-pathname)
274                      (or (featurep :unix) (featurep :windows))
275                      :downcase)))
276    (make-pathname :host      (pathname-host to)
277                   :device    (cond ((typep to 'logical-pathname)
278                                     :unspecific)
279                                    ((eq device :unspecific)
280                                     nil)
281                                    (t
282                                     device))
283                   :directory (translate-directory (pathname-directory source)
284                                                   (pathname-directory from)
285                                                   (pathname-directory to)
286                                                   case)
287                   :name      (translate-component (pathname-name source)
288                                                   (pathname-name from)
289                                                   (pathname-name to)
290                                                   case)
291                   :type      (translate-component (pathname-type source)
292                                                   (pathname-type from)
293                                                   (pathname-type to)
294                                                   case)
295                   :version   (if (null (pathname-host from))
296                                  (if (eq (pathname-version to) :wild)
297                                      (pathname-version from)
298                                      (pathname-version to))
299                                  (translate-component (pathname-version source)
300                                                       (pathname-version from)
301                                                       (pathname-version to))))))
302
303(defun logical-host-p (canonical-host)
304  (multiple-value-bind (translations present)
305      (gethash canonical-host *logical-pathname-translations*)
306    (declare (ignore translations))
307    present))
308
309(defun logical-pathname-translations (host)
310  (multiple-value-bind (translations present)
311      (gethash (canonicalize-logical-host host) *logical-pathname-translations*)
312    (unless present
313      (error 'type-error
314             :datum host
315             :expected-type '(and string (satisfies logical-host-p))))
316    translations))
317
318(defun canonicalize-logical-pathname-translations (translations host)
319  (let (result)
320    (dolist (translation translations (nreverse result))
321      (let ((from (car translation))
322            (to (cadr translation)))
323        (push (list (if (typep from 'logical-pathname)
324                        from
325                        (parse-namestring from host))
326                    (pathname to))
327              result)))))
328
329(defun %set-logical-pathname-translations (host translations)
330  (setf host (canonicalize-logical-host host))
331  ;; Avoid undefined host error in CANONICALIZE-LOGICAL-PATHNAME-TRANSLATIONS.
332  (unless (logical-host-p host)
333    (setf (gethash host *logical-pathname-translations*) nil))
334  (setf (gethash host *logical-pathname-translations*)
335        (canonicalize-logical-pathname-translations translations host)))
336
337(defsetf logical-pathname-translations %set-logical-pathname-translations)
338
339(defun translate-logical-pathname (pathname &key)
340  (typecase pathname
341    (logical-pathname
342     (let* ((host (pathname-host pathname))
343            (translations (logical-pathname-translations host)))
344       (dolist (translation translations
345                            (error 'file-error
346                                   :pathname pathname
347                                   :format-control "No translation for ~S"
348                                   :format-arguments (list pathname)))
349         (let ((from-wildcard (car translation))
350               (to-wildcard (cadr translation)))
351           (when (pathname-match-p pathname from-wildcard)
352             (return (translate-logical-pathname
353                      (translate-pathname pathname from-wildcard to-wildcard))))))))
354    (pathname pathname)
355    (t
356     (translate-logical-pathname (pathname pathname)))))
357
358(defun load-logical-pathname-translations (host)
359  (declare (type string host))
360  (multiple-value-bind (ignore found)
361      (gethash (canonicalize-logical-host host)
362               *logical-pathname-translations*)
363    (declare (ignore ignore))
364    (unless found
365      (error "The logical host ~S was not found." host))))
366
367(defun logical-pathname (pathspec)
368  (typecase pathspec
369    (logical-pathname pathspec)
370    (string
371     (%make-logical-pathname pathspec))
372    (stream
373     (let ((result (pathname pathspec)))
374       (if (typep result 'logical-pathname)
375           result
376           (error 'simple-type-error
377                  :datum result
378                  :expected-type 'logical-pathname))))
379    (t
380     (error 'type-error
381            :datum pathspec
382            :expected-type '(or logical-pathname string stream)))))
383
384(defun parse-namestring (thing
385                         &optional host (default-pathname *default-pathname-defaults*)
386                         &key (start 0) end junk-allowed)
387  (declare (ignore junk-allowed)) ; FIXME
388  (cond ((eq host :unspecific)
389         (setf host nil))
390        (host
391         (setf host (canonicalize-logical-host host))))
392  (typecase thing
393    (stream
394     (values (pathname thing) start))
395    (pathname
396     (values thing start))
397    (string
398     (unless end
399       (setf end (length thing)))
400     (%parse-namestring (subseq thing start end) host default-pathname))
401    (t
402     (error 'type-error
403            :format-control "~S cannot be converted to a pathname."
404            :format-arguments (list thing)))))
Note: See TracBrowser for help on using the repository browser.