unofficial mirror of notmuch@notmuchmail.org
 help / color / mirror / code / Atom feed
* [PATCH] emacs: add notmuch-expr, sexp-style queries
@ 2020-05-13 10:00 Tom Fitzhenry
  2020-05-21 21:32 ` Daniel Kahn Gillmor
  2020-11-13 12:01 ` [PATCH v2] " Tom Fitzhenry
  0 siblings, 2 replies; 6+ messages in thread
From: Tom Fitzhenry @ 2020-05-13 10:00 UTC (permalink / raw)
  To: notmuch; +Cc: Tom Fitzhenry

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\""
---
 emacs/Makefile.local       |   1 +
 emacs/notmuch-expr-test.el |  75 ++++++++++++++++++++++++
 emacs/notmuch-expr.el      | 117 +++++++++++++++++++++++++++++++++++++
 emacs/notmuch.el           |   1 +
 4 files changed, 194 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 141f5868..32f55388 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..3e13f545
--- /dev/null
+++ b/emacs/notmuch-expr-test.el
@@ -0,0 +1,75 @@
+(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")))))
diff --git a/emacs/notmuch-expr.el b/emacs/notmuch-expr.el
new file mode 100644
index 00000000..b6ba442a
--- /dev/null
+++ b/emacs/notmuch-expr.el
@@ -0,0 +1,117 @@
+;;; 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 "folder: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)
+  ;; FIXME Escape s.
+  (concat "\"" s "\""))
+
+(provide 'notmuch-expr)
+;;; notmuch-expr.el ends here
diff --git a/emacs/notmuch.el b/emacs/notmuch.el
index a980c7a2..d25a28ea 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.23.1

^ permalink raw reply related	[flat|nested] 6+ messages in thread

* Re: [PATCH] emacs: add notmuch-expr, sexp-style queries
  2020-05-13 10:00 [PATCH] emacs: add notmuch-expr, sexp-style queries Tom Fitzhenry
@ 2020-05-21 21:32 ` Daniel Kahn Gillmor
  2020-11-13 12:01 ` [PATCH v2] " Tom Fitzhenry
  1 sibling, 0 replies; 6+ messages in thread
From: Daniel Kahn Gillmor @ 2020-05-21 21:32 UTC (permalink / raw)
  To: Tom Fitzhenry, notmuch; +Cc: Tom Fitzhenry


[-- Attachment #1.1: Type: text/plain, Size: 572 bytes --]

On Wed 2020-05-13 20:00:24 +1000, Tom Fitzhenry wrote:
> 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\""

I like this idea!

> +(defun notmuch-expr--quote (s)
> +  ;; FIXME Escape s.
> +  (concat "\"" s "\""))

Shouldn't this FIXME be resolved before we consider merging?

          --dkg

[-- Attachment #1.2: signature.asc --]
[-- Type: application/pgp-signature, Size: 227 bytes --]

[-- Attachment #2: Type: text/plain, Size: 0 bytes --]



^ permalink raw reply	[flat|nested] 6+ messages in thread

* [PATCH v2] emacs: add notmuch-expr, sexp-style queries
  2020-05-13 10:00 [PATCH] emacs: add notmuch-expr, sexp-style queries Tom Fitzhenry
  2020-05-21 21:32 ` Daniel Kahn Gillmor
@ 2020-11-13 12:01 ` Tom Fitzhenry
  2020-11-16  9:48   ` David Edmondson
                     ` (2 more replies)
  1 sibling, 3 replies; 6+ messages in thread
From: Tom Fitzhenry @ 2020-11-13 12:01 UTC (permalink / raw)
  To: notmuch; +Cc: Tom Fitzhenry

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\""
---
 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

^ permalink raw reply related	[flat|nested] 6+ messages in thread

* Re: [PATCH v2] emacs: add notmuch-expr, sexp-style queries
  2020-11-13 12:01 ` [PATCH v2] " Tom Fitzhenry
@ 2020-11-16  9:48   ` David Edmondson
  2020-11-16 21:09   ` Jonas Bernoulli
  2020-11-22 14:07   ` David Bremner
  2 siblings, 0 replies; 6+ messages in thread
From: David Edmondson @ 2020-11-16  9:48 UTC (permalink / raw)
  To: Tom Fitzhenry, notmuch; +Cc: Tom Fitzhenry

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!

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH v2] emacs: add notmuch-expr, sexp-style queries
  2020-11-13 12:01 ` [PATCH v2] " Tom Fitzhenry
  2020-11-16  9:48   ` David Edmondson
@ 2020-11-16 21:09   ` Jonas Bernoulli
  2020-11-22 14:07   ` David Bremner
  2 siblings, 0 replies; 6+ messages in thread
From: Jonas Bernoulli @ 2020-11-16 21:09 UTC (permalink / raw)
  To: Tom Fitzhenry, notmuch; +Cc: Tom Fitzhenry

Tom Fitzhenry <tom@tom-fitzhenry.me.uk> writes:

> 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\""
> ---
>  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

I recently improved consistency of the library headers.  They
don't confirm to the Emacs conventions but its close enough,
and I think internal consistency would be nice to maintain here.

Don't specify "URL".  "Homepage" would be better, but since this
is not an entry point library its better to omit this.  I think
"notmuch.el" is the only library that specifies this currently.

Likewise because "notmuch-expr" is not a standalone _package_
"Package-Requires" should not be specified.  By the way, notmuch
now requires emacs 25.1, so specifying a lower version would not
make sense here anyway.

     Cheers,
     Jonas

> +
> +;;; 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

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH v2] emacs: add notmuch-expr, sexp-style queries
  2020-11-13 12:01 ` [PATCH v2] " Tom Fitzhenry
  2020-11-16  9:48   ` David Edmondson
  2020-11-16 21:09   ` Jonas Bernoulli
@ 2020-11-22 14:07   ` David Bremner
  2 siblings, 0 replies; 6+ messages in thread
From: David Bremner @ 2020-11-22 14:07 UTC (permalink / raw)
  To: Tom Fitzhenry, notmuch; +Cc: Tom Fitzhenry

Tom Fitzhenry <tom@tom-fitzhenry.me.uk> writes:

> +(require 'ert)
> +(require 'notmuch-expr)

Any ideas (from anyone) how we can run these tests in the notmuch test
suite? I guess some kind of shim might be needed, but perhaps it can
just be wrapped in one or more test_emacs calls.

> +
> +(defmacro notmuch-expr (query)
> +  "Compile an sexp QUERY into a textual notmuch query."
> +  `(notmuch-expr--eval ,query))

Does this need to be a macro? If so I'd appreciate a brief comment as to
why. At this point optimizing performace seems pretty premature, but
maybe there are other good reasons.
> 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)
>  

I know we have just been requiring things historically, but I really
wonder if it's the right thing to do, especially since it seems the
library is not (yet) used for anything. Perhaps an autoload cookie on
the appropriate functions would be best.

By the way, I'd eventually like the CLI to parse S-expr queries (and/or
perhaps JSON queries) to avoid passing via strings (and all the problems
that entails). I'm not sure when I'll get around to it. There are also
some design details to be worked out, but hopefully it would bne a
mostly-compatible syntax with what is provided here.


^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2020-11-22 14:08 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-05-13 10:00 [PATCH] emacs: add notmuch-expr, sexp-style queries Tom Fitzhenry
2020-05-21 21:32 ` Daniel Kahn Gillmor
2020-11-13 12:01 ` [PATCH v2] " Tom Fitzhenry
2020-11-16  9:48   ` David Edmondson
2020-11-16 21:09   ` Jonas Bernoulli
2020-11-22 14:07   ` David Bremner

Code repositories for project(s) associated with this public inbox

	https://yhetil.org/notmuch.git/

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).