Re: [PATCH v4 01/16] add util/search-path.{c, h} to test for executables in $PATH
[notmuch-archives.git] / bd / 8c55191e30b69a0a6c641b97f9c2171b4824aa
1 Return-Path: <bremner@tethera.net>\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 50EAF431FC2\r
6         for <notmuch@notmuchmail.org>; Sat,  8 Dec 2012 14:57:54 -0800 (PST)\r
7 X-Virus-Scanned: Debian amavisd-new at olra.theworths.org\r
8 X-Spam-Flag: NO\r
9 X-Spam-Score: 0\r
10 X-Spam-Level: \r
11 X-Spam-Status: No, score=0 tagged_above=-999 required=5 tests=[none]\r
12         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 I7wXgAni6h5I for <notmuch@notmuchmail.org>;\r
16         Sat,  8 Dec 2012 14:57:51 -0800 (PST)\r
17 Received: from tesseract.cs.unb.ca (tesseract.cs.unb.ca [131.202.240.238])\r
18         (using TLSv1 with cipher AES256-SHA (256/256 bits))\r
19         (No client certificate requested)\r
20         by olra.theworths.org (Postfix) with ESMTPS id F0D61429E3E\r
21         for <notmuch@notmuchmail.org>; Sat,  8 Dec 2012 14:57:42 -0800 (PST)\r
22 Received: from fctnnbsc30w-142167090129.dhcp-dynamic.fibreop.nb.bellaliant.net\r
23         ([142.167.90.129] helo=zancas.localnet)\r
24         by tesseract.cs.unb.ca with esmtpsa\r
25         (TLS1.0:DHE_RSA_AES_128_CBC_SHA1:16) (Exim 4.72)\r
26         (envelope-from <bremner@tethera.net>)\r
27         id 1ThTL3-00066g-If; Sat, 08 Dec 2012 18:57:42 -0400\r
28 Received: from bremner by zancas.localnet with local (Exim 4.80)\r
29         (envelope-from <bremner@tethera.net>)\r
30         id 1ThTKy-0000qc-44; Sat, 08 Dec 2012 18:57:36 -0400\r
31 From: david@tethera.net\r
32 To: notmuch@notmuchmail.org\r
33 Subject: [Patch v5 04/11] tag-util.[ch]: New files for common tagging routines\r
34 Date: Sat,  8 Dec 2012 18:56:54 -0400\r
35 Message-Id: <1355007421-3069-5-git-send-email-david@tethera.net>\r
36 X-Mailer: git-send-email 1.7.10.4\r
37 In-Reply-To: <1355007421-3069-1-git-send-email-david@tethera.net>\r
38 References: <1355007421-3069-1-git-send-email-david@tethera.net>\r
39 X-Spam_bar: -\r
40 Cc: David Bremner <bremner@debian.org>\r
41 X-BeenThere: notmuch@notmuchmail.org\r
42 X-Mailman-Version: 2.1.13\r
43 Precedence: list\r
44 List-Id: "Use and development of the notmuch mail system."\r
45         <notmuch.notmuchmail.org>\r
46 List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,\r
47         <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>\r
48 List-Archive: <http://notmuchmail.org/pipermail/notmuch>\r
49 List-Post: <mailto:notmuch@notmuchmail.org>\r
50 List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>\r
51 List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,\r
52         <mailto:notmuch-request@notmuchmail.org?subject=subscribe>\r
53 X-List-Received-Date: Sat, 08 Dec 2012 22:57:54 -0000\r
54 \r
55 From: David Bremner <bremner@debian.org>\r
56 \r
57 These are meant to be shared between notmuch-tag and notmuch-restore.\r
58 \r
59 The bulk of the routines implement a "tag operation list" abstract\r
60 data type act as a structured representation of a set of tag\r
61 operations (typically coming from a single tag command or line of\r
62 input).\r
63 ---\r
64  Makefile.local |    1 +\r
65  tag-util.c     |  305 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++\r
66  tag-util.h     |  135 +++++++++++++++++++++++++\r
67  3 files changed, 441 insertions(+)\r
68  create mode 100644 tag-util.c\r
69  create mode 100644 tag-util.h\r
70 \r
71 diff --git a/Makefile.local b/Makefile.local\r
72 index 0db1713..c274f07 100644\r
73 --- a/Makefile.local\r
74 +++ b/Makefile.local\r
75 @@ -275,6 +275,7 @@ notmuch_client_srcs =               \\r
76         query-string.c          \\r
77         mime-node.c             \\r
78         crypto.c                \\r
79 +       tag-util.c\r
80  \r
81  notmuch_client_modules = $(notmuch_client_srcs:.c=.o)\r
82  \r
83 diff --git a/tag-util.c b/tag-util.c\r
84 new file mode 100644\r
85 index 0000000..164fdf0\r
86 --- /dev/null\r
87 +++ b/tag-util.c\r
88 @@ -0,0 +1,305 @@\r
89 +#include <assert.h>\r
90 +#include "string-util.h"\r
91 +#include "tag-util.h"\r
92 +#include "hex-escape.h"\r
93 +\r
94 +#define TAG_OP_LIST_INITIAL_SIZE 10\r
95 +\r
96 +struct _tag_operation_t {\r
97 +    const char *tag;\r
98 +    notmuch_bool_t remove;\r
99 +};\r
100 +\r
101 +struct _tag_op_list_t {\r
102 +    tag_operation_t *ops;\r
103 +    size_t count;\r
104 +    size_t size;\r
105 +};\r
106 +\r
107 +static int\r
108 +line_error (tag_parse_status_t status,\r
109 +           const char *line,\r
110 +           const char *format, ...)\r
111 +{\r
112 +    va_list va_args;\r
113 +\r
114 +    va_start (va_args, format);\r
115 +\r
116 +    fprintf (stderr, status < 0 ? "Error: " : "Warning: ");\r
117 +    vfprintf (stderr, format, va_args);\r
118 +    fprintf (stderr, " [%s]\n", line);\r
119 +    return status;\r
120 +}\r
121 +\r
122 +int\r
123 +parse_tag_line (void *ctx, char *line,\r
124 +               tag_op_flag_t flags,\r
125 +               char **query_string,\r
126 +               tag_op_list_t *tag_ops)\r
127 +{\r
128 +    char *tok = line;\r
129 +    size_t tok_len = 0;\r
130 +    char *line_for_error;\r
131 +    int ret = 0;\r
132 +\r
133 +    chomp_newline (line);\r
134 +\r
135 +    line_for_error = talloc_strdup (ctx, line);\r
136 +    if (line_for_error == NULL) {\r
137 +       fprintf (stderr, "Error: out of memory\n");\r
138 +       return -1;\r
139 +    }\r
140 +\r
141 +    /* remove leading space */\r
142 +    while (*tok == ' ' || *tok == '\t')\r
143 +       tok++;\r
144 +\r
145 +    /* Skip empty and comment lines. */\r
146 +    if (*tok == '\0' || *tok == '#') {\r
147 +       ret = 2;\r
148 +       goto DONE;\r
149 +    }\r
150 +\r
151 +    tag_op_list_reset (tag_ops);\r
152 +\r
153 +    /* Parse tags. */\r
154 +    while ((tok = strtok_len (tok + tok_len, " ", &tok_len)) != NULL) {\r
155 +       notmuch_bool_t remove;\r
156 +       char *tag;\r
157 +\r
158 +       /* Optional explicit end of tags marker. */\r
159 +       if (tok_len == 2 && strncmp (tok, "--", tok_len) == 0) {\r
160 +           tok = strtok_len (tok + tok_len, " ", &tok_len);\r
161 +           if (tok == NULL) {\r
162 +               ret = line_error (TAG_PARSE_INVALID, line_for_error,\r
163 +                                 "no query string after --");\r
164 +               goto DONE;\r
165 +           }\r
166 +           break;\r
167 +       }\r
168 +\r
169 +       /* Implicit end of tags. */\r
170 +       if (*tok != '-' && *tok != '+')\r
171 +           break;\r
172 +\r
173 +       /* If tag is terminated by NUL, there's no query string. */\r
174 +       if (*(tok + tok_len) == '\0') {\r
175 +           ret = line_error (TAG_PARSE_INVALID, line_for_error,\r
176 +                             "no query string");\r
177 +           goto DONE;\r
178 +       }\r
179 +\r
180 +       /* Terminate, and start next token after terminator. */\r
181 +       *(tok + tok_len++) = '\0';\r
182 +\r
183 +       remove = (*tok == '-');\r
184 +       tag = tok + 1;\r
185 +\r
186 +       /* Maybe refuse empty tags. */\r
187 +       if (! (flags & TAG_FLAG_BE_GENEROUS) && *tag == '\0') {\r
188 +           ret = line_error (TAG_PARSE_INVALID, line_for_error,\r
189 +                             "empty tag");\r
190 +           goto DONE;\r
191 +       }\r
192 +\r
193 +       /* Decode tag. */\r
194 +       if (hex_decode_inplace (tag) != HEX_SUCCESS) {\r
195 +           ret = line_error (TAG_PARSE_INVALID, line_for_error,\r
196 +                             "hex decoding of tag %s failed", tag);\r
197 +           goto DONE;\r
198 +       }\r
199 +\r
200 +       if (tag_op_list_append (ctx, tag_ops, tag, remove)) {\r
201 +           ret = line_error (TAG_PARSE_OUT_OF_MEMORY, line_for_error,\r
202 +                             "aborting");\r
203 +           goto DONE;\r
204 +       }\r
205 +    }\r
206 +\r
207 +    if (tok == NULL) {\r
208 +       /* use a different error message for testing */\r
209 +       ret = line_error (TAG_PARSE_INVALID, line_for_error,\r
210 +                         "missing query string");\r
211 +       goto DONE;\r
212 +    }\r
213 +\r
214 +    /* tok now points to the query string */\r
215 +    if (hex_decode_inplace (tok) != HEX_SUCCESS) {\r
216 +       ret = line_error (TAG_PARSE_INVALID, line_for_error,\r
217 +                         "hex decoding of query %s failed", tok);\r
218 +       goto DONE;\r
219 +    }\r
220 +\r
221 +    *query_string = tok;\r
222 +\r
223 +  DONE:\r
224 +    talloc_free (line_for_error);\r
225 +    return ret;\r
226 +}\r
227 +\r
228 +static inline void\r
229 +message_error (notmuch_message_t *message,\r
230 +              notmuch_status_t status,\r
231 +              const char *format, ...)\r
232 +{\r
233 +    va_list va_args;\r
234 +\r
235 +    va_start (va_args, format);\r
236 +\r
237 +    vfprintf (stderr, format, va_args);\r
238 +    fprintf (stderr, "Message-ID: %s\n", notmuch_message_get_message_id (message));\r
239 +    fprintf (stderr, "Status: %s\n", notmuch_status_to_string (status));\r
240 +}\r
241 +\r
242 +notmuch_status_t\r
243 +tag_op_list_apply (notmuch_message_t *message,\r
244 +                  tag_op_list_t *list,\r
245 +                  tag_op_flag_t flags)\r
246 +{\r
247 +    size_t i;\r
248 +    notmuch_status_t status = 0;\r
249 +    tag_operation_t *tag_ops = list->ops;\r
250 +\r
251 +    status = notmuch_message_freeze (message);\r
252 +    if (status) {\r
253 +       message_error (message, status, "freezing message");\r
254 +       return status;\r
255 +    }\r
256 +\r
257 +    if (flags & TAG_FLAG_REMOVE_ALL) {\r
258 +       status = notmuch_message_remove_all_tags (message);\r
259 +       if (status) {\r
260 +           message_error (message, status, "removing all tags");\r
261 +           return status;\r
262 +       }\r
263 +    }\r
264 +\r
265 +    for (i = 0; i < list->count; i++) {\r
266 +       if (tag_ops[i].remove) {\r
267 +           status = notmuch_message_remove_tag (message, tag_ops[i].tag);\r
268 +           if (status) {\r
269 +               message_error (message, status, "removing tag %s", tag_ops[i].tag);\r
270 +               return status;\r
271 +           }\r
272 +       } else {\r
273 +           status = notmuch_message_add_tag (message, tag_ops[i].tag);\r
274 +           if (status) {\r
275 +               message_error (message, status, "adding tag %s", tag_ops[i].tag);\r
276 +               return status;\r
277 +           }\r
278 +\r
279 +       }\r
280 +    }\r
281 +\r
282 +    status = notmuch_message_thaw (message);\r
283 +    if (status) {\r
284 +       message_error (message, status, "thawing message");\r
285 +       return status;\r
286 +    }\r
287 +\r
288 +\r
289 +    if (flags & TAG_FLAG_MAILDIR_SYNC) {\r
290 +       status = notmuch_message_tags_to_maildir_flags (message);\r
291 +       if (status) {\r
292 +           message_error (message, status, "synching tags to maildir");\r
293 +           return status;\r
294 +       }\r
295 +    }\r
296 +\r
297 +    return NOTMUCH_STATUS_SUCCESS;\r
298 +\r
299 +}\r
300 +\r
301 +\r
302 +/* Array of tagging operations (add or remove.  Size will be increased\r
303 + * as necessary. */\r
304 +\r
305 +tag_op_list_t *\r
306 +tag_op_list_create (void *ctx)\r
307 +{\r
308 +    tag_op_list_t *list;\r
309 +\r
310 +    list = talloc (ctx, tag_op_list_t);\r
311 +    if (list == NULL)\r
312 +       return NULL;\r
313 +\r
314 +    list->size = TAG_OP_LIST_INITIAL_SIZE;\r
315 +    list->count = 0;\r
316 +\r
317 +    list->ops = talloc_array (ctx, tag_operation_t, list->size);\r
318 +    if (list->ops == NULL)\r
319 +       return NULL;\r
320 +\r
321 +    return list;\r
322 +}\r
323 +\r
324 +\r
325 +int\r
326 +tag_op_list_append (void *ctx,\r
327 +                   tag_op_list_t *list,\r
328 +                   const char *tag,\r
329 +                   notmuch_bool_t remove)\r
330 +{\r
331 +    /* Make room if current array is full.  This should be a fairly\r
332 +     * rare case, considering the initial array size.\r
333 +     */\r
334 +\r
335 +    if (list->count == list->size) {\r
336 +       list->size *= 2;\r
337 +       list->ops = talloc_realloc (ctx, list->ops, tag_operation_t,\r
338 +                                   list->size);\r
339 +       if (list->ops == NULL) {\r
340 +           fprintf (stderr, "Out of memory.\n");\r
341 +           return 1;\r
342 +       }\r
343 +    }\r
344 +\r
345 +    /* add the new operation */\r
346 +\r
347 +    list->ops[list->count].tag = tag;\r
348 +    list->ops[list->count].remove = remove;\r
349 +    list->count++;\r
350 +    return 0;\r
351 +}\r
352 +\r
353 +/*\r
354 + *   Is the i'th tag operation a remove?\r
355 + */\r
356 +\r
357 +notmuch_bool_t\r
358 +tag_op_list_isremove (const tag_op_list_t *list, size_t i)\r
359 +{\r
360 +    assert (i < list->count);\r
361 +    return list->ops[i].remove;\r
362 +}\r
363 +\r
364 +/*\r
365 + * Reset a list to contain no operations\r
366 + */\r
367 +\r
368 +void\r
369 +tag_op_list_reset (tag_op_list_t *list)\r
370 +{\r
371 +    list->count = 0;\r
372 +}\r
373 +\r
374 +/*\r
375 + * Return the number of operations in a list\r
376 + */\r
377 +\r
378 +size_t\r
379 +tag_op_list_size (const tag_op_list_t *list)\r
380 +{\r
381 +    return list->count;\r
382 +}\r
383 +\r
384 +/*\r
385 + *   return the i'th tag in the list\r
386 + */\r
387 +\r
388 +const char *\r
389 +tag_op_list_tag (const tag_op_list_t *list, size_t i)\r
390 +{\r
391 +    assert (i < list->count);\r
392 +    return list->ops[i].tag;\r
393 +}\r
394 diff --git a/tag-util.h b/tag-util.h\r
395 new file mode 100644\r
396 index 0000000..99b0fa0\r
397 --- /dev/null\r
398 +++ b/tag-util.h\r
399 @@ -0,0 +1,135 @@\r
400 +#ifndef _TAG_UTIL_H\r
401 +#define _TAG_UTIL_H\r
402 +\r
403 +#include "notmuch-client.h"\r
404 +\r
405 +typedef struct _tag_operation_t tag_operation_t;\r
406 +typedef struct _tag_op_list_t tag_op_list_t;\r
407 +\r
408 +/* Use powers of 2 */\r
409 +typedef enum {\r
410 +    TAG_FLAG_NONE = 0,\r
411 +\r
412 +    /* Operations are synced to maildir, if possible.\r
413 +     */\r
414 +    TAG_FLAG_MAILDIR_SYNC = (1 << 0),\r
415 +\r
416 +    /* Remove all tags from message before applying list.\r
417 +     */\r
418 +    TAG_FLAG_REMOVE_ALL = (1 << 1),\r
419 +\r
420 +    /* Don't try to avoid database operations. Useful when we\r
421 +     * know that message passed needs these operations.\r
422 +     */\r
423 +    TAG_FLAG_PRE_OPTIMIZED = (1 << 2),\r
424 +\r
425 +    /* Accept strange tags that might be user error;\r
426 +     * intended for use by notmuch-restore.\r
427 +     */\r
428 +    TAG_FLAG_BE_GENEROUS = (1 << 3)\r
429 +\r
430 +} tag_op_flag_t;\r
431 +\r
432 +/* These should obey the convention that fatal errors are negative,\r
433 + * skipped lines are positive.\r
434 + */\r
435 +typedef enum {\r
436 +    TAG_PARSE_OUT_OF_MEMORY = -1,\r
437 +\r
438 +    /* Line parsed successfuly. */\r
439 +    TAG_PARSE_SUCCESS = 0,\r
440 +\r
441 +    /* Line has a syntax error */\r
442 +    TAG_PARSE_INVALID = 1,\r
443 +\r
444 +    /* Line was blank or a comment */\r
445 +    TAG_PARSE_SKIPPED = 2\r
446 +\r
447 +} tag_parse_status_t;\r
448 +\r
449 +/* Parse a string of the following format:\r
450 + *\r
451 + * +<tag>|-<tag> [...] [--] <search-terms>\r
452 + *\r
453 + * Each line is interpreted similarly to "notmuch tag" command line\r
454 + * arguments. The delimiter is one or more spaces ' '. Any characters\r
455 + * in <tag> and <search-terms> MAY be hex encoded with %NN where NN is\r
456 + * the hexadecimal value of the character. Any ' ' and '%' characters\r
457 + * in <tag> and <search-terms> MUST be hex encoded (using %20 and %25,\r
458 + * respectively). Any characters that are not part of <tag> or\r
459 + * <search-terms> MUST NOT be hex encoded.\r
460 + *\r
461 + * Leading and trailing space ' ' is ignored. Empty lines and lines\r
462 + * beginning with '#' are ignored.\r
463 + *\r
464 + *\r
465 + * Output Parameters:\r
466 + *     ops     contains a list of tag operations\r
467 + *     query_str the search terms.\r
468 + */\r
469 +tag_parse_status_t\r
470 +parse_tag_line (void *ctx, char *line,\r
471 +               tag_op_flag_t flags,\r
472 +               char **query_str, tag_op_list_t *ops);\r
473 +\r
474 +/*\r
475 + * Create an empty list of tag operations\r
476 + *\r
477 + * ctx is passed to talloc\r
478 + */\r
479 +\r
480 +tag_op_list_t *\r
481 +tag_op_list_create (void *ctx);\r
482 +\r
483 +/*\r
484 + * Add a tag operation (delete iff remove == TRUE) to a list.\r
485 + * The list is expanded as necessary.\r
486 + */\r
487 +\r
488 +int\r
489 +tag_op_list_append (void *ctx,\r
490 +                   tag_op_list_t *list,\r
491 +                   const char *tag,\r
492 +                   notmuch_bool_t remove);\r
493 +\r
494 +/*\r
495 + * Apply a list of tag operations, in order, to a given message.\r
496 + *\r
497 + * Flags can be bitwise ORed; see enum above for possibilies.\r
498 + */\r
499 +\r
500 +notmuch_status_t\r
501 +tag_op_list_apply (notmuch_message_t *message,\r
502 +                  tag_op_list_t *tag_ops,\r
503 +                  tag_op_flag_t flags);\r
504 +\r
505 +/*\r
506 + * Return the number of operations in a list\r
507 + */\r
508 +\r
509 +size_t\r
510 +tag_op_list_size (const tag_op_list_t *list);\r
511 +\r
512 +/*\r
513 + * Reset a list to contain no operations\r
514 + */\r
515 +\r
516 +void\r
517 +tag_op_list_reset (tag_op_list_t *list);\r
518 +\r
519 +\r
520 +/*\r
521 + *   return the i'th tag in the list\r
522 + */\r
523 +\r
524 +const char *\r
525 +tag_op_list_tag (const tag_op_list_t *list, size_t i);\r
526 +\r
527 +/*\r
528 + *   Is the i'th tag operation a remove?\r
529 + */\r
530 +\r
531 +notmuch_bool_t\r
532 +tag_op_list_isremove (const tag_op_list_t *list, size_t i);\r
533 +\r
534 +#endif\r
535 -- \r
536 1.7.10.4\r
537 \r