# HG changeset patch # User Chong Yidong # Date 1228430970 0 # Node ID ecb3dba6e9322cf1bd3768bb1c9c7ab329088cb6 # Parent f525c6b7ac648fe240befbc850709dcc58314ab2 Sync with rmailout.el. diff -r f525c6b7ac64 -r ecb3dba6e932 lisp/mail/pmailout.el --- a/lisp/mail/pmailout.el Thu Dec 04 22:49:21 2008 +0000 +++ b/lisp/mail/pmailout.el Thu Dec 04 22:49:30 2008 +0000 @@ -1,4 +1,4 @@ -;;; pmailout.el --- "PMAIL" mail reader for Emacs: output message to a file. +;;; pmailout.el --- "PMAIL" mail reader for Emacs: output message to a file ;; Copyright (C) 1985, 1987, 1993, 1994, 2001, 2002, 2003, 2004, ;; 2005, 2006, 2007, 2008 Free Software Foundation, Inc. @@ -25,12 +25,9 @@ ;;; Code: +(require 'pmail) (provide 'pmailout) -(eval-when-compile - (require 'pmail) - (require 'pmaildesc)) - ;;;###autoload (defcustom pmail-output-file-alist nil "*Alist matching regexps to suggested output Pmail files. @@ -45,40 +42,70 @@ sexp))) :group 'pmail-output) -;;;###autoload -(defcustom pmail-fields-not-to-output nil - "*Regexp describing fields to exclude when outputting a message to a file." - :type '(choice (const :tag "None" nil) - regexp) - :group 'pmail-output) +(defun pmail-output-read-pmail-file-name () + "Read the file name to use for `pmail-output-to-pmail-file'. +Set `pmail-default-pmail-file' to this name as well as returning it." + (let ((default-file + (let (answer tail) + (setq tail pmail-output-file-alist) + ;; Suggest a file based on a pattern match. + (while (and tail (not answer)) + (save-excursion + (set-buffer pmail-buffer) + (goto-char (point-min)) + (if (re-search-forward (car (car tail)) nil t) + (setq answer (eval (cdr (car tail))))) + (setq tail (cdr tail)))) + ;; If no suggestions, use same file as last time. + (expand-file-name (or answer pmail-default-pmail-file))))) + (let ((read-file + (expand-file-name + (read-file-name + (concat "Output message to Pmail file (default " + (file-name-nondirectory default-file) + "): ") + (file-name-directory default-file) + (abbreviate-file-name default-file)) + (file-name-directory default-file)))) + ;; If the user enters just a directory, + ;; use the name within that directory chosen by the default. + (setq pmail-default-pmail-file + (if (file-directory-p read-file) + (expand-file-name (file-name-nondirectory default-file) + read-file) + read-file))))) (defun pmail-output-read-file-name () "Read the file name to use for `pmail-output'. Set `pmail-default-file' to this name as well as returning it." - (let* ((default-file - (with-current-buffer pmail-buffer - (expand-file-name - (or (catch 'answer - (dolist (i pmail-output-file-alist) - (goto-char (point-min)) - (when (re-search-forward (car i) nil t) - (throw 'answer (eval (cdr i)))))) - pmail-default-file)))) - (read-file - (expand-file-name - (read-file-name - (concat "Output message to Pmail (mbox) file: (default " - (file-name-nondirectory default-file) "): ") - (file-name-directory default-file) - (abbreviate-file-name default-file)) - (file-name-directory default-file)))) - (setq pmail-default-file - (if (file-directory-p read-file) + (let ((default-file + (let (answer tail) + (setq tail pmail-output-file-alist) + ;; Suggest a file based on a pattern match. + (while (and tail (not answer)) + (save-excursion + (goto-char (point-min)) + (if (re-search-forward (car (car tail)) nil t) + (setq answer (eval (cdr (car tail))))) + (setq tail (cdr tail)))) + ;; If no suggestion, use same file as last time. + (or answer pmail-default-file)))) + (let ((read-file + (expand-file-name + (read-file-name + (concat "Output message to Unix mail file (default " + (file-name-nondirectory default-file) + "): ") + (file-name-directory default-file) + (abbreviate-file-name default-file)) + (file-name-directory default-file)))) + (setq pmail-default-file + (if (file-directory-p read-file) + (expand-file-name (file-name-nondirectory default-file) + read-file) (expand-file-name - (file-name-nondirectory default-file) read-file) - (expand-file-name - (or read-file (file-name-nondirectory default-file)) - (file-name-directory default-file)))))) + (or read-file (file-name-nondirectory default-file)) + (file-name-directory default-file))))))) (declare-function pmail-update-summary "pmailsum" (&rest ignore)) @@ -86,7 +113,7 @@ ;;; look at them before you change the calling method. ;;;###autoload (defun pmail-output-to-pmail-file (file-name &optional count stay) - "Append the current message to an Pmail (mbox) file named FILE-NAME. + "Append the current message to an Pmail file named FILE-NAME. If the file does not exist, ask if it should be created. If file is being visited, the message is appended to the Emacs buffer visiting that file. @@ -101,35 +128,137 @@ If the optional argument STAY is non-nil, then leave the last filed message up instead of moving forward to the next non-deleted message." - (interactive (list (pmail-output-read-file-name) - (prefix-numeric-value current-prefix-arg))) - ;; Use the 'pmail-output function to perform the output. - (pmail-output file-name count nil nil) - ;; Deal with the next message - (if pmail-delete-after-output - (unless (if (and (= count 0) stay) + (interactive + (list (pmail-output-read-pmail-file-name) + (prefix-numeric-value current-prefix-arg))) + (or count (setq count 1)) + (setq file-name + (expand-file-name file-name + (file-name-directory pmail-default-pmail-file))) + (if (and (file-readable-p file-name) (not (mail-file-babyl-p file-name))) + (pmail-output file-name count) + (pmail-maybe-set-message-counters) + (setq file-name (abbreviate-file-name file-name)) + (or (find-buffer-visiting file-name) + (file-exists-p file-name) + (if (yes-or-no-p + (concat "\"" file-name "\" does not exist, create it? ")) + (let ((file-buffer (create-file-buffer file-name))) + (save-excursion + (set-buffer file-buffer) + (pmail-insert-pmail-file-header) + (let ((require-final-newline nil) + (coding-system-for-write + (or pmail-file-coding-system + 'emacs-mule-unix))) + (write-region (point-min) (point-max) file-name t 1))) + (kill-buffer file-buffer)) + (error "Output file does not exist"))) + (while (> count 0) + (let (redelete) + (unwind-protect + (progn + (set-buffer pmail-buffer) + ;; Temporarily turn off Deleted attribute. + ;; Do this outside the save-restriction, since it would + ;; shift the place in the buffer where the visible text starts. + (if (pmail-message-deleted-p pmail-current-message) + (progn (setq redelete t) + (pmail-set-attribute "deleted" nil))) + (save-restriction + (widen) + ;; Decide whether to append to a file or to an Emacs buffer. + (save-excursion + (let ((buf (find-buffer-visiting file-name)) + (cur (current-buffer)) + (beg (1+ (pmail-msgbeg pmail-current-message))) + (end (1+ (pmail-msgend pmail-current-message))) + (coding-system-for-write + (or pmail-file-coding-system + 'emacs-mule-unix))) + (if (not buf) + ;; Output to a file. + (if pmail-fields-not-to-output + ;; Delete some fields while we output. + (let ((obuf (current-buffer))) + (set-buffer (get-buffer-create " pmail-out-temp")) + (insert-buffer-substring obuf beg end) + (pmail-delete-unwanted-fields) + (append-to-file (point-min) (point-max) file-name) + (set-buffer obuf) + (kill-buffer (get-buffer " pmail-out-temp"))) + (append-to-file beg end file-name)) + (if (eq buf (current-buffer)) + (error "Can't output message to same file it's already in")) + ;; File has been visited, in buffer BUF. + (set-buffer buf) + (let ((buffer-read-only nil) + (msg (and (boundp 'pmail-current-message) + pmail-current-message))) + ;; If MSG is non-nil, buffer is in PMAIL mode. + (if msg + (progn + ;; Turn on auto save mode, if it's off in this + ;; buffer but enabled by default. + (and (not buffer-auto-save-file-name) + auto-save-default + (auto-save-mode t)) + (pmail-maybe-set-message-counters) + (widen) + (narrow-to-region (point-max) (point-max)) + (insert-buffer-substring cur beg end) + (goto-char (point-min)) + (widen) + (search-backward "\n\^_") + (narrow-to-region (point) (point-max)) + (pmail-delete-unwanted-fields) + (pmail-count-new-messages t) + (if (pmail-summary-exists) + (pmail-select-summary + (pmail-update-summary))) + (pmail-show-message msg)) + ;; Output file not in pmail mode => just insert at the end. + (narrow-to-region (point-min) (1+ (buffer-size))) + (goto-char (point-max)) + (insert-buffer-substring cur beg end) + (pmail-delete-unwanted-fields))))))) + (pmail-set-attribute "filed" t)) + (if redelete (pmail-set-attribute "deleted" t)))) + (setq count (1- count)) + (if pmail-delete-after-output + (unless + (if (and (= count 0) stay) (pmail-delete-message) (pmail-delete-forward)) - (setq count 0)) - (when (> count 0) - (unless (when (not stay) - (pmail-next-undeleted-message 1)) - (setq count 0))))) + (setq count 0)) + (if (> count 0) + (unless + (if (not stay) (pmail-next-undeleted-message 1)) + (setq count 0))))))) + +;;;###autoload +(defcustom pmail-fields-not-to-output nil + "*Regexp describing fields to exclude when outputting a message to a file." + :type '(choice (const :tag "None" nil) + regexp) + :group 'pmail-output) -(defun pmail-delete-unwanted-fields () - "Delete from the buffer header fields we don't want output." - (when pmail-fields-not-to-output - (save-excursion - (let ((limit (pmail-header-get-limit)) - (inhibit-point-motion-hooks t) - start) +;; Delete from the buffer header fields we don't want output. +;; NOT-PMAIL if t means this buffer does not have the full header +;; and *** EOOH *** that a message in an Pmail file has. +(defun pmail-delete-unwanted-fields (&optional not-pmail) + (if pmail-fields-not-to-output + (save-excursion (goto-char (point-min)) - (while (re-search-forward pmail-fields-not-to-output limit t) - (forward-line 0) - (setq start (point)) - (while (progn (forward-line 1) (looking-at "[ \t]+")) - (goto-char (line-end-position))) - (delete-region start (point))))))) + ;; Find the end of the header. + (if (and (or not-pmail (search-forward "\n*** EOOH ***\n" nil t)) + (search-forward "\n\n" nil t)) + (let ((end (point-marker))) + (goto-char (point-min)) + (while (re-search-forward pmail-fields-not-to-output end t) + (beginning-of-line) + (delete-region (point) + (progn (forward-line 1) (point))))))))) ;;; There are functions elsewhere in Emacs that use this function; ;;; look at them before you change the calling method. @@ -160,71 +289,111 @@ (and pmail-default-file (file-name-directory pmail-default-file)))) (if (and (file-readable-p file-name) (mail-file-babyl-p file-name)) - (error "BABYL output not supported.") - (with-current-buffer pmail-buffer - (let ((orig-count count) - (pmailbuf (current-buffer)) - (destbuf (find-buffer-visiting file-name)) - (case-fold-search t)) - (while (> count 0) - (with-temp-buffer - (insert-buffer-substring pmailbuf) - ;; ensure we can write without barfing on exotic characters - (setq buffer-file-coding-system - (or pmail-file-coding-system 'raw-text)) - ;; prune junk headers - (pmail-delete-unwanted-fields) - (if (not destbuf) - ;; The destination file is not being visited, just write - ;; out the processed message. - (write-region (point-min) (point-max) file-name - t (when noattribute 'nomsg)) - ;; The destination file is being visited. Update it. - (let ((msg-string (buffer-string))) - (with-current-buffer destbuf - ;; Determine if the destination file is an Pmail file. - (let ((buffer-read-only nil) - (dest-current-message - (and (boundp 'pmail-current-message) - pmail-current-message))) - (if dest-current-message - ;; The buffer is an Pmail buffer. Append the - ;; message. - (progn - (widen) - (narrow-to-region (point-max) (point-max)) - (insert msg-string) - (insert "\n") - (pmail-process-new-messages) - (pmail-show-message dest-current-message)) - ;; The destination file is not an Pmail file, just - ;; insert at the end. - (goto-char (point-max)) - (insert msg-string))))))) - (unless noattribute - (when (equal major-mode 'pmail-mode) - (pmail-set-attribute "filed" t) - (pmail-header-hide-headers))) - (setq count (1- count)) - (unless from-gnus + (pmail-output-to-pmail-file file-name count) + (set-buffer pmail-buffer) + (let ((orig-count count) + (pmailbuf (current-buffer)) + (case-fold-search t) + (tembuf (get-buffer-create " pmail-output")) + (original-headers-p + (and (not from-gnus) + (save-excursion + (save-restriction + (narrow-to-region (pmail-msgbeg pmail-current-message) (point-max)) + (goto-char (point-min)) + (forward-line 1) + (= (following-char) ?0))))) + header-beginning + mail-from mime-version content-type) + (while (> count 0) + ;; Preserve the Mail-From and MIME-Version fields + ;; even if they have been pruned. + (or from-gnus + (save-excursion + (save-restriction + (widen) + (goto-char (pmail-msgbeg pmail-current-message)) + (setq header-beginning (point)) + (search-forward "\n*** EOOH ***\n") + (narrow-to-region header-beginning (point)) + (setq mail-from (mail-fetch-field "Mail-From")) + (unless pmail-enable-mime + (setq mime-version (mail-fetch-field "MIME-Version") + content-type (mail-fetch-field "Content-type")))))) + (save-excursion + (set-buffer tembuf) + (erase-buffer) + (insert-buffer-substring pmailbuf) + (when pmail-enable-mime + (if original-headers-p + (delete-region (goto-char (point-min)) + (if (search-forward "\n*** EOOH ***\n") + (match-end 0))) + (goto-char (point-min)) + (forward-line 2) + (delete-region (point-min)(point)) + (search-forward "\n*** EOOH ***\n") + (delete-region (match-beginning 0) + (if (search-forward "\n\n") + (1- (match-end 0))))) + (setq buffer-file-coding-system (or pmail-file-coding-system + 'raw-text))) + (pmail-delete-unwanted-fields t) + (or (bolp) (insert "\n")) + (goto-char (point-min)) + (if mail-from + (insert mail-from "\n") + (insert "From " + (mail-strip-quoted-names (or (mail-fetch-field "from") + (mail-fetch-field "really-from") + (mail-fetch-field "sender") + "unknown")) + " " (current-time-string) "\n")) + (when mime-version + (insert "MIME-Version: " mime-version) + ;; Some malformed MIME messages set content-type to nil. + (when content-type + (insert "\nContent-type: " content-type "\n"))) + ;; ``Quote'' "\nFrom " as "\n>From " + ;; (note that this isn't really quoting, as there is no requirement + ;; that "\n[>]+From " be quoted in the same transparent way.) + (let ((case-fold-search nil)) + (while (search-forward "\nFrom " nil t) + (forward-char -5) + (insert ?>))) + (write-region (point-min) (point-max) file-name t + (if noattribute 'nomsg))) + (or noattribute + (if (equal major-mode 'pmail-mode) + (pmail-set-attribute "filed" t))) + (setq count (1- count)) + (or from-gnus (let ((next-message-p (if pmail-delete-after-output (pmail-delete-forward) - (when (> count 0) - (pmail-next-undeleted-message 1)))) + (if (> count 0) + (pmail-next-undeleted-message 1)))) (num-appended (- orig-count count))) - (when (and (> count 0) (not next-message-p)) - (error (format "Only %d message%s appended" num-appended - (if (= num-appended 1) "" "s"))) - (setq count 0))))))))) + (if (and next-message-p original-headers-p) + (pmail-toggle-header)) + (if (and (> count 0) (not next-message-p)) + (progn + (error "%s" + (save-excursion + (set-buffer pmailbuf) + (format "Only %d message%s appended" num-appended + (if (= num-appended 1) "" "s")))) + (setq count 0)))))) + (kill-buffer tembuf)))) ;;;###autoload (defun pmail-output-body-to-file (file-name) "Write this message body to the file FILE-NAME. FILE-NAME defaults, interactively, from the Subject field of the message." (interactive - (let ((default-file (or (mail-fetch-field "Subject") - pmail-default-body-file))) + (let ((default-file + (or (mail-fetch-field "Subject") + pmail-default-body-file))) (list (setq pmail-default-body-file (read-file-name "Output message body to file: " @@ -232,21 +401,20 @@ default-file nil default-file))))) (setq file-name - (expand-file-name - file-name - (and pmail-default-body-file - (file-name-directory pmail-default-body-file)))) + (expand-file-name file-name + (and pmail-default-body-file + (file-name-directory pmail-default-body-file)))) (save-excursion (goto-char (point-min)) (search-forward "\n\n") (and (file-exists-p file-name) - (not (y-or-n-p (message "File %s exists; overwrite? " file-name))) + (not (y-or-n-p (format "File %s exists; overwrite? " file-name))) (error "Operation aborted")) (write-region (point) (point-max) file-name) - (when (equal major-mode 'pmail-mode) - (pmail-desc-set-attribute pmail-current-message pmail-desc-stored-index t))) - (when pmail-delete-after-output - (pmail-delete-forward))) + (if (equal major-mode 'pmail-mode) + (pmail-set-attribute "stored" t))) + (if pmail-delete-after-output + (pmail-delete-forward))) ;; Local Variables: ;; change-log-default-name: "ChangeLog.pmail"