~ chicken-core (master) /pathname.scm
Trap1;;;; 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(import (only (scheme base) open-output-string get-output-string))
57
58(include "common-declarations.scm")
59
60;;; Pathname operations:
61
62;; Platform specific absolute pathname operations:
63;; absolute-pathname-root => #f or (<match> [<origin>] <root>)
64;;
65;; Not for general consumption
66
67(define absolute-pathname-root)
68(define root-origin)
69(define root-directory)
70
71(if ##sys#windows-platform
72 (let ((rx (irregex "([A-Za-z]:)?([\\/\\\\]).*")))
73 (set! absolute-pathname-root (lambda (pn) (irregex-match rx pn)))
74 (set! root-origin (lambda (rt) (and rt (irregex-match-substring rt 1))))
75 (set! root-directory (lambda (rt) (and rt (irregex-match-substring rt 2)))))
76 (let ((rx (irregex "(/).*")))
77 (set! absolute-pathname-root (lambda (pn) (irregex-match rx pn)))
78 (set! root-origin (lambda (rt) #f))
79 (set! root-directory (lambda (rt) (and rt (irregex-match-substring rt 1))))))
80
81(define (absolute-pathname? pn)
82 (##sys#check-string pn 'absolute-pathname?)
83 (irregex-match-data? (absolute-pathname-root pn)))
84
85(define-inline (*char-pds? ch)
86 (if ##sys#windows-platform
87 (memq ch '(#\\ #\/))
88 (eq? #\/ ch)))
89
90(define (chop-pds str)
91 (and str
92 (let lp ((len (string-length str)))
93 (cond ((and (fx>= len 1)
94 (*char-pds? (string-ref str (fx- len 1))))
95 (lp (fx- len 1)))
96 ((fx< len (string-length str))
97 (##sys#substring str 0 len))
98 (else str)))))
99
100(define make-pathname)
101(define make-absolute-pathname)
102
103(let ((pds (if ##sys#windows-platform "\\" "/")))
104
105 (define (conc-dirs dirs)
106 (##sys#check-list dirs 'make-pathname)
107 (let loop ((strs dirs))
108 (if (null? strs)
109 ""
110 (let ((s1 (car strs)))
111 (if (zero? (string-length s1))
112 (loop (cdr strs))
113 (string-append
114 (chop-pds (car strs))
115 pds
116 (loop (cdr strs))))))))
117
118 (define (canonicalize-dirs dirs)
119 (cond ((or (not dirs) (null? dirs)) "")
120 ((string? dirs) (conc-dirs (list dirs)))
121 (else (conc-dirs dirs))))
122
123 (define (_make-pathname loc dir file ext)
124 (let ((ext (or ext ""))
125 (file (or file "")))
126 (##sys#check-string dir loc)
127 (##sys#check-string file loc)
128 (##sys#check-string ext loc)
129 (string-append
130 dir
131 (if (and (fx>= (string-length dir) 1)
132 (fx>= (string-length file) 1)
133 (*char-pds? (string-ref file 0)))
134 (##sys#substring file 1 (string-length file))
135 file)
136 (if (and (fx> (string-length ext) 0)
137 (not (char=? (string-ref ext 0) #\.)))
138 "."
139 "")
140 ext)))
141
142 (set! make-pathname
143 (lambda (dirs file #!optional ext)
144 (_make-pathname 'make-pathname (canonicalize-dirs dirs) file ext)))
145
146 (set! make-absolute-pathname
147 (lambda (dirs file #!optional ext)
148 (_make-pathname
149 'make-absolute-pathname
150 (let ((dir (canonicalize-dirs dirs)))
151 (if (absolute-pathname? dir)
152 dir
153 (##sys#string-append pds dir)))
154 file ext))))
155
156(define decompose-pathname
157 (let* ((patt1 (if ##sys#windows-platform
158 "^(.*[\\/\\\\])?([^\\/\\\\]+)(\\.([^\\/\\\\.]+))$"
159 "^(.*/)?([^/]+)(\\.([^/.]+))$"))
160 (patt2 (if ##sys#windows-platform
161 "^(.*[\\/\\\\])?((\\.)?[^\\/\\\\]+)$"
162 "^(.*/)?((\\.)?[^/]+)$"))
163 (rx1 (irregex patt1))
164 (rx2 (irregex patt2))
165 (strip-pds
166 (lambda (dir)
167 (and dir
168 (let ((chopped (chop-pds dir)))
169 (if (fx> (string-length chopped) 0)
170 chopped
171 (##sys#substring dir 0 1)))))))
172 (lambda (pn)
173 (##sys#check-string pn 'decompose-pathname)
174 (if (fx= 0 (string-length pn))
175 (values #f #f #f)
176 (let ((ms (irregex-search rx1 pn)))
177 (if ms
178 (values
179 (strip-pds (irregex-match-substring ms 1))
180 (irregex-match-substring ms 2)
181 (irregex-match-substring ms 4))
182 (let ((ms (irregex-search rx2 pn)))
183 (if ms
184 (values
185 (strip-pds (irregex-match-substring ms 1))
186 (irregex-match-substring ms 2)
187 #f)
188 (values (strip-pds pn) #f #f)))))))))
189
190(define pathname-directory
191 (lambda (pn)
192 (let-values (((dir file ext) (decompose-pathname pn)))
193 dir)))
194
195(define pathname-file
196 (lambda (pn)
197 (let-values (((dir file ext) (decompose-pathname pn)))
198 file)))
199
200(define pathname-extension
201 (lambda (pn)
202 (let-values (((dir file ext) (decompose-pathname pn)))
203 ext)))
204
205(define pathname-strip-directory
206 (lambda (pn)
207 (let-values (((dir file ext) (decompose-pathname pn)))
208 (make-pathname #f file ext))))
209
210(define pathname-strip-extension
211 (lambda (pn)
212 (let-values (((dir file ext) (decompose-pathname pn)))
213 (make-pathname dir file))))
214
215(define pathname-replace-directory
216 (lambda (pn dir)
217 (let-values (((_ file ext) (decompose-pathname pn)))
218 (make-pathname dir file ext))))
219
220(define pathname-replace-file
221 (lambda (pn file)
222 (let-values (((dir _ ext) (decompose-pathname pn)))
223 (make-pathname dir file ext))))
224
225(define pathname-replace-extension
226 (lambda (pn ext)
227 (let-values (((dir file _) (decompose-pathname pn)))
228 (make-pathname dir file ext))))
229
230;;; normalize pathname for a particular platform
231
232(define normalize-pathname
233 (let ((bldplt (if (eq? (software-version) 'mingw) 'windows 'unix)))
234 (define (addpart part parts)
235 (cond ((string=? "." part) parts)
236 ((string=? ".." part)
237 (if (or (null? parts)
238 (string=? ".." (car parts)))
239 (cons part parts)
240 (cdr parts)))
241 (else (cons part parts))))
242 (lambda (path #!optional (platform bldplt))
243 (let ((sep (if (eq? platform 'windows) #\\ #\/)))
244 (define (pds? c)
245 (if (eq? platform 'windows)
246 (memq c '(#\/ #\\))
247 (eq? c #\/)))
248 (##sys#check-string path 'normalize-pathname)
249 (let ((len (string-length path))
250 (type #f)
251 (drive #f))
252 (let loop ((i 0) (prev 0) (parts '()))
253 (cond ((fx>= i len)
254 (when (fx> i prev)
255 (set! parts (addpart (##sys#substring path prev i) parts)))
256 (if (null? parts)
257 (let ((r (if (eq? type 'abs) (string sep) ".")))
258 (if drive (##sys#string-append drive r) r))
259 (let ((out (open-output-string))
260 (parts (##sys#fast-reverse parts)))
261 (display (car parts) out)
262 (for-each
263 (lambda (p)
264 (##sys#write-char-0 sep out)
265 (display p out))
266 (cdr parts))
267 (when (fx= i prev) (##sys#write-char-0 sep out))
268 (let ((r (get-output-string out)))
269 (when (eq? type 'abs)
270 (set! r (##sys#string-append (string sep) r)))
271 (when drive
272 (set! r (##sys#string-append drive r)))
273 r))))
274 ((pds? (string-ref path i))
275 (when (not type)
276 (set! type (if (fx= i prev) 'abs 'rel)))
277 (if (fx= i prev)
278 (loop (fx+ i 1) (fx+ i 1) parts)
279 (loop (fx+ i 1)
280 (fx+ i 1)
281 (addpart (##sys#substring path prev i) parts))))
282 ((and (null? parts)
283 (char=? (string-ref path i) #\:)
284 (eq? platform 'windows))
285 (set! drive (##sys#substring path 0 (fx+ i 1)))
286 (loop (fx+ i 1) (fx+ i 1) '()))
287 (else (loop (fx+ i 1) prev parts)))))))))
288
289;; directory pathname => list of strings
290;; does arg check
291
292(define split-directory
293 (lambda (loc dir keep?)
294 (##sys#check-string dir loc)
295 (string-split dir (if ##sys#windows-platform "/\\" "/") keep?)))
296
297;; Directory string or list only contains path-separators
298;; and/or current-directory (".") names.
299
300(define (directory-null? dir)
301 (let loop ((ls (if (list? dir) dir (split-directory 'directory-null? dir #t))))
302 (or (null? ls)
303 (and (member (car ls) '("" "."))
304 (loop (cdr ls))))))
305
306;; Directory string => {<origin> <root> <directory-list>}
307;; where any maybe #f when missing
308
309(define (decompose-directory dir)
310 (define (strip-origin-prefix org decomp)
311 #;(assert (or (not org) decomp)) ;cannot have an "origin" but no "decomp"
312 (if (not org)
313 decomp
314 (let ((1st (car decomp)))
315 (let ((olen (string-length org)))
316 (if (not (##core#inline "C_u_i_substring_equal_p" org 1st 0 0 olen))
317 ; then origin is not a prefix (really shouldn't happen)
318 decomp
319 ; else is a prefix
320 (let ((rst (cdr decomp))
321 (elen (string-length 1st)))
322 (if (fx= olen elen)
323 ; then origin is a list prefix
324 rst
325 ; else origin is a string prefix
326 (cons (##sys#substring 1st olen elen) rst))))))))
327 (let* ((ls (split-directory 'decompose-directory dir #f))
328 (rt (absolute-pathname-root dir))
329 (org (root-origin rt)))
330 (values org (root-directory rt) (strip-origin-prefix org (and (not (null? ls)) ls))))))