1 ;;;; -*- Mode: Lisp; indent-tabs-mode: nil -*-
6 (in-package :iolib.pathnames
)
8 ;;;-------------------------------------------------------------------------
10 ;;;-------------------------------------------------------------------------
12 (eval-when (:compile-toplevel
:load-toplevel
:execute
)
13 (defconstant +file-path-host-type
+
17 (defclass file-path
()
18 ((host :initarg
:host
)
19 (device :initarg
:device
)
20 (components :initarg
:components
23 (deftype file-path-designator
()
24 `(or ,+file-path-host-type
+ string
))
26 (define-condition invalid-file-path
(isys:iolib-error type-error
)
27 ((path :initarg
:path
:reader invalid-file-path-path
)
28 (reason :initform nil
:initarg
:reason
:reader invalid-file-path-reason
))
29 (:report
(lambda (condition stream
)
30 (format stream
"Invalid file path: ~S."
31 (invalid-file-path-path condition
))
32 (when-let (reason (invalid-file-path-reason condition
))
33 (format stream
"~%~A." reason
)))))
36 ;;;-------------------------------------------------------------------------
38 ;;;-------------------------------------------------------------------------
40 (defconstant +directory-delimiter
+
44 (defconstant +alternative-delimiter
+
48 (defconstant (+directory-delimiters
+ :test
#'equal
)
49 (list* +directory-delimiter
+ +alternative-delimiter
+))
51 (defconstant +execution-path-delimiter
+
55 (declaim (special *default-file-path-defaults
*))
58 ;;;-------------------------------------------------------------------------
60 ;;;-------------------------------------------------------------------------
62 (defgeneric file-path
(pathspec))
64 (defgeneric file-path-namestring
(path))
67 ;;;-------------------------------------------------------------------------
69 ;;;-------------------------------------------------------------------------
71 (defun file-path-host (pathspec &key namestring
)
72 (let ((path (file-path pathspec
)))
74 (%file-path-host-namestring path
)
75 (slot-value path
'host
))))
77 (defun file-path-device (pathspec &key namestring
)
78 (let ((path (file-path pathspec
)))
80 (%file-path-device-namestring path
)
81 (slot-value path
'device
))))
83 (defun file-path-components (pathspec &key namestring
)
84 (let ((path (file-path pathspec
)))
86 (%file-path-components-namestring path
)
87 (slot-value path
'components
))))
89 (defun split-root/nodes
(dir)
90 (if (eql :root
(car dir
))
91 (values :root
(cdr dir
))
94 (defun %file-path-directory
(path)
95 (let ((components (slot-value path
'components
)))
96 (multiple-value-bind (root nodes
)
97 (split-root/nodes components
)
99 (cons root
(butlast nodes
))
103 (defun file-path-directory (pathspec &key namestring
)
104 (let ((path (file-path pathspec
)))
106 (%file-path-directory-namestring path
)
107 (%file-path-directory path
))))
109 (defun file-path-file (pathspec &key namestring
)
110 (declare (ignore namestring
))
111 (let* ((path (file-path pathspec
))
112 (components (slot-value path
'components
)))
113 (or (lastcar (nth-value 1 (split-root/nodes components
)))
116 (defun split-name/type
(path)
117 (let ((dotpos (position #\. path
:start
1 :from-end t
)))
119 ((or (null dotpos
) (member path
'("." "..") :test
#'string
=))
121 (t (values (subseq path
0 dotpos
)
122 (full-string (subseq path
(1+ dotpos
))))))))
124 (defun file-path-file-name (pathspec)
125 (let ((file (file-path-file pathspec
)))
126 (nth-value 0 (split-name/type file
))))
128 (defun file-path-file-type (pathspec)
129 (let ((file (file-path-file pathspec
)))
130 (nth-value 1 (split-name/type file
))))
133 ;;;-------------------------------------------------------------------------
135 ;;;-------------------------------------------------------------------------
137 (defun valid-component-types-p (components)
138 (multiple-value-bind (root nodes
)
139 (split-root/nodes components
)
140 (and (member root
'(nil :root
))
141 (every #'stringp nodes
))))
143 (defmethod initialize-instance :after
((path file-path
) &key components
)
144 (check-type components
(and (not null
) (satisfies valid-component-types-p
)))
145 (setf (slot-value path
'components
) components
)
146 (dolist (node (cdr (slot-value path
'components
)))
147 (when (zerop (length node
))
148 (error 'invalid-file-path
:path
""
149 :reason
"Null filenames are not valid"))
150 (when (find-if (lambda (c) (member c
+directory-delimiters
+)) node
)
151 (error 'invalid-file-path
:path node
153 "Path components cannot contain delimiters(~A)"
155 (mapcar 'prin1-to-string
156 +directory-delimiters
+)))))))
159 ;;;-------------------------------------------------------------------------
161 ;;;-------------------------------------------------------------------------
163 (defun file-path-p (thing)
164 (typep thing
'file-path
))
166 (defun absolute-p (dir)
167 (eql :root
(car dir
)))
169 (defun absolute-file-path-p (path)
170 (check-type path file-path
)
171 (absolute-p (slot-value path
'components
)))
173 (defun relative-file-path-p (path)
174 (check-type path file-path
)
175 (not (absolute-p (slot-value path
'components
))))
178 ;;;-------------------------------------------------------------------------
180 ;;;-------------------------------------------------------------------------
182 (defmethod file-path ((path file-path
))
185 (defmethod file-path (pathspec)
186 (parse-file-path pathspec
))
188 (defmethod file-path ((pathspec pathname
))
189 (parse-file-path (namestring pathspec
)))
192 ;;;-------------------------------------------------------------------------
194 ;;;-------------------------------------------------------------------------
196 (defmethod print-object ((path file-path
) stream
)
197 (let ((ns (file-path-namestring path
)))
198 (if (or *print-readably
* *print-escape
*)
199 (format stream
"#/~S/~S" 'p ns
)
200 (write-string ns stream
))))
202 (define-literal-reader p
(stream)
203 (let ((token (read stream
)))
204 (check-type token string
)