On Friday, 2020-11-13 at 23:01:22 +11, Tom Fitzhenry wrote: > From: Tom Fitzhenry <tomfitzhenry@google.com> > > notmuch-expr allows you to write notmuch search queries in sexp style like: > > (notmuch-expr > '(and > (to "emacs-devel") > "info manual" > (or > (not (is "spam")) > (is "important")))) > > which will generate the textual query: > > "to:emacs-devel AND (NOT is:spam OR is:important) AND \"info manual\"" Reviewed-by: David Edmondson <dme@dme.org> > --- > emacs/Makefile.local | 1 + > emacs/notmuch-expr-test.el | 96 ++++++++++++++++++++++++++++ > emacs/notmuch-expr.el | 124 +++++++++++++++++++++++++++++++++++++ > emacs/notmuch.el | 1 + > 4 files changed, 222 insertions(+) > create mode 100644 emacs/notmuch-expr-test.el > create mode 100644 emacs/notmuch-expr.el > > diff --git a/emacs/Makefile.local b/emacs/Makefile.local > index d1b320c3..f68e6e31 100644 > --- a/emacs/Makefile.local > +++ b/emacs/Makefile.local > @@ -22,6 +22,7 @@ emacs_sources := \ > $(dir)/notmuch-version.el \ > $(dir)/notmuch-jump.el \ > $(dir)/notmuch-company.el \ > + $(dir)/notmuch-expr.el \ > $(dir)/notmuch-draft.el > > elpa_sources := ${emacs_sources} $(dir)/notmuch-pkg.el > diff --git a/emacs/notmuch-expr-test.el b/emacs/notmuch-expr-test.el > new file mode 100644 > index 00000000..92029fec > --- /dev/null > +++ b/emacs/notmuch-expr-test.el > @@ -0,0 +1,96 @@ > +(require 'ert) > +(require 'notmuch-expr) > + > +(ert-deftest and () > + (should > + (equal > + "(\"valued\" AND is:unread AND from:spam@example.com)" > + (notmuch-expr > + '(and > + "valued" > + (is "unread") > + (from "spam@example.com")))))) > + > +(ert-deftest body () > + (should > + (equal > + "(body:wallace AND from:gromit)" > + (notmuch-expr > + '(and > + (body "wallace") > + (from "gromit")))))) > + > +(ert-deftest regex () > + (should > + (equal > + "(subject:\"/Ca+sh/\" AND NOT is:important)" > + (notmuch-expr > + '(and > + (subject "/Ca+sh/") > + (not (is "important"))))))) > + > +(ert-deftest precedence () > + (should > + (equal > + "(to:emacs-devel AND (NOT is:spam OR is:important))" > + (notmuch-expr > + '(and > + (to "emacs-devel") > + (or > + (not (is "spam")) > + (is "important"))))))) > + > +(ert-deftest xor () > + (should > + (equal > + "is:inbox XOR is:sent" > + (notmuch-expr > + '(xor > + (is "inbox") > + (is "sent")))))) > + > +(ert-deftest literal () > + (should > + (equal > + "(is:inbox OR from:foo)" > + (notmuch-expr > + '(or > + (is "inbox") > + (literal "from:foo")))))) > + > +(ert-deftest string () > + (should > + (equal > + "(is:inbox OR \"from:foo\")" > + (notmuch-expr > + '(or > + (is "inbox") > + "from:foo"))))) > + > +(ert-deftest tag-with-spaces () > + (should > + (equal > + "is:\"a tag\"" > + (notmuch-expr > + '(tag "a tag"))))) > + > +(ert-deftest quoted-spaces () > + (should > + (equal > + "subject:\"Hello there\"" > + (notmuch-expr > + '(subject "Hello there"))))) > + > +(ert-deftest quoted-backslash () > + (should > + (equal > + "subject:\"A celebration! \\o/ Woo.\"" > + (notmuch-expr > + '(subject "A celebration! \\o/ Woo."))))) > + > +(ert-deftest quoted-quote () > + (should > + (equal > + "subject:\"Gandalf: \\\"Use the force!\\\" 2001\"" > + (notmuch-expr > + '(subject "Gandalf: \"Use the force!\" 2001"))))) > diff --git a/emacs/notmuch-expr.el b/emacs/notmuch-expr.el > new file mode 100644 > index 00000000..f5a3429f > --- /dev/null > +++ b/emacs/notmuch-expr.el > @@ -0,0 +1,124 @@ > +;;; notmuch-expr.el --- An S-exp library for building notmuch search queries -*- lexical-binding: t; -*- > + > +;; Author: Tom Fitzhenry <tomfitzhenry@google.com> > +;; Package-Requires: ((emacs "24.1")) > +;; URL: https://notmuchmail.org > + > +;;; Commentary: > + > +;; This package provides a way to build notmuch search queries via s-expressions. > +;; > +;; For example, rather than write: > + > +;; "to:emacs-devel AND (NOT is:spam OR is:important) AND \"info manual\"" > +;; > +;; this package allows you to generate the same query via s-expressions: > +;; > +;; (notmuch-expr > +;; '(and > +;; (to "emacs-devel") > +;; "info manual" > +;; (or > +;; (not (is "spam")) > +;; (is "important")))) > +;; > +;; See notmuch-expr-test.el for more examples. > +;; > +;; Some search terms are unsupported. To use those, use the `literal' atom. > +;; For example: (literal "path:spam") > +;; > +;; man page: notmuch-search-terms(7). > +;; The generated search query may change across different versions. > + > +;;; Code: > + > +(defmacro notmuch-expr (query) > + "Compile an sexp QUERY into a textual notmuch query." > + `(notmuch-expr--eval ,query)) > + > +(defun notmuch-expr--eval (expr) > + (pcase expr > + (`(tag ,s) (notmuch-expr--is s)) > + (`(is ,s) (notmuch-expr--is s)) > + (`(from ,s) (notmuch-expr--from s)) > + (`(to ,s) (notmuch-expr--to s)) > + (`(body ,s) (notmuch-expr--body s)) > + (`(subject ,s) (notmuch-expr--subject s)) > + > + ;; Boolean operators. > + (`(and . ,clauses) (notmuch-expr--and clauses)) > + (`(or . ,clauses) (notmuch-expr--or clauses)) > + (`(not ,clause) (notmuch-expr--not clause)) > + (`(xor ,c1 ,c2) (notmuch-expr--xor c1 c2)) > + > + ;; Provide an escape-hatch. > + (`(literal ,s) (notmuch-expr--literal s)) > + > + ;; Otherwise, quote. > + (s (notmuch-expr--quote s)))) > + > +(defun notmuch-expr--and (clauses) > + (concat > + "(" > + (mapconcat 'notmuch-expr--eval clauses " AND ") > + ")")) > + > +(defun notmuch-expr--or (clauses) > + (concat > + "(" > + (mapconcat 'notmuch-expr--eval clauses " OR ") > + ")")) > + > +(defun notmuch-expr--not (clauses) > + (concat "NOT " (notmuch-expr--eval clauses))) > + > +(defun notmuch-expr--xor (c1 c2) > + (concat > + (notmuch-expr--eval c1) > + " XOR " > + (notmuch-expr--eval c2))) > + > +(defun notmuch-expr--body (s) > + (concat "body:" > + (notmuch-expr--leaf s))) > + > +(defun notmuch-expr--subject (s) > + (concat "subject:" > + (notmuch-expr--leaf s))) > + > +(defun notmuch-expr--from (f) > + (concat "from:" > + (notmuch-expr--leaf f))) > + > +(defun notmuch-expr--to (f) > + (concat "to:" > + (notmuch-expr--leaf f))) > + > +(defun notmuch-expr--is (expr) > + (concat "is:" > + (notmuch-expr--leaf expr))) > + > +(defun notmuch-expr--leaf (s) > + (if (string-match-p "^[a-zA-Z0-9.@-]+$" s) > + ;; Avoid ugly quoting. > + ;; This is safe because the string is bound to a prefix > + ;; and thus it won't be misinterpreted by notmuch. > + s > + (notmuch-expr--quote s))) > + > +(defun notmuch-expr--literal (s) > + s) > + > +(defun notmuch-expr--quote (s) > + "Return a quoted version of S." > + (concat "\"" > + (replace-regexp-in-string > + (rx "\"") > + ;; We must double escape the backslash to avoid it being > + ;; interpreted as a back-reference. > + "\\\\\"" > + s) > + "\"")) > + > +(provide 'notmuch-expr) > +;;; notmuch-expr.el ends here > diff --git a/emacs/notmuch.el b/emacs/notmuch.el > index 165aaa43..8c5843e5 100644 > --- a/emacs/notmuch.el > +++ b/emacs/notmuch.el > @@ -79,6 +79,7 @@ > (require 'notmuch-maildir-fcc) > (require 'notmuch-message) > (require 'notmuch-parser) > +(require 'notmuch-expr) > > (defcustom notmuch-search-result-format > `(("date" . "%12s ") > -- > 2.28.0 > _______________________________________________ > notmuch mailing list -- notmuch@notmuchmail.org > To unsubscribe send an email to notmuch-leave@notmuchmail.org dme. -- I'm catching up with myself! _______________________________________________ notmuch mailing list -- notmuch@notmuchmail.org To unsubscribe send an email to notmuch-leave@notmuchmail.org