1 ;;;; -*- Mode: Lisp; Syntax: ANSI-Common-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
(file)
117 (let* ((dotpos (position #\. file
:start
1 :from-end t
)))
121 ((= (1+ dotpos
) (length file
))
122 (values (subseq file
0 dotpos
) nil
))
123 (t (values (subseq file
0 dotpos
)
124 (subseq file
(1+ dotpos
)))))))
126 (defun file-path-file-name (pathspec)
127 (let* ((file (file-path-file pathspec
)))
128 (switch (file :test
#'string
=)
130 (t (nth-value 0 (split-name/type file
))))))
132 (defun file-path-file-type (pathspec)
133 (let* ((file (file-path-file pathspec
)))
134 (switch (file :test
#'string
=)
136 (t (nth-value 1 (split-name/type file
))))))
139 ;;;-------------------------------------------------------------------------
141 ;;;-------------------------------------------------------------------------
143 (defun valid-component-types-p (components)
144 (multiple-value-bind (root nodes
)
145 (split-root/nodes components
)
146 (and (member root
'(nil :root
))
147 (every #'stringp nodes
))))
149 (defmethod initialize-instance :after
((path file-path
) &key components
)
150 (check-type components
(and (not null
) (satisfies valid-component-types-p
)))
151 (setf (slot-value path
'components
) components
)
152 (dolist (node (cdr (slot-value path
'components
)))
153 (when (zerop (length node
))
154 (error 'invalid-file-path
:path
""
155 :reason
"Null filenames are not valid"))
156 (when (find-if (lambda (c) (member c
+directory-delimiters
+)) node
)
157 (error 'invalid-file-path
:path node
158 :reason
"Path components cannot contain directory delimiters(#\\ and #\/)"))))
161 ;;;-------------------------------------------------------------------------
163 ;;;-------------------------------------------------------------------------
165 (defun file-path-p (thing)
166 (typep thing
'file-path
))
168 (defun absolute-p (dir)
169 (eql :root
(car dir
)))
171 (defun absolute-file-path-p (path)
172 (check-type path file-path
)
173 (absolute-p (slot-value path
'components
)))
175 (defun relative-file-path-p (path)
176 (check-type path file-path
)
177 (not (absolute-p (slot-value path
'components
))))
180 ;;;-------------------------------------------------------------------------
182 ;;;-------------------------------------------------------------------------
184 (defmethod file-path ((path file-path
))
187 (defmethod file-path (pathspec)
188 (parse-file-path pathspec
))
190 (defmethod file-path ((pathspec pathname
))
191 (parse-file-path (namestring pathspec
)))
194 ;;;-------------------------------------------------------------------------
196 ;;;-------------------------------------------------------------------------
198 (defmethod print-object ((path file-path
) stream
)
199 (let ((ns (file-path-namestring path
)))
201 (format stream
"#/p/~S" ns
)
202 (write-string ns stream
))))
204 (define-literal-reader p
(stream)
205 (let ((token (read stream
)))
206 (check-type token string
)