branch: elpa/org-mime commit 5aa551a9a266a1d6485d995a9075a463c0b76ab1 Author: Chen Bin <chenbin...@gmail.com> Commit: Chen Bin <chenbin...@gmail.com>
Revert to 1fc8edb and cancel gmail feature This reverts commit 1a57b9c926e8b23b93c8c376a6d37d34fc949285. --- .gitignore | 3 - README.org | 2 +- org-mime.el | 198 ++++++++++++++++++++++--------------------------- test/org-mime-tests.el | 128 +++++++++++++++++++------------- 4 files changed, 163 insertions(+), 168 deletions(-) diff --git a/.gitignore b/.gitignore index d29af04fd3..5ed05fd76d 100644 --- a/.gitignore +++ b/.gitignore @@ -16,9 +16,6 @@ tramp .org-id-locations *_archive -# ELPA -generated files -/org-mime-autoloads.el -/org-mime-pkg.el ### /Users/cb/.gitignore-boilerplates/Global/Vim.gitignore diff --git a/README.org b/README.org index 2330c65ceb..f319ba8d5f 100644 --- a/README.org +++ b/README.org @@ -95,7 +95,7 @@ Below code renders text between "#" in red color, #+end_src For other customization options see the org-mime customization group. ** Beautify quoted mail when replying -It already works out of box. Currently it emulates Gmail's style. +It already works out of box. Currently it emulates Gmail's style. You can go back the old style by =(setq org-mime-beautify-quoted-mail-p nil)=. ** Export options To avoid exporting TOC, you can setup =org-mime-export-options= which overrides Org default settings (but still inferior to file-local settings), #+begin_src elisp diff --git a/org-mime.el b/org-mime.el index 332b428b77..51617ba98e 100644 --- a/org-mime.el +++ b/org-mime.el @@ -1,12 +1,12 @@ -;;; org-mime.el --- org html export for text/html MIME emails -*- lexical-binding: t; -*- +;;; org-mime.el --- org html export for text/html MIME emails ;; Copyright (C) 2010-2015 Eric Schulte, 2016-2021 Chen Bin ;; Author: Eric Schulte -;; Maintainer: Chen Bin <chenbin...@gmail.com> +;; Maintainer: Chen Bin (redguardtoo) ;; Keywords: mime, mail, email, html ;; Homepage: http://github.com/org-mime/org-mime -;; Version: 0.3.0 +;; Version: 0.2.6 ;; Package-Requires: ((emacs "25.1")) ;; This file is not part of GNU Emacs. @@ -109,7 +109,8 @@ ;; (while (re-search-forward "#\\([^#]*\\)#" nil t) ;; (replace-match "<span style=\"color:red\">\\1</span>")))) ;; -;; 3. The quoted mail uses Gmail's style, so reply looks clean and modern. +;; 3. The quoted mail uses Gmail's style, so mail replies looks clean and modern. +;; If you prefer the old style, please set `org-mime-beautify-quoted-mail-p' to nil. ;; ;; 4. Please note this program can only embed exported HTML into mail. ;; Org-mode is responsible for rendering HTML. @@ -126,8 +127,11 @@ (require 'outline) (require 'org) (require 'ox-org) -(require 'message) -(require 'sendmail) + +(defcustom org-mime-beautify-quoted-mail-p t + "Beautify quoted mail in more clean HTML, like Gmail." + :group 'org-mime + :type 'boolean) (defcustom org-mime-use-property-inheritance nil "Non-nil means al MAIL_ properties apply also for sub-levels." @@ -171,13 +175,6 @@ Default (nil) selects the original org file." :group 'org-mime :type 'string) -(defcustom org-mime-mail-quoted-separators - '("^>>>>>[^>=]+==\\([^=\r\n]+\\)$" - "^\\(On [^\r\n]+ wrote:\\)$") - "Possible separators. Below the separator is mostly quoted mail." - :group 'org-mime - :type '(repeat string)) - (defvar org-mime-export-options '(:with-latex dvipng) "Default export options which may override org buffer/subtree options. You could avoid exporting section-number/author/toc. @@ -300,6 +297,47 @@ SUBTREEP is t if current node is subtree." (buffer-string))))) (vm "?"))) +(defun org-mime-beautify-quoted (html) + "Beautify quoted mail in modern UI style. +HTML is the body of the message." + (when org-mime-debug + (message "org-mime-beautify-quoted called => %s" html)) + (let ((quote-depth 0) + (line-depth 0) + (in-quote-p nil) + (quote-opening "<blockquote class=\"gmail_quote\" style=\"margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex\">\n\n<div>") + (quote-closing "\n</div></blockquote>\n")) + (with-temp-buffer + ;; clean title of quoted + (insert (replace-regexp-in-string + "<p>[\n\r]*>>>>> .* == \\([^\r\n]*\\)[\r\n]*</p>" + "<div class=\"gmail_quote\">\\1</div>" + html)) + (goto-char (point-min)) + (while (not (eobp)) + (setq line-depth 0) + (setq in-quote-p nil) + (while (looking-at ">[ \t]*") + (setq in-quote-p t) + (replace-match "") + (cl-incf line-depth)) + (cond + ((< quote-depth line-depth) + (while (< quote-depth line-depth) + (insert quote-opening) + (cl-incf quote-depth))) + ((> quote-depth line-depth) + (while (> quote-depth line-depth) + (insert quote-closing) + (cl-decf quote-depth)))) + (if (and in-quote-p (looking-at "^[ \t]*$")) + (progn + (insert "</div>\n<div>") + (forward-line) + (insert "<br /></div>\n<div>")) + (forward-line))) + (buffer-substring (point-min) (point-max))))) + (defun org-mime-multipart (plain html &optional images) "Markup PLAIN body a multipart/alternative with HTML alternatives. If html portion of message includes IMAGES they are wrapped in @@ -309,7 +347,9 @@ multipart/related part." plain (when images "<#multipart type=related>") "<#part type=text/html>\n" - html + (if org-mime-beautify-quoted-mail-p + (org-mime-beautify-quoted html) + html) images (when images "<#/multipart>\n") "<#/multipart>\n")) @@ -406,14 +446,12 @@ CURRENT-FILE is used to calculate full path of images." (buffer-string)) html)) - -(defun org-mime-insert-html-content (plain file html) - "Insert PLAIN into FILE with HTML content." - (defvar org-export-htmlize-output-type) +(defun org-mime-insert-html-content (plain file html opts) + "Insert PLAIN into FILE with HTML content and OPTS." (let* ((files (org-mime-extract-non-image-files)) - ;; Use dvipng for inline latex because MathJax doesn't work in mail + ;; dvipng for inline latex because MathJax doesn't work in mail ;; Also @see https://github.com/org-mime/org-mime/issues/16 - ;; Looks "(setq org-html-with-latex nil)" may be useful + ;; (setq org-html-with-latex nil) sometimes useful (org-html-with-latex org-mime-org-html-with-latex-default) ;; we don't want to convert org file links to html (org-html-link-org-files-as-html nil) @@ -441,7 +479,7 @@ CURRENT-FILE is used to calculate full path of images." (insert (org-mime-multipart (org-mime-apply-plain-text-hook plain) html - (if images (mapconcat #'identity images "\n")))) + (if images (mapconcat 'identity images "\n")))) ;; Attach any residual files (when files @@ -457,6 +495,13 @@ CURRENT-FILE is used to calculate full path of images." (search-forward mail-header-separator) (+ (point) 1))) +(defun org-mime-mail-signature-begin () + "Find start of signature line in email." + (save-excursion + (goto-char (point-max)) + (re-search-backward org-mime-mail-signature-separator nil t nil))) + + (defmacro org-mime-extract-tag-in-current-buffer (beginning end result) "Extract the text between BEGINNING and END and insert it into RESULT." `(when (and ,beginning ,end (< ,beginning ,end)) @@ -500,49 +545,6 @@ CURRENT-FILE is used to calculate full path of images." (list :secure-tags (nreverse secure-tags) :part-tags (nreverse part-tags))))) -(defun org-mime-find-quoted-separator (content) - "Find correct separator to extract quoted mail from CONTENT." - (let ((rlt (cl-find-if (lambda (p) - (string-match p content)) - org-mime-mail-quoted-separators))) - (when org-mime-debug - (message "org-mime-find-quoted-separator called => %s" rlt)) - rlt)) - -(defun org-mime-extract-my-reply (content) - "Extract my reply from CONTENT (no quoted content)." - (let* ((quoted-separator (org-mime-find-quoted-separator content)) - (arr (if quoted-separator (split-string content quoted-separator) - (list content))) - rlt) - - ;; extract reply - (setq rlt (plist-put rlt 'REPLY-MAIN (car arr))) - - ;; extract quoted mail - (when (> (length arr) 1) - (when (and quoted-separator (string-match quoted-separator content) ) - (setq rlt (plist-put rlt - 'REPLY-QUOTED-TITLE - (string-trim (match-string 1 content))))) - (setq arr (split-string (nth 1 arr) - org-mime-mail-signature-separator)) - (setq rlt (plist-put rlt - 'REPLY-QUOTED-MAIN - (car arr)))) - - ;; extract signature and other stuff - (when (> (length arr) 1) - (setq rlt (plist-put rlt - 'REPLY-SIGNATURE - (mapconcat 'identity (cdr arr) "--")))) - - (when org-mime-debug - (message "org-mime-extract-my-reply called. content=%s rlt=%s" - content - rlt)) - rlt)) - ;;;###autoload (defun org-mime-htmlize () "Export a portion of an email to html using `org-mode'. @@ -558,17 +560,14 @@ If called with an active region only export that region, otherwise entire body." (or (and region-p (region-beginning)) (org-mime-mail-body-begin)))) (html-end (or (and region-p (region-end)) - (point-max))) - (parsed-reply (org-mime-extract-my-reply (buffer-substring html-start - html-end))) - (reply-main (plist-get parsed-reply 'REPLY-MAIN)) - (reply-quoted-title (plist-get parsed-reply 'REPLY-QUOTED-TITLE)) - (reply-quoted-main (plist-get parsed-reply 'REPLY-QUOTED-MAIN)) - (reply-signature (plist-get parsed-reply 'REPLY-SIGNATURE)) + (or + (org-mime-mail-signature-begin) + (point-max)))) + (org-text (buffer-substring html-start html-end)) ;; to hold attachments for inline html images (opts (org-mime-get-buffer-export-options)) - (plain (org-mime-export-ascii-maybe reply-main)) - html + (plain (org-mime-export-ascii-maybe org-text)) + (html (org-mime-export-string org-text opts)) (file (make-temp-name (expand-file-name "mail" temporary-file-directory)))) @@ -581,35 +580,8 @@ If called with an active region only export that region, otherwise entire body." (insert (mapconcat #'identity secure-tags "\n")) (insert "\n")) - (setq html (org-mime-export-string reply-main opts)) - - (when (and reply-quoted-title reply-quoted-main) - (setq html (concat html - ;; class name is magic in google mail? - "<div class=\"im\">\n" - " <div class=\"gmail_quote\">\n" - " <div class=\"gmail_attr\">\n" - reply-quoted-title - "\n" - " <br>\n" - " </div>\n" - " <blockquote class=\"gmail_quote\">\n" - (org-mime-export-string reply-quoted-main opts) - "\n" - " </blockquote>\n" - " </div>\n" - "</div>\n"))) - - (when reply-signature - (setq html (concat html - "<br clear=\"all\">\n" - "<div class=\"gmail_signature\">\n" - " <br>--<br>\n" - (replace-regexp-in-string "^--\s*\\|[\r\n]+" "<br>\n" reply-signature) - "</div>\n"))) - ;; insert converted html - (org-mime-insert-html-content plain file html) + (org-mime-insert-html-content plain file html opts) ;; restore part tags (when part-tags @@ -624,11 +596,13 @@ If called with an active region only export that region, otherwise entire body." (set-text-properties 0 (length txt) nil txt) txt)))) -(defun org-mime-compose (exported file to subject headers) - "Create mail body from EXPORTED in FILE with TO, SUBJECT, HEADERS." +(defun org-mime-compose (exported file to subject headers subtreep) + "Create mail body from EXPORTED in FILE with TO, SUBJECT, HEADERS. +If SUBTREEP is t, current org node is subtree." ;; start composing mail (let* ((html (car exported)) (plain (cdr exported)) + (export-opts (org-mime-get-export-options subtreep)) patched-html) (compose-mail to subject headers nil) (message-goto-body) @@ -638,7 +612,7 @@ If called with an active region only export that region, otherwise entire body." (run-hooks 'org-mime-pre-html-hook) (buffer-string))) ;; insert text - (org-mime-insert-html-content plain file patched-html))) ; export-opts + (org-mime-insert-html-content plain file patched-html export-opts))) (defun org-mime-buffer-properties () "Extract buffer properties." @@ -681,7 +655,7 @@ The cursor ends in the TO field." (interactive) (run-hooks 'org-mime-send-buffer-hook) (let* ((org-html-klipsify-src nil) - ;; (region-p (org-region-active-p)) + (region-p (org-region-active-p)) (file (buffer-file-name (current-buffer))) (props (org-mime-buffer-properties)) (subject (or (plist-get props :MAIL_SUBJECT) @@ -697,7 +671,7 @@ The cursor ends in the TO field." (other-headers (org-mime-build-mail-other-headers cc bcc from))) - (org-mime-compose exported file to subject other-headers) ; nil + (org-mime-compose exported file to subject other-headers nil) (message-goto-to))) (defun org-mime-org-major-version () @@ -733,7 +707,6 @@ Following headline properties can determine the mail headers. (org-back-to-heading)) (when (outline-on-heading-p nil) - (defvar org-major-version) (let* ((file (buffer-file-name (current-buffer))) (props (org-mime-buffer-properties)) (subject (or (org-mime-attr "MAIL_SUBJECT") @@ -750,6 +723,9 @@ Following headline properties can determine the mail headers. ;; Thanks to Matt Price improving handling of cc & bcc headers (other-headers (org-mime-build-mail-other-headers cc bcc from)) (org-export-show-temporary-export-buffer nil) + (subtree-opts (when (fboundp 'org-export--get-subtree-options) + (org-export--get-subtree-options))) + (org-export-show-temporary-export-buffer nil) (org-major-version (org-mime-org-major-version)) ;; I wrap these bodies in export blocks because in org-mime-compose ;; they get exported again. This makes each block conditionally @@ -758,7 +734,7 @@ Following headline properties can determine the mail headers. (org-mime-export-buffer-or-subtree t)))) (save-restriction (org-narrow-to-subtree) - (org-mime-compose exported file to subject other-headers)) ; t + (org-mime-compose exported file to subject other-headers t)) (message-goto-to))))) (defun org-mime-src--remove-overlay () @@ -831,8 +807,8 @@ Following headline properties can determine the mail headers. (defvar org-mime-src-mode-map (let ((map (make-sparse-keymap))) - (define-key map (kbd "C-c C-c") #'org-mime-edit-src-exit) - (define-key map (kbd "C-x C-s") #'org-mime-edit-src-save) + (define-key map (kbd "C-c C-c") 'org-mime-edit-src-exit) + (define-key map (kbd "C-x C-s") 'org-mime-edit-src-save) map)) (define-minor-mode org-mime-src-mode @@ -915,7 +891,7 @@ Following headline properties can determine the mail headers. (message "Can not find plain text mail."))))) (defun org-mime-confirm-when-no-multipart () - "Prompt whether to send email if the buffer is not htmlized." + "Prompts whether to send email if the buffer is not htmlized." (let ((found-multipart (save-excursion (save-restriction (widen) diff --git a/test/org-mime-tests.el b/test/org-mime-tests.el index ee74224055..c043b43ee3 100644 --- a/test/org-mime-tests.el +++ b/test/org-mime-tests.el @@ -1,4 +1,4 @@ -;; org-mime-tests.el --- unit tests for org-mime -*- lexical-binding: t; -*- +;; org-mime-tests.el --- unit tests for org-mime -*- coding: utf-8 -*- ;; Author: Chen Bin <chenbin DOT sh AT gmail DOT com> @@ -25,23 +25,21 @@ (require 'org-mime) (require 'message) -(defconst org-mime--mail-header - '("To: myn...@mail.com\n" - "Subject: test subject\n" - "From: My Name <myn...@yahoo.com>\n" - "--text follows this line--\n")) -(defconst org-mime--mail-footer - '("--\n" - "Yous somebody\n\n" - "--\n" - "Some quote\n")) +(defconst mail-header '("To: myn...@mail.com\n" + "Subject: test subject\n" + "From: My Name <myn...@yahoo.com>\n" + "--text follows this line--\n")) +(defconst mail-footer '("--\n" + "Yous somebody\n\n" + "--\n" + "Some quote\n")) (defun run-org-mime-htmlize (&rest mail-body) "Create mail containing MAIL-BODY and run `org-mime-htmlize'." (with-temp-buffer - (apply #'insert org-mime--mail-header) + (apply #'insert mail-header) (apply #'insert mail-body) - (apply #'insert org-mime--mail-footer) + (apply #'insert mail-footer) (message-mode) (goto-char (point-min)) (org-mime-htmlize) @@ -77,7 +75,7 @@ (setq opts (org-mime-get-export-options t)) (should opts) (org-mime-org-subtree-htmlize) - (set-buffer (car (message-buffers))) + (switch-to-buffer (car (message-buffers))) (setq str (buffer-string))) (should (string-match "Subject: hello" str)) (should (string-match "<#multipart" str)))) @@ -103,7 +101,7 @@ (setq opts (org-mime-get-export-options t)) (should opts) (org-mime-org-buffer-htmlize) - (set-buffer (car (message-buffers))) + (switch-to-buffer (car (message-buffers))) (setq str (buffer-string))) (should (string= "My mail subject" (plist-get props :MAIL_SUBJECT))) (should (string= "Someone <some...@somewhere.tld>" (plist-get props :MAIL_TO))) @@ -140,14 +138,14 @@ (should (string= (car h) "Cc")) (should (string= (cdr h) cc)))) -;; The two ASCII export tests below check for org-mode markup for the default -;; case, where the export variable is nil or not valid, and check for absent -;; org-mode markup for the three valid plain text exports. The ASCII export -;; tests do not attempt to verify the exported coding type. +;;; The two ASCII export tests below check for org-mode markup for the default +;;; case, where the export variable is nil or not valid, and check for absent +;;; org-mode markup for the three valid plain text exports. The ASCII export +;;; tests do not attempt to verify the exported coding type. (ert-deftest test-org-mime-org-buffer-htmlize-ascii-plain-text () - (let ((orgBuf (generate-new-buffer "*org-mode-test-buf*")) - str opts) + (let (str opts) + (setq orgBuf (generate-new-buffer "*org-mode-test-buf*")) (with-current-buffer orgBuf (insert "#+OPTIONS: toc:nil num:nil\n" "\n#+begin_example\n" @@ -157,16 +155,17 @@ (goto-char (point-min)) (setq opts (org-mime-get-export-options t)) (should opts) - (dolist (backend '(nil bogus ascii latin1 utf-8)) - (setq org-mime-export-ascii backend) - (set-buffer orgBuf) - (org-mime-org-buffer-htmlize) - (set-buffer (car (message-buffers))) - (setq str (buffer-string)) - (should (string-match "<#multipart" str)) - (if (car (memq backend '(ascii latin1 utf-8))) - (should-not (string-match "#\\+begin_example" str)) - (should (string-match "#\\+begin_example" str))))) + (mapcar (lambda (backend) + (setq org-mime-export-ascii backend) + (switch-to-buffer orgBuf) + (org-mime-org-buffer-htmlize) + (switch-to-buffer (car (message-buffers))) + (setq str (buffer-string)) + (should (string-match "<#multipart" str)) + (if (car (memq backend '(ascii latin1 utf-8))) + (should-not (string-match "#\\+begin_example" str)) + (should (string-match "#\\+begin_example" str)))) + '(nil bogus ascii latin1 utf-8))) (kill-buffer orgBuf))) (ert-deftest test-org-mime-htmlize-ascii-plain-text () @@ -174,12 +173,12 @@ (mapcar (lambda (backend) (setq org-mime-export-ascii backend) (with-temp-buffer - (apply #'insert org-mime--mail-header) + (apply #'insert mail-header) (insert "#+OPTIONS: toc:nil num:nil\n" "\n#+begin_example\n" "$ echo nothing to see here\n" "#+end_example\n") - (apply #'insert org-mime--mail-footer) + (apply #'insert mail-footer) (message-mode) (goto-char (point-min)) (org-mime-htmlize) @@ -196,10 +195,10 @@ ;; Title, TOC, and Author. (ert-deftest test-org-mime-org-subtree-htmlize-ascii-opts-t () - (let (str - (org-mime-export-options nil) ;; allow subtree properties - (org-mime-export-ascii 'utf-8) - (orgBuf (generate-new-buffer "*org-mode-test-buf*"))) + (let (str opts) + (setq org-mime-export-options nil) ;; allow subtree properties + (setq org-mime-export-ascii 'utf-8) + (setq orgBuf (generate-new-buffer "*org-mode-test-buf*")) (with-current-buffer orgBuf ;; the initial options are ignored in favor of subtree options (insert "#+OPTIONS: toc:nil author:nil title:nil\n" @@ -215,13 +214,13 @@ "$ echo nothing to see here\n" "#+end_example\n") (org-mode) - (set-buffer orgBuf) + (switch-to-buffer orgBuf) ;; export subtree for Section 2 (goto-char (point-min)) (search-forward "Section 2") (goto-char (+ 1 (point))) (org-mime-org-subtree-htmlize) - (set-buffer (car (message-buffers))) + (switch-to-buffer (car (message-buffers))) (setq str (buffer-string)) (setq case-fold-search nil) ;; match case for string-match (should-not (string-match "#\\+begin_example" str)) @@ -234,10 +233,10 @@ (kill-buffer orgBuf))) (ert-deftest test-org-mime-org-subtree-htmlize-ascii-opts-nil () - (let (str - (org-mime-export-options nil) ;; allow subtree properties - (org-mime-export-ascii 'utf-8) - (orgBuf (generate-new-buffer "*org-mode-test-buf*"))) + (let (str opts) + (setq org-mime-export-options nil) ;; allow subtree properties + (setq org-mime-export-ascii 'utf-8) + (setq orgBuf (generate-new-buffer "*org-mode-test-buf*")) (with-current-buffer orgBuf ;; the initial options are ignored in favor of subtree options (insert "#+OPTIONS: toc:t author:t title:t\n" @@ -253,13 +252,13 @@ "$ echo nothing to see here\n" "#+end_example\n") (org-mode) - (set-buffer orgBuf) + (switch-to-buffer orgBuf) ;; export subtree for Section 2 (goto-char (point-min)) (search-forward "Section 2") (goto-char (+ 1 (point))) (org-mime-org-subtree-htmlize) - (set-buffer (car (message-buffers))) + (switch-to-buffer (car (message-buffers))) (setq str (buffer-string)) (setq case-fold-search nil) ;; match case for string-match (should-not (string-match "#\\+begin_example" str)) @@ -270,6 +269,37 @@ (should-not (string-match "SECTION_ONE" str))) (kill-buffer orgBuf))) +(ert-deftest test-org-mime-beautify-quoted-para-breaks () + (setq html (concat "<p>\n" + "Hello there\n" + "</p>\n" + "\n" + "<p>\n" + "> this is a long-ish para that is broken\n" + "> on two lines\n" + ">\n" + "> followed by a single-line para\n" + "</p>\n")) + (setq expected (concat "<p>\n" + "Hello there\n" + "</p>\n" + "\n" + "<p>\n" + "<blockquote class=\"gmail_quote\" style=\"margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex\">\n" + "\n" + "<div>this is a long-ish para that is broken\n" + "on two lines\n" + "</div>\n" + "<div>\n" + "<br /></div>\n" + "<div>followed by a single-line para\n" + "\n" + "</div></blockquote>\n" + "</p>\n")) + (setq beautified (org-mime-beautify-quoted html)) + (should (equal beautified expected))) + + (ert-deftest test-org-mime-extract-non-org () (let* ((content (concat "*hello world\n" "<#part type=\"application/pdf\" filename=\"1.pdl\" disposition=attachment>\n<#/part>\n" @@ -304,12 +334,4 @@ (org-mime-revert-to-plain-text-mail) (should (string= (string-trim (buffer-string)) "--text follows this line--\ntest\nhello")))) - -(ert-deftest test-org-mime-reply-quoted-separator () - (should (not (org-mime-find-quoted-separator "No pattern"))) - (should (string= (org-mime-find-quoted-separator ">>>>> \"chen\" == chen bin <chen...@email.com> writes:") - "^>>>>>[^>=]+==\\([^=\r\n]+\\)$")) - (should (string= (org-mime-find-quoted-separator "On Sat, May 14 2022 at 23:30 -07, Chen Bin <notificati...@github.com> wrote:") - "^\\(On [^\r\n]+ wrote:\\)$"))) - (ert-run-tests-batch-and-exit)