| 1 | ;;; -*- Log: hemlock.log; Package: dired -*-
|
|---|
| 2 | ;;;
|
|---|
| 3 | ;;; **********************************************************************
|
|---|
| 4 | ;;; This code was written as part of the CMU Common Lisp project at
|
|---|
| 5 | ;;; Carnegie Mellon University, and has been placed in the public domain.
|
|---|
| 6 | ;;;
|
|---|
| 7 | #+CMU (ext:file-comment
|
|---|
| 8 | "$Header$")
|
|---|
| 9 | ;;;
|
|---|
| 10 | ;;; **********************************************************************
|
|---|
| 11 | ;;;
|
|---|
| 12 | ;;; This file contains site dependent code for dired.
|
|---|
| 13 | ;;; Written by Bill Chiles.
|
|---|
| 14 | ;;;
|
|---|
| 15 |
|
|---|
| 16 | (defpackage "DIRED"
|
|---|
| 17 | (:shadow "RENAME-FILE" "DELETE-FILE")
|
|---|
| 18 | (:export "COPY-FILE" "RENAME-FILE" "FIND-FILE" "DELETE-FILE"
|
|---|
| 19 | "MAKE-DIRECTORY"
|
|---|
| 20 | "*UPDATE-DEFAULT*" "*CLOBBER-DEFAULT*" "*RECURSIVE-DEFAULT*"
|
|---|
| 21 | "*REPORT-FUNCTION*" "*ERROR-FUNCTION*" "*YESP-FUNCTION*"
|
|---|
| 22 | "PATHNAMES-FROM-PATTERN"))
|
|---|
| 23 |
|
|---|
| 24 | (in-package "DIRED")
|
|---|
| 25 |
|
|---|
| 26 | |
|---|
| 27 |
|
|---|
| 28 | ;;;; Exported parameters.
|
|---|
| 29 |
|
|---|
| 30 | (defparameter *update-default* nil
|
|---|
| 31 | "Update arguments to utilities default to this value.")
|
|---|
| 32 |
|
|---|
| 33 | (defparameter *clobber-default* t
|
|---|
| 34 | "Clobber arguments to utilities default to this value.")
|
|---|
| 35 |
|
|---|
| 36 | (defparameter *recursive-default* nil
|
|---|
| 37 | "Recursive arguments to utilities default to this value.")
|
|---|
| 38 |
|
|---|
| 39 |
|
|---|
| 40 | |
|---|
| 41 |
|
|---|
| 42 | ;;;; WILDCARDP
|
|---|
| 43 |
|
|---|
| 44 | (defconstant wildcard-char #\*
|
|---|
| 45 | "Wildcard designator for file names will match any substring.")
|
|---|
| 46 |
|
|---|
| 47 | (defmacro wildcardp (file-namestring)
|
|---|
| 48 | `(position wildcard-char (the simple-string ,file-namestring) :test #'char=))
|
|---|
| 49 |
|
|---|
| 50 |
|
|---|
| 51 | |
|---|
| 52 |
|
|---|
| 53 | ;;;; User interaction functions, variable declarations, and their defaults.
|
|---|
| 54 |
|
|---|
| 55 | (defun default-error-function (string &rest args)
|
|---|
| 56 | (apply #'error string args))
|
|---|
| 57 | ;;;
|
|---|
| 58 | (defvar *error-function* #'default-error-function
|
|---|
| 59 | "This function is called when an error is encountered in dired code.")
|
|---|
| 60 |
|
|---|
| 61 | (defun default-report-function (string &rest args)
|
|---|
| 62 | (apply #'format t string args))
|
|---|
| 63 | ;;;
|
|---|
| 64 | (defvar *report-function* #'default-report-function
|
|---|
| 65 | "This function is called when the user needs to be informed of something.")
|
|---|
| 66 |
|
|---|
| 67 | (defun default-yesp-function (string &rest args)
|
|---|
| 68 | (apply #'format t string args)
|
|---|
| 69 | (let ((answer (nstring-downcase (string-trim '(#\space #\tab) (read-line)))))
|
|---|
| 70 | (declare (simple-string answer))
|
|---|
| 71 | (or (string= answer "")
|
|---|
| 72 | (string= answer "y")
|
|---|
| 73 | (string= answer "yes")
|
|---|
| 74 | (string= answer "ye"))))
|
|---|
| 75 | ;;;
|
|---|
| 76 | (defvar *yesp-function* #'default-yesp-function
|
|---|
| 77 | "Function to query the user about clobbering an already existent file.")
|
|---|
| 78 |
|
|---|
| 79 |
|
|---|
| 80 | |
|---|
| 81 |
|
|---|
| 82 | ;;;; Copy-File
|
|---|
| 83 |
|
|---|
| 84 | ;;; WILD-MATCH objects contain information about wildcard matches. File is the
|
|---|
| 85 | ;;; Sesame namestring of the file matched, and substitute is a substring of the
|
|---|
| 86 | ;;; file-namestring of file.
|
|---|
| 87 | ;;;
|
|---|
| 88 | (defstruct (wild-match (:print-function print-wild-match)
|
|---|
| 89 | (:constructor make-wild-match (file substitute)))
|
|---|
| 90 | file
|
|---|
| 91 | substitute)
|
|---|
| 92 |
|
|---|
| 93 | (defun print-wild-match (obj str n)
|
|---|
| 94 | (declare (ignore n))
|
|---|
| 95 | (format str "#<Wild-Match ~S ~S>"
|
|---|
| 96 | (wild-match-file obj) (wild-match-substitute obj)))
|
|---|
| 97 |
|
|---|
| 98 |
|
|---|
| 99 | (defun copy-file (spec1 spec2 &key (update *update-default*)
|
|---|
| 100 | (clobber *clobber-default*)
|
|---|
| 101 | (directory () directoryp))
|
|---|
| 102 | "Copy file spec1 to spec2. A single wildcard is acceptable, and directory
|
|---|
| 103 | names may be used. If spec1 and spec2 are both directories, then a
|
|---|
| 104 | recursive copy is done of the files and subdirectory structure of spec1;
|
|---|
| 105 | if spec2 is in the subdirectory structure of spec1, the recursion will
|
|---|
| 106 | not descend into it. Use spec1/* to copy only the files in spec1 to
|
|---|
| 107 | directory spec2. If spec2 is a directory, and spec1 is a file, then
|
|---|
| 108 | spec1 is copied into spec2 with the same pathname-name. Files are
|
|---|
| 109 | copied maintaining the source's write date. If :update is non-nil, then
|
|---|
| 110 | files are only copied if the source is newer than the destination, still
|
|---|
| 111 | maintaining the source's write date; the user is not warned if the
|
|---|
| 112 | destination is newer (not the same write date) than the source. If
|
|---|
| 113 | :clobber and :update are nil, then if any file spec2 already exists, the
|
|---|
| 114 | user will be asked whether it should be overwritten or not."
|
|---|
| 115 | (cond
|
|---|
| 116 | ((not directoryp)
|
|---|
| 117 | (let* ((ses-name1 (ext:unix-namestring spec1 t))
|
|---|
| 118 | (exists1p (unix:unix-file-kind ses-name1))
|
|---|
| 119 | (ses-name2 (ext:unix-namestring spec2 nil))
|
|---|
| 120 | (pname1 (pathname ses-name1))
|
|---|
| 121 | (pname2 (pathname ses-name2))
|
|---|
| 122 | (dirp1 (directoryp pname1))
|
|---|
| 123 | (dirp2 (directoryp pname2))
|
|---|
| 124 | (wildp1 (wildcardp (file-namestring pname1)))
|
|---|
| 125 | (wildp2 (wildcardp (file-namestring pname2))))
|
|---|
| 126 | (when (and dirp1 wildp1)
|
|---|
| 127 | (funcall *error-function*
|
|---|
| 128 | "Cannot have wildcards in directory names -- ~S." pname1))
|
|---|
| 129 | (when (and dirp2 wildp2)
|
|---|
| 130 | (funcall *error-function*
|
|---|
| 131 | "Cannot have wildcards in directory names -- ~S." pname2))
|
|---|
| 132 | (when (and dirp1 (not dirp2))
|
|---|
| 133 | (funcall *error-function*
|
|---|
| 134 | "Cannot handle spec1 being a directory and spec2 a file."))
|
|---|
| 135 | (when (and wildp2 (not wildp1))
|
|---|
| 136 | (funcall *error-function*
|
|---|
| 137 | "Cannot handle destination having wildcards without ~
|
|---|
| 138 | source having wildcards."))
|
|---|
| 139 | (when (and wildp1 (not wildp2) (not dirp2))
|
|---|
| 140 | (funcall *error-function*
|
|---|
| 141 | "Cannot handle source with wildcards and destination ~
|
|---|
| 142 | without, unless destination is a directory."))
|
|---|
| 143 | (cond ((and dirp1 dirp2)
|
|---|
| 144 | (unless (directory-existsp ses-name1)
|
|---|
| 145 | (funcall *error-function*
|
|---|
| 146 | "Directory does not exist -- ~S." pname1))
|
|---|
| 147 | (unless (directory-existsp ses-name2)
|
|---|
| 148 | (enter-directory ses-name2))
|
|---|
| 149 | (recursive-copy pname1 pname2 update clobber pname2
|
|---|
| 150 | ses-name1 ses-name2))
|
|---|
| 151 | (dirp2
|
|---|
| 152 | ;; merge pname2 with pname1 to pick up a similar file-namestring.
|
|---|
| 153 | (copy-file-1 pname1 wildp1 exists1p
|
|---|
| 154 | (merge-pathnames pname2 pname1)
|
|---|
| 155 | wildp1 update clobber))
|
|---|
| 156 | (t (copy-file-1 pname1 wildp1 exists1p
|
|---|
| 157 | pname2 wildp2 update clobber)))))
|
|---|
| 158 | (directory
|
|---|
| 159 | (when (pathname-directory spec1)
|
|---|
| 160 | (funcall *error-function*
|
|---|
| 161 | "Spec1 is just a pattern when supplying directory -- ~S."
|
|---|
| 162 | spec1))
|
|---|
| 163 | (let* ((pname2 (pathname (ext:unix-namestring spec2 nil)))
|
|---|
| 164 | (dirp2 (directoryp pname2))
|
|---|
| 165 | (wildp1 (wildcardp spec1))
|
|---|
| 166 | (wildp2 (wildcardp (file-namestring pname2))))
|
|---|
| 167 | (unless wildp1
|
|---|
| 168 | (funcall *error-function*
|
|---|
| 169 | "Pattern, ~S, does not contain a wildcard."
|
|---|
| 170 | spec1))
|
|---|
| 171 | (when (and (not wildp2) (not dirp2))
|
|---|
| 172 | (funcall *error-function*
|
|---|
| 173 | "Cannot handle source with wildcards and destination ~
|
|---|
| 174 | without, unless destination is a directory."))
|
|---|
| 175 | (copy-wildcard-files spec1 wildp1
|
|---|
| 176 | (if dirp2 (merge-pathnames pname2 spec1) pname2)
|
|---|
| 177 | (if dirp2 wildp1 wildp2)
|
|---|
| 178 | update clobber directory))))
|
|---|
| 179 | (values))
|
|---|
| 180 |
|
|---|
| 181 | ;;; RECURSIVE-COPY takes two pathnames that represent directories, and
|
|---|
| 182 | ;;; the files in pname1 are copied into pname2, recursively descending into
|
|---|
| 183 | ;;; subdirectories. If a subdirectory of pname1 does not exist in pname2,
|
|---|
| 184 | ;;; it is created. Pname1 is known to exist. Forbidden-dir is originally
|
|---|
| 185 | ;;; the same as pname2; this keeps us from infinitely recursing if pname2
|
|---|
| 186 | ;;; is in the subdirectory structure of pname1. Returns t if some file gets
|
|---|
| 187 | ;;; copied.
|
|---|
| 188 | ;;;
|
|---|
| 189 | (defun recursive-copy (pname1 pname2 update clobber
|
|---|
| 190 | forbidden-dir ses-name1 ses-name2)
|
|---|
| 191 | (funcall *report-function* "~&~S ==>~% ~S~%" ses-name1 ses-name2)
|
|---|
| 192 | (dolist (spec (directory (directory-namestring pname1)))
|
|---|
| 193 | (let ((spec-ses-name (namestring spec)))
|
|---|
| 194 | (if (directoryp spec)
|
|---|
| 195 | (unless (equal (pathname spec-ses-name) forbidden-dir)
|
|---|
| 196 | (let* ((dir2-pname (merge-dirs spec pname2))
|
|---|
| 197 | (dir2-ses-name (namestring dir2-pname)))
|
|---|
| 198 | (unless (directory-existsp dir2-ses-name)
|
|---|
| 199 | (enter-directory dir2-ses-name))
|
|---|
| 200 | (recursive-copy spec dir2-pname update clobber forbidden-dir
|
|---|
| 201 | spec-ses-name dir2-ses-name)
|
|---|
| 202 | (funcall *report-function* "~&~S ==>~% ~S~%" ses-name1
|
|---|
| 203 | ses-name2)))
|
|---|
| 204 | (copy-file-2 spec-ses-name
|
|---|
| 205 | (namestring (merge-pathnames pname2 spec))
|
|---|
| 206 | update clobber)))))
|
|---|
| 207 |
|
|---|
| 208 | ;;; MERGE-DIRS picks out the last directory name in the pathname pname1 and
|
|---|
| 209 | ;;; adds it to the end of the sequence of directory names from pname2, returning
|
|---|
| 210 | ;;; a pathname.
|
|---|
| 211 | ;;;
|
|---|
| 212 | #|
|
|---|
| 213 | (defun merge-dirs (pname1 pname2)
|
|---|
| 214 | (let* ((dirs1 (pathname-directory pname1))
|
|---|
| 215 | (dirs2 (pathname-directory pname2))
|
|---|
| 216 | (dirs2-len (length dirs2))
|
|---|
| 217 | (new-dirs2 (make-array (1+ dirs2-len))))
|
|---|
| 218 | (declare (simple-vector dirs1 dirs2 new-dirs2))
|
|---|
| 219 | (replace new-dirs2 dirs2)
|
|---|
| 220 | (setf (svref new-dirs2 dirs2-len)
|
|---|
| 221 | (svref dirs1 (1- (length dirs1))))
|
|---|
| 222 | (make-pathname :directory new-dirs2 :device :absolute)))
|
|---|
| 223 | |#
|
|---|
| 224 |
|
|---|
| 225 | (defun merge-dirs (pname1 pname2)
|
|---|
| 226 | (let* ((dirs1 (pathname-directory pname1))
|
|---|
| 227 | (dirs2 (pathname-directory pname2))
|
|---|
| 228 | (dirs2-len (length dirs2))
|
|---|
| 229 | (new-dirs2 (make-list (1+ dirs2-len))))
|
|---|
| 230 | (replace new-dirs2 dirs2)
|
|---|
| 231 | (setf (nth dirs2-len new-dirs2)
|
|---|
| 232 | (nth (1- (length dirs1)) dirs1))
|
|---|
| 233 | (make-pathname :directory new-dirs2 :device :unspecific)))
|
|---|
| 234 |
|
|---|
| 235 | ;;; COPY-FILE-1 takes pathnames which either both contain a single wildcard
|
|---|
| 236 | ;;; or none. Wildp1 and Wildp2 are either nil or indexes into the
|
|---|
| 237 | ;;; file-namestring of pname1 and pname2, respectively, indicating the position
|
|---|
| 238 | ;;; of the wildcard character. If there is no wildcard, then simply call
|
|---|
| 239 | ;;; COPY-FILE-2; otherwise, resolve the wildcard and copy those matching files.
|
|---|
| 240 | ;;;
|
|---|
| 241 | (defun copy-file-1 (pname1 wildp1 exists1p pname2 wildp2 update clobber)
|
|---|
| 242 | (if wildp1
|
|---|
| 243 | (copy-wildcard-files pname1 wildp1 pname2 wildp2 update clobber)
|
|---|
| 244 | (let ((ses-name1 (namestring pname1)))
|
|---|
| 245 | (unless exists1p (funcall *error-function*
|
|---|
| 246 | "~S does not exist." ses-name1))
|
|---|
| 247 | (copy-file-2 ses-name1 (namestring pname2) update clobber))))
|
|---|
| 248 |
|
|---|
| 249 | (defun copy-wildcard-files (pname1 wildp1 pname2 wildp2 update clobber
|
|---|
| 250 | &optional directory)
|
|---|
| 251 | (multiple-value-bind (dst-before dst-after)
|
|---|
| 252 | (before-wildcard-after (file-namestring pname2) wildp2)
|
|---|
| 253 | (dolist (match (resolve-wildcard pname1 wildp1 directory))
|
|---|
| 254 | (copy-file-2 (wild-match-file match)
|
|---|
| 255 | (namestring (concatenate 'simple-string
|
|---|
| 256 | (directory-namestring pname2)
|
|---|
| 257 | dst-before
|
|---|
| 258 | (wild-match-substitute match)
|
|---|
| 259 | dst-after))
|
|---|
| 260 | update clobber))))
|
|---|
| 261 |
|
|---|
| 262 | ;;; COPY-FILE-2 copies ses-name1 to ses-name2 depending on the values of update
|
|---|
| 263 | ;;; and clobber, with respect to the documentation of COPY-FILE. If ses-name2
|
|---|
| 264 | ;;; doesn't exist, then just copy it; otherwise, if update, then only copy it
|
|---|
| 265 | ;;; if the destination's write date precedes the source's, and if not clobber
|
|---|
| 266 | ;;; and not update, then ask the user before doing the copy.
|
|---|
| 267 | ;;;
|
|---|
| 268 | (defun copy-file-2 (ses-name1 ses-name2 update clobber)
|
|---|
| 269 | (let ((secs1 (get-write-date ses-name1)))
|
|---|
| 270 | (cond ((not (probe-file ses-name2))
|
|---|
| 271 | (do-the-copy ses-name1 ses-name2 secs1))
|
|---|
| 272 | (update
|
|---|
| 273 | (let ((secs2 (get-write-date ses-name2)))
|
|---|
| 274 | (cond (clobber
|
|---|
| 275 | (do-the-copy ses-name1 ses-name2 secs1))
|
|---|
| 276 | ((and (> secs2 secs1)
|
|---|
| 277 | (funcall *yesp-function*
|
|---|
| 278 | "~&~S ==> ~S~% ~
|
|---|
| 279 | ** Destination is newer than source. ~
|
|---|
| 280 | Overwrite it? "
|
|---|
| 281 | ses-name1 ses-name2))
|
|---|
| 282 | (do-the-copy ses-name1 ses-name2 secs1))
|
|---|
| 283 | ((< secs2 secs1)
|
|---|
| 284 | (do-the-copy ses-name1 ses-name2 secs1)))))
|
|---|
| 285 | ((not clobber)
|
|---|
| 286 | (when (funcall *yesp-function*
|
|---|
| 287 | "~&~S ==> ~S~% ** Destination already exists. ~
|
|---|
| 288 | Overwrite it? "
|
|---|
| 289 | ses-name1 ses-name2)
|
|---|
| 290 | (do-the-copy ses-name1 ses-name2 secs1)))
|
|---|
| 291 | (t (do-the-copy ses-name1 ses-name2 secs1)))))
|
|---|
| 292 |
|
|---|
| 293 | (defun do-the-copy (ses-name1 ses-name2 secs1)
|
|---|
| 294 | (let* ((fd (open-file ses-name1)))
|
|---|
| 295 | (unwind-protect
|
|---|
| 296 | (multiple-value-bind (data byte-count mode)
|
|---|
| 297 | (read-file fd ses-name1)
|
|---|
| 298 | (unwind-protect (write-file ses-name2 data byte-count mode)
|
|---|
| 299 | (system:deallocate-system-memory data byte-count)))
|
|---|
| 300 | (close-file fd)))
|
|---|
| 301 | (set-write-date ses-name2 secs1)
|
|---|
| 302 | (funcall *report-function* "~&~S ==>~% ~S~%" ses-name1 ses-name2))
|
|---|
| 303 |
|
|---|
| 304 | |
|---|
| 305 |
|
|---|
| 306 | ;;;; Rename-File
|
|---|
| 307 |
|
|---|
| 308 | (defun rename-file (spec1 spec2 &key (clobber *clobber-default*)
|
|---|
| 309 | (directory () directoryp))
|
|---|
| 310 | "Rename file spec1 to spec2. A single wildcard is acceptable, and spec2 may
|
|---|
| 311 | be a directory with the result spec being the merging of spec2 with spec1.
|
|---|
| 312 | If clobber is nil and spec2 exists, then the user will be asked to confirm
|
|---|
| 313 | the renaming. As with Unix mv, if you are renaming a directory, don't
|
|---|
| 314 | specify the trailing slash."
|
|---|
| 315 | (cond
|
|---|
| 316 | ((not directoryp)
|
|---|
| 317 | (let* ((ses-name1 (ext:unix-namestring spec1 t))
|
|---|
| 318 | (exists1p (unix:unix-file-kind ses-name1))
|
|---|
| 319 | (ses-name2 (ext:unix-namestring spec2 nil))
|
|---|
| 320 | (pname1 (pathname ses-name1))
|
|---|
| 321 | (pname2 (pathname ses-name2))
|
|---|
| 322 | (dirp2 (directoryp pname2))
|
|---|
| 323 | (wildp1 (wildcardp (file-namestring pname1)))
|
|---|
| 324 | (wildp2 (wildcardp (file-namestring pname2))))
|
|---|
| 325 | (if (and dirp2 wildp2)
|
|---|
| 326 | (funcall *error-function*
|
|---|
| 327 | "Cannot have wildcards in directory names -- ~S." pname2))
|
|---|
| 328 | (if (and wildp2 (not wildp1))
|
|---|
| 329 | (funcall *error-function*
|
|---|
| 330 | "Cannot handle destination having wildcards without ~
|
|---|
| 331 | source having wildcards."))
|
|---|
| 332 | (if (and wildp1 (not wildp2) (not dirp2))
|
|---|
| 333 | (funcall *error-function*
|
|---|
| 334 | "Cannot handle source with wildcards and destination ~
|
|---|
| 335 | without, unless destination is a directory."))
|
|---|
| 336 | (if dirp2
|
|---|
| 337 | (rename-file-1 pname1 wildp1 exists1p (merge-pathnames pname2
|
|---|
| 338 | pname1)
|
|---|
| 339 | wildp1 clobber)
|
|---|
| 340 | (rename-file-1 pname1 wildp1 exists1p pname2 wildp2 clobber))))
|
|---|
| 341 | (directory
|
|---|
| 342 | (when (pathname-directory spec1)
|
|---|
| 343 | (funcall *error-function*
|
|---|
| 344 | "Spec1 is just a pattern when supplying directory -- ~S."
|
|---|
| 345 | spec1))
|
|---|
| 346 |
|
|---|
| 347 | (let* ((pname2 (pathname (ext:unix-namestring spec2 nil)))
|
|---|
| 348 | (dirp2 (directoryp pname2))
|
|---|
| 349 | (wildp1 (wildcardp spec1))
|
|---|
| 350 | (wildp2 (wildcardp (file-namestring pname2))))
|
|---|
| 351 | (unless wildp1
|
|---|
| 352 | (funcall *error-function*
|
|---|
| 353 | "Pattern, ~S, does not contain a wildcard."
|
|---|
| 354 | spec1))
|
|---|
| 355 | (when (and (not wildp2) (not dirp2))
|
|---|
| 356 | (funcall *error-function*
|
|---|
| 357 | "Cannot handle source with wildcards and destination ~
|
|---|
| 358 | without, unless destination is a directory."))
|
|---|
| 359 | (rename-wildcard-files spec1 wildp1
|
|---|
| 360 | (if dirp2 (merge-pathnames pname2 spec1) pname2)
|
|---|
| 361 | (if dirp2 wildp1 wildp2)
|
|---|
| 362 | clobber directory))))
|
|---|
| 363 | (values))
|
|---|
| 364 |
|
|---|
| 365 | ;;; RENAME-FILE-1 takes pathnames which either both contain a single wildcard
|
|---|
| 366 | ;;; or none. Wildp1 and Wildp2 are either nil or indexes into the
|
|---|
| 367 | ;;; file-namestring of pname1 and pname2, respectively, indicating the position
|
|---|
| 368 | ;;; of the wildcard character. If there is no wildcard, then simply call
|
|---|
| 369 | ;;; RENAME-FILE-2; otherwise, resolve the wildcard and rename those matching files.
|
|---|
| 370 | ;;;
|
|---|
| 371 | (defun rename-file-1 (pname1 wildp1 exists1p pname2 wildp2 clobber)
|
|---|
| 372 | (if wildp1
|
|---|
| 373 | (rename-wildcard-files pname1 wildp1 pname2 wildp2 clobber)
|
|---|
| 374 | (let ((ses-name1 (namestring pname1)))
|
|---|
| 375 | (unless exists1p (funcall *error-function*
|
|---|
| 376 | "~S does not exist." ses-name1))
|
|---|
| 377 | (rename-file-2 ses-name1 (namestring pname2) clobber))))
|
|---|
| 378 |
|
|---|
| 379 | (defun rename-wildcard-files (pname1 wildp1 pname2 wildp2 clobber
|
|---|
| 380 | &optional directory)
|
|---|
| 381 | (multiple-value-bind (dst-before dst-after)
|
|---|
| 382 | (before-wildcard-after (file-namestring pname2) wildp2)
|
|---|
| 383 | (dolist (match (resolve-wildcard pname1 wildp1 directory))
|
|---|
| 384 | (rename-file-2 (wild-match-file match)
|
|---|
| 385 | (namestring (concatenate 'simple-string
|
|---|
| 386 | (directory-namestring pname2)
|
|---|
| 387 | dst-before
|
|---|
| 388 | (wild-match-substitute match)
|
|---|
| 389 | dst-after))
|
|---|
| 390 | clobber))))
|
|---|
| 391 |
|
|---|
| 392 | (defun rename-file-2 (ses-name1 ses-name2 clobber)
|
|---|
| 393 | (cond ((and (probe-file ses-name2) (not clobber))
|
|---|
| 394 | (when (funcall *yesp-function*
|
|---|
| 395 | "~&~S ==> ~S~% ** Destination already exists. ~
|
|---|
| 396 | Overwrite it? "
|
|---|
| 397 | ses-name1 ses-name2)
|
|---|
| 398 | (sub-rename-file ses-name1 ses-name2)
|
|---|
| 399 | (funcall *report-function* "~&~S ==>~% ~S~%" ses-name1 ses-name2)))
|
|---|
| 400 | (t (sub-rename-file ses-name1 ses-name2)
|
|---|
| 401 | (funcall *report-function* "~&~S ==>~% ~S~%" ses-name1 ses-name2))))
|
|---|
| 402 |
|
|---|
| 403 |
|
|---|
| 404 | |
|---|
| 405 |
|
|---|
| 406 | ;;;; Find-File
|
|---|
| 407 |
|
|---|
| 408 | (defun find-file (file-name &optional (directory "")
|
|---|
| 409 | (find-all-p nil find-all-suppliedp))
|
|---|
| 410 | "Find the file with file-namestring file recursively looking in directory.
|
|---|
| 411 | If find-all-p is non-nil, then do not stop searching upon finding the first
|
|---|
| 412 | occurance of file. File may contain a single wildcard, which causes
|
|---|
| 413 | find-all-p to default to t instead of nil."
|
|---|
| 414 | (let* ((file (coerce file-name 'simple-string))
|
|---|
| 415 | (wildp (wildcardp file))
|
|---|
| 416 | (find-all-p (if find-all-suppliedp find-all-p wildp)))
|
|---|
| 417 | (declare (simple-string file))
|
|---|
| 418 | (catch 'found-file
|
|---|
| 419 | (if wildp
|
|---|
| 420 | (multiple-value-bind (before after)
|
|---|
| 421 | (before-wildcard-after file wildp)
|
|---|
| 422 | (find-file-aux file directory find-all-p before after))
|
|---|
| 423 | (find-file-aux file directory find-all-p))))
|
|---|
| 424 | (values))
|
|---|
| 425 |
|
|---|
| 426 | (defun find-file-aux (the-file directory find-all-p &optional before after)
|
|---|
| 427 | (declare (simple-string the-file))
|
|---|
| 428 | (dolist (spec (directory directory))
|
|---|
| 429 | (let* ((spec-ses-name (namestring spec))
|
|---|
| 430 | (spec-file-name (file-namestring spec-ses-name)))
|
|---|
| 431 | (declare (simple-string spec-ses-name spec-file-name))
|
|---|
| 432 | (if (directoryp spec)
|
|---|
| 433 | (find-file-aux the-file spec find-all-p before after)
|
|---|
| 434 | (when (if before
|
|---|
| 435 | (find-match before after spec-file-name :no-cons)
|
|---|
| 436 | (string-equal the-file spec-file-name))
|
|---|
| 437 | (print spec-ses-name)
|
|---|
| 438 | (unless find-all-p (throw 'found-file t)))))))
|
|---|
| 439 |
|
|---|
| 440 |
|
|---|
| 441 | |
|---|
| 442 |
|
|---|
| 443 | ;;;; Delete-File
|
|---|
| 444 |
|
|---|
| 445 | ;;; DELETE-FILE
|
|---|
| 446 | ;;; If spec is a directory, but recursive is nil, just pass the directory
|
|---|
| 447 | ;;; down through, letting LISP:DELETE-FILE signal an error if the directory
|
|---|
| 448 | ;;; is not empty.
|
|---|
| 449 | ;;;
|
|---|
| 450 | (defun delete-file (spec &key (recursive *recursive-default*)
|
|---|
| 451 | (clobber *clobber-default*))
|
|---|
| 452 | "Delete spec asking confirmation on each file if clobber is nil. A single
|
|---|
| 453 | wildcard is acceptable. If recursive is non-nil, then a directory spec may
|
|---|
| 454 | be given to recursively delete the entirety of the directory and its
|
|---|
| 455 | subdirectory structure. An empty directory may be specified without
|
|---|
| 456 | recursive being non-nil. When specifying a directory, the trailing slash
|
|---|
| 457 | must be included."
|
|---|
| 458 | (let* ((ses-name (ext:unix-namestring spec t))
|
|---|
| 459 | (pname (pathname ses-name))
|
|---|
| 460 | (wildp (wildcardp (file-namestring pname)))
|
|---|
| 461 | (dirp (directoryp pname)))
|
|---|
| 462 | (if dirp
|
|---|
| 463 | (if recursive
|
|---|
| 464 | (recursive-delete pname ses-name clobber)
|
|---|
| 465 | (delete-file-2 ses-name clobber))
|
|---|
| 466 | (delete-file-1 pname ses-name wildp clobber)))
|
|---|
| 467 | (values))
|
|---|
| 468 |
|
|---|
| 469 | (defun recursive-delete (directory dir-ses-name clobber)
|
|---|
| 470 | (dolist (spec (directory (directory-namestring directory)))
|
|---|
| 471 | (let ((spec-ses-name (namestring spec)))
|
|---|
| 472 | (if (directoryp spec)
|
|---|
| 473 | (recursive-delete (pathname spec-ses-name) spec-ses-name clobber)
|
|---|
| 474 | (delete-file-2 spec-ses-name clobber))))
|
|---|
| 475 | (delete-file-2 dir-ses-name clobber))
|
|---|
| 476 |
|
|---|
| 477 | (defun delete-file-1 (pname ses-name wildp clobber)
|
|---|
| 478 | (if wildp
|
|---|
| 479 | (dolist (match (resolve-wildcard pname wildp))
|
|---|
| 480 | (delete-file-2 (wild-match-file match) clobber))
|
|---|
| 481 | (delete-file-2 ses-name clobber)))
|
|---|
| 482 |
|
|---|
| 483 | (defun delete-file-2 (ses-name clobber)
|
|---|
| 484 | (when (or clobber (funcall *yesp-function* "~&Delete ~S? " ses-name))
|
|---|
| 485 | (if (directoryp ses-name)
|
|---|
| 486 | (delete-directory ses-name)
|
|---|
| 487 | (lisp:delete-file ses-name))
|
|---|
| 488 | (funcall *report-function* "~&~A~%" ses-name)))
|
|---|
| 489 |
|
|---|
| 490 |
|
|---|
| 491 | |
|---|
| 492 |
|
|---|
| 493 | ;;;; Wildcard resolution
|
|---|
| 494 |
|
|---|
| 495 | (defun pathnames-from-pattern (pattern files)
|
|---|
| 496 | "Return a list of pathnames from files whose file-namestrings match
|
|---|
| 497 | pattern. Pattern must be a non-empty string and contains only one
|
|---|
| 498 | asterisk. Files contains no directories."
|
|---|
| 499 | (declare (simple-string pattern))
|
|---|
| 500 | (when (string= pattern "")
|
|---|
| 501 | (funcall *error-function* "Must be a non-empty pattern."))
|
|---|
| 502 | (unless (= (count wildcard-char pattern :test #'char=) 1)
|
|---|
| 503 | (funcall *error-function* "Pattern must contain one asterisk."))
|
|---|
| 504 | (multiple-value-bind (before after)
|
|---|
| 505 | (before-wildcard-after pattern (wildcardp pattern))
|
|---|
| 506 | (let ((result nil))
|
|---|
| 507 | (dolist (f files result)
|
|---|
| 508 | (let* ((ses-namestring (namestring f))
|
|---|
| 509 | (f-namestring (file-namestring ses-namestring))
|
|---|
| 510 | (match (find-match before after f-namestring)))
|
|---|
| 511 | (when match (push f result)))))))
|
|---|
| 512 |
|
|---|
| 513 |
|
|---|
| 514 | ;;; RESOLVE-WILDCARD takes a pathname with a wildcard and the position of the
|
|---|
| 515 | ;;; wildcard character in the file-namestring and returns a list of wild-match
|
|---|
| 516 | ;;; objects. When directory is supplied, pname is just a pattern, or a
|
|---|
| 517 | ;;; file-namestring. It is an error for directory to be anything other than
|
|---|
| 518 | ;;; absolute pathnames in the same directory. Each wild-match object contains
|
|---|
| 519 | ;;; the Sesame namestring of a file in the same directory as pname, or
|
|---|
| 520 | ;;; directory, and a simple-string representing what the wildcard matched.
|
|---|
| 521 | ;;;
|
|---|
| 522 | (defun resolve-wildcard (pname wild-pos &optional directory)
|
|---|
| 523 | (multiple-value-bind (before after)
|
|---|
| 524 | (before-wildcard-after (if directory
|
|---|
| 525 | pname
|
|---|
| 526 | (file-namestring pname))
|
|---|
| 527 | wild-pos)
|
|---|
| 528 | (let (result)
|
|---|
| 529 | (dolist (f (or directory (directory (directory-namestring pname)))
|
|---|
| 530 | (nreverse result))
|
|---|
| 531 | (unless (directoryp f)
|
|---|
| 532 | (let* ((ses-namestring (namestring f))
|
|---|
| 533 | (f-namestring (file-namestring ses-namestring))
|
|---|
| 534 | (match (find-match before after f-namestring)))
|
|---|
| 535 | (if match
|
|---|
| 536 | (push (make-wild-match ses-namestring match) result))))))))
|
|---|
| 537 |
|
|---|
| 538 | ;;; FIND-MATCH takes a "before wildcard" and "after wildcard" string and a
|
|---|
| 539 | ;;; file-namestring. If before and after match a substring of file-namestring
|
|---|
| 540 | ;;; and are respectively left bound and right bound, then anything left in
|
|---|
| 541 | ;;; between is the match returned. If no match is found, nil is returned.
|
|---|
| 542 | ;;; NOTE: if version numbers ever really exist, then this code will have to be
|
|---|
| 543 | ;;; changed since the file-namestring of a pathname contains the version number.
|
|---|
| 544 | ;;;
|
|---|
| 545 | (defun find-match (before after file-namestring &optional no-cons)
|
|---|
| 546 | (declare (simple-string before after file-namestring))
|
|---|
| 547 | (let ((before-len (length before))
|
|---|
| 548 | (after-len (length after))
|
|---|
| 549 | (name-len (length file-namestring)))
|
|---|
| 550 | (if (>= name-len (+ before-len after-len))
|
|---|
| 551 | (let* ((start (if (string= before file-namestring
|
|---|
| 552 | :end1 before-len :end2 before-len)
|
|---|
| 553 | before-len))
|
|---|
| 554 | (end (- name-len after-len))
|
|---|
| 555 | (matchp (and start
|
|---|
| 556 | (string= after file-namestring :end1 after-len
|
|---|
| 557 | :start2 end :end2 name-len))))
|
|---|
| 558 | (if matchp
|
|---|
| 559 | (if no-cons
|
|---|
| 560 | t
|
|---|
| 561 | (subseq file-namestring start end)))))))
|
|---|
| 562 |
|
|---|
| 563 | (defun before-wildcard-after (file-namestring wild-pos)
|
|---|
| 564 | (declare (simple-string file-namestring))
|
|---|
| 565 | (values (subseq file-namestring 0 wild-pos)
|
|---|
| 566 | (subseq file-namestring (1+ wild-pos) (length file-namestring))))
|
|---|
| 567 |
|
|---|
| 568 |
|
|---|
| 569 | |
|---|
| 570 |
|
|---|
| 571 | ;;;; Miscellaneous Utilities (e.g., MAKEDIR).
|
|---|
| 572 |
|
|---|
| 573 | (defun make-directory (name)
|
|---|
| 574 | "Creates directory name. If name exists, then an error is signaled."
|
|---|
| 575 | (let ((ses-name (ext:unix-namestring name nil)))
|
|---|
| 576 | (when (unix:unix-file-kind ses-name)
|
|---|
| 577 | (funcall *error-function* "Name already exists -- ~S" ses-name))
|
|---|
| 578 | (enter-directory ses-name))
|
|---|
| 579 | t)
|
|---|
| 580 |
|
|---|
| 581 |
|
|---|
| 582 | |
|---|
| 583 |
|
|---|
| 584 | ;;;; Mach Operations
|
|---|
| 585 |
|
|---|
| 586 | (defun open-file (ses-name)
|
|---|
| 587 | (multiple-value-bind (fd err)
|
|---|
| 588 | (unix:unix-open ses-name unix:o_rdonly 0)
|
|---|
| 589 | (unless fd
|
|---|
| 590 | (funcall *error-function* "Opening ~S failed: ~A." ses-name err))
|
|---|
| 591 | fd))
|
|---|
| 592 |
|
|---|
| 593 | (defun close-file (fd)
|
|---|
| 594 | (unix:unix-close fd))
|
|---|
| 595 |
|
|---|
| 596 | (defun read-file (fd ses-name)
|
|---|
| 597 | (multiple-value-bind (winp dev-or-err ino mode nlink uid gid rdev size)
|
|---|
| 598 | (unix:unix-fstat fd)
|
|---|
| 599 | (declare (ignore ino nlink uid gid rdev))
|
|---|
| 600 | (unless winp (funcall *error-function*
|
|---|
| 601 | "Opening ~S failed: ~A." ses-name dev-or-err))
|
|---|
| 602 | (let ((storage (system:allocate-system-memory size)))
|
|---|
| 603 | (multiple-value-bind (read-bytes err)
|
|---|
| 604 | (unix:unix-read fd storage size)
|
|---|
| 605 | (when (or (null read-bytes) (not (= size read-bytes)))
|
|---|
| 606 | (system:deallocate-system-memory storage size)
|
|---|
| 607 | (funcall *error-function*
|
|---|
| 608 | "Reading file ~S failed: ~A." ses-name err)))
|
|---|
| 609 | (values storage size mode))))
|
|---|
| 610 |
|
|---|
| 611 | (defun write-file (ses-name data byte-count mode)
|
|---|
| 612 | (multiple-value-bind (fd err) (unix:unix-creat ses-name #o644)
|
|---|
| 613 | (unless fd
|
|---|
| 614 | (funcall *error-function* "Couldn't create file ~S: ~A"
|
|---|
| 615 | ses-name (unix:get-unix-error-msg err)))
|
|---|
| 616 | (multiple-value-bind (winp err) (unix:unix-write fd data 0 byte-count)
|
|---|
| 617 | (unless winp
|
|---|
| 618 | (funcall *error-function* "Writing file ~S failed: ~A"
|
|---|
| 619 | ses-name
|
|---|
| 620 | (unix:get-unix-error-msg err))))
|
|---|
| 621 | (unix:unix-fchmod fd (logand mode #o777))
|
|---|
| 622 | (unix:unix-close fd)))
|
|---|
| 623 |
|
|---|
| 624 | (defun set-write-date (ses-name secs)
|
|---|
| 625 | (multiple-value-bind (winp dev-or-err ino mode nlink uid gid rdev size atime)
|
|---|
| 626 | (unix:unix-stat ses-name)
|
|---|
| 627 | (declare (ignore ino mode nlink uid gid rdev size))
|
|---|
| 628 | (unless winp
|
|---|
| 629 | (funcall *error-function* "Couldn't stat file ~S failed: ~A."
|
|---|
| 630 | ses-name dev-or-err))
|
|---|
| 631 | (multiple-value-bind (winp err)
|
|---|
| 632 | (unix:unix-utimes ses-name atime 0 secs 0)
|
|---|
| 633 | (unless winp
|
|---|
| 634 | (funcall *error-function* "Couldn't set write date of file ~S: ~A"
|
|---|
| 635 | ses-name (unix:get-unix-error-msg err))))))
|
|---|
| 636 |
|
|---|
| 637 | (defun get-write-date (ses-name)
|
|---|
| 638 | (multiple-value-bind (winp dev-or-err ino mode nlink uid gid rdev size
|
|---|
| 639 | atime mtime)
|
|---|
| 640 | (unix:unix-stat ses-name)
|
|---|
| 641 | (declare (ignore ino mode nlink uid gid rdev size atime))
|
|---|
| 642 | (unless winp (funcall *error-function* "Couldn't stat file ~S failed: ~A."
|
|---|
| 643 | ses-name dev-or-err))
|
|---|
| 644 | mtime))
|
|---|
| 645 |
|
|---|
| 646 | ;;; SUB-RENAME-FILE must exist because we can't use Common Lisp's RENAME-FILE.
|
|---|
| 647 | ;;; This is because it merges the new name with the old name to pick up
|
|---|
| 648 | ;;; defaults, and this conflicts with Unix-oid names. For example, renaming
|
|---|
| 649 | ;;; "foo.bar" to ".baz" causes a result of "foo.baz"! This routine doesn't
|
|---|
| 650 | ;;; have this problem.
|
|---|
| 651 | ;;;
|
|---|
| 652 | (defun sub-rename-file (ses-name1 ses-name2)
|
|---|
| 653 | (multiple-value-bind (res err) (unix:unix-rename ses-name1 ses-name2)
|
|---|
| 654 | (unless res
|
|---|
| 655 | (funcall *error-function* "Failed to rename ~A to ~A: ~A."
|
|---|
| 656 | ses-name1 ses-name2 (unix:get-unix-error-msg err)))))
|
|---|
| 657 |
|
|---|
| 658 | (defun directory-existsp (ses-name)
|
|---|
| 659 | (eq (unix:unix-file-kind ses-name) :directory))
|
|---|
| 660 |
|
|---|
| 661 | (defun enter-directory (ses-name)
|
|---|
| 662 | (declare (simple-string ses-name))
|
|---|
| 663 | (let* ((length-1 (1- (length ses-name)))
|
|---|
| 664 | (name (if (= (position #\/ ses-name :test #'char= :from-end t)
|
|---|
| 665 | length-1)
|
|---|
| 666 | (subseq ses-name 0 (1- (length ses-name)))
|
|---|
| 667 | ses-name)))
|
|---|
| 668 | (multiple-value-bind (winp err) (unix:unix-mkdir name #o755)
|
|---|
| 669 | (unless winp
|
|---|
| 670 | (funcall *error-function* "Couldn't make directory ~S: ~A"
|
|---|
| 671 | name
|
|---|
| 672 | (unix:get-unix-error-msg err))))))
|
|---|
| 673 |
|
|---|
| 674 | (defun delete-directory (ses-name)
|
|---|
| 675 | (declare (simple-string ses-name))
|
|---|
| 676 | (multiple-value-bind (winp err)
|
|---|
| 677 | (unix:unix-rmdir (subseq ses-name 0
|
|---|
| 678 | (1- (length ses-name))))
|
|---|
| 679 | (unless winp
|
|---|
| 680 | (funcall *error-function* "Couldn't delete directory ~S: ~A"
|
|---|
| 681 | ses-name
|
|---|
| 682 | (unix:get-unix-error-msg err)))))
|
|---|
| 683 |
|
|---|
| 684 |
|
|---|
| 685 | |
|---|
| 686 |
|
|---|
| 687 | ;;;; Misc. Utility Utilities
|
|---|
| 688 |
|
|---|
| 689 | ;;; NSEPARATE-FILES destructively returns a list of file specs from listing.
|
|---|
| 690 | (defun nseparate-files (listing)
|
|---|
| 691 | (do (files hold)
|
|---|
| 692 | ((null listing) files)
|
|---|
| 693 | (setf hold (cdr listing))
|
|---|
| 694 | (unless (directoryp (car listing))
|
|---|
| 695 | (setf (cdr listing) files)
|
|---|
| 696 | (setf files listing))
|
|---|
| 697 | (setf listing hold)))
|
|---|
| 698 |
|
|---|
| 699 |
|
|---|
| 700 | (defun directoryp (p)
|
|---|
| 701 | (not (or (pathname-name p) (pathname-type p))))
|
|---|