1 Return-Path: <peter.feigl@gmx.at>
\r
2 X-Original-To: notmuch@notmuchmail.org
\r
3 Delivered-To: notmuch@notmuchmail.org
\r
4 Received: from localhost (localhost [127.0.0.1])
\r
5 by olra.theworths.org (Postfix) with ESMTP id 21294431FB6
\r
6 for <notmuch@notmuchmail.org>; Fri, 13 Jul 2012 01:18:07 -0700 (PDT)
\r
7 X-Virus-Scanned: Debian amavisd-new at olra.theworths.org
\r
11 X-Spam-Status: No, score=0.001 tagged_above=-999 required=5
\r
12 tests=[FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_NONE=-0.0001]
\r
14 Received: from olra.theworths.org ([127.0.0.1])
\r
15 by localhost (olra.theworths.org [127.0.0.1]) (amavisd-new, port 10024)
\r
16 with ESMTP id Ssh-EgvKis61 for <notmuch@notmuchmail.org>;
\r
17 Fri, 13 Jul 2012 01:18:06 -0700 (PDT)
\r
18 Received: from mailout-de.gmx.net (mailout-de.gmx.net [213.165.64.23])
\r
19 by olra.theworths.org (Postfix) with SMTP id 8E5B3431FAE
\r
20 for <notmuch@notmuchmail.org>; Fri, 13 Jul 2012 01:18:05 -0700 (PDT)
\r
21 Received: (qmail invoked by alias); 13 Jul 2012 08:17:59 -0000
\r
22 Received: from www.nexoid.at (EHLO mail.nexoid.at) [178.79.130.240]
\r
23 by mail.gmx.net (mp033) with SMTP; 13 Jul 2012 10:17:59 +0200
\r
24 X-Authenticated: #4563876
\r
25 X-Provags-ID: V01U2FsdGVkX18mSfJsQnom6dBKHkvNoucS8v0zN2qRATa2X2Iehy
\r
27 Received: from nexoid (localhost [127.0.0.1])
\r
28 by mail.nexoid.at (Postfix) with ESMTP id 42168E00C
\r
29 for <notmuch@notmuchmail.org>; Fri, 13 Jul 2012 10:17:56 +0200 (CEST)
\r
30 From: <craven@gmx.net>
\r
31 To: notmuch@notmuchmail.org
\r
32 Subject: Re: Proof of concept: S-Expression format
\r
33 In-Reply-To: <1342167097-25012-1-git-send-email-craven@gmx.net>
\r
34 References: <20120710191331.GE7332@mit.edu>
\r
35 <1342167097-25012-1-git-send-email-craven@gmx.net>
\r
36 User-Agent: Notmuch/0.11+77~gad6d0d5 (http://notmuchmail.org) Emacs/24.1.50.2
\r
38 Date: Fri, 13 Jul 2012 10:17:56 +0200
\r
39 Message-ID: <877gu8t6q3.fsf@nexoid.at>
\r
41 Content-Type: text/plain
\r
43 X-BeenThere: notmuch@notmuchmail.org
\r
44 X-Mailman-Version: 2.1.13
\r
46 List-Id: "Use and development of the notmuch mail system."
\r
47 <notmuch.notmuchmail.org>
\r
48 List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,
\r
49 <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>
\r
50 List-Archive: <http://notmuchmail.org/pipermail/notmuch>
\r
51 List-Post: <mailto:notmuch@notmuchmail.org>
\r
52 List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>
\r
53 List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,
\r
54 <mailto:notmuch-request@notmuchmail.org?subject=subscribe>
\r
55 X-List-Received-Date: Fri, 13 Jul 2012 08:18:07 -0000
\r
57 This patch shows how to add a new output format to notmuch-search.c.
\r
59 As an example, it adds S-Expressions. The concrete formatting can
\r
60 easily be changed, this is meant as a proof of concept that the
\r
61 changes to core notmuch code are very few and all formatting state is
\r
62 kept inside sprinter-sexp.c.
\r
64 Makefile.local | 1 +
\r
65 notmuch-search.c | 6 +-
\r
66 sprinter-sexp.c | 185 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
\r
68 4 files changed, 195 insertions(+), 1 deletion(-)
\r
69 create mode 100644 sprinter-sexp.c
\r
71 diff --git a/Makefile.local b/Makefile.local
\r
72 index b6c7e0c..cc1d58a 100644
\r
73 --- a/Makefile.local
\r
74 +++ b/Makefile.local
\r
75 @@ -292,6 +292,7 @@ notmuch_client_srcs = \
\r
78 sprinter-text-search.c \
\r
83 diff --git a/notmuch-search.c b/notmuch-search.c
\r
84 index 99fddac..2db58a5 100644
\r
85 --- a/notmuch-search.c
\r
86 +++ b/notmuch-search.c
\r
87 @@ -256,7 +256,7 @@ notmuch_search_command (void *ctx, int argc, char *argv[])
\r
88 int exclude = EXCLUDE_TRUE;
\r
91 - enum { NOTMUCH_FORMAT_JSON, NOTMUCH_FORMAT_TEXT }
\r
92 + enum { NOTMUCH_FORMAT_JSON, NOTMUCH_FORMAT_TEXT, NOTMUCH_FORMAT_SEXP }
\r
93 format_sel = NOTMUCH_FORMAT_TEXT;
\r
95 notmuch_opt_desc_t options[] = {
\r
96 @@ -267,6 +267,7 @@ notmuch_search_command (void *ctx, int argc, char *argv[])
\r
97 { NOTMUCH_OPT_KEYWORD, &format_sel, "format", 'f',
\r
98 (notmuch_keyword_t []){ { "json", NOTMUCH_FORMAT_JSON },
\r
99 { "text", NOTMUCH_FORMAT_TEXT },
\r
100 + { "sexp", NOTMUCH_FORMAT_SEXP },
\r
102 { NOTMUCH_OPT_KEYWORD, &output, "output", 'o',
\r
103 (notmuch_keyword_t []){ { "summary", OUTPUT_SUMMARY },
\r
104 @@ -298,6 +299,9 @@ notmuch_search_command (void *ctx, int argc, char *argv[])
\r
105 case NOTMUCH_FORMAT_JSON:
\r
106 format = sprinter_json_create (ctx, stdout);
\r
108 + case NOTMUCH_FORMAT_SEXP:
\r
109 + format = sprinter_sexp_create (ctx, stdout);
\r
113 config = notmuch_config_open (ctx, NULL, NULL);
\r
114 diff --git a/sprinter-sexp.c b/sprinter-sexp.c
\r
115 new file mode 100644
\r
116 index 0000000..68a5db5
\r
118 +++ b/sprinter-sexp.c
\r
120 +#include <stdbool.h>
\r
121 +#include <stdio.h>
\r
122 +#include <talloc.h>
\r
123 +#include "sprinter.h"
\r
125 +typedef enum { MAP, LIST } aggregate_t;
\r
127 +struct sprinter_sexp {
\r
128 + struct sprinter vtable;
\r
130 + /* Top of the state stack, or NULL if the printer is not currently
\r
131 + * inside any aggregate types. */
\r
132 + struct sexp_state *state;
\r
135 +struct sexp_state {
\r
136 + struct sexp_state *parent;
\r
137 + /* True if nothing has been printed in this aggregate yet.
\r
138 + * Suppresses the comma before a value. */
\r
139 + notmuch_bool_t first;
\r
140 + /* The character that closes the current aggregate. */
\r
141 + aggregate_t type;
\r
144 +static struct sprinter_sexp *
\r
145 +sexp_begin_value (struct sprinter *sp)
\r
147 + struct sprinter_sexp *spsx = (struct sprinter_sexp *) sp;
\r
149 + if (spsx->state) {
\r
150 + if (! spsx->state->first)
\r
151 + fputc (' ', spsx->stream);
\r
153 + spsx->state->first = FALSE;
\r
159 +sexp_begin_aggregate (struct sprinter *sp, aggregate_t type)
\r
161 + struct sprinter_sexp *spsx = (struct sprinter_sexp *) sp;
\r
162 + struct sexp_state *state = talloc (spsx, struct sexp_state);
\r
164 + fputc ('(', spsx->stream);
\r
165 + state->parent = spsx->state;
\r
166 + state->first = TRUE;
\r
167 + state->type = type;
\r
169 + spsx->state = state;
\r
173 +sexp_begin_map (struct sprinter *sp)
\r
175 + sexp_begin_aggregate (sp, MAP);
\r
179 +sexp_begin_list (struct sprinter *sp)
\r
181 + sexp_begin_aggregate (sp, LIST);
\r
185 +sexp_end (struct sprinter *sp)
\r
187 + struct sprinter_sexp *spsx = (struct sprinter_sexp *) sp;
\r
188 + struct sexp_state *state = spsx->state;
\r
190 + fputc (')', spsx->stream);
\r
191 + spsx->state = state->parent;
\r
192 + talloc_free (state);
\r
193 + if (spsx->state == NULL)
\r
194 + fputc ('\n', spsx->stream);
\r
196 + if (spsx->state->type == MAP)
\r
197 + fputc (')', spsx->stream);
\r
201 +sexp_string (struct sprinter *sp, const char *val)
\r
203 + static const char *const escapes[] = {
\r
204 + ['\"'] = "\\\"", ['\\'] = "\\\\", ['\b'] = "\\b",
\r
205 + ['\f'] = "\\f", ['\n'] = "\\n", ['\t'] = "\\t"
\r
207 + struct sprinter_sexp *spsx = sexp_begin_value (sp);
\r
209 + fputc ('"', spsx->stream);
\r
210 + for (; *val; ++val) {
\r
211 + unsigned char ch = *val;
\r
212 + if (ch < ARRAY_SIZE (escapes) && escapes[ch])
\r
213 + fputs (escapes[ch], spsx->stream);
\r
214 + else if (ch >= 32)
\r
215 + fputc (ch, spsx->stream);
\r
217 + fprintf (spsx->stream, "\\u%04x", ch);
\r
219 + fputc ('"', spsx->stream);
\r
220 + if (spsx->state != NULL && spsx->state->type == MAP)
\r
221 + fputc (')', spsx->stream);
\r
222 + spsx->state->first = FALSE;
\r
226 +sexp_integer (struct sprinter *sp, int val)
\r
228 + struct sprinter_sexp *spsx = sexp_begin_value (sp);
\r
230 + fprintf (spsx->stream, "%d", val);
\r
231 + if (spsx->state != NULL && spsx->state->type == MAP)
\r
232 + fputc (')', spsx->stream);
\r
236 +sexp_boolean (struct sprinter *sp, notmuch_bool_t val)
\r
238 + struct sprinter_sexp *spsx = sexp_begin_value (sp);
\r
240 + fputs (val ? "#t" : "#f", spsx->stream);
\r
241 + if (spsx->state != NULL && spsx->state->type == MAP)
\r
242 + fputc (')', spsx->stream);
\r
246 +sexp_null (struct sprinter *sp)
\r
248 + struct sprinter_sexp *spsx = sexp_begin_value (sp);
\r
250 + fputs ("'()", spsx->stream);
\r
251 + spsx->state->first = FALSE;
\r
255 +sexp_map_key (struct sprinter *sp, const char *key)
\r
257 + struct sprinter_sexp *spsx = sexp_begin_value (sp);
\r
259 + fputc ('(', spsx->stream);
\r
260 + fputs (key, spsx->stream);
\r
261 + fputs (" . ", spsx->stream);
\r
262 + spsx->state->first = TRUE;
\r
266 +sexp_set_prefix (unused (struct sprinter *sp), unused (const char *name))
\r
271 +sexp_separator (struct sprinter *sp)
\r
273 + struct sprinter_sexp *spsx = (struct sprinter_sexp *) sp;
\r
275 + fputc ('\n', spsx->stream);
\r
279 +sprinter_sexp_create (const void *ctx, FILE *stream)
\r
281 + static const struct sprinter_sexp template = {
\r
283 + .begin_map = sexp_begin_map,
\r
284 + .begin_list = sexp_begin_list,
\r
286 + .string = sexp_string,
\r
287 + .integer = sexp_integer,
\r
288 + .boolean = sexp_boolean,
\r
289 + .null = sexp_null,
\r
290 + .map_key = sexp_map_key,
\r
291 + .separator = sexp_separator,
\r
292 + .set_prefix = sexp_set_prefix,
\r
295 + struct sprinter_sexp *res;
\r
297 + res = talloc (ctx, struct sprinter_sexp);
\r
302 + res->stream = stream;
\r
303 + return &res->vtable;
\r
305 diff --git a/sprinter.h b/sprinter.h
\r
306 index 4241d65..c0146f6 100644
\r
309 @@ -55,4 +55,8 @@ sprinter_text_search_create (const void *ctx, FILE *stream);
\r
311 sprinter_json_create (const void *ctx, FILE *stream);
\r
313 +/* Create a new structure printer that emits S-Expressions. */
\r
315 +sprinter_sexp_create (const void *ctx, FILE *stream);
\r
317 #endif // NOTMUCH_SPRINTER_H
\r