From 083facc4f1a34dce5dd51c9c15f805e36a69a45e Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Sun, 1 Apr 2012 01:17:26 +0300 Subject: [PATCH] [PATCH 6/8] cli: add support for batch tagging operations to "notmuch tag" --- b2/0c2452dc9c6016a9e40e8fa84785d2f86a3168 | 387 ++++++++++++++++++++++ 1 file changed, 387 insertions(+) create mode 100644 b2/0c2452dc9c6016a9e40e8fa84785d2f86a3168 diff --git a/b2/0c2452dc9c6016a9e40e8fa84785d2f86a3168 b/b2/0c2452dc9c6016a9e40e8fa84785d2f86a3168 new file mode 100644 index 000000000..d1859c934 --- /dev/null +++ b/b2/0c2452dc9c6016a9e40e8fa84785d2f86a3168 @@ -0,0 +1,387 @@ +Return-Path: +X-Original-To: notmuch@notmuchmail.org +Delivered-To: notmuch@notmuchmail.org +Received: from localhost (localhost [127.0.0.1]) + by olra.theworths.org (Postfix) with ESMTP id BCFC942116A + for ; Sat, 31 Mar 2012 15:18:14 -0700 (PDT) +X-Virus-Scanned: Debian amavisd-new at olra.theworths.org +X-Amavis-Alert: BAD HEADER SECTION, Duplicate header field: "References" +X-Spam-Flag: NO +X-Spam-Score: -0.7 +X-Spam-Level: +X-Spam-Status: No, score=-0.7 tagged_above=-999 required=5 + tests=[RCVD_IN_DNSWL_LOW=-0.7] autolearn=disabled +Received: from olra.theworths.org ([127.0.0.1]) + by localhost (olra.theworths.org [127.0.0.1]) (amavisd-new, port 10024) + with ESMTP id 2r-5R4GUH8Mm for ; + Sat, 31 Mar 2012 15:18:11 -0700 (PDT) +Received: from mail-bk0-f53.google.com (mail-bk0-f53.google.com + [209.85.214.53]) (using TLSv1 with cipher RC4-SHA (128/128 bits)) + (No client certificate requested) + by olra.theworths.org (Postfix) with ESMTPS id E82C5421178 + for ; Sat, 31 Mar 2012 15:17:57 -0700 (PDT) +Received: by mail-bk0-f53.google.com with SMTP id j4so1547751bkw.26 + for ; Sat, 31 Mar 2012 15:17:57 -0700 (PDT) +X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; + d=google.com; s=20120113; + h=from:to:cc:subject:date:message-id:x-mailer:in-reply-to:references + :in-reply-to:references:x-gm-message-state; + bh=/UaCCC+hBf9uZWlbGQ8AotmRIGrXBkZdjJA2/ym6AuY=; + b=XWdvm3ozTPsWfqK9WjIoxIKHzhfV1IZ+1z8Nr6y8UMBRuDgSGt0swW4tV01qfueWKJ + b8ntEZoDfud5CpBiWpsBziiTQrcyT2IAH6tewqjdDZJQFZ6sxkdS+IWsxthdxCeKKCkz + RzUWNOkSbZ4q5Nz+B8fEpEJVUggJCqxHms61rIw2d8x80L7tLNiET29zoR+8hoHzc+Zw + OQaa0+Y0tG1etDvGbyxv1M7ejpzDzl6NlvoeB9MTUUDeyjUV6JXWKBCjsb9aRJyUx0ao + D/RIV6MlOzbf+47DgDSDcqRDsbaa9VqUiQCISho51E75Ga2g4PLD3lJI5mGu0mCnuIB5 + +pOg== +Received: by 10.204.133.204 with SMTP id g12mr1385964bkt.64.1333232277504; + Sat, 31 Mar 2012 15:17:57 -0700 (PDT) +Received: from localhost (dsl-hkibrasgw4-fe50f800-253.dhcp.inet.fi. + [84.248.80.253]) by mx.google.com with ESMTPS id + zx16sm28521136bkb.13.2012.03.31.15.17.55 + (version=SSLv3 cipher=OTHER); Sat, 31 Mar 2012 15:17:56 -0700 (PDT) +From: Jani Nikula +To: notmuch@notmuchmail.org +Subject: [PATCH 6/8] cli: add support for batch tagging operations to "notmuch + tag" +Date: Sun, 1 Apr 2012 01:17:26 +0300 +Message-Id: + +X-Mailer: git-send-email 1.7.5.4 +In-Reply-To: +References: +In-Reply-To: +References: +X-Gm-Message-State: + ALoCoQkhctEMCFHoQJ8ESesmTYqwPfFbxYrWe0t3innvzxFR9k4AloFJpYCd7fnDhdmxfcpZ5aoG +X-BeenThere: notmuch@notmuchmail.org +X-Mailman-Version: 2.1.13 +Precedence: list +List-Id: "Use and development of the notmuch mail system." + +List-Unsubscribe: , + +List-Archive: +List-Post: +List-Help: +List-Subscribe: , + +X-List-Received-Date: Sat, 31 Mar 2012 22:18:15 -0000 + +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 ASCII 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..32c7167 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 +|- [...] [--] ++ * ++ * 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 ASCII 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 (&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 terminating empty element and potential ++ * new tags, if necessary. This should be a fairly rare ++ * case, considering the initial array size. */ ++ if (tag_ops_count == tag_ops_array_size) { ++ tag_ops_array_size *= 2; ++ tag_ops = talloc_realloc (ctx, tag_ops, tag_operation_t, ++ tag_ops_array_size); ++ if (tag_ops == NULL) { ++ fprintf (stderr, "Out of memory.\n"); ++ return 1; ++ } ++ } ++ } ++ ++ if (tok == NULL || tag_ops_count == 0) { ++ /* FIXME: line has been modified! */ ++ fprintf (stderr, "Warning: Ignoring invalid input line: %s\n", ++ line); ++ continue; ++ } ++ ++ tag_ops[tag_ops_count].tag = NULL; ++ ++ /* tok now points to the query string */ ++ if (hex_decode_inplace (tok) != HEX_SUCCESS) { ++ /* FIXME: line has been modified! */ ++ fprintf (stderr, "Warning: Ignoring invalid input line: %s\n", ++ line); ++ continue; ++ } ++ ++ ret = tag_query (ctx, notmuch, tok, tag_ops, synchronize_flags); ++ if (ret) ++ break; ++ } ++ ++ if (line) ++ free (line); ++ ++ return ret || interrupted; ++} ++ + int + notmuch_tag_command (void *ctx, int argc, char *argv[]) + { +- tag_operation_t *tag_ops; ++ tag_operation_t *tag_ops = NULL; + int tag_ops_count = 0; +- char *query_string; ++ char *query_string = NULL; + notmuch_config_t *config; + notmuch_database_t *notmuch; + struct sigaction action; + notmuch_bool_t synchronize_flags; +- int i; ++ notmuch_bool_t use_stdin = FALSE; ++ int i, opt_index; + int ret; + + /* Setup our handler for SIGINT */ +@@ -187,42 +352,56 @@ notmuch_tag_command (void *ctx, int argc, char *argv[]) + action.sa_flags = SA_RESTART; + sigaction (SIGINT, &action, NULL); + +- argc--; argv++; /* skip subcommand argument */ ++ notmuch_opt_desc_t options[] = { ++ { NOTMUCH_OPT_BOOLEAN, &use_stdin, "stdin", 0, 0 }, ++ { 0, 0, 0, 0, 0 } ++ }; + +- /* Array of tagging operations (add or remove), terminated with an +- * empty element. */ +- tag_ops = talloc_array (ctx, tag_operation_t, argc + 1); +- if (tag_ops == NULL) { +- fprintf (stderr, "Out of memory.\n"); ++ opt_index = parse_arguments (argc, argv, options, 1); ++ if (opt_index < 0) + return 1; +- } + +- for (i = 0; i < argc; i++) { +- if (strcmp (argv[i], "--") == 0) { +- i++; +- break; ++ if (use_stdin) { ++ if (opt_index != argc) { ++ fprintf (stderr, "Can't specify both cmdline and stdin!\n"); ++ return 1; + } +- if (argv[i][0] == '+' || argv[i][0] == '-') { +- tag_ops[tag_ops_count].tag = argv[i] + 1; +- tag_ops[tag_ops_count].remove = (argv[i][0] == '-'); +- tag_ops_count++; +- } else { +- break; ++ } else { ++ /* Array of tagging operations (add or remove), terminated with an ++ * empty element. */ ++ tag_ops = talloc_array (ctx, tag_operation_t, argc - opt_index + 1); ++ if (tag_ops == NULL) { ++ fprintf (stderr, "Out of memory.\n"); ++ return 1; + } +- } + +- tag_ops[tag_ops_count].tag = NULL; ++ for (i = opt_index; i < argc; i++) { ++ if (strcmp (argv[i], "--") == 0) { ++ i++; ++ break; ++ } ++ if (argv[i][0] == '+' || argv[i][0] == '-') { ++ tag_ops[tag_ops_count].tag = argv[i] + 1; ++ tag_ops[tag_ops_count].remove = (argv[i][0] == '-'); ++ tag_ops_count++; ++ } else { ++ break; ++ } ++ } + +- if (tag_ops_count == 0) { +- fprintf (stderr, "Error: 'notmuch tag' requires at least one tag to add or remove.\n"); +- return 1; +- } ++ tag_ops[tag_ops_count].tag = NULL; + +- query_string = query_string_from_args (ctx, argc - i, &argv[i]); ++ if (tag_ops_count == 0) { ++ fprintf (stderr, "Error: 'notmuch tag' requires at least one tag to add or remove.\n"); ++ return 1; ++ } + +- if (*query_string == '\0') { +- fprintf (stderr, "Error: notmuch tag requires at least one search term.\n"); +- return 1; ++ query_string = query_string_from_args (ctx, argc - i, &argv[i]); ++ ++ if (*query_string == '\0') { ++ fprintf (stderr, "Error: notmuch tag requires at least one search term.\n"); ++ return 1; ++ } + } + + config = notmuch_config_open (ctx, NULL, NULL); +@@ -236,7 +415,10 @@ notmuch_tag_command (void *ctx, int argc, char *argv[]) + + synchronize_flags = notmuch_config_get_maildir_synchronize_flags (config); + +- ret = tag_query (ctx, notmuch, query_string, tag_ops, synchronize_flags); ++ if (use_stdin) ++ ret = tag_file (ctx, notmuch, stdin, synchronize_flags); ++ else ++ ret = tag_query (ctx, notmuch, query_string, tag_ops, synchronize_flags); + + notmuch_database_close (notmuch); + +-- +1.7.5.4 + -- 2.26.2