yossi@Neon.Stanford.EDU (Joseph Friedman) (12/07/89)
Hi folks! I recently discovered GNU Emacs, and became pretty addicted to it (some people will do anything to avoid work...) Anyway, I took Johan Vromans' summary mode and hacked it up. I also added MM-style mail forwarding. The result is this chunk of e-lisp code, which I am posting here. Since this is my first serious attempt at programming in e-lisp, I would appreciate any comments. -yossi (Yossi@CS.Stanford.EDU) ------------------------------- Cut Here ---------------------------------- ; ; A few enhancements to VM. First off, I fixed some bugs in Johan Vromans' ; summary mode, and extended his stuff to cover %f in the format (NOTE: I ; changed the names of his variables from jv-... to vm-hack-...) ; I also added an MM-style message forwarding. ; ; Here's Johan Vromans' README, with the name changes: ; ; > Hi VM freaks, ; > ; > I've been using the following extension to VM's summary handling for ; > some time, and I like it. So I want to share it with you. ; > ; > Features: ; > ; > - when a mail originates from me, the recipient is shown instead of ; > the sender, e.g. "To info-vm@cs.odu.edu". ; > ; > - when a mail was originally sent to a mailing list, the name of the ; > mailing list is shown instead, e.g. "[info-vm]". ; > ; > A user-settable variable "vm-hack-summary-mode" determines whether this ; > is never done (value = nil), only for the primary mailbox folder ; > (value = t), or for all folders (other values). ; > The local key "%" is set up to switch between this modes: "%" will ; > show all sender names, while "C-u %" will enable the above processing. ; > ; > The user-variable "vm-hack-mailing-lists" holds a list of all mailing lists ; > I partcipate in, e.g. ; > ; > (setq vm-hack-mailing-lists ; > (list ; > "perl-users" ; > "info-vm" "bug-vm" ; > .....)) ; > ; > Happy hacking! ; > Johan ; > -- jv@mh.nl via internet backbones ; > Johan Vromans uucp: ..!{uunet,hp4nl}!mh.nl!jv ; > Multihouse Automatisering bv phone/fax: +31 1820 62944/62500 ; > Doesburgweg 7, 2803 PL Gouda, The Netherlands ; > ------------------------ "Arms are made for hugging" --------------------- ; ; The variable vm-hack-mm-forward controls the MM-style mail forwarding. ; See the documentation of the variable for more details. I have tested ; this code on GNU Emacs v18.55, running under ULTRIX v3.1A. Since I ; hack on sendmail, it is possible that this code will not work on your ; system. Please let me know if it breaks. ; ; NOTE: If you expect to receive MM-style forwarded messages, you'd probably ; want to add Resent-From, Resent-To, and Resent-Date to your ; vm-visible-headers. ; ; You'll need to add (require 'vm-hack) to your vm-mode-hooks. ; (provide 'vm-hack) (load "vm-summary") (load "vm-reply") (defvar vm-hack-summary-mode t "\ *Enables intelligent handling of From-addresses in VM summary list. Values are 'nil' (no handling), 't' (primary mailbox only) or anything else, which causes all mailboxes to be handled.") (defvar vm-hack-mailing-lists nil "*List of mailing lists.") (defvar vm-hack-mm-forward t "\ *Controls the forwarding style of messages in VM. Values: t: MM-style forwarding. When you use VM's \"forward\" command to forward a message from person A to person B, the header is constructed in such a way that B will think he got it from A directly, except that the subject is modified according to vm-forwarding-subject-format (if it is non-nil); the header also indicates that the message is \"Resent-From\" you. The body of the message is left unchanged. nil: The old VM forwarding.") (define-key vm-mode-map "%" 'vm-hack-summary-shownames) ; we need the host name (defvar vm-hack-hostname "**JUNK**" "\ *Hostname used by vm-hack-summary for recognizing an outgoing message.") (if (string= vm-hack-hostname "**JUNK**") (let (vm-hack-hostname-process) (setq vm-hack-hostname-process (start-process "host-name" nil "sh" "-c" "cat > /dev/null; hostname")) (set-process-filter vm-hack-hostname-process '(lambda (process string) (setq vm-hack-hostname string))) (process-send-eof vm-hack-hostname-process) (while (string= vm-hack-hostname "**JUNK**") (sleep-for 1)) (setq vm-hack-hostname (substring vm-hack-hostname 0 (string-match "$" vm-hack-hostname))))) (defun vm-hack-subs-mailing-list (hdr) "Return name of mailing list, if found in HDR." (let ((ll vm-hack-mailing-lists) (case-fold-search t) el res) (while (not (null ll)) (setq el (car-safe ll)) (setq ll (cdr-safe ll)) (if (string-match (regexp-quote el) hdr) (progn (setq ll nil) ; terminate loop (setq res (concat "[" el "]"))))) res)) (defun vm-su-full-name (m) ;; modified version of vm's vm-su-full-name. ;; Depending on the value of vm-hack-summary-mode, some post-processing ;; is done on the name displayed. ;; ;; If the mail is from/to a mailing list, the name of the list is ;; displayed. ;; If the mail originates from me, the recipient is shown. ;; (let (temp temp-full-name) (setq temp-full-name (or (vm-full-name-of m) (progn (vm-su-do-author m) (vm-full-name-of m)))) (cond ;; return what we have if no postprocessing selected ((null vm-hack-summary-mode) temp-full-name) ;; idem, if only the primary mailbox must be handled ((and (eq vm-hack-summary-mode 't) (not vm-primary-inbox-p)) temp-full-name) ;; first, try mailing lists ((vm-hack-subs-mailing-list (concat (vm-get-header-contents m "To") " " (vm-get-header-contents m "Cc")))) ;; if not, maybe recipient? ((and (or (equal temp-full-name (user-login-name)) (equal temp-full-name (user-full-name))) (setq temp (vm-su-do-recipient-address m))) (concat "To " temp)) ;; nope - return the full name (t temp-full-name)))) (defun vm-su-from (m) ;; modified version of vm's vm-su-from (let (temp temp-from) (setq temp-from (or (vm-from-of m) (progn (vm-su-do-author m) (vm-from-of m)))) (cond ;; return what we have if no postprocessing selected ((null vm-hack-summary-mode) temp-from) ;; idem, if only the primary mailbox must be handled ((and (eq vm-hack-summary-mode 't) (not vm-primary-inbox-p)) temp-from) ;; first, try mailing lists ((vm-hack-subs-mailing-list (concat (vm-get-header-contents m "To") " " (vm-get-header-contents m "Cc")))) ;; if not, maybe recipient? ((and (or (equal temp-from (user-login-name)) (equal temp-from (concat (user-login-name) "@" vm-hack-hostname))) (setq temp (vm-su-do-recipient-address m))) (concat "To " temp)) ;; nope - return the from (t temp-from)))) (defun vm-su-do-recipient-address (m) (let (to) (setq to (or (vm-get-header-contents m "To") (vm-get-header-contents m "Apparently-To"))) (cond ((null to) (setq to "???")) ((string-match "^\\(\\([^<,]+[^ \t\n]\\)[ \t\n]+\\)?<\\([^>]+\\)>" to) (setq to (substring to (match-beginning 3) (match-end 3)))) ((string-match "[^,]*(\\([^),]+\\))[^,]*" to) (setq to (concat (substring to (match-beginning 0) (1- (match-beginning 1))) (substring to (1+ (match-end 1)) (match-end 0)))))) ;; ewe ewe see pee... (if (and vm-gargle-uucp (string-match "\\([^!@:.]+\\)\\(\\.[^!@:]+\\)?!\\([^!@: \t\n]+\\)\\(@\\([^!@:. \t\n]+\\)\\(.[^ \t\n]+\\)?\\)?[ \t\n]*$" to)) (setq to (concat (substring to (match-beginning 3) (match-end 3)) "@" (if (and (match-beginning 5) (match-beginning 2) (not (match-beginning 6))) (concat (substring to (match-beginning 5) (match-end 5)) ".") "") (substring to (match-beginning 1) (or (match-end 2) (match-end 1))) (if (match-end 2) "" ".UUCP")))) to)) (defun vm-hack-summary-shownames (&optional dont) "Show full names in summary instead of mailing lists etc., or the other way around if prefix arg is supplied." (interactive "P") (let ((vm-hack-summary-mode dont)) (vm-summarize nil))) ; ; A slight change to vm-do-reply. Need to turn off vm-hack-summary-mode ; when figuring out the vm-included-text-attribution-format, since some ; people out there may reply to themselves. ; (defun vm-do-reply (to-all include-text) (vm-follow-summary-cursor) (if vm-mail-buffer (set-buffer vm-mail-buffer)) (vm-error-if-folder-empty) (save-restriction (widen) (let ((mail-buffer (current-buffer)) (text-start (vm-text-of (car vm-message-pointer))) (text-end (vm-text-end-of (car vm-message-pointer))) (mp vm-message-pointer) to cc subject message-id tmp) (cond ((setq to (vm-get-header-contents (car mp) "Reply-To"))) ((setq to (vm-get-header-contents (car mp) "From"))) ((setq to (vm-grok-From_-author (car mp)))) (t (error "Cannot find a From: or Reply-To: header in message"))) (setq subject (vm-get-header-contents (car mp) "Subject") message-id (and vm-in-reply-to-format (vm-sprintf 'vm-in-reply-to-format (car mp)))) (if to-all (progn (setq cc (vm-get-header-contents (car mp) "To")) (setq tmp (vm-get-header-contents (car mp) "Cc")) (if tmp (if cc (setq cc (concat cc ",\n\t" tmp)) (setq cc tmp))))) (if vm-strip-reply-headers (let ((mail-use-rfc822 t)) (require 'mail-utils) (and to (setq to (mail-strip-quoted-names to))) (and cc (setq cc (mail-strip-quoted-names cc))))) (if (mail nil to subject message-id cc) (progn (use-local-map (copy-keymap (current-local-map))) (local-set-key "\C-c\C-y" 'vm-yank-message) (local-set-key "\C-c\C-s" 'vm-mail-send) (local-set-key "\C-c\C-c" 'vm-mail-send-and-exit) (local-set-key "\C-c\C-v" vm-mode-map) (setq vm-mail-buffer mail-buffer vm-message-pointer mp) (cond (include-text (goto-char (point-max)) (insert-buffer-substring mail-buffer text-start text-end) (goto-char (- (point) (- text-end text-start))) (save-excursion (if vm-included-text-attribution-format (let ((vm-hack-summary-mode nil)) (insert (vm-sprintf 'vm-included-text-attribution-format (car mp))))) (while (and (re-search-forward "^" nil t) (not (eobp))) (replace-match vm-included-text-prefix t t)))))))))) ; ; A modified vm-forward-message. ; (defun vm-forward-message () "Forward the current message to one or more third parties. You will be placed in a *mail* buffer as is usual with replies, but you must fill in the To: or Resent-To: header manually." (interactive) (vm-follow-summary-cursor) (if vm-mail-buffer (set-buffer vm-mail-buffer)) (vm-error-if-folder-empty) (let ((b (current-buffer)) (m (car vm-message-pointer)) start) (save-restriction (widen) (if vm-hack-mm-forward ; use MM-style forwarding (cond ((mail nil nil nil) (use-local-map (copy-keymap (current-local-map))) (local-set-key "\C-c\C-y" 'vm-yank-message) (local-set-key "\C-c\C-v" vm-mode-map) (setq vm-mail-buffer b) ; reconstruct the whole thing (erase-buffer) ; put header of old message (insert-buffer-substring b (save-excursion (set-buffer b) (goto-char (vm-start-of m)) (forward-line 1) (point)) (save-excursion (set-buffer b) (goto-char (vm-text-of m)) (forward-line -1) (point))) (set-window-start (get-buffer-window (current-buffer)) (point)) ; if diff't subject format, reformat subject line (and vm-forwarding-subject-format (goto-char (point-min)) (re-search-forward "^Subject:[ \t]+\\([^\n]*\n\\([ \t][^\n]*\n\\)*\\)" (point-max) t) (progn (delete-region (match-beginning 0) (match-end 0)) (goto-char (point-max)) (insert "Subject: " (save-excursion (set-buffer b) (vm-sprintf 'vm-forwarding-subject-format m))) (insert "\n"))) ; add new header fields (goto-char (point-max)) (insert "Resent-From: " (user-login-name) "\n") (insert "Resent-To: ") (save-excursion (insert "\n") (insert mail-header-separator "\n") ; include original message (insert-buffer-substring b (goto-char (vm-text-of m)) (vm-end-of m))))) ; nope. use old VM forwarding (cond ((mail nil nil (and vm-forwarding-subject-format (vm-sprintf 'vm-forwarding-subject-format m))) (use-local-map (copy-keymap (current-local-map))) (local-set-key "\C-c\C-y" 'vm-yank-message) (local-set-key "\C-c\C-v" vm-mode-map) (setq vm-mail-buffer b) (goto-char (point-max)) (insert "------- Start of forwarded message -------\n") (setq start (point)) (insert-buffer-substring b (save-excursion (set-buffer b) (goto-char (vm-start-of m)) (forward-line 1) (point)) (vm-text-end-of m)) (if vm-rfc934-forwarding (vm-rfc934-char-stuff-region start (point))) (insert "------- End of forwarded message -------\n") (goto-char (point-min)) (end-of-line))))))) ; ; A hacked-up version of sendmail-send-it that understands my forwarding ; mechanism ; (defun vm-hack-sendmail-send-it () (let ((errbuf (if mail-interactive (generate-new-buffer " sendmail errors") 0)) (tembuf (generate-new-buffer " sendmail temp")) (case-fold-search nil) (resent nil) delimline (mailbuf (current-buffer))) (unwind-protect (save-excursion (set-buffer tembuf) (erase-buffer) (insert-buffer-substring mailbuf) (goto-char (point-max)) ;; require one newline at the end. (or (= (preceding-char) ?\n) (insert ?\n)) ;; Change header-delimiter to be what sendmail expects. (goto-char (point-min)) (re-search-forward (concat "^" (regexp-quote mail-header-separator) "\n")) (replace-match "\n") (backward-char 2) (setq delimline (point-marker)) (if mail-aliases (expand-mail-aliases (point-min) delimline)) (goto-char (point-min)) ;; ignore any blank lines in the header (while (and (re-search-forward "\n\n\n*" delimline t) (< (point) delimline)) (replace-match "\n")) (let ((case-fold-search t)) ;; Find and handle any FCC fields. (goto-char (point-min)) (if (re-search-forward "^FCC:" delimline t) (mail-do-fcc delimline)) ;; If there is a From and no Sender, put it a Sender. (goto-char (point-min)) (and (re-search-forward "^From:" delimline t) (not (save-excursion (goto-char (point-min)) (re-search-forward "^Sender:" delimline t))) (progn (forward-line 1) (insert "Sender: " (user-login-name) "\n"))) ;; don't send out a blank subject line (goto-char (point-min)) (if (re-search-forward "^Subject:[ \t]*\n" delimline t) (replace-match "")) ;; if resent message, find out the real recipient (save-excursion (goto-char delimline) (if (re-search-backward "^Resent-To:[ \t]*\\([^\n]+\n\\(^[ \t]+[^ \t][^\n]*\n\\)*\\)" (point-min) t) (let ((s (buffer-substring (match-beginning 1) (match-end 1)))) (while (not (string= s "")) (string-match "[^ ,\n]+" s) (setq resent (append resent (list (substring s (match-beginning 0) (match-end 0))))) (setq s (substring s (match-end 0) (length s))) (and (string-match "[, \t\n][ \t\n]*" s) (setq s (substring s (match-end 0) (length s)))))))) (if mail-interactive (save-excursion (set-buffer errbuf) (erase-buffer)))) (apply 'call-process-region (append (list (point-min) (point-max) (if (boundp 'sendmail-program) sendmail-program "/usr/lib/sendmail") nil errbuf nil "-oi") (if (null resent) (list "-t") resent) ;; Always specify who from, ;; since some systems have broken sendmails. (list "-f" (user-login-name)) ;;; ;; Don't say "from root" if running under su. ;;; (and (equal (user-real-login-name) "root") ;;; (list "-f" (user-login-name))) ;; These mean "report errors by mail" ;; and "deliver in background". (if (null mail-interactive) '("-oem" "-odb")))) (if mail-interactive (save-excursion (set-buffer errbuf) (goto-char (point-min)) (while (re-search-forward "\n\n* *" nil t) (replace-match "; ")) (if (not (zerop (buffer-size))) (error "Sending...failed to %s" (buffer-substring (point-min) (point-max))))))) (kill-buffer tembuf) (if (bufferp errbuf) (kill-buffer errbuf))))) (setq send-mail-function 'vm-hack-sendmail-send-it)