Re: [PATCH] emacs: wash: make word-wrap bound message width
[notmuch-archives.git] / 40 / 235554518d43339946f6fe93530aedc6978587
1 Return-Path: <amdragon@mit.edu>\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 BD3FF431FB6\r
6         for <notmuch@notmuchmail.org>; Fri, 13 Jul 2012 19:02:15 -0700 (PDT)\r
7 X-Virus-Scanned: Debian amavisd-new at olra.theworths.org\r
8 X-Spam-Flag: NO\r
9 X-Spam-Score: -0.7\r
10 X-Spam-Level: \r
11 X-Spam-Status: No, score=-0.7 tagged_above=-999 required=5\r
12         tests=[RCVD_IN_DNSWL_LOW=-0.7] autolearn=disabled\r
13 Received: from olra.theworths.org ([127.0.0.1])\r
14         by localhost (olra.theworths.org [127.0.0.1]) (amavisd-new, port 10024)\r
15         with ESMTP id WeD2gnFmBSmI for <notmuch@notmuchmail.org>;\r
16         Fri, 13 Jul 2012 19:02:13 -0700 (PDT)\r
17 Received: from dmz-mailsec-scanner-2.mit.edu (DMZ-MAILSEC-SCANNER-2.MIT.EDU\r
18         [18.9.25.13])\r
19         by olra.theworths.org (Postfix) with ESMTP id 3D3DC431FAE\r
20         for <notmuch@notmuchmail.org>; Fri, 13 Jul 2012 19:02:13 -0700 (PDT)\r
21 X-AuditID: 1209190d-b7fd56d000000933-cd-5000d32426b7\r
22 Received: from mailhub-auth-2.mit.edu ( [18.7.62.36])\r
23         by dmz-mailsec-scanner-2.mit.edu (Symantec Messaging Gateway) with SMTP\r
24         id 25.F7.02355.423D0005; Fri, 13 Jul 2012 22:02:12 -0400 (EDT)\r
25 Received: from outgoing.mit.edu (OUTGOING-AUTH.MIT.EDU [18.7.22.103])\r
26         by mailhub-auth-2.mit.edu (8.13.8/8.9.2) with ESMTP id q6E22CSA007243; \r
27         Fri, 13 Jul 2012 22:02:12 -0400\r
28 Received: from awakening.csail.mit.edu (awakening.csail.mit.edu [18.26.4.91])\r
29         (authenticated bits=0)\r
30         (User authenticated as amdragon@ATHENA.MIT.EDU)\r
31         by outgoing.mit.edu (8.13.6/8.12.4) with ESMTP id q6E22Bcu009180\r
32         (version=TLSv1/SSLv3 cipher=AES256-SHA bits=256 verify=NOT);\r
33         Fri, 13 Jul 2012 22:02:12 -0400 (EDT)\r
34 Received: from amthrax by awakening.csail.mit.edu with local (Exim 4.77)\r
35         (envelope-from <amdragon@mit.edu>)\r
36         id 1SprgR-0000e7-CG; Fri, 13 Jul 2012 22:02:11 -0400\r
37 Date: Fri, 13 Jul 2012 22:02:11 -0400\r
38 From: Austin Clements <amdragon@MIT.EDU>\r
39 To: Peter Feigl <craven@gmx.net>\r
40 Subject: Re: [PATCH v5 2/3] Add structured output formatter for JSON and text.\r
41 Message-ID: <20120714020211.GC31670@mit.edu>\r
42 References: <20120710191331.GE7332@mit.edu>\r
43         <1342167097-25012-1-git-send-email-craven@gmx.net>\r
44         <1342167097-25012-3-git-send-email-craven@gmx.net>\r
45 MIME-Version: 1.0\r
46 Content-Type: text/plain; charset=us-ascii\r
47 Content-Disposition: inline\r
48 In-Reply-To: <1342167097-25012-3-git-send-email-craven@gmx.net>\r
49 User-Agent: Mutt/1.5.21 (2010-09-15)\r
50 X-Brightmail-Tracker:\r
51  H4sIAAAAAAAAA+NgFmphleLIzCtJLcpLzFFi42IRYrdT0VW5zBBg0N5hYLG3oZ3R4vrNmcwO\r
52         TB6LN+1n83i26hZzAFMUl01Kak5mWWqRvl0CV8aa+T+YCjbmVbz6u4K9gXFuSBcjJ4eEgInE\r
53         xDmn2SFsMYkL99azdTFycQgJ7GOUuLlwKiuEs4FR4u70zSwQzkkmiWWzL0A5SxglTjVtBirj\r
54         4GARUJV4d7oMZBSbgIbEtv3LGUFsEQEFiWfrmsBsZgFpiW+/m5lAbGEBP4nVRw+wgti8AjoS\r
55         J3fMhlo9lVFiycZzUAlBiZMzn7BANGtJ3Pj3kglkF8ig5f84QMKcAvYS7Ve6wWaKCqhITDm5\r
56         jW0Co9AsJN2zkHTPQuhewMi8ilE2JbdKNzcxM6c4NVm3ODkxLy+1SNdILzezRC81pXQTIyiw\r
57         OSV5dzC+O6h0iFGAg1GJhzfVnyFAiDWxrLgy9xCjJAeTkijvrItAIb6k/JTKjMTijPii0pzU\r
58         4kOMEhzMSiK85m1AOd6UxMqq1KJ8mJQ0B4uSOO+VlJv+QgLpiSWp2ampBalFMFkZDg4lCd6w\r
59         S0CNgkWp6akVaZk5JQhpJg5OkOE8QMP1QWp4iwsSc4sz0yHypxgVpcR5i0ESAiCJjNI8uF5Y\r
60         4nnFKA70ijCvM0gVDzBpwXW/AhrMBDR41s9//kCDSxIRUlINjNLnBN5Mfnp499K57OViUm2m\r
61         CRa+h2wKPyh9temqti7aVnp+n5b6iouxZ8sCF/bMbJpSKbREW/GC3p1LkpV7lGYKuWT4z7F/\r
62         sjX82Gux5pZHa3QjpPcqMnkcfO/Zlry48L1+59TFpw9tOiumtvLKTNEzMx6qbzI1im9b45Of\r
63         N/PWcv4VCddMmJRYijMSDbWYi4oTAdGJc5oXAwAA\r
64 Cc: notmuch@notmuchmail.org\r
65 X-BeenThere: notmuch@notmuchmail.org\r
66 X-Mailman-Version: 2.1.13\r
67 Precedence: list\r
68 List-Id: "Use and development of the notmuch mail system."\r
69         <notmuch.notmuchmail.org>\r
70 List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,\r
71         <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>\r
72 List-Archive: <http://notmuchmail.org/pipermail/notmuch>\r
73 List-Post: <mailto:notmuch@notmuchmail.org>\r
74 List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>\r
75 List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,\r
76         <mailto:notmuch-request@notmuchmail.org?subject=subscribe>\r
77 X-List-Received-Date: Sat, 14 Jul 2012 02:02:15 -0000\r
78 \r
79 Quoth Peter Feigl on Jul 13 at 10:11 am:\r
80 > From: <craven@gmx.net>\r
81\r
82 > Using the new structured printer support in sprinter.h, implement\r
83 > sprinter_json_create, which returns a new JSON structured output\r
84 > formatter. The formatter prints output similar to the existing JSON, but\r
85 > with differences in whitespace (mostly newlines, --output=summary prints\r
86 > the entire message summary on one line, not split across multiple lines).\r
87\r
88 > Also implement a "structured" formatter that prints the current plain\r
89 > text format. This passes all tests, but the exact formatting is probably\r
90 > specific to notmuch-search and cannot easily (if at all) be adapted to\r
91 > be used across all of notmuch-{search,reply,show,...}.\r
92 > ---\r
93 >  Makefile.local         |   2 +\r
94 >  sprinter-json.c        | 184 +++++++++++++++++++++++++++++++++++++++++++++++\r
95 >  sprinter-text-search.c | 190 +++++++++++++++++++++++++++++++++++++++++++++++++\r
96 >  sprinter.h             |   8 +++\r
97 >  4 files changed, 384 insertions(+)\r
98 >  create mode 100644 sprinter-json.c\r
99 >  create mode 100644 sprinter-text-search.c\r
100\r
101 > diff --git a/Makefile.local b/Makefile.local\r
102 > index a890df2..b6c7e0c 100644\r
103 > --- a/Makefile.local\r
104 > +++ b/Makefile.local\r
105 > @@ -290,6 +290,8 @@ notmuch_client_srcs =             \\r
106 >       notmuch-show.c          \\r
107 >       notmuch-tag.c           \\r
108 >       notmuch-time.c          \\r
109 > +     sprinter-json.c         \\r
110 > +     sprinter-text-search.c  \\r
111 >       query-string.c          \\r
112 >       mime-node.c             \\r
113 >       crypto.c                \\r
114 > diff --git a/sprinter-json.c b/sprinter-json.c\r
115 > new file mode 100644\r
116 > index 0000000..215151d\r
117 > --- /dev/null\r
118 > +++ b/sprinter-json.c\r
119 > @@ -0,0 +1,184 @@\r
120 > +#include <stdbool.h>\r
121 > +#include <stdio.h>\r
122 > +#include <talloc.h>\r
123 > +#include "sprinter.h"\r
124 > +\r
125 > +struct sprinter_json {\r
126 > +    struct sprinter vtable;\r
127 > +    FILE *stream;\r
128 > +    /* Top of the state stack, or NULL if the printer is not currently\r
129 > +     * inside any aggregate types. */\r
130 > +    struct json_state *state;\r
131 > +\r
132 > +    /* A flag to signify that a separator should be inserted in the\r
133 > +     * output as soon as possible.\r
134 > +     */\r
135 > +    notmuch_bool_t insert_separator;\r
136 > +};\r
137 > +\r
138 > +struct json_state {\r
139 > +    struct json_state *parent;\r
140 > +    /* True if nothing has been printed in this aggregate yet.\r
141 > +     * Suppresses the comma before a value. */\r
142 > +    notmuch_bool_t first;\r
143 > +    /* The character that closes the current aggregate. */\r
144 > +    char close;\r
145 > +};\r
146 > +\r
147 > +/* Helper function to set up the stream to print a value.  If this\r
148 > + * value follows another value, prints a comma. */\r
149 > +static struct sprinter_json *\r
150 > +json_begin_value (struct sprinter *sp)\r
151 > +{\r
152 > +    struct sprinter_json *spj = (struct sprinter_json *) sp;\r
153 > +\r
154 > +    if (spj->state) {\r
155 > +     if (! spj->state->first) {\r
156 > +         fputc (',', spj->stream);\r
157 > +         if (spj->insert_separator) {\r
158 > +             fputc ('\n', spj->stream);\r
159 > +             spj->insert_separator = FALSE;\r
160 > +         } else\r
161 > +             fputc (' ', spj->stream);\r
162 > +     } else\r
163 > +         spj->state->first = FALSE;\r
164 > +    }\r
165 > +    return spj;\r
166 > +}\r
167 > +\r
168 > +/* Helper function to begin an aggregate type.  Prints the open\r
169 > + * character and pushes a new state frame. */\r
170 > +static void\r
171 > +json_begin_aggregate (struct sprinter *sp, char open, char close)\r
172 > +{\r
173 > +    struct sprinter_json *spj = json_begin_value (sp);\r
174 > +    struct json_state *state = talloc (spj, struct json_state);\r
175 > +\r
176 > +    fputc (open, spj->stream);\r
177 > +    state->parent = spj->state;\r
178 > +    state->first = TRUE;\r
179 > +    state->close = close;\r
180 > +    spj->state = state;\r
181 > +}\r
182 > +\r
183 > +static void\r
184 > +json_begin_map (struct sprinter *sp)\r
185 > +{\r
186 > +    json_begin_aggregate (sp, '{', '}');\r
187 > +}\r
188 > +\r
189 > +static void\r
190 > +json_begin_list (struct sprinter *sp)\r
191 > +{\r
192 > +    json_begin_aggregate (sp, '[', ']');\r
193 > +}\r
194 > +\r
195 > +static void\r
196 > +json_end (struct sprinter *sp)\r
197 > +{\r
198 > +    struct sprinter_json *spj = (struct sprinter_json *) sp;\r
199 > +    struct json_state *state = spj->state;\r
200 > +\r
201 > +    fputc (spj->state->close, spj->stream);\r
202 > +    spj->state = state->parent;\r
203 > +    talloc_free (state);\r
204 > +    if (spj->state == NULL)\r
205 > +     fputc ('\n', spj->stream);\r
206 > +}\r
207 > +\r
208 > +static void\r
209 > +json_string (struct sprinter *sp, const char *val)\r
210 > +{\r
211 > +    static const char *const escapes[] = {\r
212 > +     ['\"'] = "\\\"", ['\\'] = "\\\\", ['\b'] = "\\b",\r
213 > +     ['\f'] = "\\f",  ['\n'] = "\\n",  ['\t'] = "\\t"\r
214 > +    };\r
215 > +    struct sprinter_json *spj = json_begin_value (sp);\r
216 > +\r
217 > +    fputc ('"', spj->stream);\r
218 > +    for (; *val; ++val) {\r
219 > +     unsigned char ch = *val;\r
220 > +     if (ch < ARRAY_SIZE (escapes) && escapes[ch])\r
221 > +         fputs (escapes[ch], spj->stream);\r
222 > +     else if (ch >= 32)\r
223 > +         fputc (ch, spj->stream);\r
224 > +     else\r
225 > +         fprintf (spj->stream, "\\u%04x", ch);\r
226 > +    }\r
227 > +    fputc ('"', spj->stream);\r
228 > +}\r
229 > +\r
230 > +static void\r
231 > +json_integer (struct sprinter *sp, int val)\r
232 > +{\r
233 > +    struct sprinter_json *spj = json_begin_value (sp);\r
234 > +\r
235 > +    fprintf (spj->stream, "%d", val);\r
236 > +}\r
237 > +\r
238 > +static void\r
239 > +json_boolean (struct sprinter *sp, notmuch_bool_t val)\r
240 > +{\r
241 > +    struct sprinter_json *spj = json_begin_value (sp);\r
242 > +\r
243 > +    fputs (val ? "true" : "false", spj->stream);\r
244 > +}\r
245 > +\r
246 > +static void\r
247 > +json_null (struct sprinter *sp)\r
248 > +{\r
249 > +    struct sprinter_json *spj = json_begin_value (sp);\r
250 > +\r
251 > +    fputs ("null", spj->stream);\r
252 > +}\r
253 > +\r
254 > +static void\r
255 > +json_map_key (struct sprinter *sp, const char *key)\r
256 > +{\r
257 > +    struct sprinter_json *spj = (struct sprinter_json *) sp;\r
258 > +\r
259 > +    json_string (sp, key);\r
260 > +    fputs (": ", spj->stream);\r
261 > +    spj->state->first = TRUE;\r
262 > +}\r
263 > +\r
264 > +static void\r
265 > +json_set_prefix (unused (struct sprinter *sp), unused (const char *name))\r
266 > +{\r
267 > +}\r
268 > +\r
269 > +static void\r
270 > +json_separator (struct sprinter *sp)\r
271 > +{\r
272 > +    struct sprinter_json *spj = (struct sprinter_json *) sp;\r
273 > +\r
274 > +    spj->insert_separator = TRUE;\r
275 > +}\r
276 > +\r
277 > +struct sprinter *\r
278 > +sprinter_json_create (const void *ctx, FILE *stream)\r
279 > +{\r
280 > +    static const struct sprinter_json template = {\r
281 > +     .vtable = {\r
282 > +         .begin_map = json_begin_map,\r
283 > +         .begin_list = json_begin_list,\r
284 > +         .end = json_end,\r
285 > +         .string = json_string,\r
286 > +         .integer = json_integer,\r
287 > +         .boolean = json_boolean,\r
288 > +         .null = json_null,\r
289 > +         .map_key = json_map_key,\r
290 > +         .separator = json_separator,\r
291 > +         .set_prefix = json_set_prefix,\r
292 > +     }\r
293 > +    };\r
294 > +    struct sprinter_json *res;\r
295 > +\r
296 > +    res = talloc (ctx, struct sprinter_json);\r
297 > +    if (! res)\r
298 > +     return NULL;\r
299 > +\r
300 > +    *res = template;\r
301 > +    res->stream = stream;\r
302 > +    return &res->vtable;\r
303 > +}\r
304 > diff --git a/sprinter-text-search.c b/sprinter-text-search.c\r
305 > new file mode 100644\r
306 > index 0000000..95ed9cb\r
307 > --- /dev/null\r
308 > +++ b/sprinter-text-search.c\r
309 > @@ -0,0 +1,190 @@\r
310 > +#include <stdbool.h>\r
311 > +#include <stdio.h>\r
312 > +#include <talloc.h>\r
313 > +#include "sprinter.h"\r
314 > +\r
315 > +/* "Structured printer" interface for unstructured text printing.\r
316 > + * This is at best a misuse of the interface, but it simplifies the code\r
317 > + * in notmuch-search.c considerably.\r
318 > + */\r
319 > +\r
320 > +struct sprinter_text_search {\r
321 > +    struct sprinter vtable;\r
322 > +    FILE *stream;\r
323 > +\r
324 > +    /* The current name or prefix to be printed with string/integer/boolean\r
325 > +     * data.\r
326 > +     */\r
327 > +    const char *current_name;\r
328 \r
329 "current_prefix"?  Or maybe just "prefix"?  This is the only place\r
330 where you use the term "name" for this.\r
331 \r
332 > +\r
333 > +    /* A flag to indicate if this is the first tag. Used in list of tags\r
334 > +     * for summary.\r
335 > +     */\r
336 > +    notmuch_bool_t first_tag;\r
337 > +};\r
338 > +\r
339 > +/* struct text_search_state { */\r
340 > +/*     struct text_search_state *parent; */\r
341 > +/* }; */\r
342 \r
343 Left over scratch code?\r
344 \r
345 > +\r
346 > +static notmuch_bool_t\r
347 > +current_context (struct sprinter_text_search *sptxt, const char *marker)\r
348 > +{\r
349 > +    return (sptxt->current_name != NULL\r
350 > +         && ! strncmp (marker, sptxt->current_name, strlen (marker)));\r
351 > +}\r
352 \r
353 All of this context stuff seems way more complicated than having a few\r
354 special cases in notmuch-search.c.  This is, in effect, just moving\r
355 this special casing from there to here, but since the text format is\r
356 highly irregular, attempting to generalize it is only going to\r
357 obfuscate it.\r
358 \r
359 I think the simplest and most readable thing to do is to make all of\r
360 these functions no-ops (literally empty function bodies) except\r
361 text_search_separator and text_search_set_prefix, which should be like\r
362 you have them, and text_search_string:\r
363 \r
364 static void\r
365 text_search_string (struct sprinter *sp, const char *val)\r
366 {\r
367     struct sprinter_text_search *sptxt = (struct sprinter_text_search *) sp;\r
368 \r
369     if (sptxt->current_name)\r
370         fprintf (sptxt->stream, "%s:", sptxt->current_name);\r
371     print_sanitized_string (sptxt->stream, val);\r
372 }\r
373 \r
374 For the summary output, you'll have to format the summary line in\r
375 notmuch-search.c, much like you did in v4, which was a much simpler\r
376 and more readable way to put together the text format lines.\r
377 \r
378 > +\r
379 > +static void\r
380 > +print_sanitized_string (FILE *stream, const char *str)\r
381 > +{\r
382 > +    if (NULL == str)\r
383 > +     return;\r
384 > +\r
385 > +    for (; *str; str++) {\r
386 > +     if ((unsigned char) (*str) < 32)\r
387 > +         fputc ('?', stream);\r
388 > +     else\r
389 > +         fputc (*str, stream);\r
390 > +    }\r
391 > +}\r
392 > +\r
393 > +static void\r
394 > +text_search_begin_map (unused (struct sprinter *sp))\r
395 > +{\r
396 > +}\r
397 > +\r
398 > +static void\r
399 > +text_search_begin_list (struct sprinter *sp)\r
400 > +{\r
401 > +    struct sprinter_text_search *sptxt = (struct sprinter_text_search *) sp;\r
402 > +\r
403 > +    if (current_context (sptxt, "tags")) {\r
404 > +     fputs (" (", sptxt->stream);\r
405 > +     sptxt->first_tag = TRUE;\r
406 > +    }\r
407 > +}\r
408 > +\r
409 > +static void\r
410 > +text_search_end (struct sprinter *sp)\r
411 > +{\r
412 > +    struct sprinter_text_search *sptxt = (struct sprinter_text_search *) sp;\r
413 > +\r
414 > +    if (current_context (sptxt, "tags")) {\r
415 > +     fputc (')', sptxt->stream);\r
416 > +     sptxt->current_name = NULL;\r
417 > +    }\r
418 > +}\r
419 > +\r
420 > +static void\r
421 > +text_search_string (struct sprinter *sp, const char *val)\r
422 > +{\r
423 > +    struct sprinter_text_search *sptxt = (struct sprinter_text_search *) sp;\r
424 > +\r
425 > +    if (sptxt->current_name != NULL) {\r
426 > +     if (current_context (sptxt, "thread"))\r
427 > +         fprintf ( sptxt->stream, "thread:%s ", val);\r
428 > +     else if (current_context (sptxt, "date_relative"))\r
429 > +         fprintf ( sptxt->stream, "%12s ", val);\r
430 > +     else if (current_context (sptxt, "authors")) {\r
431 > +         print_sanitized_string (sptxt->stream, val);\r
432 > +         fputs ("; ", sptxt->stream);\r
433 > +     } else if (current_context (sptxt, "subject"))\r
434 > +         print_sanitized_string (sptxt->stream, val);\r
435 > +     else if (current_context (sptxt, "tags")) {\r
436 > +         if (! sptxt->first_tag)\r
437 > +             fputc (' ', sptxt->stream);\r
438 > +         else\r
439 > +             sptxt->first_tag = FALSE;\r
440 > +\r
441 > +         fputs (val, sptxt->stream);\r
442 > +     } else {\r
443 > +         fputs (sptxt->current_name, sptxt->stream);\r
444 > +         fputc (':', sptxt->stream);\r
445 > +         fputs (val, sptxt->stream);\r
446 > +     }\r
447 > +    } else {\r
448 > +     fputs (val, sptxt->stream);\r
449 > +    }\r
450 > +}\r
451 > +\r
452 > +static void\r
453 > +text_search_integer (struct sprinter *sp, int val)\r
454 > +{\r
455 > +    struct sprinter_text_search *sptxt = (struct sprinter_text_search *) sp;\r
456 > +\r
457 > +    if (sptxt->current_name != NULL) {\r
458 > +     if (current_context (sptxt, "matched"))\r
459 > +         fprintf ( sptxt->stream, "[%d/", val);\r
460 > +     else if (current_context (sptxt, "total"))\r
461 > +         fprintf ( sptxt->stream, "%d] ", val);\r
462 > +    } else\r
463 > +     fprintf (sptxt->stream, "%d", val);\r
464 > +}\r
465 > +\r
466 > +static void\r
467 > +text_search_boolean (struct sprinter *sp, notmuch_bool_t val)\r
468 > +{\r
469 > +    struct sprinter_text_search *sptxt = (struct sprinter_text_search *) sp;\r
470 > +\r
471 > +    fputs (val ? "true" : "false", sptxt->stream);\r
472 > +}\r
473 > +\r
474 > +static void\r
475 > +text_search_null (struct sprinter *sp)\r
476 > +{\r
477 > +    struct sprinter_text_search *sptxt = (struct sprinter_text_search *) sp;\r
478 > +\r
479 > +    fputs ("null", sptxt->stream);\r
480 > +}\r
481 > +\r
482 > +static void\r
483 > +text_search_map_key (struct sprinter *sp, const char *key)\r
484 > +{\r
485 > +    struct sprinter_text_search *sptxt = (struct sprinter_text_search *) sp;\r
486 > +\r
487 > +    sptxt->current_name = key;\r
488 > +}\r
489 > +\r
490 > +static void\r
491 > +text_search_separator (struct sprinter *sp)\r
492 > +{\r
493 > +    struct sprinter_text_search *sptxt = (struct sprinter_text_search *) sp;\r
494 > +\r
495 > +    fputc ('\n', sptxt->stream);\r
496 > +}\r
497 > +\r
498 > +static void\r
499 > +text_search_set_prefix (struct sprinter *sp, const char *name)\r
500 > +{\r
501 > +    struct sprinter_text_search *sptxt = (struct sprinter_text_search *) sp;\r
502 > +\r
503 > +    sptxt->current_name = name;\r
504 > +}\r
505 > +\r
506 > +struct sprinter *\r
507 > +sprinter_text_search_create (const void *ctx, FILE *stream)\r
508 > +{\r
509 > +    static const struct sprinter_text_search template = {\r
510 > +     .vtable = {\r
511 > +         .begin_map = text_search_begin_map,\r
512 > +         .begin_list = text_search_begin_list,\r
513 > +         .end = text_search_end,\r
514 > +         .string = text_search_string,\r
515 > +         .integer = text_search_integer,\r
516 > +         .boolean = text_search_boolean,\r
517 > +         .null = text_search_null,\r
518 > +         .map_key = text_search_map_key,\r
519 > +         .separator = text_search_separator,\r
520 > +         .set_prefix = text_search_set_prefix,\r
521 > +     }\r
522 > +    };\r
523 > +    struct sprinter_text_search *res;\r
524 > +\r
525 > +    res = talloc (ctx, struct sprinter_text_search);\r
526 > +    if (! res)\r
527 > +     return NULL;\r
528 > +\r
529 > +    *res = template;\r
530 > +    res->stream = stream;\r
531 > +    return &res->vtable;\r
532 > +}\r
533 > diff --git a/sprinter.h b/sprinter.h\r
534 > index c9cd6a6..4241d65 100644\r
535 > --- a/sprinter.h\r
536 > +++ b/sprinter.h\r
537 > @@ -47,4 +47,12 @@ typedef struct sprinter {\r
538 >      void (*separator)(struct sprinter *);\r
539 >  } sprinter_t;\r
540 >  \r
541 > +/* Create a new unstructured printer that emits the default Text format for search. */\r
542 \r
543 Wrap at 70 columns.\r
544 \r
545 > +struct sprinter *\r
546 > +sprinter_text_search_create (const void *ctx, FILE *stream);\r
547 > +\r
548 > +/* Create a new structure printer that emits JSON. */\r
549 > +struct sprinter *\r
550 > +sprinter_json_create (const void *ctx, FILE *stream);\r
551 > +\r
552 >  #endif // NOTMUCH_SPRINTER_H\r