~ chicken-core (chicken-5) /pathname.scm


  1;;;; pathname.scm - Pathname operations
  2;
  3; Copyright (c) 2008-2022, The CHICKEN Team
  4; Copyright (c) 2000-2007, Felix L. Winkelmann
  5; All rights reserved.
  6;
  7; Redistribution and use in source and binary forms, with or without
  8; modification, are permitted provided that the following conditions
  9; are met:
 10;
 11;   Redistributions of source code must retain the above copyright
 12;   notice, this list of conditions and the following disclaimer.
 13;
 14;   Redistributions in binary form must reproduce the above copyright
 15;   notice, this list of conditions and the following disclaimer in
 16;   the documentation and/or other materials provided with the
 17;   distribution.
 18;
 19;   Neither the name of the author nor the names of its contributors
 20;     may be used to endorse or promote products derived from this
 21;     software without specific prior written permission.
 22;
 23; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 24; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 25; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 26; FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 27; COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 28; INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 29; (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 30; SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 31; HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 32; STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 33; ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 34; OF THE POSSIBILITY OF SUCH DAMAGE.
 35
 36(declare
 37  (unit pathname)
 38  (uses data-structures irregex)
 39  (fixnum)
 40  (disable-interrupts))
 41
 42(module chicken.pathname
 43  (absolute-pathname? decompose-directory decompose-pathname
 44   directory-null? make-absolute-pathname make-pathname
 45   normalize-pathname pathname-directory pathname-extension
 46   pathname-file pathname-replace-directory pathname-replace-extension
 47   pathname-replace-file pathname-strip-directory
 48   pathname-strip-extension)
 49
 50(import scheme
 51        chicken.base
 52        chicken.fixnum
 53        chicken.irregex
 54        chicken.platform
 55        chicken.string)
 56
 57(include "common-declarations.scm")
 58
 59;;; Pathname operations:
 60
 61;; Platform specific absolute pathname operations:
 62;; absolute-pathname-root => #f or (<match> [<origin>] <root>)
 63;;
 64;; Not for general consumption
 65
 66(define absolute-pathname-root)
 67(define root-origin)
 68(define root-directory)
 69
 70(if ##sys#windows-platform
 71    (let ((rx (irregex "([A-Za-z]:)?([\\/\\\\]).*")))
 72      (set! absolute-pathname-root (lambda (pn) (irregex-match rx pn)))
 73      (set! root-origin (lambda (rt) (and rt (irregex-match-substring rt 1))))
 74      (set! root-directory (lambda (rt) (and rt (irregex-match-substring rt 2)))))
 75    (let ((rx (irregex "(/).*")))
 76      (set! absolute-pathname-root (lambda (pn) (irregex-match rx pn)))
 77      (set! root-origin (lambda (rt) #f))
 78      (set! root-directory (lambda (rt) (and rt (irregex-match-substring rt 1))))))
 79
 80(define (absolute-pathname? pn)
 81  (##sys#check-string pn 'absolute-pathname?)
 82  (irregex-match-data? (absolute-pathname-root pn)))
 83
 84(define-inline (*char-pds? ch)
 85  (if ##sys#windows-platform
 86      (memq ch '(#\\ #\/))
 87      (eq? #\/ ch)))
 88
 89(define (chop-pds str)
 90  (and str
 91       (let lp ((len (##sys#size str)))
 92	 (cond ((and (fx>= len 1)
 93		     (*char-pds? (##core#inline "C_subchar" str (fx- len 1))))
 94		(lp (fx- len 1)))
 95	       ((fx< len (##sys#size str))
 96		(##sys#substring str 0 len))
 97	       (else str)))))
 98
 99(define make-pathname)
100(define make-absolute-pathname)
101
102(let ((pds (if ##sys#windows-platform "\\" "/")))
103
104  (define (conc-dirs dirs)
105    (##sys#check-list dirs 'make-pathname)
106    (let loop ((strs dirs))
107      (if (null? strs)
108	  ""
109	  (let ((s1 (car strs)))
110	    (if (zero? (string-length s1))
111		(loop (cdr strs))
112		(string-append
113		 (chop-pds (car strs))
114		 pds
115		 (loop (cdr strs))))))))
116
117  (define (canonicalize-dirs dirs)
118    (cond ((or (not dirs) (null? dirs)) "")
119	  ((string? dirs) (conc-dirs (list dirs)))
120	  (else           (conc-dirs dirs))))
121
122  (define (_make-pathname loc dir file ext)
123    (let ((ext (or ext ""))
124	  (file (or file "")))
125      (##sys#check-string dir loc)
126      (##sys#check-string file loc)
127      (##sys#check-string ext loc)
128      (string-append
129       dir
130       (if (and (fx>= (##sys#size dir) 1)
131		(fx>= (##sys#size file) 1)
132		(*char-pds? (##core#inline "C_subchar" file 0)))
133	   (##sys#substring file 1 (##sys#size file))
134	   file)
135       (if (and (fx> (##sys#size ext) 0)
136		(not (char=? (##core#inline "C_subchar" ext 0) #\.)))
137	   "."
138	   "")
139       ext)))
140
141  (set! make-pathname
142    (lambda (dirs file #!optional ext)
143      (_make-pathname 'make-pathname (canonicalize-dirs dirs) file ext)))
144
145  (set! make-absolute-pathname
146    (lambda (dirs file #!optional ext)
147      (_make-pathname
148       'make-absolute-pathname
149       (let ((dir (canonicalize-dirs dirs)))
150	 (if (absolute-pathname? dir)
151	     dir
152	     (##sys#string-append pds dir)))
153       file ext))))
154
155(define decompose-pathname
156  (let* ((patt1 (if ##sys#windows-platform
157                    "^(.*[\\/\\\\])?([^\\/\\\\]+)(\\.([^\\/\\\\.]+))$"
158                    "^(.*/)?([^/]+)(\\.([^/.]+))$"))
159	 (patt2 (if ##sys#windows-platform
160                    "^(.*[\\/\\\\])?((\\.)?[^\\/\\\\]+)$"
161                    "^(.*/)?((\\.)?[^/]+)$"))
162	 (rx1 (irregex patt1))
163	 (rx2 (irregex patt2))
164         (strip-pds
165          (lambda (dir)
166            (and dir
167                 (let ((chopped (chop-pds dir)))
168                   (if (fx> (##sys#size chopped) 0)
169                       chopped
170                       (##sys#substring dir 0 1)))))))
171    (lambda (pn)
172      (##sys#check-string pn 'decompose-pathname)
173      (if (fx= 0 (##sys#size pn))
174	  (values #f #f #f)
175	  (let ((ms (irregex-search rx1 pn)))
176	    (if ms
177		(values
178		 (strip-pds (irregex-match-substring ms 1))
179		 (irregex-match-substring ms 2)
180		 (irregex-match-substring ms 4))
181		(let ((ms (irregex-search rx2 pn)))
182		  (if ms
183		      (values
184		       (strip-pds (irregex-match-substring ms 1))
185		       (irregex-match-substring ms 2)
186		       #f)
187		      (values (strip-pds pn) #f #f)))))))))
188
189(define pathname-directory
190  (lambda (pn)
191    (let-values (((dir file ext) (decompose-pathname pn)))
192      dir)))
193
194(define pathname-file
195  (lambda (pn)
196    (let-values (((dir file ext) (decompose-pathname pn)))
197      file)))
198
199(define pathname-extension
200  (lambda (pn)
201    (let-values (((dir file ext) (decompose-pathname pn)))
202      ext)))
203
204(define pathname-strip-directory
205  (lambda (pn)
206    (let-values (((dir file ext) (decompose-pathname pn)))
207      (make-pathname #f file ext))))
208
209(define pathname-strip-extension
210  (lambda (pn)
211    (let-values (((dir file ext) (decompose-pathname pn)))
212      (make-pathname dir file))))
213
214(define pathname-replace-directory
215  (lambda (pn dir)
216    (let-values (((_ file ext) (decompose-pathname pn)))
217      (make-pathname dir file ext))))
218
219(define pathname-replace-file
220  (lambda (pn file)
221    (let-values (((dir _ ext) (decompose-pathname pn)))
222      (make-pathname dir file ext))))
223
224(define pathname-replace-extension
225  (lambda (pn ext)
226    (let-values (((dir file _) (decompose-pathname pn)))
227      (make-pathname dir file ext))))
228
229;;; normalize pathname for a particular platform
230
231(define normalize-pathname
232  (let ((bldplt (if (eq? (software-version) 'mingw32) 'windows 'unix)))
233    (define (addpart part parts)
234      (cond ((string=? "." part) parts)
235            ((string=? ".." part)
236	     (if (or (null? parts)
237		     (string=? ".." (car parts)))
238		 (cons part parts)
239		 (cdr parts)))
240            (else (cons part parts))))
241    (lambda (path #!optional (platform bldplt))
242      (let ((sep (if (eq? platform 'windows) #\\ #\/)))
243        (define (pds? c)
244          (if (eq? platform 'windows)
245              (memq c '(#\/ #\\))
246              (eq? c #\/)))
247	(##sys#check-string path 'normalize-pathname)
248	(let ((len (##sys#size path))
249	      (type #f)
250	      (drive #f))
251	  (let loop ((i 0) (prev 0) (parts '()))
252	    (cond ((fx>= i len)
253		   (when (fx> i prev)
254		     (set! parts (addpart (##sys#substring path prev i) parts)))
255		   (if (null? parts)
256		       (let ((r (if (eq? type 'abs) (string sep) ".")))
257			 (if drive (##sys#string-append drive r) r))
258		       (let ((out (open-output-string))
259			     (parts (##sys#fast-reverse parts)))
260			 (display (car parts) out)
261			 (for-each
262			  (lambda (p)
263			    (##sys#write-char-0 sep out)
264			    (display p out))
265			  (cdr parts))
266			 (when (fx= i prev) (##sys#write-char-0 sep out))
267			 (let ((r (get-output-string out)))
268			   (when (eq? type 'abs)
269			     (set! r (##sys#string-append (string sep) r)))
270			   (when drive
271			     (set! r (##sys#string-append drive r)))
272			   r))))
273		  ((pds? (string-ref path i))
274		   (when (not type)
275		     (set! type (if (fx= i prev) 'abs 'rel)))
276		   (if (fx= i prev)
277		       (loop (fx+ i 1) (fx+ i 1) parts)
278		       (loop (fx+ i 1)
279			     (fx+ i 1)
280			     (addpart (##sys#substring path prev i) parts))))
281		  ((and (null? parts)
282			(char=? (string-ref path i) #\:)
283			(eq? platform 'windows))
284		   (set! drive (##sys#substring path 0 (fx+ i 1)))
285		   (loop (fx+ i 1) (fx+ i 1) '()))
286		  (else (loop (fx+ i 1) prev parts)))))))))
287
288;; directory pathname => list of strings
289;; does arg check
290
291(define split-directory
292  (lambda (loc dir keep?)
293    (##sys#check-string dir loc)
294    (string-split dir (if ##sys#windows-platform "/\\" "/") keep?)))
295
296;; Directory string or list only contains path-separators
297;; and/or current-directory (".") names.
298
299(define (directory-null? dir)
300  (let loop ((ls (if (list? dir) dir (split-directory 'directory-null? dir #t))))
301    (or (null? ls)
302        (and (member (car ls) '("" "."))
303             (loop (cdr ls))))))
304
305;; Directory string => {<origin> <root> <directory-list>}
306;; where any maybe #f when missing
307
308(define (decompose-directory dir)
309  (define (strip-origin-prefix org decomp)
310    #;(assert (or (not org) decomp)) ;cannot have an "origin" but no "decomp"
311    (if (not org)
312        decomp
313        (let ((1st (car decomp)))
314          (let ((olen (##sys#size org)))
315            (if (not (##core#inline "C_substring_compare" org 1st 0 0 olen))
316                ; then origin is not a prefix (really shouldn't happen)
317                decomp
318                ; else is a prefix
319                (let ((rst (cdr decomp))
320                      (elen (##sys#size 1st)))
321                  (if (fx= olen elen)
322                      ; then origin is a list prefix
323                      rst
324                      ; else origin is a string prefix
325                      (cons (##sys#substring 1st olen elen) rst))))))))
326  (let* ((ls (split-directory 'decompose-directory dir #f))
327         (rt (absolute-pathname-root dir))
328         (org (root-origin rt)))
329    (values org (root-directory rt) (strip-origin-prefix org (and (not (null? ls)) ls))))))
Trap