From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mp1 ([2001:41d0:2:4a6f::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by ms11 with LMTPS id WFmVHf9Ksl/xZQAA0tVLHw (envelope-from ) for ; Mon, 16 Nov 2020 09:48:47 +0000 Received: from aspmx1.migadu.com ([2001:41d0:2:4a6f::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by mp1 with LMTPS id cMNxGf9Ksl+yQwAAbx9fmQ (envelope-from ) for ; Mon, 16 Nov 2020 09:48:47 +0000 Received: from mail.notmuchmail.org (nmbug.tethera.net [144.217.243.247]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) server-signature RSA-PSS (2048 bits)) (No client certificate requested) by aspmx1.migadu.com (Postfix) with ESMTPS id 60FF194011C for ; Mon, 16 Nov 2020 09:48:43 +0000 (UTC) Received: from nmbug.tethera.net (localhost [127.0.0.1]) by mail.notmuchmail.org (Postfix) with ESMTP id A3D9A28824; Mon, 16 Nov 2020 04:48:34 -0500 (EST) Received: from mail-wm1-x32d.google.com (mail-wm1-x32d.google.com [IPv6:2a00:1450:4864:20::32d]) by mail.notmuchmail.org (Postfix) with ESMTPS id BB14620088 for ; Mon, 16 Nov 2020 04:48:32 -0500 (EST) Received: by mail-wm1-x32d.google.com with SMTP id d142so23103692wmd.4 for ; Mon, 16 Nov 2020 01:48:32 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=dme-org.20150623.gappssmtp.com; s=20150623; h=to:cc:subject:in-reply-to:references:from:date:message-id :mime-version; bh=+nhIsN6zLvv76Mp2i+vNqAShUkZH6jIsb3HmMBT2DM8=; b=Yhh5ASOviNAyosEOowA3dkWkxNzFmwhi7zPXZYRnD34JJIiZJSaKwfUHcffYg7JZby UeJv5dAuy6cNlLaPEqzoTvWKtkZBuoSww/WTsFqrRzp4qKD5ApDvLGAoARex5MYMbov7 aPNBcCSjdzKocSpEQ3Og8OxJw4qqmHiA7FAV7Jncvq/fLnh9EDxOFOzSIPk589+Ydl7O /XoRYOdiUtgowcpvk4iJTyakr183B/LqlhCcOiC+CBEvkgGSMCn08ihMhGJxBU6D4F/M BH5+UD5hliyWwusQSYzdb1YWBHzTCIN2RVwQCCL4xtxW/+FfWSadavHa6fP2Va9i1wft 5Mng== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:to:cc:subject:in-reply-to:references:from:date :message-id:mime-version; bh=+nhIsN6zLvv76Mp2i+vNqAShUkZH6jIsb3HmMBT2DM8=; b=gKeegS35YajTsDHkM2PVxceGTzqlDy4LesMKRGb4b8JxjZJe3SU8+0i5Oe1b/Bj80O WSR1wjKzPYpeqftVybkZWVQqbE/IoSd0AtVcm9N2Xh4Ip97bCkTX8Nfw5QhqFdXmd40x cHdHYj86HFXZASnBFOFvATx049aiFCb0UC0QwabvRaZZ2nV/k0IeeOpIQqBDy90PTI1V w3BZY8RNl4qfvPDk7P16TvDZMGkpWJmHf6+MFbwEM4vB9hbf17YO6HKeIlAOA2HSSQDB N1HajZ+j5R6KzByjFvu8UMHT9D/sgTPl/1sB0OX3RzhAYrdwk3XqV26OO3Z4beI0OB1z cmRw== X-Gm-Message-State: AOAM5334m/3WonfXUSoqnB58U96QmxdnHjI2Cy9/jj5yVpo7h+3cCNuh XwNmk4TdGONtuxMBQP51gZSqzw== X-Google-Smtp-Source: ABdhPJztk8yu+3YbXZWkRZ1nk+wOXQezx07E2N9nZZULWx0O2jFtJOslTo+TcBD6QVjpFlWhrSDBEA== X-Received: by 2002:a1c:454:: with SMTP id 81mr5317763wme.107.1605520109817; Mon, 16 Nov 2020 01:48:29 -0800 (PST) Received: from disaster-area.hh.sledj.net (disaster-area.hh.sledj.net. [2001:8b0:bb71:7140:64::1]) by smtp.gmail.com with ESMTPSA id t13sm22756128wru.67.2020.11.16.01.48.28 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 16 Nov 2020 01:48:29 -0800 (PST) Received: from localhost (disaster-area.hh.sledj.net [local]) by disaster-area.hh.sledj.net (OpenSMTPD) with ESMTPA id b2f842af; Mon, 16 Nov 2020 09:48:28 +0000 (UTC) To: Tom Fitzhenry , notmuch@notmuchmail.org Subject: Re: [PATCH v2] emacs: add notmuch-expr, sexp-style queries In-Reply-To: <20201113120122.26105-1-tom@tom-fitzhenry.me.uk> References: <20200513100024.8474-1-tom@tom-fitzhenry.me.uk> <20201113120122.26105-1-tom@tom-fitzhenry.me.uk> X-HGTTG: zarquon From: David Edmondson Date: Mon, 16 Nov 2020 09:48:28 +0000 Message-ID: MIME-Version: 1.0 Message-ID-Hash: Q2OG7OWBJQFD32RXIRJZAXSRDID6JF7H X-Message-ID-Hash: Q2OG7OWBJQFD32RXIRJZAXSRDID6JF7H X-MailFrom: dme@dme.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; emergency; loop; banned-address; member-moderation; header-match-notmuch.notmuchmail.org-0; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; suspicious-header CC: Tom Fitzhenry X-Mailman-Version: 3.2.1 Precedence: list List-Id: "Use and development of the notmuch mail system." List-Help: List-Post: List-Subscribe: List-Unsubscribe: Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit X-Scanner: ns3122888.ip-94-23-21.eu Authentication-Results: aspmx1.migadu.com; dkim=fail (body hash did not verify) header.d=dme-org.20150623.gappssmtp.com header.s=20150623 header.b=Yhh5ASOv; dmarc=none; spf=pass (aspmx1.migadu.com: domain of notmuch-bounces@notmuchmail.org designates 144.217.243.247 as permitted sender) smtp.mailfrom=notmuch-bounces@notmuchmail.org X-Spam-Score: -0.01 X-TUID: NuSQtRWJJnrv On Friday, 2020-11-13 at 23:01:22 +11, Tom Fitzhenry wrote: > From: Tom Fitzhenry > > 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 > --- > 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 > +;; 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!