[PATCH v2 4/6] cli: add support for batch tagging operations to "notmuch tag"
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
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