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