--- /dev/null
+Return-Path: <dominik@with-h.at>\r
+X-Original-To: notmuch@notmuchmail.org\r
+Delivered-To: notmuch@notmuchmail.org\r
+Received: from localhost (localhost [127.0.0.1])\r
+ by olra.theworths.org (Postfix) with ESMTP id D222F431FAF\r
+ for <notmuch@notmuchmail.org>; Wed, 18 Jul 2012 11:11:14 -0700 (PDT)\r
+X-Virus-Scanned: Debian amavisd-new at olra.theworths.org\r
+X-Spam-Flag: NO\r
+X-Spam-Score: 0\r
+X-Spam-Level: \r
+X-Spam-Status: No, score=0 tagged_above=-999 required=5 tests=[none]\r
+ autolearn=disabled\r
+Received: from olra.theworths.org ([127.0.0.1])\r
+ by localhost (olra.theworths.org [127.0.0.1]) (amavisd-new, port 10024)\r
+ with ESMTP id MF9c3hsdbnQO for <notmuch@notmuchmail.org>;\r
+ Wed, 18 Jul 2012 11:11:10 -0700 (PDT)\r
+Received: from www73.your-server.de (www73.your-server.de [213.133.104.73])\r
+ (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits))\r
+ (No client certificate requested)\r
+ by olra.theworths.org (Postfix) with ESMTPS id 6E715431FAE\r
+ for <notmuch@notmuchmail.org>; Wed, 18 Jul 2012 11:11:10 -0700 (PDT)\r
+Received: from [78.46.5.203] (helo=sslproxy01.your-server.de)\r
+ by www73.your-server.de with esmtpsa (TLSv1:AES256-SHA:256)\r
+ (Exim 4.74) (envelope-from <dominik@with-h.at>)\r
+ id 1SrYiF-0003Pp-V6; Wed, 18 Jul 2012 20:11:03 +0200\r
+Received: from [132.187.246.85] (helo=fuckup.wlan1.rzuw)\r
+ by sslproxy01.your-server.de with esmtpsa\r
+ (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32) (Exim 4.72)\r
+ (envelope-from <dominik@with-h.at>)\r
+ id 1SrYi8-0006ph-86; Wed, 18 Jul 2012 20:10:56 +0200\r
+From: Dominik Peteler <dominik@with-h.at>\r
+To: notmuch@notmuchmail.org\r
+Subject: [PATCHv2] cli: Hooks for tag-command\r
+Date: Wed, 18 Jul 2012 20:09:30 +0200\r
+Message-Id: <1342634970-12991-1-git-send-email-dominik@with-h.at>\r
+X-Mailer: git-send-email 1.7.11.2\r
+In-Reply-To:\r
+ <CAB+hUn_XHQQ9MDaVXvOMzfM8SdKCWUgnjNQvxJaQWWyc3H621Q@mail.gmail.com>\r
+X-Authenticated-Sender: dude@with-h.at\r
+X-Virus-Scanned: Clear (ClamAV 0.97.3/15151/Wed Jul 18 17:49:52 2012)\r
+X-Mailman-Approved-At: Wed, 18 Jul 2012 13:16:17 -0700\r
+Cc: Dominik Peteler <dominik@with-h.at>\r
+X-BeenThere: notmuch@notmuchmail.org\r
+X-Mailman-Version: 2.1.13\r
+Precedence: list\r
+List-Id: "Use and development of the notmuch mail system."\r
+ <notmuch.notmuchmail.org>\r
+List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,\r
+ <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>\r
+List-Archive: <http://notmuchmail.org/pipermail/notmuch>\r
+List-Post: <mailto:notmuch@notmuchmail.org>\r
+List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>\r
+List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,\r
+ <mailto:notmuch-request@notmuchmail.org?subject=subscribe>\r
+X-List-Received-Date: Wed, 18 Jul 2012 18:11:15 -0000\r
+\r
+hello,\r
+\r
+I improved my patch according to Janis mail:\r
+ * new cli syntax: notmuch tag [ --no-hooks ] -- <tag ops> [ -- ] <search terms>\r
+ * adjusted man pages and wrote tests\r
+\r
+I had the idea to improve this feature by passing the message-ids or the filename to the hooks.\r
+What's your opinion about that ? Any suggestions ?\r
+\r
+regards\r
+\r
+dominik\r
+\r
+\r
+\r
+There are two hooks:\r
+ * pre-tag: Run before tagging\r
+ * post-tag: Run after\r
+\r
+This allows users to react on changes of tags. For example,\r
+you might want to move a message to a special Maildir\r
+depending on its notmuch tags.\r
+---\r
+ man/man1/notmuch-tag.1 | 22 +++++++++++++++++++-\r
+ man/man5/notmuch-hooks.5 | 19 ++++++++++++++++++\r
+ notmuch-tag.c | 52 +++++++++++++++++++++++++++++++++++++++++++++---\r
+ test/hooks | 36 +++++++++++++++++++++++++++++++++\r
+ test/tagging | 28 ++++++++++++++++++++++++++\r
+ 5 files changed, 153 insertions(+), 4 deletions(-)\r
+\r
+diff --git a/man/man1/notmuch-tag.1 b/man/man1/notmuch-tag.1\r
+index d810e1b..e00e189 100644\r
+--- a/man/man1/notmuch-tag.1\r
++++ b/man/man1/notmuch-tag.1\r
+@@ -4,7 +4,11 @@ notmuch-tag \- add/remove tags for all messages matching the search terms\r
+ \r
+ .SH SYNOPSIS\r
+ .B notmuch tag\r
+-.RI "+<" tag> "|\-<" tag "> [...] [\-\-] <" search-term ">..."\r
++.RI "+<" tag "> |\-<" tag "> [...] [\-\-] <" search-term ">..."\r
++\r
++.B notmuch tag\r
++.RB "[" --no-hooks "]"\r
++.RI "\-\- +<" tag "> |\-<" tag "> [...] \-\- <" search-term ">..."\r
+ \r
+ .SH DESCRIPTION\r
+ \r
+@@ -29,6 +33,22 @@ updates the maildir flags according to tag changes if the\r
+ configuration option is enabled. See \fBnotmuch-config\fR(1) for\r
+ details.\r
+ \r
++The\r
++.B tag\r
++command supports hooks. See \fBnotmuch-hooks(5)\fR\r
++for more details on hooks.\r
++\r
++Supported options for\r
++.B tag\r
++include\r
++.RS 4\r
++.TP 4\r
++.BR \-\-no\-hooks\r
++\r
++Prevents hooks from being run.\r
++.RE\r
++.RE\r
++\r
+ .SH SEE ALSO\r
+ \r
+ \fBnotmuch\fR(1), \fBnotmuch-config\fR(1), \fBnotmuch-count\fR(1),\r
+diff --git a/man/man5/notmuch-hooks.5 b/man/man5/notmuch-hooks.5\r
+index b914a29..e193ef5 100644\r
+--- a/man/man5/notmuch-hooks.5\r
++++ b/man/man5/notmuch-hooks.5\r
+@@ -38,6 +38,25 @@ the scan or import.\r
+ Typically this hook is used to perform additional query\-based tagging on the\r
+ imported messages.\r
+ .RE\r
++.RS 4\r
++.TP 4\r
++.B pre\-tag\r
++This hook is invoked by the\r
++.B tag\r
++command before tagging messages. If this\r
++hook exits with a non-zero status, notmuch will abort further processing of the\r
++.B tag\r
++command.\r
++.RE\r
++.RS 4\r
++.TP 4\r
++.B post\-tag\r
++This hook is invoked by the\r
++.B tag\r
++command after messages have been tagged. The hook will not be run if there have been any errors during\r
++the tagging.\r
++.RE\r
++\r
+ \r
+ .SH SEE ALSO\r
+ \r
+diff --git a/notmuch-tag.c b/notmuch-tag.c\r
+index 7d18639..7572059 100644\r
+--- a/notmuch-tag.c\r
++++ b/notmuch-tag.c\r
+@@ -174,9 +174,17 @@ notmuch_tag_command (void *ctx, int argc, char *argv[])\r
+ int tag_ops_count = 0;\r
+ char *query_string;\r
+ notmuch_config_t *config;\r
++ const char *db_path;\r
+ notmuch_database_t *notmuch;\r
+ struct sigaction action;\r
+ notmuch_bool_t synchronize_flags;\r
++ /* Points to the position of the "--" delimiters, e. g.\r
++ * <optional arguments> arg_delimiters[0] <tag ops> arg_delimiters[1] <search terms>\r
++ *\r
++ * arg_delimiters[0] may remain -1 if there are no arguments given\r
++ * arg_delimiters[0] may remain -1 if there is no delimiter between tag ops and search terms */\r
++ int arg_delimiters[2] = {-1, -1};\r
++ notmuch_bool_t run_hooks = TRUE;\r
+ int i;\r
+ int ret;\r
+ \r
+@@ -197,11 +205,37 @@ notmuch_tag_command (void *ctx, int argc, char *argv[])\r
+ return 1;\r
+ }\r
+ \r
++ /* Determine position of delimiters */\r
+ for (i = 0; i < argc; i++) {\r
+ if (strcmp (argv[i], "--") == 0) {\r
+- i++;\r
+- break;\r
++ if (arg_delimiters[1] == -1) {\r
++ arg_delimiters[1] = i;\r
++ } else if (arg_delimiters[0] == -1) {\r
++ arg_delimiters[0] = arg_delimiters[1];\r
++ arg_delimiters[1] = i;\r
++ } else {\r
++ fprintf (stderr, "Error: 'notmuch tag' requires delimiter \"--\" at most two times.\n");\r
++ return 1;\r
++ }\r
+ }\r
++ }\r
++\r
++ /* Process arguments if present */\r
++ for (i = 0; i < arg_delimiters[0]; i++) {\r
++ if (strcmp (argv[i], "--no-hooks") == 0) {\r
++ run_hooks = FALSE;\r
++ } else {\r
++ fprintf (stderr, "Error: 'notmuch tag' doesn't recognize argument '%s'.\n", argv[i]);\r
++ return 1;\r
++ }\r
++ }\r
++\r
++ /* Set arg_delimiters[1] to argc if no delimiters at all are present */\r
++ if (arg_delimiters[1] == -1)\r
++ arg_delimiters[1] = argc;\r
++\r
++ /* Read tag ops */\r
++ for (i = arg_delimiters[0]+1; i < arg_delimiters[1]; i++) {\r
+ if (argv[i][0] == '+' || argv[i][0] == '-') {\r
+ tag_ops[tag_ops_count].tag = argv[i] + 1;\r
+ tag_ops[tag_ops_count].remove = (argv[i][0] == '-');\r
+@@ -229,7 +263,15 @@ notmuch_tag_command (void *ctx, int argc, char *argv[])\r
+ if (config == NULL)\r
+ return 1;\r
+ \r
+- if (notmuch_database_open (notmuch_config_get_database_path (config),\r
++ db_path = notmuch_config_get_database_path (config);\r
++\r
++ if (run_hooks) {\r
++ ret = notmuch_run_hook (db_path, "pre-tag");\r
++ if (ret)\r
++ return ret;\r
++ }\r
++\r
++ if (notmuch_database_open (db_path,\r
+ NOTMUCH_DATABASE_MODE_READ_WRITE, ¬much))\r
+ return 1;\r
+ \r
+@@ -239,5 +281,9 @@ notmuch_tag_command (void *ctx, int argc, char *argv[])\r
+ \r
+ notmuch_database_destroy (notmuch);\r
+ \r
++ if (!ret && run_hooks) {\r
++ ret = notmuch_run_hook (db_path, "post-tag");\r
++ }\r
++\r
+ return ret;\r
+ }\r
+diff --git a/test/hooks b/test/hooks\r
+index 77e8569..ae857cc 100755\r
+--- a/test/hooks\r
++++ b/test/hooks\r
+@@ -31,6 +31,7 @@ rm_hooks () {\r
+ # add a message to generate mail dir and database\r
+ add_message\r
+ \r
++# {pre,post}-new hooks\r
+ test_begin_subtest "pre-new is run"\r
+ rm_hooks\r
+ generate_message\r
+@@ -101,4 +102,39 @@ EOF\r
+ chmod +x "${HOOK_DIR}/pre-new"\r
+ test_expect_code 1 "hook execution failure" "notmuch new"\r
+ \r
++\r
++\r
++# {pre,post}-tag hooks\r
++test_begin_subtest "pre-tag is run"\r
++rm_hooks\r
++generate_message\r
++create_echo_hook "pre-tag" expected output\r
++notmuch tag +foo -- '*' > /dev/null\r
++test_expect_equal_file expected output\r
++\r
++test_begin_subtest "post-tag is run"\r
++rm_hooks\r
++generate_message\r
++create_echo_hook "post-tag" expected output\r
++notmuch tag +foo -- '*' > /dev/null\r
++test_expect_equal_file expected output\r
++\r
++test_begin_subtest "pre-tag is run before post-new"\r
++rm_hooks\r
++generate_message\r
++create_echo_hook "pre-tag" pre-tag.expected pre-tag.output\r
++create_echo_hook "post-tag" post-tag.expected post-tag.output\r
++notmuch tag +foo -- '*' > /dev/null\r
++test_expect_equal_file post-tag.expected post-tag.output\r
++\r
++test_begin_subtest "pre-tag non-zero exit status (hook status)"\r
++rm_hooks\r
++generate_message\r
++create_failing_hook "pre-tag"\r
++output=`notmuch tag +foo -- '*' 2>&1`\r
++test_expect_equal "$output" "Error: pre-tag hook failed with status 13"\r
++\r
++# depends on the previous subtest leaving broken hook behind\r
++test_expect_code 1 "pre-tag non-zero exit status (notmuch status)" "notmuch tag +foo -- '*'"\r
++\r
+ test_done\r
+diff --git a/test/tagging b/test/tagging\r
+index e4782ed..5167f4f 100755\r
+--- a/test/tagging\r
++++ b/test/tagging\r
+@@ -46,4 +46,32 @@ test_expect_equal "$output" "\\r
+ thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; One (:\" inbox tag1 unread)\r
+ thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Two (inbox tag1 tag4 unread)"\r
+ \r
++test_begin_subtest "Arguments mixed with tag ops"\r
++notmuch tag +-no-hooks --no-hooks -- One\r
++notmuch tag --no-hooks +-no-hooks -tag4 -- Two\r
++output=$(notmuch search \* | notmuch_search_sanitize)\r
++test_expect_equal "$output" "\\r
++thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; One (:\" inbox tag1 unread)\r
++thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Two (-no-hooks inbox tag1 unread)"\r
++notmuch tag --no-hooks -- Two\r
++\r
++test_begin_subtest "Arguments with correct position"\r
++notmuch tag --no-hooks -- +tag4 -tag4 -- One\r
++output=$(notmuch search \* | notmuch_search_sanitize)\r
++test_expect_equal "$output" "\\r
++thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; One (:\" inbox tag1 unread)\r
++thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Two (inbox tag1 unread)"\r
++\r
++test_begin_subtest "Missing arguments"\r
++notmuch tag -- +tag4 -tag4 -- One\r
++output=$(notmuch search \* | notmuch_search_sanitize)\r
++test_expect_equal "$output" "\\r
++thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; One (:\" inbox tag1 unread)\r
++thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Two (inbox tag1 unread)"\r
++\r
++test_begin_subtest "Unknown argument"\r
++output=$(notmuch tag --no-blubb -- +tag4 -tag4 -- One 2>&1)\r
++test_expect_equal "$output" "\\r
++Error: 'notmuch tag' doesn't recognize argument '--no-blubb'."\r
++\r
+ test_done\r
+-- \r
+1.7.11.2\r
+\r