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