Revision: miles@gnu.org--gnu-2005/emacs--unicode--0--patch-78
[bpt/emacs.git] / lisp / find-lisp.el
1 ;;; find-lisp.el --- emulation of find in Emacs Lisp
2
3 ;; Author: Peter Breton
4 ;; Created: Fri Mar 26 1999
5 ;; Keywords: unix
6 ;; Time-stamp: <2001-07-16 12:42:35 pavel>
7
8 ;; Copyright (C) 1999, 2000, 2002, 2003, 2004,
9 ;; 2005 Free Software Foundation, Inc.
10
11 ;; This file is part of GNU Emacs.
12
13 ;; GNU Emacs is free software; you can redistribute it and/or modify
14 ;; it under the terms of the GNU General Public License as published by
15 ;; the Free Software Foundation; either version 2, or (at your option)
16 ;; any later version.
17
18 ;; GNU Emacs is distributed in the hope that it will be useful,
19 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
20 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 ;; GNU General Public License for more details.
22
23 ;; You should have received a copy of the GNU General Public License
24 ;; along with GNU Emacs; see the file COPYING. If not, write to the
25 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
26 ;; Boston, MA 02110-1301, USA.
27
28 ;;; Commentary:
29 ;;
30 ;; This is a very generalized form of find; it basically implements a
31 ;; recursive directory descent. The conditions which bound the search
32 ;; are expressed as predicates, and I have not addressed the question
33 ;; of how to wrap up the common chores that find does in a simpler
34 ;; format than writing code for all the various predicates.
35 ;;
36 ;; Some random thoughts are to express simple queries directly with
37 ;; user-level functions, and perhaps use some kind of forms interface
38 ;; for medium-level queries. Really complicated queries can be
39 ;; expressed in Lisp.
40 ;;
41
42 ;;; Todo
43 ;;
44 ;; It would be nice if we could sort the results without running the find
45 ;; again. Maybe that could work by storing the original file attributes?
46
47 ;;; Code:
48
49 ;; Internal variables
50
51 (defvar find-lisp-regexp nil
52 "Internal variable.")
53
54 (defconst find-lisp-line-indent " "
55 "Indentation for dired file lines.")
56
57 (defvar find-lisp-file-predicate nil
58 "Predicate for choosing to include files.")
59
60 (defvar find-lisp-directory-predicate nil
61 "Predicate for choosing to descend into directories.")
62
63 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
64 ;; Debugging Code
65 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
66
67 (defvar find-lisp-debug-buffer "*Find Lisp Debug*"
68 "Buffer for debugging information.")
69
70 (defvar find-lisp-debug nil
71 "Whether debugging is enabled.")
72
73 (defun find-lisp-debug-message (message)
74 "Print a debug message MESSAGE in `find-lisp-debug-buffer'."
75 (set-buffer (get-buffer-create find-lisp-debug-buffer))
76 (goto-char (point-max))
77 (insert message "\n"))
78
79 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
80 ;; Directory and File predicates
81 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
82
83 (defun find-lisp-default-directory-predicate (dir parent)
84 "True if DIR is not a dot file, and not a symlink.
85 PARENT is the parent directory of DIR."
86 (and find-lisp-debug
87 (find-lisp-debug-message
88 (format "Processing directory %s in %s" dir parent)))
89 ;; Skip current and parent directories
90 (not (or (string= dir ".")
91 (string= dir "..")
92 ;; Skip directories which are symlinks
93 ;; Easy way to circumvent recursive loops
94 (file-symlink-p dir))))
95
96 (defun find-lisp-default-file-predicate (file dir)
97 "True if FILE matches `find-lisp-regexp'.
98 DIR is the directory containing FILE."
99 (and find-lisp-debug
100 (find-lisp-debug-message
101 (format "Processing file %s in %s" file dir)))
102 (and (not (file-directory-p (expand-file-name file dir)))
103 (string-match find-lisp-regexp file)))
104
105 (defun find-lisp-file-predicate-is-directory (file dir)
106 "True if FILE is a directory.
107 Argument DIR is the directory containing FILE."
108 (and find-lisp-debug
109 (find-lisp-debug-message
110 (format "Processing file %s in %s" file dir)))
111 (and (file-directory-p (expand-file-name file dir))
112 (not (or (string= file ".")
113 (string= file "..")))))
114
115 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
116 ;; Find functions
117 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
118
119 (defun find-lisp-find-files (directory regexp)
120 "Find files in DIRECTORY which match REGEXP."
121 (let ((file-predicate 'find-lisp-default-file-predicate)
122 (directory-predicate 'find-lisp-default-directory-predicate)
123 (find-lisp-regexp regexp))
124 (find-lisp-find-files-internal
125 directory
126 file-predicate
127 directory-predicate)))
128
129 ;; Workhorse function
130 (defun find-lisp-find-files-internal (directory file-predicate
131 directory-predicate)
132 "Find files under DIRECTORY which satisfy FILE-PREDICATE.
133 FILE-PREDICATE is a function which takes two arguments: the file and its
134 directory.
135
136 DIRECTORY-PREDICATE is used to decide whether to descend into directories.
137 It is a function which takes two arguments, the directory and its parent."
138 (setq directory (file-name-as-directory directory))
139 (let (results sub-results)
140 (dolist (file (directory-files directory nil nil t))
141 (let ((fullname (expand-file-name file directory)))
142 (when (file-readable-p (expand-file-name file directory))
143 ;; If a directory, check it we should descend into it
144 (and (file-directory-p fullname)
145 (funcall directory-predicate file directory)
146 (progn
147 (setq sub-results
148 (find-lisp-find-files-internal
149 fullname
150 file-predicate
151 directory-predicate))
152 (if results
153 (nconc results sub-results)
154 (setq results sub-results))))
155 ;; For all files and directories, call the file predicate
156 (and (funcall file-predicate file directory)
157 (if results
158 (nconc results (list fullname))
159 (setq results (list fullname)))))))
160 results))
161
162 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
163 ;; Find-dired all in Lisp
164 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
165
166 ;;;###autoload
167 (defun find-lisp-find-dired (dir regexp)
168 "Find files in DIR, matching REGEXP."
169 (interactive "DFind files in directory: \nsMatching regexp: ")
170 (let ((find-lisp-regexp regexp))
171 (find-lisp-find-dired-internal
172 dir
173 'find-lisp-default-file-predicate
174 'find-lisp-default-directory-predicate
175 "*Find Lisp Dired*")))
176
177 ;; Just the subdirectories
178 ;;;###autoload
179 (defun find-lisp-find-dired-subdirectories (dir)
180 "Find all subdirectories of DIR."
181 (interactive "DFind subdirectories of directory: ")
182 (find-lisp-find-dired-internal
183 dir
184 'find-lisp-file-predicate-is-directory
185 'find-lisp-default-directory-predicate
186 "*Find Lisp Dired Subdirectories*"))
187
188 ;; Most of this is lifted from find-dired.el
189 ;;
190 (defun find-lisp-find-dired-internal (dir file-predicate
191 directory-predicate buffer-name)
192 "Run find (Lisp version) and go into Dired mode on a buffer of the output."
193 (let ((dired-buffers dired-buffers)
194 buf
195 (regexp find-lisp-regexp))
196 ;; Expand DIR ("" means default-directory), and make sure it has a
197 ;; trailing slash.
198 (setq dir (abbreviate-file-name
199 (file-name-as-directory (expand-file-name dir))))
200 ;; Check that it's really a directory.
201 (or (file-directory-p dir)
202 (error "find-dired needs a directory: %s" dir))
203 (or
204 (and (buffer-name)
205 (string= buffer-name (buffer-name)))
206 (switch-to-buffer (setq buf (get-buffer-create buffer-name))))
207 (widen)
208 (kill-all-local-variables)
209 (setq buffer-read-only nil)
210 (erase-buffer)
211 (setq default-directory dir)
212 (dired-mode dir)
213
214 (use-local-map (append (make-sparse-keymap) (current-local-map)))
215
216 (make-local-variable 'find-lisp-file-predicate)
217 (setq find-lisp-file-predicate file-predicate)
218 (make-local-variable 'find-lisp-directory-predicate)
219 (setq find-lisp-directory-predicate directory-predicate)
220 (make-local-variable 'find-lisp-regexp)
221 (setq find-lisp-regexp regexp)
222
223 (make-local-variable 'revert-buffer-function)
224 (setq revert-buffer-function
225 (function
226 (lambda(ignore1 ignore2)
227 (find-lisp-insert-directory
228 default-directory
229 find-lisp-file-predicate
230 find-lisp-directory-predicate
231 'ignore)
232 )
233 ))
234
235 ;; Set subdir-alist so that Tree Dired will work:
236 (if (fboundp 'dired-simple-subdir-alist)
237 ;; will work even with nested dired format (dired-nstd.el,v 1.15
238 ;; and later)
239 (dired-simple-subdir-alist)
240 ;; else we have an ancient tree dired (or classic dired, where
241 ;; this does no harm)
242 (set (make-local-variable 'dired-subdir-alist)
243 (list (cons default-directory (point-min-marker)))))
244 (find-lisp-insert-directory
245 dir file-predicate directory-predicate 'ignore)
246 (goto-char (point-min))
247 (dired-goto-next-file)))
248
249 (defun find-lisp-insert-directory (dir
250 file-predicate
251 directory-predicate
252 sort-function)
253 "Insert the results of `find-lisp-find-files' in the current buffer."
254 (let ((buffer-read-only nil)
255 (files (find-lisp-find-files-internal
256 dir
257 file-predicate
258 directory-predicate))
259 (len (length dir)))
260 (erase-buffer)
261 ;; Subdir headlerline must come first because the first marker in
262 ;; subdir-alist points there.
263 (insert find-lisp-line-indent dir ":\n")
264 ;; Make second line a ``find'' line in analogy to the ``total'' or
265 ;; ``wildcard'' line.
266 ;;
267 ;; No analog for find-lisp?
268 (insert find-lisp-line-indent "\n")
269 ;; Run the find function
270 (mapcar
271 (function
272 (lambda(file)
273 (find-lisp-find-dired-insert-file
274 (substring file len)
275 (current-buffer))))
276 (sort files 'string-lessp))
277 ;; FIXME: Sort function is ignored for now
278 ;; (funcall sort-function files))
279 (goto-char (point-min))
280 (dired-goto-next-file)))
281
282 ;;;###autoload
283 (defun find-lisp-find-dired-filter (regexp)
284 "Change the filter on a find-lisp-find-dired buffer to REGEXP."
285 (interactive "sSet filter to regexp: ")
286 (setq find-lisp-regexp regexp)
287 (revert-buffer))
288
289 (defun find-lisp-find-dired-insert-file (file buffer)
290 (set-buffer buffer)
291 (insert find-lisp-line-indent
292 (find-lisp-format file (file-attributes file) (list "")
293 (current-time))))
294
295 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
296 ;; Lifted from ls-lisp. We don't want to require it, because that
297 ;; would alter the insert-directory function.
298 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
299
300 (defun find-lisp-format (file-name file-attr switches now)
301 (let ((file-type (nth 0 file-attr)))
302 (concat (if (memq ?i switches) ; inode number
303 (format "%6d " (nth 10 file-attr)))
304 ;; nil is treated like "" in concat
305 (if (memq ?s switches) ; size in K
306 (format "%4d " (1+ (/ (nth 7 file-attr) 1024))))
307 (nth 8 file-attr) ; permission bits
308 ;; numeric uid/gid are more confusing than helpful
309 ;; Emacs should be able to make strings of them.
310 ;; user-login-name and user-full-name could take an
311 ;; optional arg.
312 (format " %3d %-8s %-8s %8d "
313 (nth 1 file-attr) ; no. of links
314 (if (= (user-uid) (nth 2 file-attr))
315 (user-login-name)
316 (int-to-string (nth 2 file-attr))) ; uid
317 (if (eq system-type 'ms-dos)
318 "root" ; everything is root on MSDOS.
319 (int-to-string (nth 3 file-attr))) ; gid
320 (nth 7 file-attr) ; size in bytes
321 )
322 (find-lisp-format-time file-attr switches now)
323 " "
324 file-name
325 (if (stringp file-type) ; is a symbolic link
326 (concat " -> " file-type)
327 "")
328 "\n")))
329
330 (defun find-lisp-time-index (switches)
331 ;; Return index into file-attributes according to ls SWITCHES.
332 (cond
333 ((memq ?c switches) 6) ; last mode change
334 ((memq ?u switches) 4) ; last access
335 ;; default is last modtime
336 (t 5)))
337
338 (defun find-lisp-format-time (file-attr switches now)
339 ;; Format time string for file with attributes FILE-ATTR according
340 ;; to SWITCHES (a list of ls option letters of which c and u are recognized).
341 ;; Use the same method as `ls' to decide whether to show time-of-day or year,
342 ;; depending on distance between file date and NOW.
343 (let* ((time (nth (find-lisp-time-index switches) file-attr))
344 (diff16 (- (car time) (car now)))
345 (diff (+ (ash diff16 16) (- (car (cdr time)) (car (cdr now)))))
346 (past-cutoff (- (* 6 30 24 60 60))) ; 6 30-day months
347 (future-cutoff (* 60 60))) ; 1 hour
348 (format-time-string
349 (if (and
350 (<= past-cutoff diff) (<= diff future-cutoff)
351 ;; Sanity check in case `diff' computation overflowed.
352 (<= (1- (ash past-cutoff -16)) diff16)
353 (<= diff16 (1+ (ash future-cutoff -16))))
354 "%b %e %H:%M"
355 "%b %e %Y")
356 time)))
357
358 (provide 'find-lisp)
359
360 ;; Local Variables:
361 ;; autocompile: t
362 ;; End:
363
364 ;;; arch-tag: a711374c-f12a-46f6-aa18-ba7d77b9602a
365 ;;; find-lisp.el ends here