Trailing whitepace deleted.
[bpt/emacs.git] / lisp / mh-e / mh-index.el
CommitLineData
bdcfe844
BW
1;;; mh-index -- MH-E interface to indexing programs
2
3;; Copyright (C) 2002 Free Software Foundation, Inc.
4
c3d9274a 5;; Author: Satyaki Das <satyaki@theforce.stanford.edu>
bdcfe844
BW
6;; Maintainer: Bill Wohler <wohler@newt.com>
7;; Keywords: mail
8;; See: mh-e.el
9
10;; This file is part of GNU Emacs.
11
12;; GNU Emacs is free software; you can redistribute it and/or modify
13;; it under the terms of the GNU General Public License as published by
14;; the Free Software Foundation; either version 2, or (at your option)
15;; any later version.
16
17;; GNU Emacs is distributed in the hope that it will be useful,
18;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20;; GNU General Public License for more details.
21
22;; You should have received a copy of the GNU General Public License
23;; along with GNU Emacs; see the file COPYING. If not, write to the
24;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
25;; Boston, MA 02111-1307, USA.
26
27;;; Commentary:
28
29;;; (1) The following search engines are supported:
30;;; swish++
31;;; swish-e
32;;; namazu
33;;; glimpse
34;;; grep
a1506d29 35;;;
bdcfe844
BW
36;;; (2) To use this package, you first have to build an index. Please read
37;;; the documentation for `mh-index-search' to get started. That
38;;; documentation will direct you to the specific instructions for your
39;;; particular indexer.
bdcfe844
BW
40
41;;; Change Log:
42
a1506d29 43;; $Id: mh-index.el,v 1.2 2003/02/03 20:55:30 wohler Exp $
bdcfe844
BW
44
45;;; Code:
46
47(require 'cl)
48(require 'mh-e)
49(require 'mh-mime)
3d7ca223 50(require 'mh-pick)
bdcfe844 51
bdcfe844
BW
52(autoload 'gnus-local-map-property "gnus-util")
53(autoload 'gnus-eval-format "gnus-spec")
54(autoload 'widget-convert-button "wid-edit")
55(autoload 'executable-find "executable")
56
bdcfe844
BW
57;; Support different indexing programs
58(defvar mh-indexer-choices
59 '((swish++
3d7ca223
BW
60 mh-swish++-binary mh-swish++-execute-search mh-swish++-next-result
61 mh-swish++-regexp-builder)
bdcfe844 62 (swish
3d7ca223
BW
63 mh-swish-binary mh-swish-execute-search mh-swish-next-result nil)
64 (mairix
65 mh-mairix-binary mh-mairix-execute-search mh-mairix-next-result
66 mh-mairix-regexp-builder)
bdcfe844 67 (namazu
3d7ca223 68 mh-namazu-binary mh-namazu-execute-search mh-namazu-next-result nil)
bdcfe844 69 (glimpse
3d7ca223
BW
70 mh-glimpse-binary mh-glimpse-execute-search mh-glimpse-next-result nil)
71 (pick
72 mh-pick-binary mh-pick-execute-search mh-pick-next-result
73 mh-pick-regexp-builder)
bdcfe844 74 (grep
3d7ca223 75 mh-grep-binary mh-grep-execute-search mh-grep-next-result nil))
bdcfe844
BW
76 "List of possible indexer choices.")
77(defvar mh-indexer nil
78 "Chosen index program.")
79(defvar mh-index-execute-search-function nil
80 "Function which executes the search program.")
81(defvar mh-index-next-result-function nil
82 "Function to parse the next line of output.")
3d7ca223
BW
83(defvar mh-index-regexp-builder nil
84 "Function used to construct search regexp.")
bdcfe844 85
c3d9274a
BW
86;; FIXME: This should be a defcustom...
87(defvar mh-index-folder "+mhe-index"
88 "Folder that contains the folders resulting from the index searches.")
89
90;; Temporary buffers for search results
bdcfe844 91(defvar mh-index-temp-buffer " *mh-index-temp*")
c3d9274a
BW
92(defvar mh-checksum-buffer " *mh-checksum-buffer*")
93
94\f
bdcfe844 95
c3d9274a
BW
96;;; A few different checksum programs are supported. The supported programs
97;;; are:
98;;; 1. md5sum
99;;; 2. md5
100;;; 3. openssl
101;;;
102;;; To add support for your favorite checksum program add a clause to the cond
103;;; statement in mh-checksum-choose. This should set the variable
104;;; mh-checksum-cmd to the command line needed to run the checsum program and
105;;; should set mh-checksum-parser to a function which returns a cons cell
106;;; containing the message number and checksum string.
107
108(defvar mh-checksum-cmd)
109(defvar mh-checksum-parser)
110
111(defun mh-checksum-choose ()
112 "Check if a program to create a checksum is present."
113 (unless (boundp 'mh-checksum-cmd)
114 (let ((exec-path (append '("/sbin" "/usr/sbin") exec-path)))
115 (cond ((executable-find "md5sum")
116 (setq mh-checksum-cmd (list (executable-find "md5sum")))
117 (setq mh-checksum-parser #'mh-md5sum-parser))
118 ((executable-find "openssl")
119 (setq mh-checksum-cmd (list (executable-find "openssl") "md5"))
120 (setq mh-checksum-parser #'mh-openssl-parser))
121 ((executable-find "md5")
122 (setq mh-checksum-cmd (list (executable-find "md5")))
123 (setq mh-checksum-parser #'mh-md5-parser))
124 (t (error "No suitable checksum program"))))))
125
126(defun mh-md5sum-parser ()
127 "Parse md5sum output."
128 (let ((begin (line-beginning-position))
129 (end (line-end-position))
130 first-space last-slash)
131 (setq first-space (search-forward " " end t))
132 (goto-char end)
133 (setq last-slash (search-backward "/" begin t))
134 (cond ((and first-space last-slash)
135 (cons (car (read-from-string (buffer-substring-no-properties
136 (1+ last-slash) end)))
137 (buffer-substring-no-properties begin (1- first-space))))
138 (t (cons nil nil)))))
139
140(defun mh-openssl-parser ()
141 "Parse openssl output."
142 (let ((begin (line-beginning-position))
143 (end (line-end-position))
144 last-space last-slash)
145 (goto-char end)
146 (setq last-space (search-backward " " begin t))
147 (setq last-slash (search-backward "/" begin t))
148 (cond ((and last-slash last-space)
149 (cons (car (read-from-string (buffer-substring-no-properties
150 (1+ last-slash) (1- last-space))))
151 (buffer-substring-no-properties (1+ last-space) end))))))
152
153(defalias 'mh-md5-parser 'mh-openssl-parser)
bdcfe844
BW
154
155\f
156
c3d9274a 157;;; Make sure that we don't produce too long a command line.
bdcfe844 158
c3d9274a
BW
159(defvar mh-index-max-cmdline-args 500
160 "Maximum number of command line args.")
161
162(defun mh-index-execute (cmd &rest args)
163 "Partial imitation of xargs.
164The current buffer contains a list of strings, one on each line. The function
165will execute CMD with ARGS and pass the first `mh-index-max-cmdline-args'
166strings to it. This is repeated till all the strings have been used."
167 (goto-char (point-min))
168 (let ((out (get-buffer-create " *mh-xargs-output*")))
169 (save-excursion
170 (set-buffer out)
171 (erase-buffer))
172 (while (not (eobp))
173 (let ((arg-list (reverse args))
174 (count 0))
175 (while (and (not (eobp)) (< count mh-index-max-cmdline-args))
176 (push (buffer-substring-no-properties (point) (line-end-position))
177 arg-list)
178 (incf count)
179 (forward-line))
180 (apply #'call-process cmd nil (list out nil) nil (nreverse arg-list))))
181 (erase-buffer)
182 (insert-buffer-substring out)))
bdcfe844
BW
183
184\f
185
c3d9274a
BW
186(defun mh-index-update-single-msg (msg checksum origin-map)
187 "Update various maps for one message.
188MSG is a index folder message, CHECKSUM its MD5 hash and ORIGIN-MAP, if
189non-nil, a hashtable containing which maps each message in the index folder to
190the folder and message that it was copied from. The function updates the hash
191tables `mh-index-msg-checksum-map' and `mh-index-checksum-origin-map'.
192
193This function should only be called in the appropriate index folder buffer."
194 (cond ((and origin-map (gethash checksum mh-index-checksum-origin-map))
195 (let* ((intermediate (gethash msg origin-map))
196 (ofolder (car intermediate))
197 (omsg (cdr intermediate)))
198 ;; This is most probably a duplicate. So eliminate it.
199 (call-process "rm" nil nil nil
200 (format "%s%s/%s" mh-user-path
201 (substring mh-current-folder 1) msg))
202 (remhash omsg (gethash ofolder mh-index-data))))
203 (t
204 (setf (gethash msg mh-index-msg-checksum-map) checksum)
205 (when origin-map
206 (setf (gethash checksum mh-index-checksum-origin-map)
207 (gethash msg origin-map))))))
208
209;;;###mh-autoload
210(defun mh-index-update-maps (folder &optional origin-map)
211 "Annotate all as yet unannotated messages in FOLDER with their MD5 hash.
212As a side effect msg -> checksum map is updated. Optional argument ORIGIN-MAP
213is a hashtable which maps each message in the index folder to the original
214folder and message from whence it was copied. If present the
215checksum -> (origin-folder, origin-index) map is updated too."
216 (clrhash mh-index-msg-checksum-map)
217 (save-excursion
218 ;; Clear temp buffer
219 (set-buffer (get-buffer-create mh-checksum-buffer))
220 (erase-buffer)
221 ;; Run scan to check if any messages needs MD5 annotations at all
222 (with-temp-buffer
223 (mh-exec-cmd-output mh-scan-prog nil "-width" "80"
224 "-format" "%(msg)\n%{x-mhe-checksum}\n"
225 folder "all")
226 (goto-char (point-min))
227 (let (msg checksum)
228 (while (not (eobp))
229 (setq msg (buffer-substring-no-properties
230 (point) (line-end-position)))
231 (forward-line)
232 (save-excursion
233 (cond ((eolp)
234 ;; need to compute checksum
235 (set-buffer mh-checksum-buffer)
236 (insert mh-user-path (substring folder 1) "/" msg "\n"))
237 (t
238 ;; update maps
239 (setq checksum (buffer-substring-no-properties
240 (point) (line-end-position)))
241 (let ((msg (car (read-from-string msg))))
242 (set-buffer folder)
243 (mh-index-update-single-msg msg checksum origin-map)))))
244 (forward-line))))
245 ;; Run checksum program if needed
246 (unless (and (eobp) (bobp))
247 (apply #'mh-index-execute mh-checksum-cmd)
248 (goto-char (point-min))
249 (while (not (eobp))
250 (let* ((intermediate (funcall mh-checksum-parser))
251 (msg (car intermediate))
252 (checksum (cdr intermediate)))
253 (when msg
254 ;; annotate
255 (mh-exec-cmd "anno" folder msg "-component" "X-MHE-Checksum"
256 "-nodate" "-text" checksum "-inplace")
257 ;; update maps
258 (save-excursion
259 (set-buffer folder)
260 (mh-index-update-single-msg msg checksum origin-map)))
261 (forward-line))))))
262
263(defun mh-index-generate-pretty-name (string)
264 "Given STRING generate a name which is suitable for use as a folder name.
265White space from the beginning and end are removed. All spaces in the name are
266replaced with underscores and all / are replaced with $. If STRING is longer
3d7ca223
BW
267than 20 it is truncated too. STRING could be a list of strings in which case
268they are concatenated to construct the base name."
c3d9274a 269 (with-temp-buffer
3d7ca223
BW
270 (if (stringp string)
271 (insert string)
272 (when (car string) (insert (car string)))
273 (dolist (s (cdr string))
274 (insert "_" s)))
275 (setq string (mh-replace-string "-lbrace" " "))
276 (setq string (mh-replace-string "-rbrace" " "))
277 (subst-char-in-region (point-min) (point-max) ?( ? t)
278 (subst-char-in-region (point-min) (point-max) ?) ? t)
279 (subst-char-in-region (point-min) (point-max) ?- ? t)
c3d9274a 280 (goto-char (point-min))
3d7ca223 281 (while (and (not (eobp)) (memq (char-after) '(? ?\t ?\n ?\r ?_)))
c3d9274a
BW
282 (delete-char 1))
283 (goto-char (point-max))
3d7ca223 284 (while (and (not (bobp)) (memq (char-before) '(? ?\t ?\n ?\r ?_)))
c3d9274a 285 (delete-backward-char 1))
3d7ca223 286 (subst-char-in-region (point-min) (point-max) ? ?_ t)
c3d9274a
BW
287 (subst-char-in-region (point-min) (point-max) ?\t ?_ t)
288 (subst-char-in-region (point-min) (point-max) ?\n ?_ t)
289 (subst-char-in-region (point-min) (point-max) ?\r ?_ t)
290 (subst-char-in-region (point-min) (point-max) ?/ ?$ t)
291 (truncate-string-to-width (buffer-substring (point-min) (point-max)) 20)))
292
293;;;###mh-autoload
3d7ca223
BW
294(defun* mh-index-search (redo-search-flag folder search-regexp
295 &optional window-config)
bdcfe844
BW
296 "Perform an indexed search in an MH mail folder.
297
c3d9274a
BW
298If REDO-SEARCH-FLAG is non-nil and the current folder buffer was generated by a
299index search, then the search is repeated. Otherwise, FOLDER is searched with
300SEARCH-REGEXP and the results are presented in an MH-E folder. If FOLDER is
3d7ca223
BW
301\"+\" then mail in all folders are searched. Optional argument WINDOW-CONFIG
302stores the window configuration that will be restored after the user quits the
303folder containing the index search results.
bdcfe844
BW
304
305Four indexing programs are supported; if none of these are present, then grep
306is used. This function picks the first program that is available on your
307system. If you would prefer to use a different program, set the customization
308variable `mh-index-program' accordingly.
309
310The documentation for the following functions describes how to generate the
311index for each program:
312
313 - `mh-swish++-execute-search'
314 - `mh-swish-execute-search'
3d7ca223 315 - `mh-mairix-execute-search'
bdcfe844 316 - `mh-namazu-execute-search'
c3d9274a
BW
317 - `mh-glimpse-execute-search'
318
3d7ca223
BW
319If none of these programs are present then we use pick. If desired grep can be
320used instead. Details about these methods can be found in:
321
322 - `mh-pick-execute-search'
323 - `mh-grep-execute-search'
324
c3d9274a
BW
325This and related functions use an X-MHE-Checksum header to cache the MD5
326checksum of a message. This means that already present X-MHE-Checksum headers
327in the incoming email could result in messages not being found. The following
328procmail recipe should avoid this:
329
330 :0 wf
331 | formail -R \"X-MHE-Checksum\" \"Old-X-MHE-Checksum\"
332
333This has the effect of renaming already present X-MHE-Checksum headers."
bdcfe844 334 (interactive
c3d9274a
BW
335 (list current-prefix-arg
336 (progn
bdcfe844 337 (unless mh-find-path-run (mh-find-path))
c3d9274a 338 (or (and current-prefix-arg (car mh-index-previous-search))
3d7ca223 339 (mh-prompt-for-folder "Search" "+" nil "all" t)))
bdcfe844
BW
340 (progn
341 ;; Yes, we do want to call mh-index-choose every time in case the
342 ;; user has switched the indexer manually.
343 (unless (mh-index-choose) (error "No indexing program found"))
c3d9274a 344 (or (and current-prefix-arg (cadr mh-index-previous-search))
3d7ca223 345 mh-index-regexp-builder
c3d9274a
BW
346 (read-string (format "%s regexp: "
347 (upcase-initials
3d7ca223
BW
348 (symbol-name mh-indexer))))))
349 (if (and (not
350 (and current-prefix-arg (cadr mh-index-previous-search)))
351 mh-index-regexp-builder)
352 (current-window-configuration)
353 nil)))
354 (when (symbolp search-regexp)
355 (mh-search-folder folder window-config)
356 (setq mh-searching-function 'mh-index-do-search)
357 (return-from mh-index-search))
c3d9274a
BW
358 (mh-checksum-choose)
359 (let ((result-count 0)
3d7ca223 360 (old-window-config (or window-config mh-previous-window-config))
c3d9274a
BW
361 (previous-search mh-index-previous-search)
362 (index-folder (format "%s/%s" mh-index-folder
363 (mh-index-generate-pretty-name search-regexp))))
364 ;; Create a new folder for the search results or recreate the old one...
365 (if (and redo-search-flag mh-index-previous-search)
366 (let ((buffer-name (buffer-name (current-buffer))))
367 (mh-process-or-undo-commands buffer-name)
368 (save-excursion (mh-exec-cmd-quiet nil "rmf" buffer-name))
369 (mh-exec-cmd-quiet nil "folder" "-create" "-fast" buffer-name)
370 (setq index-folder buffer-name))
371 (setq index-folder (mh-index-new-folder index-folder)))
372
373 (let ((folder-path (format "%s%s" mh-user-path (substring folder 1)))
374 (folder-results-map (make-hash-table :test #'equal))
375 (origin-map (make-hash-table :test #'equal)))
bdcfe844 376 ;; Run search program...
c3d9274a 377 (message "Executing %s... " mh-indexer)
bdcfe844
BW
378 (funcall mh-index-execute-search-function folder-path search-regexp)
379
c3d9274a 380 ;; Parse indexer output
bdcfe844
BW
381 (message "Processing %s output... " mh-indexer)
382 (goto-char (point-min))
c3d9274a
BW
383 (loop for next-result = (funcall mh-index-next-result-function)
384 when (null next-result) return nil
385 do (unless (eq next-result 'error)
386 (unless (gethash (car next-result) folder-results-map)
387 (setf (gethash (car next-result) folder-results-map)
388 (make-hash-table :test #'equal)))
389 (setf (gethash (cadr next-result)
390 (gethash (car next-result) folder-results-map))
391 t)))
392
393 ;; Copy the search results over
394 (maphash #'(lambda (folder msgs)
395 (let ((msgs (sort (loop for msg being the hash-keys of msgs
396 collect msg)
397 #'<)))
398 (mh-exec-cmd "refile" msgs "-src" folder
399 "-link" index-folder)
400 (loop for msg in msgs
401 do (incf result-count)
402 (setf (gethash result-count origin-map)
403 (cons folder msg)))))
404 folder-results-map)
bdcfe844
BW
405
406 ;; Generate scan lines for the hits.
c3d9274a
BW
407 (let ((mh-show-threads-flag nil))
408 (mh-visit-folder index-folder () (list folder-results-map origin-map)))
409
bdcfe844
BW
410 (goto-char (point-min))
411 (forward-line)
c3d9274a
BW
412 (mh-update-sequences)
413 (mh-recenter nil)
414
415 ;; Maintain history
3d7ca223 416 (when (or (and redo-search-flag previous-search) window-config)
c3d9274a
BW
417 (setq mh-previous-window-config old-window-config))
418 (setq mh-index-previous-search (list folder search-regexp))
bdcfe844 419
bdcfe844
BW
420 (message "%s found %s matches in %s folders"
421 (upcase-initials (symbol-name mh-indexer))
c3d9274a
BW
422 (loop for msg-hash being hash-values of mh-index-data
423 sum (hash-table-count msg-hash))
424 (loop for msg-hash being hash-values of mh-index-data
425 count (> (hash-table-count msg-hash) 0))))))
426
3d7ca223
BW
427;;;###mh-autoload
428(defun mh-index-do-search ()
429 "Construct appropriate regexp and call `mh-index-search'."
430 (interactive)
431 (unless (mh-index-choose) (error "No indexing program found"))
432 (let* ((regexp-list (mh-pick-parse-search-buffer))
433 (pattern (funcall mh-index-regexp-builder regexp-list)))
434 (if pattern
435 (mh-index-search nil mh-current-folder pattern
436 mh-previous-window-config)
437 (error "No search terms"))))
438
439(defun mh-replace-string (old new)
440 "Replace all occurrences of OLD with NEW in the current buffer."
441 (goto-char (point-min))
442 (while (search-forward old nil t)
443 (replace-match new)))
444
445;;;###mh-autoload
446(defun mh-index-parse-search-regexp (input-string)
447 "Construct parse tree for INPUT-STRING.
448All occurrences of &, |, ! and ~ in INPUT-STRING are replaced by AND, OR and
449NOT as appropriate. Then the resulting string is parsed."
450 (let (input)
451 (with-temp-buffer
452 (insert input-string)
453 (downcase-region (point-min) (point-max))
454 ;; replace tabs
455 (mh-replace-string "\t" " ")
456 ;; synonyms of AND
457 (mh-replace-string "&" " and ")
458 (mh-replace-string " -and " " and ")
459 ;; synonyms of OR
460 (mh-replace-string "|" " or ")
461 (mh-replace-string " -or " " or ")
462 ;; synonyms of NOT
463 (mh-replace-string "!" " not ")
464 (mh-replace-string "~" " not ")
465 (mh-replace-string " -not " " not ")
466 ;; synonyms of left brace
467 (mh-replace-string "(" " ( ")
468 (mh-replace-string " -lbrace " " ( ")
469 ;; synonyms of right brace
470 (mh-replace-string ")" " ) ")
471 (mh-replace-string " -rbrace " " ) ")
472 ;; get the normalized input
473 (setq input (format "( %s )" (buffer-substring (point-min) (point-max)))))
474
475 (let ((tokens (mh-index-add-implicit-ops (split-string input)))
476 (op-stack ())
477 (operand-stack ())
478 oper1)
479 (dolist (token tokens)
480 (cond ((equal token "(") (push 'paren op-stack))
481 ((equal token "not") (push 'not op-stack))
482 ((equal token "or") (push 'or op-stack))
483 ((equal token "and") (push 'and op-stack))
484 ((equal token ")")
485 (multiple-value-setq (op-stack operand-stack)
486 (mh-index-evaluate op-stack operand-stack))
487 (when (eq (car op-stack) 'not)
488 (pop op-stack)
489 (push `(not ,(pop operand-stack)) operand-stack))
490 (when (eq (car op-stack) 'and)
491 (pop op-stack)
492 (setq oper1 (pop operand-stack))
493 (push `(and ,(pop operand-stack) ,oper1) operand-stack)))
494 ((eq (car op-stack) 'not)
495 (pop op-stack)
496 (push `(not ,token) operand-stack)
497 (when (eq (car op-stack) 'and)
498 (pop op-stack)
499 (setq oper1 (pop operand-stack))
500 (push `(and ,(pop operand-stack) ,oper1) operand-stack)))
501 ((eq (car op-stack) 'and)
502 (pop op-stack)
503 (push `(and ,(pop operand-stack) ,token) operand-stack))
504 (t (push token operand-stack))))
505 (prog1 (pop operand-stack)
506 (when (or op-stack operand-stack)
507 (error "Invalid regexp: %s" input))))))
508
509(defun mh-index-add-implicit-ops (tokens)
510 "Add implicit operators in the list TOKENS."
511 (let ((result ())
512 (literal-seen nil)
513 current)
514 (while tokens
515 (setq current (pop tokens))
516 (cond ((or (equal current ")") (equal current "and") (equal current "or"))
517 (setq literal-seen nil)
518 (push current result))
519 ((and literal-seen
520 (push "and" result)
521 (setq literal-seen nil)
522 nil))
523 (t
524 (push current result)
525 (unless (or (equal current "(") (equal current "not"))
526 (setq literal-seen t)))))
527 (nreverse result)))
528
529(defun mh-index-evaluate (op-stack operand-stack)
530 "Read expression till starting paren based on OP-STACK and OPERAND-STACK."
531 (block mh-index-evaluate
532 (let (op oper1)
533 (while op-stack
534 (setq op (pop op-stack))
535 (cond ((eq op 'paren)
536 (return-from mh-index-evaluate (values op-stack operand-stack)))
537 ((eq op 'not)
538 (push `(not ,(pop operand-stack)) operand-stack))
539 ((or (eq op 'and) (eq op 'or))
540 (setq oper1 (pop operand-stack))
541 (push `(,op ,(pop operand-stack) ,oper1) operand-stack))))
542 (error "Ran out of tokens"))))
543
c3d9274a
BW
544;;;###mh-autoload
545(defun mh-index-next-folder (&optional backward-flag)
546 "Jump to the next folder marker.
547The function is only applicable to folders displaying index search results.
548With non-nil optional argument BACKWARD-FLAG, jump to the previous group of
549results."
550 (interactive "P")
551 (if (or (null mh-index-data)
552 (memq 'unthread mh-view-ops))
553 (message "Only applicable in an unthreaded MH-E index search buffer")
554 (let ((point (point)))
555 (forward-line (if backward-flag -1 1))
556 (cond ((if backward-flag
557 (re-search-backward "^+" (point-min) t)
558 (re-search-forward "^+" (point-max) t))
559 (beginning-of-line))
560 ((and (if backward-flag
561 (goto-char (point-max))
562 (goto-char (point-min)))
563 nil))
564 ((if backward-flag
565 (re-search-backward "^+" (point-min) t)
566 (re-search-forward "^+" (point-max) t))
567 (beginning-of-line))
568 (t (goto-char point))))))
569
570;;;###mh-autoload
571(defun mh-index-previous-folder ()
572 "Jump to the previous folder marker."
bdcfe844 573 (interactive)
c3d9274a
BW
574 (mh-index-next-folder t))
575
576(defun mh-folder-exists-p (folder)
577 "Check if FOLDER exists."
578 (and (mh-folder-name-p folder)
579 (save-excursion
580 (with-temp-buffer
581 (mh-exec-cmd-output "folder" nil "-fast" "-nocreate" folder)
582 (goto-char (point-min))
583 (not (eobp))))))
584
585(defun mh-msg-exists-p (msg folder)
586 "Check if MSG exists in FOLDER."
587 (file-exists-p (format "%s%s/%s" mh-user-path (substring folder 1) msg)))
588
589(defun mh-index-new-folder (name)
590 "Create and return an MH folder name based on NAME.
591If the folder NAME already exists then check if NAME<2> exists. If it doesn't
592then it is created and returned. Otherwise try NAME<3>. This is repeated till
593we find a new folder name."
594 (unless (mh-folder-name-p name)
595 (error "The argument should be a valid MH folder name"))
596 (let ((chosen-name name))
597 (block unique-name
598 (unless (mh-folder-exists-p name)
599 (return-from unique-name))
600 (loop for index from 2
601 do (let ((new-name (format "%s<%s>" name index)))
602 (unless (mh-folder-exists-p new-name)
603 (setq chosen-name new-name)
604 (return-from unique-name)))))
605 (mh-exec-cmd-quiet nil "folder" "-create" "-fast" chosen-name)
3d7ca223 606 (mh-remove-from-sub-folders-cache chosen-name)
c3d9274a
BW
607 (when (boundp 'mh-speed-folder-map)
608 (mh-speed-add-folder chosen-name))
c3d9274a
BW
609 chosen-name))
610
611;;;###mh-autoload
612(defun mh-index-insert-folder-headers ()
613 "Annotate the search results with original folder names."
614 (let ((cur-msg (mh-get-msg-num nil))
615 (old-buffer-modified-flag (buffer-modified-p))
616 (buffer-read-only nil)
617 current-folder last-folder)
bdcfe844 618 (goto-char (point-min))
c3d9274a
BW
619 (while (not (eobp))
620 (setq current-folder (car (gethash (gethash (mh-get-msg-num nil)
621 mh-index-msg-checksum-map)
622 mh-index-checksum-origin-map)))
623 (when (and current-folder (not (eq current-folder last-folder)))
624 (insert (if last-folder "\n" "") current-folder "\n")
625 (setq last-folder current-folder))
626 (forward-line))
627 (when cur-msg (mh-goto-msg cur-msg t))
628 (set-buffer-modified-p old-buffer-modified-flag)))
629
630;;;###mh-autoload
631(defun mh-index-delete-folder-headers ()
632 "Delete the folder headers."
633 (let ((cur-msg (mh-get-msg-num nil))
634 (old-buffer-modified-flag (buffer-modified-p))
635 (buffer-read-only nil))
3d7ca223
BW
636 (while (and (not cur-msg) (not (eobp)))
637 (forward-line)
638 (setq cur-msg (mh-get-msg-num nil)))
c3d9274a
BW
639 (goto-char (point-min))
640 (while (not (eobp))
641 (if (or (char-equal (char-after) ?+) (char-equal (char-after) 10))
642 (delete-region (point) (progn (forward-line) (point)))
bdcfe844 643 (forward-line)))
c3d9274a
BW
644 (when cur-msg (mh-goto-msg cur-msg t t))
645 (set-buffer-modified-p old-buffer-modified-flag)))
bdcfe844 646
c3d9274a
BW
647;;;###mh-autoload
648(defun mh-index-visit-folder ()
649 "Visit original folder from where the message at point was found."
bdcfe844 650 (interactive)
c3d9274a
BW
651 (unless mh-index-data
652 (error "Not in an index folder"))
653 (let (folder msg)
654 (save-excursion
655 (cond ((and (bolp) (eolp))
656 (ignore-errors (forward-line -1))
657 (setq msg (mh-get-msg-num t)))
658 ((equal (char-after (line-beginning-position)) ?+)
659 (setq folder (buffer-substring-no-properties
660 (line-beginning-position) (line-end-position))))
661 (t (setq msg (mh-get-msg-num t)))))
662 (when (not folder)
663 (setq folder (car (gethash (gethash msg mh-index-msg-checksum-map)
664 mh-index-checksum-origin-map))))
665 (mh-visit-folder
666 folder (loop for x being the hash-keys of (gethash folder mh-index-data)
667 when (mh-msg-exists-p x folder) collect x))))
668
669(defun mh-index-match-checksum (msg folder checksum)
670 "Check if MSG in FOLDER has X-MHE-Checksum header value of CHECKSUM."
671 (with-temp-buffer
672 (mh-exec-cmd-output mh-scan-prog nil "-width" "80"
673 "-format" "%{x-mhe-checksum}\n" folder msg)
674 (goto-char (point-min))
675 (string-equal (buffer-substring-no-properties (point) (line-end-position))
676 checksum)))
677
678;;;###mh-autoload
679(defun mh-index-execute-commands ()
680 "Delete/refile the actual messages.
681The copies in the searched folder are then deleted/refiled to get the desired
682result. Before deleting the messages we make sure that the message being
683deleted is identical to the one that the user has marked in the index buffer."
684 (let ((message-table (make-hash-table :test #'equal)))
685 (dolist (msg-list (cons mh-delete-list (mapcar #'cdr mh-refile-list)))
686 (dolist (msg msg-list)
687 (let* ((checksum (gethash msg mh-index-msg-checksum-map))
688 (pair (gethash checksum mh-index-checksum-origin-map)))
689 (when (and checksum (car pair) (cdr pair)
690 (mh-index-match-checksum (cdr pair) (car pair) checksum))
691 (push (cdr pair) (gethash (car pair) message-table))
692 (remhash (cdr pair) (gethash (car pair) mh-index-data))))))
693 (maphash (lambda (folder msgs)
694 (apply #'mh-exec-cmd "rmm" folder (mh-coalesce-msg-list msgs)))
695 message-table)))
bdcfe844
BW
696
697\f
698
699;; Glimpse interface
700
701(defvar mh-glimpse-binary (executable-find "glimpse"))
702(defvar mh-glimpse-directory ".glimpse")
703
c3d9274a 704;;;###mh-autoload
bdcfe844
BW
705(defun mh-glimpse-execute-search (folder-path search-regexp)
706 "Execute glimpse and read the results.
707
708In the examples below, replace /home/user/Mail with the path to your MH
709directory.
710
711First create the directory /home/user/Mail/.glimpse. Then create the file
712/home/user/Mail/.glimpse/.glimpse_exclude with the following contents:
713
714 */.*
715 */#*
716 */,*
717 */*~
718 ^/home/user/Mail/.glimpse
c3d9274a 719 ^/home/user/Mail/mhe-index
bdcfe844
BW
720
721If there are any directories you would like to ignore, append lines like the
722following to .glimpse_exclude:
723
724 ^/home/user/Mail/scripts
725
c3d9274a
BW
726You do not want to index the folders that hold the results of your searches
727since they tend to be ephemeral and the original messages are indexed anyway.
728The configuration file above assumes that the results are found in sub-folders
729of `mh-index-folder' which is +mhe-index by default.
730
bdcfe844
BW
731Use the following command line to generate the glimpse index. Run this
732daily from cron:
733
734 glimpseindex -H /home/user/Mail/.glimpse /home/user/Mail
735
736FOLDER-PATH is the directory in which SEARCH-REGEXP is used to search."
737 (set-buffer (get-buffer-create mh-index-temp-buffer))
738 (erase-buffer)
739 (call-process mh-glimpse-binary nil '(t nil) nil
c3d9274a 740 ;(format "-%s" fuzz)
bdcfe844 741 "-i" "-y"
c3d9274a 742 "-H" (format "%s%s" mh-user-path mh-glimpse-directory)
bdcfe844
BW
743 "-F" (format "^%s" folder-path)
744 search-regexp)
745 (goto-char (point-min)))
746
747(defun mh-glimpse-next-result ()
748 "Read the next result.
749Parse it and return the message folder, message index and the match. If no
750other matches left then return nil. If the current record is invalid return
751'error."
752 (prog1
c3d9274a
BW
753 (block nil
754 (when (eobp)
755 (return nil))
756 (let ((eol-pos (line-end-position))
757 (bol-pos (line-beginning-position))
758 folder-start msg-end)
759 (goto-char bol-pos)
760 (unless (search-forward mh-user-path eol-pos t)
bdcfe844 761 (return 'error))
c3d9274a
BW
762 (setq folder-start (point))
763 (unless (search-forward ": " eol-pos t)
764 (return 'error))
765 (let ((match (buffer-substring-no-properties (point) eol-pos)))
766 (forward-char -2)
767 (setq msg-end (point))
768 (unless (search-backward "/" folder-start t)
769 (return 'error))
770 (list (format "+%s" (buffer-substring-no-properties
771 folder-start (point)))
772 (let ((val (ignore-errors (read-from-string
773 (buffer-substring-no-properties
774 (1+ (point)) msg-end)))))
775 (if (and (consp val) (integerp (car val)))
776 (car val)
777 (return 'error)))
778 match))))
bdcfe844
BW
779 (forward-line)))
780
781\f
782
3d7ca223
BW
783;; Pick interface
784
785(defvar mh-index-pick-folder)
786(defvar mh-pick-binary "pick")
787
788(defun mh-pick-execute-search (folder-path search-regexp)
789 "Execute pick.
790
791Unlike the other index search programs \"pick\" only searches messages present
792in the folder itself and does not descend into any sub-folders that may be
793present.
794
795FOLDER-PATH is the directory containing the mails to be searched and
796SEARCH-REGEXP is the pattern that pick gets."
797 (set-buffer (get-buffer-create mh-index-temp-buffer))
798 (erase-buffer)
799 (setq mh-index-pick-folder
800 (concat "+" (substring folder-path (length mh-user-path))))
801 (apply #'call-process (expand-file-name "pick" mh-progs) nil '(t nil) nil
802 mh-index-pick-folder "-list" search-regexp)
803 (goto-char (point-min)))
804
805(defun mh-pick-next-result ()
806 "Return the next pick search result."
807 (prog1 (block nil
808 (when (eobp) (return nil))
809 (unless (re-search-forward "^[1-9][0-9]*$" (line-end-position) t)
810 (return 'error))
811 (list mh-index-pick-folder
812 (car (read-from-string (buffer-substring-no-properties
813 (line-beginning-position)
814 (line-end-position))))
815 nil))
816 (forward-line)))
817
818\f
819
bdcfe844
BW
820;; Grep interface
821
822(defvar mh-grep-binary (executable-find "grep"))
823
824(defun mh-grep-execute-search (folder-path search-regexp)
825 "Execute grep and read the results.
826FOLDER-PATH is the directory in which SEARCH-REGEXP is used to search."
827 (set-buffer (get-buffer-create mh-index-temp-buffer))
828 (erase-buffer)
829 (call-process mh-grep-binary nil '(t nil) nil
830 "-i" "-r" search-regexp folder-path)
831 (goto-char (point-min)))
832
833(defun mh-grep-next-result ()
834 "Read the next result.
835Parse it and return the message folder, message index and the match. If no
836other matches left then return nil. If the current record is invalid return
837'error."
838 (prog1
c3d9274a
BW
839 (block nil
840 (when (eobp)
841 (return nil))
842 (let ((eol-pos (line-end-position))
843 (bol-pos (line-beginning-position))
844 folder-start msg-end)
845 (goto-char bol-pos)
846 (unless (search-forward mh-user-path eol-pos t)
bdcfe844 847 (return 'error))
c3d9274a
BW
848 (setq folder-start (point))
849 (unless (search-forward ":" eol-pos t)
850 (return 'error))
851 (let ((match (buffer-substring-no-properties (point) eol-pos)))
852 (forward-char -1)
853 (setq msg-end (point))
854 (unless (search-backward "/" folder-start t)
855 (return 'error))
856 (list (format "+%s" (buffer-substring-no-properties
857 folder-start (point)))
858 (let ((val (ignore-errors (read-from-string
859 (buffer-substring-no-properties
860 (1+ (point)) msg-end)))))
861 (if (and (consp val) (integerp (car val)))
862 (car val)
863 (return 'error)))
864 match))))
bdcfe844
BW
865 (forward-line)))
866
867\f
868
3d7ca223
BW
869;; Mairix interface
870
871(defvar mh-mairix-binary (executable-find "mairix"))
872(defvar mh-mairix-directory ".mairix")
873(defvar mh-mairix-folder nil)
874
875(defun mh-mairix-execute-search (folder-path search-regexp-list)
876 "Execute mairix and read the results.
877
878In the examples below replace /home/user/Mail with the path to your MH
879directory.
880
881First create the directory /home/user/Mail/.mairix. Then create the file
882/home/user/Mail/.mairix/config with the following contents:
883
884 # This should contain the same thing as your `mh-user-path'
885 base=/home/user/Mail
a1506d29 886
3d7ca223
BW
887 # List of folders that should be indexed. 3 dots at the end means there are
888 # subfolders within the folder
889 mh_folders=archive...:inbox:drafts:news:sent:trash
a1506d29 890
3d7ca223
BW
891 vfolder_format=raw
892 database=/home/user/Mail/mairix/database
893
894Use the following command line to generate the mairix index. Run this daily
895from cron:
896
897 mairix -f /home/user/Mail/.mairix/config
898
899FOLDER-PATH is the directory in which SEARCH-REGEXP-LIST is used to search."
900 (set-buffer (get-buffer-create mh-index-temp-buffer))
901 (erase-buffer)
902 (unless mh-mairix-binary
903 (error "Set mh-mairix-binary appropriately"))
904 (apply #'call-process mh-mairix-binary nil '(t nil) nil
905 "-f" (format "%s%s/config" mh-user-path mh-mairix-directory)
906 search-regexp-list)
907 (goto-char (point-min))
908 (setq mh-mairix-folder
909 (let ((last-char (substring folder-path (1- (length folder-path)))))
910 (if (equal last-char "/")
911 folder-path
912 (format "%s/" folder-path)))))
913
914(defun mh-mairix-next-result ()
915 "Return next result from mairix output."
916 (prog1
917 (block nil
918 (when (or (eobp) (and (bolp) (eolp)))
919 (return nil))
920 (unless (eq (char-after) ?/)
921 (return error))
922 (let ((start (point))
923 end msg-start)
924 (setq end (line-end-position))
925 (unless (search-forward mh-mairix-folder end t)
926 (return 'error))
927 (goto-char (match-beginning 0))
928 (unless (equal (point) start)
929 (return 'error))
930 (goto-char end)
931 (unless (search-backward "/" start t)
932 (return 'error))
933 (setq msg-start (1+ (point)))
934 (goto-char start)
935 (unless (search-forward mh-user-path end t)
936 (return 'error))
937 (list (format "+%s" (buffer-substring-no-properties
938 (point) (1- msg-start)))
939 (car (read-from-string
940 (buffer-substring-no-properties msg-start end)))
941 ())))
942 (forward-line)))
943
944(defun mh-mairix-regexp-builder (regexp-list)
945 "Generate query for mairix.
946REGEXP-LIST is an alist of fields and values."
947 (let ((result ()))
948 (dolist (pair regexp-list)
949 (when (cdr pair)
950 (push
951 (concat
952 (cond ((eq (car pair) 'to) "t:")
953 ((eq (car pair) 'from) "f:")
954 ((eq (car pair) 'cc) "c:")
955 ((eq (car pair) 'subject) "s:")
956 ((eq (car pair) 'date) "d:")
957 (t ""))
958 (let ((sop (cdr (mh-mairix-convert-to-sop* (cdr pair))))
959 (final ""))
960 (dolist (conjunct sop)
961 (let ((expr-list (cdr conjunct))
962 (expr-string ""))
963 (dolist (e expr-list)
964 (setq expr-string (concat expr-string "+"
965 (if (atom e) "" "~")
966 (if (atom e) e (cadr e)))))
967 (setq final (concat final "," (substring expr-string 1)))))
968 (substring final 1)))
969 result)))
970 result))
971
972(defun mh-mairix-convert-to-sop* (expr)
973 "Convert EXPR to sum of product form."
974 (cond ((atom expr) `(or (and ,expr)))
975 ((eq (car expr) 'or)
976 (cons 'or
977 (loop for e in (mapcar #'mh-mairix-convert-to-sop* (cdr expr))
978 append (cdr e))))
979 ((eq (car expr) 'and)
980 (let ((conjuncts (mapcar #'mh-mairix-convert-to-sop* (cdr expr)))
981 result next-factor)
982 (setq result (pop conjuncts))
983 (while conjuncts
984 (setq next-factor (pop conjuncts))
985 (setq result (let ((res ()))
986 (dolist (t1 (cdr result))
987 (dolist (t2 (cdr next-factor))
988 (push `(and ,@(cdr t1) ,@(cdr t2)) res)))
989 (cons 'or res))))
990 result))
991 ((atom (cadr expr)) `(or (and ,expr)))
992 ((eq (caadr expr) 'not) (mh-mairix-convert-to-sop* (cadadr expr)))
993 ((eq (caadr expr) 'and) (mh-mairix-convert-to-sop*
994 `(or ,@(mapcar #'(lambda (x) `(not ,x))
995 (cdadr expr)))))
996 ((eq (caadr expr) 'or) (mh-mairix-convert-to-sop*
997 `(and ,@(mapcar #'(lambda (x) `(not ,x))
998 (cdadr expr)))))
999 (t (error "Unreachable: %s" expr))))
1000
1001\f
1002
bdcfe844
BW
1003;; Swish interface
1004
1005(defvar mh-swish-binary (executable-find "swish-e"))
1006(defvar mh-swish-directory ".swish")
1007(defvar mh-swish-folder nil)
1008
c3d9274a 1009;;;###mh-autoload
bdcfe844
BW
1010(defun mh-swish-execute-search (folder-path search-regexp)
1011 "Execute swish-e and read the results.
1012
1013In the examples below, replace /home/user/Mail with the path to your MH
1014directory.
1015
1016First create the directory /home/user/Mail/.swish. Then create the file
1017/home/user/Mail/.swish/config with the following contents:
1018
1019 IndexDir /home/user/Mail
1020 IndexFile /home/user/Mail/.swish/index
1021 IndexName \"Mail Index\"
1022 IndexDescription \"Mail Index\"
1023 IndexPointer \"http://nowhere\"
1024 IndexAdmin \"nobody\"
1025 #MetaNames automatic
1026 IndexReport 3
1027 FollowSymLinks no
1028 UseStemming no
1029 IgnoreTotalWordCountWhenRanking yes
1030 WordCharacters abcdefghijklmnopqrstuvwxyz0123456789-
1031 BeginCharacters abcdefghijklmnopqrstuvwxyz
1032 EndCharacters abcdefghijklmnopqrstuvwxyz0123456789
1033 IgnoreLimit 50 1000
1034 IndexComments 0
1035 FileRules pathname contains /home/user/Mail/.swish
c3d9274a 1036 FileRules pathname contains /home/user/Mail/mhe-index
bdcfe844 1037 FileRules filename is index
3d7ca223 1038 FileRules filename is \\..*
bdcfe844
BW
1039 FileRules filename is #.*
1040 FileRules filename is ,.*
1041 FileRules filename is .*~
1042
1043If there are any directories you would like to ignore, append lines like the
1044following to config:
1045
1046 FileRules pathname contains /home/user/Mail/scripts
1047
c3d9274a
BW
1048You do not want to index the folders that hold the results of your searches
1049since they tend to be ephemeral and the original messages are indexed anyway.
1050The configuration file above assumes that the results are found in sub-folders
1051of `mh-index-folder' which is +mhe-index by default.
1052
bdcfe844
BW
1053Use the following command line to generate the swish index. Run this
1054daily from cron:
1055
1056 swish-e -c /home/user/Mail/.swish/config
1057
1058FOLDER-PATH is the directory in which SEARCH-REGEXP is used to search."
1059 (set-buffer (get-buffer-create mh-index-temp-buffer))
1060 (erase-buffer)
1061 (unless mh-swish-binary
1062 (error "Set mh-swish-binary appropriately"))
1063 (call-process mh-swish-binary nil '(t nil) nil
1064 "-w" search-regexp
1065 "-f" (format "%s%s/index" mh-user-path mh-swish-directory))
1066 (goto-char (point-min))
1067 (setq mh-swish-folder
1068 (let ((last-char (substring folder-path (1- (length folder-path)))))
1069 (if (equal last-char "/")
1070 folder-path
1071 (format "%s/" folder-path)))))
1072
1073(defun mh-swish-next-result ()
1074 "Get the next result from swish output."
1075 (prog1
1076 (block nil
1077 (when (or (eobp) (equal (char-after (point)) ?.))
1078 (return nil))
1079 (when (equal (char-after (point)) ?#)
1080 (return 'error))
1081 (let* ((start (search-forward " " (line-end-position) t))
1082 (end (search-forward " " (line-end-position) t)))
1083 (unless (and start end)
1084 (return 'error))
1085 (setq end (1- end))
1086 (unless (file-exists-p (buffer-substring-no-properties start end))
1087 (return 'error))
1088 (unless (search-backward "/" start t)
1089 (return 'error))
1090 (list (let* ((s (buffer-substring-no-properties start (1+ (point)))))
1091 (unless (string-match mh-swish-folder s)
1092 (return 'error))
1093 (if (string-match mh-user-path s)
1094 (format "+%s"
1095 (substring s (match-end 0) (1- (length s))))
1096 (return 'error)))
1097 (let* ((s (buffer-substring-no-properties (1+ (point)) end))
1098 (val (ignore-errors (read-from-string s))))
1099 (if (and (consp val) (numberp (car val)))
1100 (car val)
1101 (return 'error)))
1102 nil)))
1103 (forward-line)))
1104
1105\f
1106
1107;; Swish++ interface
1108
1109(defvar mh-swish++-binary (or (executable-find "search++")
c3d9274a 1110 (executable-find "search")))
bdcfe844
BW
1111(defvar mh-swish++-directory ".swish++")
1112
c3d9274a 1113;;;###mh-autoload
bdcfe844
BW
1114(defun mh-swish++-execute-search (folder-path search-regexp)
1115 "Execute swish++ and read the results.
1116
1117In the examples below, replace /home/user/Mail with the path to your MH
1118directory.
1119
1120First create the directory /home/user/Mail/.swish++. Then create the file
1121/home/user/Mail/.swish++/swish++.conf with the following contents:
1122
c3d9274a
BW
1123 IncludeMeta Bcc Cc Comments Content-Description From Keywords
1124 IncludeMeta Newsgroups Resent-To Subject To
1125 IncludeMeta Message-Id References In-Reply-To
1126 IncludeFile Mail *
1127 IndexFile /home/user/Mail/.swish++/swish++.index
bdcfe844
BW
1128
1129Use the following command line to generate the swish index. Run this
1130daily from cron:
1131
c3d9274a
BW
1132 find /home/user/Mail -path /home/user/Mail/mhe-index -prune \\
1133 -o -path /home/user/Mail/.swish++ -prune \\
1134 -o -name \"[0-9]*\" -print \\
1135 | index -c /home/user/Mail/.swish++/swish++.conf /home/user/Mail
1136
1137You do not want to index the folders that hold the results of your searches
1138since they tend to be ephemeral and the original messages are indexed anyway.
1139The command above assumes that the results are found in sub-folders of
1140`mh-index-folder' which is +mhe-index by default.
bdcfe844
BW
1141
1142On some systems (Debian GNU/Linux, for example), use index++ instead of index.
1143
1144FOLDER-PATH is the directory in which SEARCH-REGEXP is used to search."
1145 (set-buffer (get-buffer-create mh-index-temp-buffer))
1146 (erase-buffer)
1147 (unless mh-swish++-binary
1148 (error "Set mh-swish++-binary appropriately"))
1149 (call-process mh-swish++-binary nil '(t nil) nil
1150 "-m" "10000"
1151 (format "-i%s%s/swish++.index"
1152 mh-user-path mh-swish++-directory)
1153 search-regexp)
1154 (goto-char (point-min))
1155 (setq mh-swish-folder
1156 (let ((last-char (substring folder-path (1- (length folder-path)))))
1157 (if (equal last-char "/")
1158 folder-path
1159 (format "%s/" folder-path)))))
1160
1161(defalias 'mh-swish++-next-result 'mh-swish-next-result)
1162
3d7ca223
BW
1163(defun mh-swish++-regexp-builder (regexp-list)
1164 "Generate query for swish++.
1165REGEXP-LIST is an alist of fields and values."
1166 (let ((regexp "") meta)
1167 (dolist (elem regexp-list)
1168 (when (cdr elem)
1169 (setq regexp (concat regexp " and "
1170 (if (car elem) "(" "")
1171 (if (car elem) (symbol-name (car elem)) "")
1172 (if (car elem) " = " "")
1173 (mh-swish++-print-regexp (cdr elem))
1174 (if (car elem) ")" "")))))
1175 (substring regexp 4)))
1176
1177(defun mh-swish++-print-regexp (expr)
1178 "Return infix expression corresponding to EXPR."
1179 (cond ((atom expr) (format "%s" expr))
1180 ((eq (car expr) 'not)
1181 (format "(not %s)" (mh-swish++-print-regexp (cadr expr))))
1182 (t (format "(%s %s %s)" (mh-swish++-print-regexp (cadr expr))
1183 (symbol-name (car expr))
1184 (mh-swish++-print-regexp (caddr expr))))))
1185
bdcfe844
BW
1186\f
1187
1188;; Namazu interface
1189
1190(defvar mh-namazu-binary (executable-find "namazu"))
1191(defvar mh-namazu-directory ".namazu")
1192(defvar mh-namazu-folder nil)
1193
c3d9274a 1194;;;###mh-autoload
bdcfe844
BW
1195(defun mh-namazu-execute-search (folder-path search-regexp)
1196 "Execute namazu and read the results.
1197
1198In the examples below, replace /home/user/Mail with the path to your MH
1199directory.
1200
1201First create the directory /home/user/Mail/.namazu. Then create the file
1202/home/user/Mail/.namazu/mknmzrc with the following contents:
1203
1204 package conf; # Don't remove this line!
1205 $ADDRESS = 'user@localhost';
1206 $ALLOW_FILE = \"[0-9]*\";
c3d9274a
BW
1207 $EXCLUDE_PATH = \"^/home/user/Mail/(mhe-index|spam)\";
1208
1209In the above example configuration, none of the mail files contained in the
1210directories /home/user/Mail/mhe-index and /home/user/Mail/spam are indexed.
1211
1212You do not want to index the folders that hold the results of your searches
1213since they tend to be ephemeral and the original messages are indexed anyway.
1214The configuration file above assumes that the results are found in sub-folders
1215of `mh-index-folder' which is +mhe-index by default.
bdcfe844
BW
1216
1217Use the following command line to generate the namazu index. Run this
1218daily from cron:
1219
1220 mknmz -f /home/user/Mail/.namazu/mknmzrc -O /home/user/Mail/.namazu \\
1221 /home/user/Mail
1222
1223FOLDER-PATH is the directory in which SEARCH-REGEXP is used to search."
1224 (let ((namazu-index-directory
c3d9274a 1225 (format "%s%s" mh-user-path mh-namazu-directory)))
bdcfe844
BW
1226 (unless (file-exists-p namazu-index-directory)
1227 (error "Namazu directory %s not present" namazu-index-directory))
1228 (unless (executable-find mh-namazu-binary)
1229 (error "Set mh-namazu-binary appropriately"))
1230 (set-buffer (get-buffer-create mh-index-temp-buffer))
1231 (erase-buffer)
1232 (call-process mh-namazu-binary nil '(t nil) nil
1233 "-alR" search-regexp namazu-index-directory)
1234 (goto-char (point-min))
1235 (setq mh-namazu-folder
1236 (let ((last (substring folder-path (1- (length folder-path)))))
1237 (if (equal last "/")
1238 folder-path
1239 (format "%s/" folder-path))))))
1240
1241(defun mh-namazu-next-result ()
1242 "Get the next result from namazu output."
1243 (prog1
1244 (block nil
1245 (when (eobp) (return nil))
1246 (let ((file-name (buffer-substring-no-properties
1247 (point) (line-end-position))))
1248 (unless (equal (string-match mh-namazu-folder file-name) 0)
1249 (return 'error))
1250 (unless (file-exists-p file-name)
1251 (return 'error))
1252 (string-match mh-user-path file-name)
1253 (let* ((folder/msg (substring file-name (match-end 0)))
c3d9274a 1254 (mark (mh-search-from-end ?/ folder/msg)))
bdcfe844
BW
1255 (unless mark (return 'error))
1256 (list (format "+%s" (substring folder/msg 0 mark))
1257 (let ((n (ignore-errors (read-from-string
1258 (substring folder/msg (1+ mark))))))
1259 (if (and (consp n) (numberp (car n)))
1260 (car n)
1261 (return 'error)))
1262 nil))))
1263 (forward-line)))
1264
1265\f
1266
1267(defun mh-index-choose ()
1268 "Choose an indexing function.
1269The side-effects of this function are that the variables `mh-indexer',
1270`mh-index-execute-search-function', and `mh-index-next-result-function' are
1271set according to the first indexer in `mh-indexer-choices' present on the
1272system."
1273 (block nil
1274 ;; The following favors the user's preference; otherwise, the last
1275 ;; automatically chosen indexer is used for efficiency rather than going
1276 ;; through the list.
1277 (let ((program-alist (cond (mh-index-program
1278 (list
c3d9274a 1279 (assoc mh-index-program mh-indexer-choices)))
bdcfe844
BW
1280 (mh-indexer
1281 (list (assoc mh-indexer mh-indexer-choices)))
1282 (t mh-indexer-choices))))
1283 (while program-alist
1284 (let* ((current (pop program-alist))
1285 (executable (symbol-value (cadr current))))
1286 (when executable
1287 (setq mh-indexer (car current))
3d7ca223
BW
1288 (setq mh-index-execute-search-function (nth 2 current))
1289 (setq mh-index-next-result-function (nth 3 current))
1290 (setq mh-index-regexp-builder (nth 4 current))
bdcfe844
BW
1291 (return mh-indexer))))
1292 nil)))
1293
1294\f
1295
bdcfe844
BW
1296(provide 'mh-index)
1297
1298;;; Local Variables:
c3d9274a 1299;;; indent-tabs-mode: nil
bdcfe844
BW
1300;;; sentence-end-double-space: nil
1301;;; End:
1302
1303;;; mh-index ends here