]> granicus.if.org Git - neomutt/commitdiff
Add new pattern type ~I for external searches 1419/head
authorIan Zimmerman <itz@no-use.mooo.com>
Wed, 14 Nov 2018 17:14:11 +0000 (09:14 -0800)
committerRichard Russon <rich@flatcap.org>
Wed, 12 Dec 2018 11:13:54 +0000 (11:13 +0000)
Call external commands, like mairix, to perform searches of the index.

doc/manual.xml.head
globals.h
init.h
mutt.h
pattern.c
pattern.h

index cd037453220fd894f91517993d4ee4eece5b4531..96df26e47e1f17a103d69428c78379e4f62f3295 100644 (file)
@@ -7305,6 +7305,16 @@ set index_format="%4C %Z %{%b %d} %-15.15L (%?l?%4l&amp;%4c?)%*  %s"
                   <quote>Message-ID</quote> field
                 </entry>
               </row>
+              <row>
+                <entry>~I <emphasis>QUERY</emphasis></entry>
+                <entry>
+                  messages whose <quote>Message-ID</quote> field is included in
+                  the results returned from en external search program, when the program
+                  is run with <emphasis>QUERY</emphasis> as its argument.
+                  This is explained in greater detail in the variable reference entry
+                  <xref linkend="external-search-command"/>,
+                </entry>
+              </row>
               <row>
                 <entry>~k</entry>
                 <entry>
index 4c8a874c85dc7386fda1580284f620d27765f960..09a161e518a9239e0dc1533a7ed1d6f488bde45f 100644 (file)
--- a/globals.h
+++ b/globals.h
@@ -139,6 +139,7 @@ WHERE char *IndentString;                  ///< Config: String used to indent 'r
 WHERE char *PrintCommand;                  ///< Config: External command to print a message
 WHERE char *NewMailCommand;                ///< Config: External command to run when new mail arrives
 WHERE char *Realname;                      ///< Config: Real name of the user
+WHERE char *SearchCommand;                 ///< Config: External search command
 WHERE char *Shell;                         ///< Config: External command to run subshells in
 WHERE char *SimpleSearch;                  ///< Config: Pattern to search for when search doesn't contain ~'s
 #ifdef USE_SMTP
diff --git a/init.h b/init.h
index 8c42be46729a1c553a498fc723225b89ce306d86..b766ba391725deeb6a6e7611437d8a61c306dbd9 100644 (file)
--- a/init.h
+++ b/init.h
@@ -928,6 +928,34 @@ struct ConfigDef MuttVars[] = {
   ** .pp
   ** Escape character to use for functions in the built-in editor.
   */
+  { "external_search_command",   DT_COMMAND, R_NONE, &SearchCommand, 0 },
+  /*
+   ** .pp
+   ** If set, contains the name of the external program used by ``~I'' patterns.
+   ** This will usually be a wrapper script around mairix, mu, or similar
+   ** indexers other than notmuch (for which there is optional special support).
+   ** .pp
+   ** Here is an example how it works.  Let's assume $$external_search_command
+   ** is set to ``mairix_filter'', and mairix_filter is a script which
+   ** runs the old but well loved mairix indexer with the arguments
+   ** given to mairix_filter, in the ``raw'' mode of mairix, producing
+   ** on the standard output a list of Message-IDs, one per line.
+   ** (This can be the type of clean and simple script called a \fIone-liner\fP.)
+   ** .pp
+   ** Now if NeoMutt gets a limit or tag command followed by the pattern
+   ** ``~I "-t s:bleeping="'', mairix_filter runs mairix with the
+   ** arguments from inside the quotes (the quotes are needed because
+   ** of the space after ``-t''), mairix finds all messages with
+   ** ``bleeping'' in the Subject plus all messages sharing threads
+   ** with these and outputs their file names, and mairix_filter
+   ** translates the file names into Message-IDs.  Finally, NeoMutt
+   ** reads the Message-IDs and targets the matching messages with the
+   ** command given to it.
+   ** .pp
+   ** You, the user, still have to rewrite the mairix_filter script to
+   ** match the behavior of your indexer, but this should help users
+   ** of indexers other than notmuch to integrate them cleanly with NeoMutt.
+   */
   { "fast_reply",       DT_BOOL, R_NONE, &FastReply, false },
   /*
   ** .pp
diff --git a/mutt.h b/mutt.h
index b9a6e0099dbdfcfea43b445ac3cf161ea69a2452..d2eb0886227209405ec8bd4d5b4a36f6b0a17d6d 100644 (file)
--- a/mutt.h
+++ b/mutt.h
@@ -144,6 +144,7 @@ enum MuttMisc
   MUTT_UNREFERENCED,    ///< Message is unreferenced in the thread
   MUTT_BROKEN,          ///< Message is part of a broken thread
   MUTT_ID,              ///< Pattern matches email's Message-Id
+  MUTT_ID_EXTERNAL,     ///< Message-Id is among results from an external query
   MUTT_BODY,            ///< Pattern matches email's body
   MUTT_HEADER,          ///< Pattern matches email's header
   MUTT_HORMEL,          ///< Pattern matches email's spam score
index 2b33cc60f9d20dc462893afd8043ef9e54fd6476..9d4af76f46d2e50be5564566907d67e0cbca142a 100644 (file)
--- a/pattern.c
+++ b/pattern.c
@@ -47,6 +47,7 @@
 #include "context.h"
 #include "copy.h"
 #include "curs_lib.h"
+#include "filter.h"
 #include "globals.h"
 #include "group.h"
 #include "handler.h"
@@ -245,6 +246,79 @@ static bool eat_regex(struct Pattern *pat, struct Buffer *s, struct Buffer *err)
   return true;
 }
 
+/**
+ * add_query_msgid - Parse a Message-Id and add it to a list - Implements mutt_file_map_t
+ * @retval true Always
+ */
+static bool add_query_msgid(char *line, int line_num, void *user_data)
+{
+  struct ListHead *msgid_list = (struct ListHead *) (user_data);
+  char *nows = mutt_str_skip_whitespace(line);
+  if (!*nows)
+    return true;
+  mutt_str_remove_trailing_ws(nows);
+  mutt_list_insert_tail(msgid_list, mutt_str_strdup(nows));
+  return true;
+}
+
+/**
+ * eat_query - Parse a query for an external search program
+ * @param pat  Pattern to match
+ * @param s   String to parse
+ * @param err Buffer for error messages
+ * @retval true If the pattern was read successfully
+ */
+static bool eat_query(struct Pattern *pat, struct Buffer *s, struct Buffer *err)
+{
+  struct Buffer cmd_buf;
+  struct Buffer tok_buf;
+  FILE *fp = NULL;
+
+  if (!SearchCommand)
+  {
+    mutt_buffer_printf(err, "%s", _("No search command defined"));
+    return false;
+  }
+
+  mutt_buffer_init(&tok_buf);
+  char *pexpr = s->dptr;
+  if (mutt_extract_token(&tok_buf, s, MUTT_TOKEN_PATTERN | MUTT_TOKEN_COMMENT) != 0 ||
+      !tok_buf.data)
+  {
+    mutt_buffer_printf(err, _("Error in expression: %s"), pexpr);
+    return false;
+  }
+  if (*tok_buf.data == '\0')
+  {
+    mutt_buffer_printf(err, "%s", _("Empty expression"));
+    FREE(&tok_buf.data);
+    return false;
+  }
+
+  mutt_buffer_init(&cmd_buf);
+  mutt_buffer_addstr(&cmd_buf, SearchCommand);
+  mutt_buffer_addch(&cmd_buf, ' ');
+  mutt_buffer_addstr(&cmd_buf, tok_buf.data);
+  FREE(&tok_buf.data);
+
+  mutt_message(_("Running search command: %s ..."), cmd_buf.data);
+  pat->ismulti = true;
+  mutt_list_clear(&pat->p.multi_cases);
+  pid_t pid = mutt_create_filter(cmd_buf.data, NULL, &fp, NULL);
+  if (pid < 0)
+  {
+    mutt_buffer_printf(err, "unable to fork command: %s\n", cmd_buf.data);
+    FREE(&cmd_buf.data);
+    return false;
+  }
+
+  mutt_file_map_lines(add_query_msgid, &pat->p.multi_cases, fp, 0);
+  mutt_file_fclose(&fp);
+  mutt_wait_filter(pid);
+  FREE(&cmd_buf.data);
+  return true;
+}
+
 /**
  * get_offset - Calculate a symbolic offset
  * @param tm   Store the time here
@@ -980,7 +1054,9 @@ static bool eat_message_range(struct Pattern *pat, struct Buffer *s, struct Buff
  */
 static int patmatch(const struct Pattern *pat, const char *buf)
 {
-  if (pat->stringmatch)
+  if (pat->ismulti)
+    return (mutt_list_find(&pat->p.multi_cases, buf) == NULL);
+  else if (pat->stringmatch)
     return pat->ign_case ? !strcasestr(buf, pat->p.str) : !strstr(buf, pat->p.str);
   else if (pat->groupmatch)
     return !mutt_group_match(pat->p.g, buf);
@@ -1167,6 +1243,7 @@ static const struct PatternFlags Flags[] = {
   { 'h', MUTT_HEADER,          MUTT_FULL_MSG, eat_regex },
   { 'H', MUTT_HORMEL,          0,             eat_regex },
   { 'i', MUTT_ID,              0,             eat_regex },
+  { 'I', MUTT_ID_EXTERNAL,     0,             eat_query },
   { 'k', MUTT_PGP_KEY,         0,             NULL },
   { 'l', MUTT_LIST,            0,             NULL },
   { 'L', MUTT_ADDRESS,         0,             eat_regex },
@@ -1254,7 +1331,9 @@ void mutt_pattern_free(struct Pattern **pat)
     tmp = *pat;
     *pat = (*pat)->next;
 
-    if (tmp->stringmatch)
+    if (tmp->ismulti)
+      mutt_list_free(&tmp->p.multi_cases);
+    else if (tmp->stringmatch)
       FREE(&tmp->p.str);
     else if (tmp->groupmatch)
       tmp->p.g = NULL;
@@ -1937,6 +2016,7 @@ int mutt_pattern_exec(struct Pattern *pat, enum PatternExecFlag flags,
         return 0;
       return pat->not^(e->env->subject &&patmatch(pat, e->env->subject) == 0);
     case MUTT_ID:
+    case MUTT_ID_EXTERNAL:
       if (!e->env)
         return 0;
       return pat->not^(e->env->message_id &&patmatch(pat, e->env->message_id) == 0);
index d05c0b94f30b7263dd7c5313de22dfc368284137..a29c898c4b83f02df2ebb6c48760be1831a548fc 100644 (file)
--- a/pattern.h
+++ b/pattern.h
@@ -50,6 +50,7 @@ struct Pattern
   bool groupmatch : 1;
   bool ign_case : 1; /**< ignore case for local stringmatch searches */
   bool isalias : 1;
+  bool ismulti : 1; /**< multiple case (only for I pattern now) */
   int min;
   int max;
   struct Pattern *next;
@@ -58,6 +59,7 @@ struct Pattern
     regex_t *regex;
     struct Group *g;
     char *str;
+    struct ListHead multi_cases;
   } p;
 };