;;; -*- Mode: LISP; Syntax: COMMON-LISP; Package: CL-FAD; Base: 10 -*- ;;; $Header$ ;;; Copyright (c) 2004-2005, Peter Seibel and Dr. Edmund Weitz. All rights reserved. ;;; Redistribution and use in source and binary forms, with or without ;;; modification, are permitted provided that the following conditions ;;; are met: ;;; * Redistributions of source code must retain the above copyright ;;; notice, this list of conditions and the following disclaimer. ;;; * Redistributions in binary form must reproduce the above ;;; copyright notice, this list of conditions and the following ;;; disclaimer in the documentation and/or other materials ;;; provided with the distribution. ;;; THIS SOFTWARE IS PROVIDED BY THE AUTHORS 'AS IS' AND ANY EXPRESSED ;;; OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED ;;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ;;; ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY ;;; DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL ;;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE ;;; GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ;;; INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, ;;; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING ;;; NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS ;;; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. (in-package :cl-fad) (defun component-present-p (value) "Helper function for DIRECTORY-PATHNAME-P which checks whether VALUE is neither NIL nor the keyword :UNSPECIFIC." (and value (not (eql value :unspecific)))) (defun directory-pathname-p (pathspec) "Returns NIL if PATHSPEC \(a pathname designator) does not designate a directory, PATHSPEC otherwise. It is irrelevant whether file or directory designated by PATHSPEC does actually exist." (and (not (component-present-p (pathname-name pathspec))) (not (component-present-p (pathname-type pathspec))) pathspec)) (defun pathname-as-directory (pathspec) "Converts the non-wild pathname designator PATHSPEC to directory form." (let ((pathname (pathname pathspec))) (when (wild-pathname-p pathname) (error "Can't reliably convert wild pathnames.")) (cond ((not (directory-pathname-p pathspec)) (make-pathname :directory (append (or (pathname-directory pathname) (list :relative)) (list (file-namestring pathname))) :name nil :type nil :defaults pathname)) (t pathname)))) (defun directory-wildcard (dirname) "Returns a wild pathname designator that designates all files within the directory named by the non-wild pathname designator DIRNAME." (make-pathname :name #-:cormanlisp :wild #+:cormanlisp "*" :type #-(or :clisp :cormanlisp) :wild #+:clisp nil #+:cormanlisp "*" :defaults (pathname-as-directory dirname))) #+:clisp (defun clisp-subdirectories-wildcard (wildcard) "Creates a wild pathname specifically for CLISP such that sub-directories are returned by DIRECTORY." (make-pathname :directory (append (pathname-directory wildcard) (list :wild)) :name nil :type nil :defaults wildcard)) (defun list-directory (dirname) "Returns a fresh list of pathnames corresponding to the truenames of all files within the directory named by the non-wild pathname designator DIRNAME. The pathnames of sub-directories are returned in directory form - see PATHNAME-AS-DIRECTORY." (when (wild-pathname-p dirname) (error "Can only list concrete directory names.")) #+:ecl (directory (pathname-as-directory dirname)) #-:ecl (let ((wildcard (directory-wildcard dirname))) #+:abcl (system::list-directory dirname) #+(or :sbcl :cmu :lispworks) (directory wildcard) #+:openmcl (directory wildcard :directories t) #+:allegro (directory wildcard :directories-are-files nil) #+:clisp (nconc (directory wildcard) (directory (clisp-subdirectories-wildcard wildcard))) #+:cormanlisp (nconc (directory wildcard) (cl::directory-subdirs dirname))) #-(or :sbcl :cmu :lispworks :openmcl :allegro :clisp :cormanlisp :ecl :abcl) (error "LIST-DIRECTORY not implemented")) (defun pathname-as-file (pathspec) "Converts the non-wild pathname designator PATHSPEC to file form." (let ((pathname (pathname pathspec))) (when (wild-pathname-p pathname) (error "Can't reliably convert wild pathnames.")) (cond ((directory-pathname-p pathspec) (let* ((directory (pathname-directory pathname)) (name-and-type (pathname (first (last directory))))) (make-pathname :directory (butlast directory) :name (pathname-name name-and-type) :type (pathname-type name-and-type) :defaults pathname))) (t pathname)))) (defun file-exists-p (pathspec) "Checks whether the file named by the pathname designator PATHSPEC exists and returns its truename if this is the case, NIL otherwise. The truename is returned in `canonical' form, i.e. the truename of a directory is returned as if by PATHNAME-AS-DIRECTORY." #+(or :sbcl :lispworks :openmcl :ecl) (probe-file pathspec) #+:allegro (or (excl:probe-directory (pathname-as-directory pathspec)) (probe-file pathspec)) #+(or :cmu :abcl) (or (probe-file (pathname-as-directory pathspec)) (probe-file pathspec)) #+:cormanlisp (or (and (ccl:directory-p pathspec) (pathname-as-directory pathspec)) (probe-file pathspec)) #+:clisp (or (ignore-errors (let ((directory-form (pathname-as-directory pathspec))) (when (ext:probe-directory directory-form) directory-form))) (ignore-errors (probe-file (pathname-as-file pathspec)))) #-(or :sbcl :cmu :lispworks :openmcl :allegro :clisp :cormanlisp :ecl :abcl) (error "FILE-EXISTS-P not implemented")) (defun directory-exists-p (pathspec) "Checks whether the file named by the pathname designator PATHSPEC exists and if it is a directory. Returns its truename if this is the case, NIL otherwise. The truename is returned in directory form as if by PATHNAME-AS-DIRECTORY." #+:allegro (and (excl:probe-directory pathspec) (pathname-as-directory (truename pathspec))) #+:lispworks (and (lw:file-directory-p pathspec) (pathname-as-directory (truename pathspec))) #-(or :allegro :lispworks) (let ((result (file-exists-p pathspec))) (and result (directory-pathname-p result) result))) (defun walk-directory (dirname fn &key directories (if-does-not-exist :error) (test (constantly t))) "Recursively applies the function FN to all files within the directory named by the non-wild pathname designator DIRNAME and all of its sub-directories. FN will only be applied to files for which the function TEST returns a true value. If DIRECTORIES is true, FN and TEST are applied to directories as well, and FN is guaranteed to be applied to the directory's contents first. IF-DOES-NOT-EXIST must be one of :ERROR or :IGNORE where :ERROR means that an error will be signaled if the directory DIRNAME does not exist." (labels ((walk (name) (cond ((directory-pathname-p name) (dolist (file (list-directory name)) (walk file)) (when (and directories (funcall test name)) (funcall fn name))) ((funcall test name) (funcall fn name))))) (let ((pathname-as-directory (pathname-as-directory dirname))) (case if-does-not-exist ((:error) (cond ((not (file-exists-p pathname-as-directory)) (error "File ~S does not exist." pathname-as-directory)) (t (walk pathname-as-directory)))) ((:ignore) (when (file-exists-p pathname-as-directory) (walk pathname-as-directory))) (otherwise (error "IF-DOES-NOT-EXIST must be one of :ERROR or :IGNORE.")))) (values))) #-:sbcl (defvar *stream-buffer-size* 8192) #-:sbcl (defun copy-stream (from to) "Copy into TO \(a stream) from FROM \(also a stream) until the end of TO is reached, in blocks of *stream-buffer-size*. The streams should have the same element type." (unless (subtypep (stream-element-type to) (stream-element-type from)) (error "Incompatible streams ~A and ~A." from to)) (let ((buf (make-array *stream-buffer-size* :element-type (stream-element-type from)))) (loop (let ((pos #-(or :clisp :cmu) (read-sequence buf from) #+:clisp (ext:read-byte-sequence buf from :no-hang nil) #+:cmu (sys:read-n-bytes from buf 0 *stream-buffer-size* nil))) (when (zerop pos) (return)) (write-sequence buf to :end pos))))) #+:sbcl (declaim (inline copy-stream)) #+:sbcl (defun copy-stream (from to) "Copy into TO \(a stream) from FROM \(also a stream) until the end of TO is reached. The streams should have the same element type." (sb-executable:copy-stream from to)) (defun copy-file (from to &key overwrite) "Copies the file designated by the non-wild pathname designator FROM to the file designated by the non-wild pathname designator TO. If OVERWRITE is true overwrites the file designtated by TO if it exists." #+:allegro (excl.osi:copy-file from to :overwrite overwrite) #-:allegro (let ((element-type #-:cormanlisp '(unsigned-byte 8) #+:cormanlisp 'unsigned-byte)) (with-open-file (in from :element-type element-type) (with-open-file (out to :element-type element-type :direction :output :if-exists (if overwrite :supersede #-:cormanlisp :error #+:cormanlisp nil)) #+:cormanlisp (unless out (error (make-condition 'file-error :pathname to :format-control "File already exists."))) (copy-stream in out)))) (values)) (defun delete-directory-and-files (dirname &key (if-does-not-exist :error)) "Recursively deletes all files and directories within the directory designated by the non-wild pathname designator DIRNAME including DIRNAME itself. IF-DOES-NOT-EXIST must be one of :ERROR or :IGNORE where :ERROR means that an error will be signaled if the directory DIRNAME does not exist." #+:allegro (excl.osi:delete-directory-and-files dirname :if-does-not-exist if-does-not-exist) #-:allegro (walk-directory dirname (lambda (file) (cond ((directory-pathname-p file) #+:lispworks (lw:delete-directory file) #+:cmu (multiple-value-bind (ok err-number) (unix:unix-rmdir (namestring file)) (unless ok (error "Error number ~A when trying to delete ~A" err-number file))) #+:sbcl (sb-posix:rmdir file) #+:clisp (ext:delete-dir file) #+:openmcl (ccl:delete-directory file) #+:cormanlisp (win32:delete-directory file) #+:ecl (si:rmdir file) #+:abcl (delete-file file)) (t (delete-file file)))) :directories t :if-does-not-exist if-does-not-exist) (values)) (pushnew :cl-fad *features*) ;; stuff for Nikodemus Siivola's HYPERDOC ;; see ;; and #+:abcl (defvar *hyperdoc-base-uri* "http://weitz.de/cl-fad/") #+:abcl (let ((exported-symbols-alist (loop for symbol being the external-symbols of :cl-fad collect (cons symbol (concatenate 'string "#" (string-downcase symbol)))))) (defun hyperdoc-lookup (symbol type) (declare (ignore type)) (cdr (assoc symbol exported-symbols-alist :test #'eq))))