1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
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
|