[PATCH v2 4/6] cli: add support for batch tagging operations to "notmuch tag"

2012-04-14 Thread Jani Nikula
Add support for batch tagging operations through stdin to "notmuch
tag". This can be enabled with the new --stdin command line option to
"notmuch new". The input must consist of lines of the format:

T +|- [...] [--] 

Each line is interpreted similarly to "notmuch tag" command line
arguments. The delimiter is one or more spaces ' '. Any characters in
 and  MAY be hex encoded with %NN where NN is the
hexadecimal value of the character. Any ' ' and '%' characters in
 and  MUST be hex encoded (using %20 and %25,
respectively). Any characters that are not part of  or
 MUST NOT be hex encoded.

Leading and trailing space ' ' is ignored. Empty lines and lines
beginning with '#' are ignored.

Signed-off-by: Jani Nikula 
---
 notmuch-tag.c |  244 +---
 1 files changed, 213 insertions(+), 31 deletions(-)

diff --git a/notmuch-tag.c b/notmuch-tag.c
index 05feed3..e67555f 100644
--- a/notmuch-tag.c
+++ b/notmuch-tag.c
@@ -19,6 +19,7 @@
  */

 #include "notmuch-client.h"
+#include "hex-escape.h"

 static volatile sig_atomic_t interrupted;

@@ -167,17 +168,181 @@ tag_query (void *ctx, notmuch_database_t *notmuch, const 
char *query_string,
 return interrupted;
 }

+/* like strtok(3), but without state, and doesn't modify s. usage pattern:
+ *
+ * const char *tok = input;
+ * const char *delim = " \t";
+ * size_t tok_len = 0;
+ *
+ * while ((tok = strtok_len (tok + tok_len, delim, _len)) != NULL) {
+ * // do stuff with string tok of length tok_len
+ * }
+ */
+static
+char *strtok_len(char *s, const char *delim, size_t *len)
+{
+/* skip initial delims */
+s += strspn (s, delim);
+
+/* length of token */
+*len = strcspn (s, delim);
+
+return *len ? s : NULL;
+}
+
+/* Tag messages according to 'input', which must consist of lines of
+ * the format:
+ *
+ * T +|- [...] [--] 
+ *
+ * Each line is interpreted similarly to "notmuch tag" command line
+ * arguments. The delimiter is one or more spaces ' '. Any characters
+ * in  and  MAY be hex encoded with %NN where NN is
+ * the hexadecimal value of the character. Any ' ' and '%' characters
+ * in  and  MUST be hex encoded (using %20 and %25,
+ * respectively). Any characters that are not part of  or
+ *  MUST NOT be hex encoded.
+ *
+ * Leading and trailing space ' ' is ignored. Empty lines and lines
+ * beginning with '#' are ignored.
+ */
+static int
+tag_file (void *ctx, notmuch_database_t *notmuch, FILE *input,
+ notmuch_bool_t synchronize_flags)
+{
+char *line = NULL;
+size_t line_size;
+ssize_t line_len;
+tag_operation_t *tag_ops;
+int tag_ops_array_size = 10;
+int ret = 0;
+
+/* Array of tagging operations (add or remove), terminated with an
+ * empty element. Size will be increased as necessary. */
+tag_ops = talloc_array (ctx, tag_operation_t, tag_ops_array_size);
+if (tag_ops == NULL) {
+   fprintf (stderr, "Out of memory.\n");
+   return 1;
+}
+
+while ((line_len = getline (, _size, input)) != -1 &&
+  !interrupted) {
+   char *tok;
+   size_t tok_len;
+   int tag_ops_count = 0;
+
+   chomp_newline (line);
+
+   tok = strtok_len (line, " ", _len);
+
+   /* Skip empty and comment lines. */
+   if (tok == NULL || *tok == '#')
+   continue;
+
+   /* T for tagging is the only recognized action for now. */
+   if (strncmp (tok, "T", tok_len) != 0) {
+   fprintf (stderr, "Warning: Ignoring invalid input line: %s\n",
+line);
+   continue;
+   }
+
+   /* Parse tags. */
+   while ((tok = strtok_len (tok + tok_len, " ", _len)) != NULL) {
+   notmuch_bool_t remove;
+   char *tag;
+
+   /* Optional explicit end of tags marker. */
+   if (strncmp (tok, "--", tok_len) == 0) {
+   tok = strtok_len (tok + tok_len, " ", _len);
+   break;
+   }
+
+   /* Implicit end of tags. */
+   if (*tok != '-' && *tok != '+')
+   break;
+
+   /* If tag is terminated by NUL, there's no query string. */
+   if (*(tok + tok_len) == '\0') {
+   tok = NULL;
+   break;
+   }
+
+   /* Terminate, and start next token after terminator. */
+   *(tok + tok_len++) = '\0';
+
+   remove = (*tok == '-');
+   tag = tok + 1;
+
+   /* Refuse empty tags. */
+   if (*tag == '\0') {
+   tok = NULL;
+   break;
+   }
+
+   /* Decode tag. */
+   if (hex_decode_inplace (tag) != HEX_SUCCESS) {
+   tok = NULL;
+   break;
+   }
+
+   tag_ops[tag_ops_count].tag = tag;
+   tag_ops[tag_ops_count].remove = remove;
+   tag_ops_count++;
+
+   /* Make room for terminating empty element and potential
+* new tags, if necessary. This should be a fairly rare
+* case, 

[PATCH v2 4/6] cli: add support for batch tagging operations to notmuch tag

2012-04-14 Thread Jani Nikula
Add support for batch tagging operations through stdin to notmuch
tag. This can be enabled with the new --stdin command line option to
notmuch new. The input must consist of lines of the format:

T +tag|-tag [...] [--] search-terms

Each line is interpreted similarly to notmuch tag command line
arguments. The delimiter is one or more spaces ' '. Any characters in
tag and search-terms MAY be hex encoded with %NN where NN is the
hexadecimal value of the character. Any ' ' and '%' characters in
tag and search-terms MUST be hex encoded (using %20 and %25,
respectively). Any characters that are not part of tag or
search-terms MUST NOT be hex encoded.

Leading and trailing space ' ' is ignored. Empty lines and lines
beginning with '#' are ignored.

Signed-off-by: Jani Nikula j...@nikula.org
---
 notmuch-tag.c |  244 +---
 1 files changed, 213 insertions(+), 31 deletions(-)

diff --git a/notmuch-tag.c b/notmuch-tag.c
index 05feed3..e67555f 100644
--- a/notmuch-tag.c
+++ b/notmuch-tag.c
@@ -19,6 +19,7 @@
  */
 
 #include notmuch-client.h
+#include hex-escape.h
 
 static volatile sig_atomic_t interrupted;
 
@@ -167,17 +168,181 @@ tag_query (void *ctx, notmuch_database_t *notmuch, const 
char *query_string,
 return interrupted;
 }
 
+/* like strtok(3), but without state, and doesn't modify s. usage pattern:
+ *
+ * const char *tok = input;
+ * const char *delim =  \t;
+ * size_t tok_len = 0;
+ *
+ * while ((tok = strtok_len (tok + tok_len, delim, tok_len)) != NULL) {
+ * // do stuff with string tok of length tok_len
+ * }
+ */
+static
+char *strtok_len(char *s, const char *delim, size_t *len)
+{
+/* skip initial delims */
+s += strspn (s, delim);
+
+/* length of token */
+*len = strcspn (s, delim);
+
+return *len ? s : NULL;
+}
+
+/* Tag messages according to 'input', which must consist of lines of
+ * the format:
+ *
+ * T +tag|-tag [...] [--] search-terms
+ *
+ * Each line is interpreted similarly to notmuch tag command line
+ * arguments. The delimiter is one or more spaces ' '. Any characters
+ * in tag and search-terms MAY be hex encoded with %NN where NN is
+ * the hexadecimal value of the character. Any ' ' and '%' characters
+ * in tag and search-terms MUST be hex encoded (using %20 and %25,
+ * respectively). Any characters that are not part of tag or
+ * search-terms MUST NOT be hex encoded.
+ *
+ * Leading and trailing space ' ' is ignored. Empty lines and lines
+ * beginning with '#' are ignored.
+ */
+static int
+tag_file (void *ctx, notmuch_database_t *notmuch, FILE *input,
+ notmuch_bool_t synchronize_flags)
+{
+char *line = NULL;
+size_t line_size;
+ssize_t line_len;
+tag_operation_t *tag_ops;
+int tag_ops_array_size = 10;
+int ret = 0;
+
+/* Array of tagging operations (add or remove), terminated with an
+ * empty element. Size will be increased as necessary. */
+tag_ops = talloc_array (ctx, tag_operation_t, tag_ops_array_size);
+if (tag_ops == NULL) {
+   fprintf (stderr, Out of memory.\n);
+   return 1;
+}
+
+while ((line_len = getline (line, line_size, input)) != -1 
+  !interrupted) {
+   char *tok;
+   size_t tok_len;
+   int tag_ops_count = 0;
+
+   chomp_newline (line);
+
+   tok = strtok_len (line,  , tok_len);
+
+   /* Skip empty and comment lines. */
+   if (tok == NULL || *tok == '#')
+   continue;
+
+   /* T for tagging is the only recognized action for now. */
+   if (strncmp (tok, T, tok_len) != 0) {
+   fprintf (stderr, Warning: Ignoring invalid input line: %s\n,
+line);
+   continue;
+   }
+
+   /* Parse tags. */
+   while ((tok = strtok_len (tok + tok_len,  , tok_len)) != NULL) {
+   notmuch_bool_t remove;
+   char *tag;
+
+   /* Optional explicit end of tags marker. */
+   if (strncmp (tok, --, tok_len) == 0) {
+   tok = strtok_len (tok + tok_len,  , tok_len);
+   break;
+   }
+
+   /* Implicit end of tags. */
+   if (*tok != '-'  *tok != '+')
+   break;
+
+   /* If tag is terminated by NUL, there's no query string. */
+   if (*(tok + tok_len) == '\0') {
+   tok = NULL;
+   break;
+   }
+
+   /* Terminate, and start next token after terminator. */
+   *(tok + tok_len++) = '\0';
+
+   remove = (*tok == '-');
+   tag = tok + 1;
+
+   /* Refuse empty tags. */
+   if (*tag == '\0') {
+   tok = NULL;
+   break;
+   }
+
+   /* Decode tag. */
+   if (hex_decode_inplace (tag) != HEX_SUCCESS) {
+   tok = NULL;
+   break;
+   }
+
+   tag_ops[tag_ops_count].tag = tag;
+   tag_ops[tag_ops_count].remove = remove;
+   tag_ops_count++;
+
+   /* Make room for