;;; -*- 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))))