| 1 |
;;; -*- Mode: LISP; Syntax: COMMON-LISP; Package: CL-FAD; Base: 10 -*- |
|---|
| 2 |
;;; $Header: /usr/local/cvsrep/cl-fad/fad.lisp,v 1.33 2008/03/12 00:10:43 edi Exp $ |
|---|
| 3 |
|
|---|
| 4 |
;;; Copyright (c) 2004, Peter Seibel. All rights reserved. |
|---|
| 5 |
;;; Copyright (c) 2004-2008, Dr. Edmund Weitz. 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 |
|---|
| 15 |
;;; copyright notice, this list of conditions and the following |
|---|
| 16 |
;;; disclaimer in the documentation and/or other materials |
|---|
| 17 |
;;; provided with the distribution. |
|---|
| 18 |
|
|---|
| 19 |
;;; THIS SOFTWARE IS PROVIDED BY THE AUTHORS 'AS IS' AND ANY EXPRESSED |
|---|
| 20 |
;;; OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
|---|
| 21 |
;;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
|---|
| 22 |
;;; ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY |
|---|
| 23 |
;;; DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
|---|
| 24 |
;;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE |
|---|
| 25 |
;;; GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
|---|
| 26 |
;;; INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, |
|---|
| 27 |
;;; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
|---|
| 28 |
;;; NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
|---|
| 29 |
;;; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|---|
| 30 |
|
|---|
| 31 |
(in-package :cl-fad) |
|---|
| 32 |
|
|---|
| 33 |
(defun component-present-p (value) |
|---|
| 34 |
"Helper function for DIRECTORY-PATHNAME-P which checks whether VALUE |
|---|
| 35 |
is neither NIL nor the keyword :UNSPECIFIC." |
|---|
| 36 |
(and value (not (eql value :unspecific)))) |
|---|
| 37 |
|
|---|
| 38 |
(defun directory-pathname-p (pathspec) |
|---|
| 39 |
"Returns NIL if PATHSPEC \(a pathname designator) does not designate |
|---|
| 40 |
a directory, PATHSPEC otherwise. It is irrelevant whether file or |
|---|
| 41 |
directory designated by PATHSPEC does actually exist." |
|---|
| 42 |
(and |
|---|
| 43 |
(not (component-present-p (pathname-name pathspec))) |
|---|
| 44 |
(not (component-present-p (pathname-type pathspec))) |
|---|
| 45 |
pathspec)) |
|---|
| 46 |
|
|---|
| 47 |
(defun pathname-as-directory (pathspec) |
|---|
| 48 |
"Converts the non-wild pathname designator PATHSPEC to directory |
|---|
| 49 |
form." |
|---|
| 50 |
(let ((pathname (pathname pathspec))) |
|---|
| 51 |
(when (wild-pathname-p pathname) |
|---|
| 52 |
(error "Can't reliably convert wild pathnames.")) |
|---|
| 53 |
(cond ((not (directory-pathname-p pathspec)) |
|---|
| 54 |
(make-pathname :directory (append (or (pathname-directory pathname) |
|---|
| 55 |
(list :relative)) |
|---|
| 56 |
(list (file-namestring pathname))) |
|---|
| 57 |
:name nil |
|---|
| 58 |
:type nil |
|---|
| 59 |
:defaults pathname)) |
|---|
| 60 |
(t pathname)))) |
|---|
| 61 |
|
|---|
| 62 |
(defun directory-wildcard (dirname) |
|---|
| 63 |
"Returns a wild pathname designator that designates all files within |
|---|
| 64 |
the directory named by the non-wild pathname designator DIRNAME." |
|---|
| 65 |
(when (wild-pathname-p dirname) |
|---|
| 66 |
(error "Can only make wildcard directories from non-wildcard directories.")) |
|---|
| 67 |
(make-pathname :name #-:cormanlisp :wild #+:cormanlisp "*" |
|---|
| 68 |
:type #-(or :clisp :cormanlisp) :wild |
|---|
| 69 |
#+:clisp nil |
|---|
| 70 |
#+:cormanlisp "*" |
|---|
| 71 |
:defaults (pathname-as-directory dirname))) |
|---|
| 72 |
|
|---|
| 73 |
#+:clisp |
|---|
| 74 |
(defun clisp-subdirectories-wildcard (wildcard) |
|---|
| 75 |
"Creates a wild pathname specifically for CLISP such that |
|---|
| 76 |
sub-directories are returned by DIRECTORY." |
|---|
| 77 |
(make-pathname :directory (append (pathname-directory wildcard) |
|---|
| 78 |
(list :wild)) |
|---|
| 79 |
:name nil |
|---|
| 80 |
:type nil |
|---|
| 81 |
:defaults wildcard)) |
|---|
| 82 |
|
|---|
| 83 |
(defun list-directory (dirname) |
|---|
| 84 |
"Returns a fresh list of pathnames corresponding to the truenames of |
|---|
| 85 |
all files within the directory named by the non-wild pathname |
|---|
| 86 |
designator DIRNAME. The pathnames of sub-directories are returned in |
|---|
| 87 |
directory form - see PATHNAME-AS-DIRECTORY." |
|---|
| 88 |
(when (wild-pathname-p dirname) |
|---|
| 89 |
(error "Can only list concrete directory names.")) |
|---|
| 90 |
#+:ecl |
|---|
| 91 |
(let ((dir (pathname-as-directory dirname))) |
|---|
| 92 |
(concatenate 'list |
|---|
| 93 |
(directory (merge-pathnames (pathname "*/") dir)) |
|---|
| 94 |
(directory (merge-pathnames (pathname "*.*") dir)))) |
|---|
| 95 |
#-:ecl |
|---|
| 96 |
(let ((wildcard (directory-wildcard dirname))) |
|---|
| 97 |
#+:abcl (system::list-directory dirname) |
|---|
| 98 |
#+(or :sbcl :cmu :scl :lispworks) (directory wildcard) |
|---|
| 99 |
#+(or :openmcl :digitool) (directory wildcard :directories t) |
|---|
| 100 |
#+:allegro (directory wildcard :directories-are-files nil) |
|---|
| 101 |
#+:clisp (nconc (directory wildcard :if-does-not-exist :keep) |
|---|
| 102 |
(directory (clisp-subdirectories-wildcard wildcard))) |
|---|
| 103 |
#+:cormanlisp (nconc (directory wildcard) |
|---|
| 104 |
(cl::directory-subdirs dirname))) |
|---|
| 105 |
#-(or :sbcl :cmu :scl :lispworks :openmcl :allegro :clisp :cormanlisp :ecl :abcl :digitool) |
|---|
| 106 |
(error "LIST-DIRECTORY not implemented")) |
|---|
| 107 |
|
|---|
| 108 |
(defun pathname-as-file (pathspec) |
|---|
| 109 |
"Converts the non-wild pathname designator PATHSPEC to file form." |
|---|
| 110 |
(let ((pathname (pathname pathspec))) |
|---|
| 111 |
(when (wild-pathname-p pathname) |
|---|
| 112 |
(error "Can't reliably convert wild pathnames.")) |
|---|
| 113 |
(cond ((directory-pathname-p pathspec) |
|---|
| 114 |
(let* ((directory (pathname-directory pathname)) |
|---|
| 115 |
(name-and-type (pathname (first (last directory))))) |
|---|
| 116 |
(make-pathname :directory (butlast directory) |
|---|
| 117 |
:name (pathname-name name-and-type) |
|---|
| 118 |
:type (pathname-type name-and-type) |
|---|
| 119 |
:defaults pathname))) |
|---|
| 120 |
(t pathname)))) |
|---|
| 121 |
|
|---|
| 122 |
(defun file-exists-p (pathspec) |
|---|
| 123 |
"Checks whether the file named by the pathname designator PATHSPEC |
|---|
| 124 |
exists and returns its truename if this is the case, NIL otherwise. |
|---|
| 125 |
The truename is returned in `canonical' form, i.e. the truename of a |
|---|
| 126 |
directory is returned as if by PATHNAME-AS-DIRECTORY." |
|---|
| 127 |
#+(or :sbcl :lispworks :openmcl :ecl :digitool) (probe-file pathspec) |
|---|
| 128 |
#+:allegro (or (excl:probe-directory (pathname-as-directory pathspec)) |
|---|
| 129 |
(probe-file pathspec)) |
|---|
| 130 |
#+(or :cmu :scl :abcl) (or (probe-file (pathname-as-directory pathspec)) |
|---|
| 131 |
(probe-file pathspec)) |
|---|
| 132 |
#+:cormanlisp (or (and (ccl:directory-p pathspec) |
|---|
| 133 |
(pathname-as-directory pathspec)) |
|---|
| 134 |
(probe-file pathspec)) |
|---|
| 135 |
#+:clisp (or (ignore-errors |
|---|
| 136 |
(let ((directory-form (pathname-as-directory pathspec))) |
|---|
| 137 |
(when (ext:probe-directory directory-form) |
|---|
| 138 |
directory-form))) |
|---|
| 139 |
(ignore-errors |
|---|
| 140 |
(probe-file (pathname-as-file pathspec)))) |
|---|
| 141 |
#-(or :sbcl :cmu :scl :lispworks :openmcl :allegro :clisp :cormanlisp :ecl :abcl :digitool) |
|---|
| 142 |
(error "FILE-EXISTS-P not implemented")) |
|---|
| 143 |
|
|---|
| 144 |
(defun directory-exists-p (pathspec) |
|---|
| 145 |
"Checks whether the file named by the pathname designator PATHSPEC |
|---|
| 146 |
exists and if it is a directory. Returns its truename if this is the |
|---|
| 147 |
case, NIL otherwise. The truename is returned in directory form as if |
|---|
| 148 |
by PATHNAME-AS-DIRECTORY." |
|---|
| 149 |
#+:allegro |
|---|
| 150 |
(and (excl:probe-directory pathspec) |
|---|
| 151 |
(pathname-as-directory (truename pathspec))) |
|---|
| 152 |
#+:lispworks |
|---|
| 153 |
(and (lw:file-directory-p pathspec) |
|---|
| 154 |
(pathname-as-directory (truename pathspec))) |
|---|
| 155 |
#-(or :allegro :lispworks) |
|---|
| 156 |
(let ((result (file-exists-p pathspec))) |
|---|
| 157 |
(and result |
|---|
| 158 |
(directory-pathname-p result) |
|---|
| 159 |
result))) |
|---|
| 160 |
|
|---|
| 161 |
(defun walk-directory (dirname fn &key directories |
|---|
| 162 |
(if-does-not-exist :error) |
|---|
| 163 |
(test (constantly t))) |
|---|
| 164 |
"Recursively applies the function FN to all files within the |
|---|
| 165 |
directory named by the non-wild pathname designator DIRNAME and all of |
|---|
| 166 |
its sub-directories. FN will only be applied to files for which the |
|---|
| 167 |
function TEST returns a true value. If DIRECTORIES is not NIL, FN and |
|---|
| 168 |
TEST are applied to directories as well. If DIRECTORIES is :DEPTH-FIRST, |
|---|
| 169 |
FN will be applied to the directory's contents first. If |
|---|
| 170 |
DIRECTORIES is :BREADTH-FIRST and TEST returns NIL, the |
|---|
| 171 |
directory's content will be skipped. IF-DOES-NOT-EXIST must be |
|---|
| 172 |
one of :ERROR or :IGNORE where :ERROR means that an error will be |
|---|
| 173 |
signaled if the directory DIRNAME does not exist." |
|---|
| 174 |
(labels ((walk (name) |
|---|
| 175 |
(cond |
|---|
| 176 |
((directory-pathname-p name) |
|---|
| 177 |
;; the code is written in a slightly awkward way for |
|---|
| 178 |
;; backward compatibility |
|---|
| 179 |
(cond ((not directories) |
|---|
| 180 |
(dolist (file (list-directory name)) |
|---|
| 181 |
(walk file))) |
|---|
| 182 |
((eql directories :breadth-first) |
|---|
| 183 |
(when (funcall test name) |
|---|
| 184 |
(funcall fn name) |
|---|
| 185 |
(dolist (file (list-directory name)) |
|---|
| 186 |
(walk file)))) |
|---|
| 187 |
;; :DEPTH-FIRST is implicit |
|---|
| 188 |
(t (dolist (file (list-directory name)) |
|---|
| 189 |
(walk file)) |
|---|
| 190 |
(when (funcall test name) |
|---|
| 191 |
(funcall fn name))))) |
|---|
| 192 |
((funcall test name) |
|---|
| 193 |
(funcall fn name))))) |
|---|
| 194 |
(let ((pathname-as-directory (pathname-as-directory dirname))) |
|---|
| 195 |
(case if-does-not-exist |
|---|
| 196 |
((:error) |
|---|
| 197 |
(cond ((not (file-exists-p pathname-as-directory)) |
|---|
| 198 |
(error "File ~S does not exist." |
|---|
| 199 |
pathname-as-directory)) |
|---|
| 200 |
(t (walk pathname-as-directory)))) |
|---|
| 201 |
((:ignore) |
|---|
| 202 |
(when (file-exists-p pathname-as-directory) |
|---|
| 203 |
(walk pathname-as-directory))) |
|---|
| 204 |
(otherwise |
|---|
| 205 |
(error "IF-DOES-NOT-EXIST must be one of :ERROR or :IGNORE.")))) |
|---|
| 206 |
(values))) |
|---|
| 207 |
|
|---|
| 208 |
#-:sbcl |
|---|
| 209 |
(defvar *stream-buffer-size* 8192) |
|---|
| 210 |
#-:sbcl |
|---|
| 211 |
(defun copy-stream (from to &optional (checkp t)) |
|---|
| 212 |
"Copies into TO \(a stream) from FROM \(also a stream) until the end |
|---|
| 213 |
of FROM is reached, in blocks of *stream-buffer-size*. The streams |
|---|
| 214 |
should have the same element type. If CHECKP is true, the streams are |
|---|
| 215 |
checked for compatibility of their types." |
|---|
| 216 |
(when checkp |
|---|
| 217 |
(unless (subtypep (stream-element-type to) (stream-element-type from)) |
|---|
| 218 |
(error "Incompatible streams ~A and ~A." from to))) |
|---|
| 219 |
(let ((buf (make-array *stream-buffer-size* |
|---|
| 220 |
:element-type (stream-element-type from)))) |
|---|
| 221 |
(loop |
|---|
| 222 |
(let ((pos #-(or :clisp :cmu) (read-sequence buf from) |
|---|
| 223 |
#+:clisp (ext:read-byte-sequence buf from :no-hang nil) |
|---|
| 224 |
#+:cmu (sys:read-n-bytes from buf 0 *stream-buffer-size* nil))) |
|---|
| 225 |
(when (zerop pos) (return)) |
|---|
| 226 |
(write-sequence buf to :end pos)))) |
|---|
| 227 |
(values)) |
|---|
| 228 |
|
|---|
| 229 |
#+:sbcl |
|---|
| 230 |
(declaim (inline copy-stream)) |
|---|
| 231 |
#+:sbcl |
|---|
| 232 |
(defun copy-stream (from to) |
|---|
| 233 |
"Copies into TO \(a stream) from FROM \(also a stream) until the end |
|---|
| 234 |
of FROM is reached. The streams should have the same element type." |
|---|
| 235 |
(sb-executable:copy-stream from to) |
|---|
| 236 |
(values)) |
|---|
| 237 |
|
|---|
| 238 |
(defun copy-file (from to &key overwrite) |
|---|
| 239 |
"Copies the file designated by the non-wild pathname designator FROM |
|---|
| 240 |
to the file designated by the non-wild pathname designator TO. If |
|---|
| 241 |
OVERWRITE is true overwrites the file designtated by TO if it exists." |
|---|
| 242 |
#+:allegro (excl.osi:copy-file from to :overwrite overwrite) |
|---|
| 243 |
#-:allegro |
|---|
| 244 |
(let ((element-type #-:cormanlisp '(unsigned-byte 8) |
|---|
| 245 |
#+:cormanlisp 'unsigned-byte)) |
|---|
| 246 |
(with-open-file (in from :element-type element-type) |
|---|
| 247 |
(with-open-file (out to :element-type element-type |
|---|
| 248 |
:direction :output |
|---|
| 249 |
:if-exists (if overwrite |
|---|
| 250 |
:supersede |
|---|
| 251 |
#-:cormanlisp :error |
|---|
| 252 |
#+:cormanlisp nil)) |
|---|
| 253 |
#+:cormanlisp |
|---|
| 254 |
(unless out |
|---|
| 255 |
(error (make-condition 'file-error |
|---|
| 256 |
:pathname to |
|---|
| 257 |
:format-control "File already exists."))) |
|---|
| 258 |
(copy-stream in out)))) |
|---|
| 259 |
(values)) |
|---|
| 260 |
|
|---|
| 261 |
(defun delete-directory-and-files (dirname &key (if-does-not-exist :error)) |
|---|
| 262 |
"Recursively deletes all files and directories within the directory |
|---|
| 263 |
designated by the non-wild pathname designator DIRNAME including |
|---|
| 264 |
DIRNAME itself. IF-DOES-NOT-EXIST must be one of :ERROR or :IGNORE |
|---|
| 265 |
where :ERROR means that an error will be signaled if the directory |
|---|
| 266 |
DIRNAME does not exist." |
|---|
| 267 |
#+:allegro (excl.osi:delete-directory-and-files dirname |
|---|
| 268 |
:if-does-not-exist if-does-not-exist) |
|---|
| 269 |
#-:allegro (walk-directory dirname |
|---|
| 270 |
(lambda (file) |
|---|
| 271 |
(cond ((directory-pathname-p file) |
|---|
| 272 |
#+:lispworks (lw:delete-directory file) |
|---|
| 273 |
#+:cmu (multiple-value-bind (ok err-number) |
|---|
| 274 |
(unix:unix-rmdir (namestring (truename file))) |
|---|
| 275 |
(unless ok |
|---|
| 276 |
(error "Error number ~A when trying to delete ~A" |
|---|
| 277 |
err-number file))) |
|---|
| 278 |
#+:scl (multiple-value-bind (ok errno) |
|---|
| 279 |
(unix:unix-rmdir (ext:unix-namestring (truename file))) |
|---|
| 280 |
(unless ok |
|---|
| 281 |
(error "~@<Error deleting ~S: ~A~@:>" |
|---|
| 282 |
file (unix:get-unix-error-msg errno)))) |
|---|
| 283 |
#+:sbcl (sb-posix:rmdir file) |
|---|
| 284 |
#+:clisp (ext:delete-dir file) |
|---|
| 285 |
#+:openmcl (ccl:delete-directory file) |
|---|
| 286 |
#+:cormanlisp (win32:delete-directory file) |
|---|
| 287 |
#+:ecl (si:rmdir file) |
|---|
| 288 |
#+(or :abcl :digitool) (delete-file file)) |
|---|
| 289 |
(t (delete-file file)))) |
|---|
| 290 |
:directories t |
|---|
| 291 |
:if-does-not-exist if-does-not-exist) |
|---|
| 292 |
(values)) |
|---|
| 293 |
|
|---|
| 294 |
(pushnew :cl-fad *features*) |
|---|
| 295 |
|
|---|
| 296 |
;; stuff for Nikodemus Siivola's HYPERDOC |
|---|
| 297 |
;; see <http://common-lisp.net/project/hyperdoc/> |
|---|
| 298 |
;; and <http://www.cliki.net/hyperdoc> |
|---|
| 299 |
;; also used by LW-ADD-ONS |
|---|
| 300 |
|
|---|
| 301 |
#-:abcl |
|---|
| 302 |
(defvar *hyperdoc-base-uri* "http://weitz.de/cl-fad/") |
|---|
| 303 |
|
|---|
| 304 |
#-:abcl |
|---|
| 305 |
(let ((exported-symbols-alist |
|---|
| 306 |
(loop for symbol being the external-symbols of :cl-fad |
|---|
| 307 |
collect (cons symbol |
|---|
| 308 |
(concatenate 'string |
|---|
| 309 |
"#" |
|---|
| 310 |
(string-downcase symbol)))))) |
|---|
| 311 |
(defun hyperdoc-lookup (symbol type) |
|---|
| 312 |
(declare (ignore type)) |
|---|
| 313 |
(cdr (assoc symbol |
|---|
| 314 |
exported-symbols-alist |
|---|
| 315 |
:test #'eq)))) |
|---|