Done using
    find third_party/lisp/{sclf,mime4cl,npg} \
      -name '*.lisp' -or -name '*.asd' \
      -exec bash -c 'expand -i -t 8 "$0" | sponge "$0"' {} \;
Change-Id: If84afac9c1d5cbc74e137a5aa0ae61472f0f1e90
Reviewed-on: https://cl.tvl.fyi/c/depot/+/5066
Tested-by: BuildkiteCI
Reviewed-by: sterni <sternenseemann@systemli.org>
		
	
			
		
			
				
	
	
		
			404 lines
		
	
	
	
		
			16 KiB
		
	
	
	
		
			Common Lisp
		
	
	
	
	
	
			
		
		
	
	
			404 lines
		
	
	
	
		
			16 KiB
		
	
	
	
		
			Common Lisp
		
	
	
	
	
	
| ;;;  directory.lisp --- filesystem directory access
 | |
| 
 | |
| ;;;  Copyright (C) 2006, 2007, 2008, 2009, 2010 by Walter C. Pelissero
 | |
| ;;;  Copyright (C) 2021 by the TVL Authors
 | |
| 
 | |
| ;;;  Author: Walter C. Pelissero <walter@pelissero.de>
 | |
| ;;;  Project: sclf
 | |
| 
 | |
| #+cmu (ext:file-comment "$Module: directory.lisp $")
 | |
| 
 | |
| ;;; This library is free software; you can redistribute it and/or
 | |
| ;;; modify it under the terms of the GNU Lesser General Public License
 | |
| ;;; as published by the Free Software Foundation; either version 2.1
 | |
| ;;; of the License, or (at your option) any later version.
 | |
| ;;; This library is distributed in the hope that it will be useful,
 | |
| ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
| ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 | |
| ;;; Lesser General Public License for more details.
 | |
| ;;; You should have received a copy of the GNU Lesser General Public
 | |
| ;;; License along with this library; if not, write to the Free
 | |
| ;;; Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
 | |
| ;;; 02111-1307 USA
 | |
| 
 | |
| 
 | |
| (cl:in-package :sclf)
 | |
| 
 | |
| (defun pathname-as-directory (pathname)
 | |
|   "Converts PATHNAME to directory form and return it."
 | |
|   (setf pathname (pathname pathname))
 | |
|   (if (pathname-name pathname)
 | |
|       (make-pathname :directory (append (or (pathname-directory pathname)
 | |
|                                             '(:relative))
 | |
|                                         (list (file-namestring pathname)))
 | |
|                      :name nil
 | |
|                      :type nil
 | |
|                      :defaults pathname)
 | |
|       pathname))
 | |
| 
 | |
| (defun d+ (path &rest rest)
 | |
|   "Concatenate directory pathname parts and return a pathname."
 | |
|   (make-pathname :defaults path
 | |
|                  :directory (append (pathname-directory path) rest)))
 | |
| 
 | |
| (defun delete-directory (pathname)
 | |
|   "Remove directory PATHNAME.  Return PATHNAME."
 | |
|   #+cmu (multiple-value-bind (done errno)
 | |
|              (unix:unix-rmdir (namestring pathname))
 | |
|            (unless done
 | |
|              (error "Unable to delete directory ~A (errno=~A)"
 | |
|                     pathname errno)))
 | |
|   #+sbcl (sb-posix:rmdir pathname)
 | |
|   #+lispworks (lw:delete-directory pathname)
 | |
|   #-(or cmu sbcl)
 | |
|   (error "DELETE-DIRECTORY not implemented for you lisp system.")
 | |
|   pathname)
 | |
| 
 | |
| (defun list-directory (pathname &key truenamep)
 | |
|   "List content of directory PATHNAME.  If TRUENAMEP is true don't try
 | |
| to follow symbolic links."
 | |
|   #-(or sbcl cmu) (declare (ignore truenamep))
 | |
|   (let (#+cmu (lisp::*ignore-wildcards* t))
 | |
|     (directory (make-pathname :defaults (pathname-as-directory pathname)
 | |
|                               :name :wild
 | |
|                               :type :wild
 | |
|                               :version :wild)
 | |
|                #+cmu :truenamep #+cmu truenamep
 | |
|                #+sbcl :resolve-symlinks #+sbcl truenamep)))
 | |
| 
 | |
| (defun traverse-directory-tree (root-pathname proc &key truenamep test depth-first)
 | |
|   "Call PROC on all pathnames under ROOT-PATHNAME, both files and
 | |
| directories.  Unless TRUENAMEP is true, this function doesn't try
 | |
| to lookup the truename of files, as finding the truename may be a
 | |
| superfluous and noxious activity expecially when you expect
 | |
| broken symbolic links in your filesystem."
 | |
|   (check-type root-pathname pathname)
 | |
|   (check-type proc (or function symbol))
 | |
|   (check-type test (or function symbol null))
 | |
|   (labels ((ls (dir)
 | |
|              (declare (type pathname dir))
 | |
|              (list-directory dir :truenamep truenamep))
 | |
|            (traverse? (file)
 | |
|              (declare (type pathname file))
 | |
|              (and (not (pathname-name file))
 | |
|                   (or truenamep
 | |
|                       (not (symbolic-link-p file)))
 | |
|                   (or (not test)
 | |
|                       (funcall test file))))
 | |
|            (traverse-pre-order (dir)
 | |
|              (declare (type pathname dir))
 | |
|              (loop
 | |
|                 for file in (ls dir)
 | |
|                 do (funcall proc file)
 | |
|                 when (traverse? file)
 | |
|                 do (traverse-pre-order file)))
 | |
|            (traverse-post-order (dir)
 | |
|              (declare (type pathname dir))
 | |
|              (loop
 | |
|                 for file in (ls dir)
 | |
|                 when (traverse? file)
 | |
|                 do (traverse-post-order file)
 | |
|                 do (funcall proc file))))
 | |
|     (if depth-first
 | |
|         (traverse-post-order root-pathname)
 | |
|         (traverse-pre-order root-pathname))
 | |
|     (values)))
 | |
| 
 | |
| (defmacro do-directory-tree ((file root-pathname &key truenamep test depth-first) &body body)
 | |
|   "Call TRAVERSE-DIRECTORY-TREE with BODY es procedure."
 | |
|   `(traverse-directory-tree ,root-pathname
 | |
|                             #'(lambda (,file)
 | |
|                                 ,@body)
 | |
|                             :truenamep ,truenamep
 | |
|                             :test ,test
 | |
|                             :depth-first ,depth-first))
 | |
| 
 | |
| (defun empty-directory-p (pathname)
 | |
|   (and (directory-p pathname)
 | |
|        (endp (list-directory pathname))))
 | |
| 
 | |
| (defun remove-empty-directories (root)
 | |
|   (do-directory-tree (pathname root :depth-first t)
 | |
|     (when (empty-directory-p pathname)
 | |
|       (delete-directory pathname))))
 | |
| 
 | |
| (defun map-directory-tree (pathname function)
 | |
|   "Apply FUNCTION to every file in a directory tree starting from
 | |
| PATHNAME.  Return the list of results."
 | |
|   (be return-list '()
 | |
|     (do-directory-tree (directory-entry pathname)
 | |
|       (push (funcall function directory-entry) return-list))
 | |
|     (nreverse return-list)))
 | |
| 
 | |
| (defun find-files (root-pathname matcher-function &key truenamep)
 | |
|   "In the directory tree rooted at ROOT-PATHNAME, find files that
 | |
| when the pathname is applied to MATCHER-FUNCTION will return
 | |
| true.  Return the list of files found.  Unless TRUENAMEP is true
 | |
| this function doesn't try to lookup the truename of
 | |
| files. Finding the truename may be a superfluous and noxious
 | |
| activity expecially when you expect broken symbolic links in your
 | |
| filesystem.  (This may not apply to your particular lisp
 | |
| system.)"
 | |
|   (be files '()
 | |
|     (do-directory-tree (file root-pathname :truenamep truenamep)
 | |
|       (when (funcall matcher-function file)
 | |
|         (push file files)))
 | |
|     (nreverse files)))
 | |
| 
 | |
| (defun delete-directory-tree (pathname)
 | |
|   "Recursively delete PATHNAME and all the directory structure below
 | |
| it.
 | |
| 
 | |
| WARNING: depending on the way the DIRECTORY function is implemented on
 | |
| your Lisp system this function may follow Unix symbolic links and thus
 | |
| delete files outside the PATHNAME hierarchy.  Check this before using
 | |
| this function in your programs."
 | |
|   (if (pathname-name pathname)
 | |
|       (delete-file pathname)
 | |
|       (progn
 | |
|         (dolist (file (list-directory pathname))
 | |
|           (delete-directory-tree file))
 | |
|         (delete-directory pathname))))
 | |
| 
 | |
| (defun make-directory (pathname &optional (mode #o777))
 | |
|   "Create a new directory in the filesystem.  Permissions MODE
 | |
| will be assigned to it.  Return PATHNAME."
 | |
|   #+cmu (multiple-value-bind (done errno)
 | |
|             (unix:unix-mkdir (native-namestring pathname) mode)
 | |
|           (unless done
 | |
|             (error "Unable to create directory ~A (errno=~A)." pathname errno)))
 | |
|   #+sbcl (sb-posix:mkdir pathname mode)
 | |
|   #-(or cmu sbcl)
 | |
|   (error "MAKE-DIRECTORY is not implemented for this Lisp system.")
 | |
|   pathname)
 | |
| 
 | |
| ;; At least on SBCL/CMUCL + Unix + NFS this function is faster than
 | |
| ;; ENSURE-DIRECTORIES-EXIST, because it doesn't check all the pathname
 | |
| ;; components starting from the root; it proceeds from the leaf and
 | |
| ;; crawls the directory tree upward only if necessary."
 | |
| (defun ensure-directory (pathname &key verbose (mode #o777))
 | |
|   "Just like ENSURE-DIRECTORIES-EXIST but, in some situations,
 | |
| it's faster."
 | |
|   (labels ((ensure (path)
 | |
|              (unless (probe-file path)
 | |
|                (be* tail (last (pathname-directory path) 2)
 | |
|                     last (cdr tail)
 | |
|                  (setf (cdr tail) nil)
 | |
|                  (unwind-protect
 | |
|                       (ensure path)
 | |
|                    (setf (cdr tail) last))
 | |
|                  (make-directory path mode)
 | |
|                  (when verbose
 | |
|                    (format t "Created ~S~%" path))))))
 | |
|     (ensure (make-pathname :defaults pathname
 | |
|                            :name nil :type nil
 | |
|                            :version nil))))
 | |
| 
 | |
| (defun make-temp-directory (&optional (default-pathname *tmp-file-defaults*) (mode #o777))
 | |
|   "Create a new directory and return its pathname.
 | |
| If DEFAULT-PATHNAME is specified and not NIL it's used as
 | |
| defaults to produce the pathname of the directory.  Return the
 | |
| pathname of the temporary directory."
 | |
|   (loop
 | |
|      for name = (pathname-as-directory (temp-file-name default-pathname))
 | |
|      when (ignore-errors (make-directory name mode))
 | |
|      return name))
 | |
| 
 | |
| (defmacro with-temp-directory ((path &rest make-temp-directory-args) &body body)
 | |
|   "Execute BODY with PATH bound to the pathname of a new unique
 | |
| temporary directory.  On exit of BODY the directory tree starting from
 | |
| PATH will be automatically removed from the filesystem.  Return what
 | |
| BODY returns.  BODY is _not_ executed within the PATH directory; the
 | |
| working directory is never changed."
 | |
|   `(be ,path (make-temp-directory ,@make-temp-directory-args)
 | |
|      (unwind-protect
 | |
|           (progn ,@body)
 | |
|        (delete-directory-tree ,path))))
 | |
| 
 | |
| (defun current-directory ()
 | |
|   "Return the pathname of the current directory."
 | |
|   (truename (make-pathname :directory '(:relative))))
 | |
| 
 | |
| (defun ensure-home-translations ()
 | |
|   "Ensure that the logical pathname translations for the host \"home\"
 | |
| are defined."
 | |
|   ;; CMUCL already defines a HOME translation of its own and gets
 | |
|   ;; angry if we try to redefine it
 | |
|   #-cmu
 | |
|   (be home (user-homedir-pathname)
 | |
|     ;; we should discard and replace whatever has been defined in any
 | |
|     ;; rc file during compilation
 | |
|     (setf (logical-pathname-translations "home")
 | |
|           (list
 | |
|            (list "**;*.*.*"
 | |
|                  (make-pathname :defaults home
 | |
|                                 :directory (append (pathname-directory home)
 | |
|                                                    '(:wild-inferiors))
 | |
|                                 :name :wild
 | |
|                                 :type :wild))))))
 | |
| 
 | |
| ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 | |
| 
 | |
| (defun parse-native-namestring (string &optional host (defaults *default-pathname-defaults*)
 | |
|                                 &key (start 0) end junk-allowed)
 | |
|   #+sbcl (sb-ext:parse-native-namestring string host defaults
 | |
|                                          :start start
 | |
|                                          :end end
 | |
|                                          :junk-allowed junk-allowed)
 | |
|   #-sbcl (let (#+cmu(lisp::*ignore-wildcards* t))
 | |
|            (parse-namestring string host defaults
 | |
|                              :start start
 | |
|                              :end end
 | |
|                              :junk-allowed junk-allowed)))
 | |
| 
 | |
| (defun native-namestring (pathname)
 | |
|   #+sbcl (sb-ext:native-namestring pathname)
 | |
|   #-sbcl (let (#+cmu (lisp::*ignore-wildcards* t))
 | |
|            (namestring pathname)))
 | |
| 
 | |
| (defun native-file-namestring (pathname)
 | |
|   #+sbcl (sb-ext:native-namestring
 | |
|           (make-pathname :name (pathname-name pathname)
 | |
|                          :type (pathname-type pathname)))
 | |
|   #+cmu (be lisp::*ignore-wildcards* t
 | |
|           (file-namestring pathname)))
 | |
| 
 | |
| (defun native-pathname (thing)
 | |
|   #+sbcl (sb-ext:native-pathname thing)
 | |
|   #+cmu (be lisp::*ignore-wildcards* t
 | |
|           (pathname thing)))
 | |
| 
 | |
| ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 | |
| 
 | |
| (defun bits-set-p (x bits)
 | |
|   (= (logand x bits)
 | |
|      bits))
 | |
| 
 | |
| (defun directory-p (pathname)
 | |
|   "Return true if PATHNAME names a directory on the filesystem."
 | |
|   #-clisp (awhen (unix-stat (native-namestring pathname))
 | |
|             (bits-set-p (stat-mode it)
 | |
|                         #+sbcl sb-posix:s-ifdir
 | |
|                         #+cmu unix:s-ifdir))
 | |
|   #+clisp (ext:probe-directory (pathname-as-directory pathname)))
 | |
| 
 | |
| (defun regular-file-p (pathname)
 | |
|   "Return true if PATHNAME names a regular file on the filesystem."
 | |
|   #-(or sbcl cmu) (error "don't know how to check whether a file might be a regular file")
 | |
|   (awhen (unix-stat (native-namestring pathname))
 | |
|     (bits-set-p (stat-mode it)
 | |
|                 #+sbcl sb-posix:s-ifreg
 | |
|                 #+cmu unix:s-ifreg)))
 | |
| 
 | |
| (defun file-readable-p (pathname)
 | |
|   #+sbcl (sb-unix:unix-access (native-namestring pathname) sb-unix:r_ok)
 | |
|   #+cmu (unix:unix-access (native-namestring pathname) unix:r_ok)
 | |
|   #-(or sbcl cmu) (error "don't know how to check whether a file might be readable"))
 | |
| 
 | |
| (defun file-writable-p (pathname)
 | |
|   #+sbcl (sb-unix:unix-access (native-namestring pathname) sb-unix:w_ok)
 | |
|   #+cmu (unix:unix-access (native-namestring pathname) unix:w_ok)
 | |
|   #-(or sbcl cmu) (error "don't know how to check whether a file might be writable"))
 | |
| 
 | |
| (defun file-executable-p (pathname)
 | |
|   #+sbcl (sb-unix:unix-access (native-namestring pathname) sb-unix:x_ok)
 | |
|   #+cmu (unix:unix-access (native-namestring pathname) unix:x_ok)
 | |
|   #-(or sbcl cmu) (error "don't know how to check whether a file might be executable"))
 | |
| 
 | |
| ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 | |
| 
 | |
| (defstruct (unix-file-stat (:conc-name stat-))
 | |
|   device
 | |
|   inode
 | |
|   links
 | |
|   atime
 | |
|   mtime
 | |
|   ctime
 | |
|   size
 | |
|   blksize
 | |
|   blocks
 | |
|   uid
 | |
|   gid
 | |
|   mode)
 | |
| 
 | |
| (defun unix-stat (pathname)
 | |
|   ;; this could be different depending on the unix systems
 | |
|   (multiple-value-bind (ok? device inode mode links uid gid rdev
 | |
|                             size atime mtime ctime
 | |
|                             blksize blocks)
 | |
|       (#+cmu unix:unix-lstat
 | |
|        #+sbcl sb-unix:unix-lstat
 | |
|        (if (stringp pathname)
 | |
|            pathname
 | |
|            (native-namestring pathname)))
 | |
|     (declare (ignore rdev))
 | |
|     (when ok?
 | |
|       (make-unix-file-stat :device device
 | |
|                            :inode inode
 | |
|                            :links links
 | |
|                            :atime atime
 | |
|                            :mtime mtime
 | |
|                            :ctime ctime
 | |
|                            :size size
 | |
|                            :blksize blksize
 | |
|                            :blocks blocks
 | |
|                            :uid uid
 | |
|                            :gid gid
 | |
|                            :mode mode))))
 | |
| 
 | |
| (defun stat-modification-time (stat)
 | |
|   "Return the modification time of the STAT structure as Lisp
 | |
| Universal Time, which is not the same as the Unix time."
 | |
|   (unix->universal-time (stat-mtime stat)))
 | |
| 
 | |
| (defun stat-creation-time (stat)
 | |
|   "Return the creation time of the STAT structure as Lisp
 | |
| Universal Time, which is not the same as the Unix time."
 | |
|   (unix->universal-time (stat-ctime stat)))
 | |
| 
 | |
| (defun file-modification-time (file)
 | |
|   "Return the modification time of FILE as Lisp Universal Time, which
 | |
| is not the same as the Unix time."
 | |
|   (awhen (unix-stat file)
 | |
|     (stat-modification-time it)))
 | |
| 
 | |
| (defun file-creation-time (file)
 | |
|   "Return the creation time of FILE as Lisp Universal Time, which
 | |
| is not the same as the Unix time."
 | |
|   (awhen (unix-stat file)
 | |
|     (stat-creation-time it)))
 | |
| 
 | |
| (defun read-symbolic-link (symlink)
 | |
|   "Return the pathname the SYMLINK points to.  That is, it's
 | |
| contents."
 | |
|   #+sbcl (sb-posix:readlink (native-namestring symlink))
 | |
|   #+cmu (unix:unix-readlink (native-namestring symlink)))
 | |
| 
 | |
| ;; FILE-LENGTH is a bit idiosyncratic in this respect.  Besides, Unix
 | |
| ;; allows to get to know the file size without being able to open a
 | |
| ;; file; just ask politely.
 | |
| (defun file-size (pathname)
 | |
|   (stat-size (unix-stat pathname)))
 | |
| 
 | |
| (defun symbolic-link-p (pathname)
 | |
|   #-(or sbcl cmu) (error "don't know hot to test for symbolic links.")
 | |
|   (aand (unix-stat pathname)
 | |
|         (bits-set-p (stat-mode it)
 | |
|                     #+sbcl sb-posix:s-iflnk
 | |
|                     #+cmu unix:s-iflnk)))
 | |
| 
 | |
| (defun broken-link-p (pathname)
 | |
|  (when (symbolic-link-p pathname)
 | |
|    #+cmu (not (ignore-errors (truename pathname)))
 | |
|    ;; On a broken symlink SBCL returns the link path without resolving
 | |
|    ;; the link itself.  De gustibus non est disputandum.
 | |
|    #+sbcl (equalp pathname (probe-file pathname))))
 | |
| 
 | |
| (defun move-file (old new)
 | |
|   "Just like RENAME-FILE, but doesn't carry on to NEW file the type of
 | |
| OLD file, if NEW doesn't specify one.  It does what most people would
 | |
| expect from a rename function, which RENAME-FILE doesn't do.
 | |
| So (MOVE-FILE \"foo.bar\" \"foo\") does rename foo.bar to foo, losing
 | |
| the \"bar\" type; RENAME-FILE wouldn't allow you that."
 | |
|   #+sbcl (sb-posix:rename (native-namestring old) (native-namestring new))
 | |
|   #+cmu (unix:unix-rename (native-namestring old) (native-namestring new)))
 |