* mu4e: abstract access to the mu4e-message plist

This commit is contained in:
djcb 2012-09-26 12:25:38 +03:00
parent 3f305f665a
commit 7d831184b7
6 changed files with 210 additions and 137 deletions

View File

@ -30,6 +30,7 @@ dist_lisp_LISP= \
mu4e-headers.el \
mu4e-main.el \
mu4e-mark.el \
mu4e-message.el \
mu4e-meta.el \
mu4e-proc.el \
mu4e-speedbar.el \

View File

@ -99,7 +99,6 @@ forwarded or edited) in `mu4e-compose-pre-hook.")
"message/rfc822"
(or (plist-get mu4e-captured-message :subject) "No subject")
"attachment")))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defun mu4e~compose-user-agent-construct ()
@ -137,8 +136,8 @@ comma-separated string. Normally, this the concatenation of the
existing References (which may be empty) and the message-id. If the
message-id is empty, returns the old References. If both are empty,
return nil."
(let ((refs (plist-get msg :references))
(old-msgid (plist-get msg :message-id)))
(let ((refs (mu4e-message-field msg :references))
(old-msgid (mu4e-message-field msg :message-id)))
(when old-msgid
(setq refs (append refs (list old-msgid)))
(mapconcat

View File

@ -39,6 +39,7 @@
(require 'mu4e-mark)
(require 'mu4e-compose)
(require 'mu4e-actions)
(require 'mu4e-message)
;; the headers view
(defgroup mu4e-headers nil
@ -197,7 +198,7 @@ in the database. This function will update the current list of
headers."
(when (buffer-live-p mu4e~headers-buffer)
(with-current-buffer mu4e~headers-buffer
(let* ((docid (plist-get msg :docid))
(let* ((docid (mu4e-message-field msg :docid))
(point (mu4e~headers-docid-pos docid)))
(when point ;; is the message present in this list?
@ -316,27 +317,27 @@ display may be different)."
`mu4e-user-mail-address-regexp', show the To address; otherwise
show the from address; prefixed with the appropriate
`mu4e-headers-from-or-to-prefix'."
(let ((addr (cdr-safe (car-safe (plist-get msg :from)))))
(let ((addr (cdr-safe (car-safe (mu4e-message-field msg :from)))))
(if (and addr (string-match mu4e-user-mail-address-regexp addr))
(concat (cdr mu4e-headers-from-or-to-prefix)
(mu4e~headers-contact-str (plist-get msg :to)))
(mu4e~headers-contact-str (mu4e-message-field msg :to)))
(concat (car mu4e-headers-from-or-to-prefix)
(mu4e~headers-contact-str (plist-get msg :from))))))
(mu4e~headers-contact-str (mu4e-message-field msg :from))))))
;; note: this function is very performance-sensitive
(defun mu4e~headers-header-handler (msg &optional point)
"Create a one line description of MSG in this buffer, at POINT,
if provided, or at the end of the buffer otherwise."
(let ((docid (plist-get msg :docid)) (line ""))
(let ((docid (mu4e-message-field msg :docid)) (line ""))
(dolist (f-w mu4e-headers-fields)
(let ((field (car f-w)) (width (cdr f-w))
(val (plist-get msg (car f-w))) (str))
(val (mu4e-message-field msg (car f-w))) (str))
(setq str
(case field
(:subject
(concat ;; prefix subject with a thread indicator
(mu4e~headers-thread-prefix (plist-get msg :thread))
;; "["(plist-get (plist-get msg :thread) :path) "] "
(mu4e~headers-thread-prefix (mu4e-message-field msg :thread))
;; "["(plist-get (mu4e-message-field msg :thread) :path) "] "
val))
((:maildir :path) val)
((:to :from :cc :bcc) (mu4e~headers-contact-str val))
@ -356,7 +357,7 @@ if provided, or at the end of the buffer otherwise."
(truncate-string-to-width str width 0 ?\s t)) " ")))))
;; now, propertize it.
(setq line (propertize line 'face
(case (car-safe (plist-get msg :flags))
(case (car-safe (mu4e-message-field msg :flags))
('draft 'mu4e-draft-face)
('trash 'mu4e-trashed-face)
((unread new) 'mu4e-unread-face)
@ -875,7 +876,7 @@ matching messages with that mark."
(defun mu4e~headers-get-thread-info (msg what)
"Get WHAT (a symbol, either path or thread-id) for MSG."
(let* ((thread (or (plist-get msg :thread)
(let* ((thread (or (mu4e-message-field msg :thread)
(mu4e-error "No thread info found")))
(path (or (plist-get thread :path)
(mu4e-error "No threadpath found"))))

164
mu4e/mu4e-message.el Normal file
View File

@ -0,0 +1,164 @@
;;; mu4e-message.el -- part of mu4e, the mu mail user agent
;;
;; Copyright (C) 2012 Dirk-Jan C. Binnema
;; Author: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
;; Maintainer: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
;; This file is not part of GNU Emacs.
;;
;; GNU Emacs is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;; Functions to get data from mu4e-message plist structure
;;; Code:
(eval-when-compile (byte-compile-disable-warning 'cl-functions))
(require 'cl)
(require 'html2text)
(defcustom mu4e-html2text-command nil
"Shell command that converts HTML from stdin into plain text on
stdout. If this is not defined, the emacs `html2text' tool will be
used when faced with html-only message. If you use htmltext, it's
recommended you use \"html2text -utf8 -width 72\"."
:type 'string
:group 'mu4e-view
:safe 'stringp)
(defcustom mu4e-view-prefer-html nil
"Whether to base the body display on the HTML-version of the
e-mail message (if there is any."
:type 'boolean
:group 'mu4e-view)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defsubst mu4e-message-field (msg field)
"Retrieve FIELD from message plist MSG. FIELD is one
of :from, :to, :cc, :bcc, :subject, :data, :message-id, :path, :maildir,
:priority, :attachments, :references, :in-reply-to, :body-txt, :body-html
A message plist looks something like:
\(:docid 32461
:from ((\"Nikola Tesla\" . \"niko@example.com\"))
:to ((\"Thomas Edison\" . \"tom@example.com\"))
:cc ((\"Rupert The Monkey\" . \"rupert@example.com\"))
:subject \"RE: what about the 50K?\"
:date (20369 17624 0)
:size 4337
:message-id \"6BDC23465F79238C8233AB82D81EE81AF0114E4E74@123213.mail.example.com\"
:path \"/home/tom/Maildir/INBOX/cur/133443243973_1.10027.atlas:2,S\"
:maildir \"/INBOX\"
:priority normal
:flags (seen)
:attachments
((:index 2 :name \"photo.jpg\" :mime-type \"image/jpeg\" :size 147331)
(:index 3 :name \"book.pdf\" :mime-type \"application/pdf\" :size 192220))
:references (\"6BDC23465F79238C8384574032D81EE81AF0114E4E74@123213.mail.example.com\"
\"6BDC23465F79238203498230942D81EE81AF0114E4E74@123213.mail.example.com\")
:in-reply-to \"6BDC23465F79238203498230942D81EE81AF0114E4E74@123213.mail.example.com\"
:body-txt \"Hi Tom, ...\"
\)).
Some notes on the format:
- The address fields are lists of pairs (NAME . EMAIL), where NAME can be nil.
- The date is in format emacs uses in `current-time'
- Attachments are a list of elements with fields :index (the number of
the MIME-part), :name (the file name, if any), :mime-type (the
MIME-type, if any) and :size (the size in bytes, if any).
- Messages in the Headers view come from the database and do not have
:attachments. :body-txt or :body-html fields. Message in the
Message view use the actual message file, and do include these fields."
;; after all this documentation, the spectacular implementation
(plist-get msg field))
(defun mu4e-message-for-each (msg field func)
"Call FUNC for each element in the field FIELD (which must be a
lists-type field). FUNC takes the element as its arg."
(let ((lst (mu4e-message-field msg field)))
(unless (listp lst)
(error "Not a list type"))
(dolist (elm (mu4e-message-field msg field))
(funcall func elm))))
(defun mu4e-message-for-each-contact-field (msg field func)
"Call FUNC for each element of contact
FIELD (:to, :cc, :bcc, :from). FUNC takes two args, strings
name (possibly nil) and an email address."
(unless (member field '(:to :from :bcc :cc))
(error "Not a contacts field"))
(mu4e-message-for-each msg field
(lambda (contact)
(funcall func (car contact) (cdr contact)))))
(defun mu4e-message-body-text (msg)
"Get the body in text form for this message, which is either :body-txt,
or if not available, :body-html converted to text. By default, it
uses the emacs built-in `html2text'. Alternatively, if
`mu4e-html2text-command' is non-nil, it will use that. Normally,
function prefers the text part, but this can be changed by setting
`mu4e-view-prefer-html'."
(let* ((txt (mu4e-message-field msg :body-txt))
(html (mu4e-message-field msg :body-html))
(body
(cond
;; does it look like some text? ie., 10x the length of the text
;; should be longer than the html, an heuristic to guard against
;; 'This messages requires html' text bodies.
((and (> (* 10 (length txt)) (length html))
;; use html if it's prefered, unless there is no html
(or (not mu4e-view-prefer-html) (not html)))
txt)
;; otherwise, it there some html?
(html
(with-temp-buffer
(insert html)
;; if defined, use the external tool
(if mu4e-html2text-command
(shell-command-on-region (point-min) (point-max)
mu4e-html2text-command nil t)
;; otherwise...
(html2text))
(buffer-string)))
(t ;; otherwise, an empty body
""))))
;; and finally, remove some crap from the remaining string; it seems
;; esp. outlook lies about its encoding (ie., it says 'iso-8859-1' but
;; really it's 'windows-1252'), thus giving us these funky chars. here, we
;; either remove them, or replace with 'what-was-meant' (heuristically)
(with-temp-buffer
(insert body)
(goto-char (point-min))
(while (re-search-forward "[  ’]" nil t)
(replace-match
(cond
((string= (match-string 0) "’") "'")
(t ""))))
(buffer-string))))
(defsubst mu4e-message-part-field (msgpart field)
"Get some field in a message part; a part would look something like:
(:index 2 :name \"photo.jpg\" :mime-type \"image/jpeg\" :size 147331)."
(plist-get msgpart field))
(provide 'mu4e-message)

View File

@ -28,25 +28,11 @@
(eval-when-compile (byte-compile-disable-warning 'cl-functions))
(require 'cl)
(require 'html2text)
(require 'mu4e-message)
(require 'mu4e-vars)
(require 'mu4e-about)
(require 'doc-view)
(defcustom mu4e-html2text-command nil
"Shell command that converts HTML from stdin into plain text on
stdout. If this is not defined, the emacs `html2text' tool will be
used when faced with html-only message. If you use htmltext, it's
recommended you use \"html2text -utf8 -width 72\"."
:type 'string
:group 'mu4e-view
:safe 'stringp)
(defcustom mu4e-view-prefer-html nil
"Whether to base the body display on the HTML-version of the
e-mail message (if there is any."
:type 'boolean
:group 'mu4e-view)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -377,52 +363,8 @@ http://cr.yp.to/proto/maildir.html "
(t (propertize "?" 'face 'mu4e-system-face))))
(defun mu4e-body-text (msg)
"Get the body in text form for this message, which is either :body-txt,
or if not available, :body-html converted to text. By default, it
uses the emacs built-in `html2text'. Alternatively, if
`mu4e-html2text-command' is non-nil, it will use that. Normally,
function prefers the text part, but this can be changed by setting
`mu4e-view-prefer-html'."
(let* ((txt (plist-get msg :body-txt))
(html (plist-get msg :body-html))
(body
(cond
;; does it look like some text? ie., 10x the length of the text
;; should be longer than the html, an heuristic to guard against
;; 'This messages requires html' text bodies.
((and (> (* 10 (length txt)) (length html))
;; use html if it's prefered, unless there is no html
(or (not mu4e-view-prefer-html) (not html)))
txt)
;; otherwise, it there some html?
(html
(with-temp-buffer
(insert html)
;; if defined, use the external tool
(if mu4e-html2text-command
(shell-command-on-region (point-min) (point-max)
mu4e-html2text-command nil t)
;; otherwise...
(html2text))
(buffer-string)))
(t ;; otherwise, an empty body
""))))
;; and finally, remove some crap from the remaining string; it seems
;; esp. outlook lies about its encoding (ie., it says 'iso-8859-1' but
;; really it's 'windows-1252'), thus giving us these funky chars. here, we
;; either remove them, or replace with 'what-was-meant' (heuristically)
(with-temp-buffer
(insert body)
(goto-char (point-min))
(while (re-search-forward "[  ’]" nil t)
(replace-match
(cond
((string= (match-string 0) "’") "'")
(t ""))))
(buffer-string))))
(defalias 'mu4e-body-text 'mu4e-message-body-text) ;; backward compatibility
(defun mu4e-display-manual ()
"Display the mu4e manual page for the current mode, or go to the
@ -435,44 +377,8 @@ top level if there is none."
(t "mu4e"))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defsubst mu4e-msg-field (msg field)
"Retrieve FIELD from message plist MSG. FIELD is one
of :from, :to, :cc, :bcc, :subject, :data, :message-id, :path, :maildir,
:priority, :attachments, :references, :in-reply-to, :body-txt, :body-html
A message plist looks something like:
\(:docid 32461
:from ((\"Nikola Tesla\" . \"niko@example.com\"))
:to ((\"Thomas Edison\" . \"tom@example.com\"))
:cc ((\"Rupert The Monkey\" . \"rupert@example.com\"))
:subject \"RE: what about the 50K?\"
:date (20369 17624 0)
:size 4337
:message-id \"6BDC23465F79238C8233AB82D81EE81AF0114E4E74@123213.mail.example.com\"
:path \"/home/tom/Maildir/INBOX/cur/133443243973_1.10027.atlas:2,S\"
:maildir \"/INBOX\"
:priority normal
:flags (seen)
:attachments
((:index 2 :name \"photo.jpg\" :mime-type \"image/jpeg\" :size 147331)
(:index 3 :name \"book.pdf\" :mime-type \"application/pdf\" :size 192220))
:references (\"6BDC23465F79238C8384574032D81EE81AF0114E4E74@123213.mail.example.com\"
\"6BDC23465F79238203498230942D81EE81AF0114E4E74@123213.mail.example.com\")
:in-reply-to \"6BDC23465F79238203498230942D81EE81AF0114E4E74@123213.mail.example.com\"
:body-txt \"Hi Tom, ...\"
\)).
Some notes on the format:
- The address fields are lists of pairs (NAME . EMAIL), where NAME can be nil.
- The date is in format emacs uses in `current-time'
- Attachments are a list of elements with fields :index (the number of
the MIME-part), :name (the file name, if any), :mime-type (the
MIME-type, if any) and :size (the size in bytes, if any).
- Messages in the Headers view come from the database and do not have
:attachments. :body-txt or :body-html fields. Message in the
Message view use the actual message file, and do include these fields."
;; after all this documentation, the spectacular implementation
(plist-get msg field))
(defalias 'mu4e-msg-field 'mu4e-message-field) ;; backward compatibility
(defun mu4e-message-at-point (&optional raise-err)
"Get the message s-expression for the message at point in either
the headers buffer or the view buffer, or nil if there is no such

View File

@ -32,6 +32,7 @@
(require 'mu4e-proc)
(require 'mu4e-compose)
(require 'mu4e-actions)
(require 'mu4e-message)
(require 'longlines)
(require 'comint)
@ -144,7 +145,7 @@ plist."
(concat
(mapconcat
(lambda (field)
(let ((fieldval (plist-get msg field)))
(let ((fieldval (mu4e-message-field msg field)))
(case field
(:subject (mu4e~view-construct-header field fieldval))
(:path (mu4e~view-construct-header field fieldval))
@ -159,7 +160,7 @@ plist."
;; if we (`user-mail-address' are the From, show To, otherwise,
;; show From
(:from-or-to
(let* ((from (plist-get msg :from))
(let* ((from (mu4e-message-field msg :from))
(from (and from (cdar from))))
(if (and from (string-match mu4e-user-mail-address-regexp from))
(mu4e~view-construct-contacts-header msg :to)
@ -180,7 +181,7 @@ plist."
(t (mu4e-error "Unsupported field: %S" field)))))
mu4e-view-fields "")
"\n"
(mu4e-body-text msg)
(mu4e-message-body-text msg)
;; ;; decrypt maybe; depends on whether there are any such parts
;; ;; and the value of `mu4e-view-decrypt-parts'
;; (mu4e~decrypt-parts-maybe msg)
@ -204,7 +205,8 @@ REFRESH is for re-showing an already existing message.
As a side-effect, a message that is being viewed loses its 'unread'
marking if it still had that."
(let* ((embedded ;; is it registered as an embedded msg?
(when (gethash (plist-get msg :path) mu4e~path-parent-docid-map) t))
(when (gethash (mu4e-message-field msg :path)
mu4e~path-parent-docid-map) t))
(buf
(if embedded
(mu4e~view-embedded-winbuf)
@ -315,7 +317,7 @@ at POINT, or if nil, at (point)."
(format "<%s>\n%s\n%s" email
"[mouse-1] or [M-RET] to toggle long/short display"
"[mouse-2] or C to compose a mail for this recipient"))))
(plist-get msg field) ", ") t))
(mu4e-message-field msg field) ", ") t))
(defun mu4e~view-construct-flags-header (flags)
@ -331,10 +333,10 @@ at POINT, or if nil, at (point)."
(defun mu4e~view-construct-signature-header (msg)
"Construct a Signature: header, if there are any signed parts."
(let* ((parts (plist-get msg :parts))
(let* ((parts (mu4e-message-field msg :parts))
(verdicts
(remove-if 'null
(mapcar (lambda (part) (plist-get part :signature)) parts)))
(mapcar (lambda (part) (mu4e-message-part-field part :signature)) parts)))
(val (when verdicts
(mapconcat
(lambda (v)
@ -379,21 +381,21 @@ at POINT, or if nil, at (point)."
;; user-visible numbers and the part indices
(remove-if-not
(lambda (part)
(let ((mtype (plist-get part :mime-type))
(isattach (member 'attachment (plist-get part :type))))
(let ((mtype (mu4e-message-part-field part :mime-type))
(isattach (member 'attachment (mu4e-message-part-field part :type))))
(or ;; remove if it's not an attach *or* if it's an
;; image/audio/application type (but not a signature)
isattach
(string-match "^\\(image\\|audio\\)" mtype)
(and (string-match "^application" mtype)
(not (string-match "signature" mtype))))))
(plist-get msg :parts)))
(mu4e-message-field msg :parts)))
(attstr
(mapconcat
(lambda (part)
(let ((index (plist-get part :index))
(name (plist-get part :name))
(size (plist-get part :size))
(let ((index (mu4e-message-part-field part :index))
(name (mu4e-message-part-field part :name))
(size (mu4e-message-part-field part :size))
(map (make-sparse-keymap)))
(incf id)
(puthash id index mu4e~view-attach-map)
@ -431,8 +433,8 @@ at POINT, or if nil, at (point)."
;; (let ((str ""))
;; (mu4e-view-for-each-part msg
;; (lambda (msg part)
;; (when (member 'encrypted (plist-get part :type))
;; (let ((file (plist-get part :temp)))
;; (when (member 'encrypted (mu4e-message-part-field part :type))
;; (let ((file (mu4e-message-part-field part :temp)))
;; (when (and file (file-exists-p file))
;; ;; if our mu-server was build with crypto support, we only use EPA
;; ;; to push the password into gpg's memory
@ -730,8 +732,8 @@ browser is called is depending on `browse-url-browser-function' and
(when (and (display-images-p) mu4e-show-images)
(mu4e-view-for-each-part msg
(lambda (msg part)
(when (string-match "^image/" (plist-get part :mime-type))
(let ((imgfile (plist-get part :temp)))
(when (string-match "^image/" (mu4e-message-part-field part :mime-type))
(let ((imgfile (mu4e-message-part-field part :temp)))
(when (and imgfile (file-exists-p imgfile))
(save-excursion
(goto-char (point-max))
@ -893,8 +895,8 @@ number ATTNUM."
(attach
(find-if
(lambda (part)
(eq (plist-get part :index) partid))
(plist-get msg :parts))))
(eq (mu4e-message-part-field part :index) partid))
(mu4e-message-field msg :parts))))
(or attach (mu4e-error "Not a valid attachment"))))
@ -920,7 +922,7 @@ message-at-point if nil) to disk."
(and (file-exists-p path)
(not (y-or-n-p (mu4e-format "Overwrite '%s'?" path))))))
(mu4e~proc-extract
'save (plist-get msg :docid) index path)))
'save (mu4e-message-field msg :docid) index path)))
(defun mu4e-view-save-attachment-multi (&optional msg)
"Offer to save multiple email attachments from the current message.
@ -958,7 +960,7 @@ message-at-point if nil)."
(mu4e~view-get-attach-num "Attachment to open" msg)))
(att (or (mu4e~view-get-attach msg attnum)))
(index (plist-get att :index))
(docid (plist-get msg :docid))
(docid (mu4e-message-field msg :docid))
(mimetype (plist-get att :mime-type)))
(if (and mimetype (string= mimetype "message/rfc822"))
;; special handling for message-attachments; we open them in mu4e. we also
@ -990,7 +992,7 @@ user for it."
(mu4e-format "Shell command to open it with: ")
nil 'mu4e~view-open-with-hist)))
(index (plist-get att :index)))
(mu4e~view-temp-action (plist-get msg :docid) index "open-with" cmd)))
(mu4e~view-temp-action (mu4e-message-field msg :docid) index "open-with" cmd)))
(defvar mu4e~view-pipe-hist nil
"History list for the pipe argument.")
@ -1006,7 +1008,7 @@ PIPECMD is nil, ask user for it."
nil
'mu4e~view-pipe-hist)))
(index (plist-get att :index)))
(mu4e~view-temp-action (plist-get msg :docid) index "pipe" pipecmd)))
(mu4e~view-temp-action (mu4e-message-field msg :docid) index "pipe" pipecmd)))
(defun mu4e-view-open-attachment-emacs (msg attachnum)
@ -1014,7 +1016,7 @@ PIPECMD is nil, ask user for it."
(interactive)
(let* ((att (mu4e~view-get-attach msg attachnum))
(index (plist-get att :index)))
(mu4e~view-temp-action (plist-get msg :docid) index "emacs")))
(mu4e~view-temp-action (mu4e-message-field msg :docid) index "emacs")))
(defun mu4e-view-attachment-action (&optional msg)
@ -1171,7 +1173,7 @@ the results."
"Pop-up a little signature verification window for (optional) MSG
or message-at-point."
(interactive)
(let* ((path (if msg (plist-get msg :path) (mu4e-field-at-point :path)))
(let* ((path (if msg (mu4e-message-field msg :path) (mu4e-field-at-point :path)))
(cmd (format "%s verify --verbose %s"
mu4e-mu-binary
(shell-quote-argument path)))