<<< Date Index >>>     <<< Thread Index >>>

[PATCH] pattern groups



A happy new year.

This patch (against current CVS) adds the concept of "pattern
groups" to mutt.  A pattern group consists of a list of e-mail
addresses and a list of regular expressions.  If any of the
addresses or any of the regular expressions match, the pattern
group matches.

To match against a pattern group, use '$' instead of '~'
(better character suggestions would be much appreciated.)

To populate a pattern group, add

        -group <groupname>

immediately after the alias, subscribe, lists, and alternates
commands:

  alias -group mutt mutt-dev@xxxxxxxx
  alias -group mutt mutt-users@xxxxxxxx
  alternates -group mutt roessler@xxxxxxxx
  
  lists -group mutt .*@mutt.org

A hook can then talk about these groups like this:

  send-hook '$C mutt | $f mutt' ...

Missing features include support for multiple groups in a
single alias / alternates / lists, and some group maintenance
commands (such as "add / remove this regexp / address to this
group").  Documentation is also missing.

Some particularly interesting perspective that one could do
with this would be to refactor the existing alternates / lists
/ subscribe code on top of this.  One could then do things
like:

        alias -group mutt -group alternates rmutt roessler@xxxxxxxx

I haven't added this to CVS, yet, and would welcome any
comments.  I intend to commit it, though, in particular since
I'm using it to organize my own mailing habits.

Regards,
-- 
Thomas Roessler · Personal soap box at <http://log.does-not-exist.org/>.
Index: Makefile.am
===================================================================
RCS file: /cvs/mutt/mutt/Makefile.am,v
retrieving revision 3.39
diff -u -r3.39 Makefile.am
--- Makefile.am 20 Dec 2005 17:50:46 -0000      3.39
+++ Makefile.am 9 Jan 2006 13:24:27 -0000
@@ -20,7 +20,8 @@
        addrbook.c alias.c attach.c base64.c browser.c buffy.c color.c \
         crypt.c cryptglue.c \
        commands.c complete.c compose.c copy.c curs_lib.c curs_main.c date.c \
-       edit.c enter.c flags.c init.c filter.c from.c getdomain.c \
+       edit.c enter.c flags.c init.c filter.c from.c \
+       getdomain.c group.c \
        handler.c hash.c hdrline.c headers.c help.c hook.c keymap.c \
        main.c mbox.c menu.c mh.c mx.c pager.c parse.c pattern.c \
        postpone.c query.c recvattach.c recvcmd.c \
Index: globals.h
===================================================================
RCS file: /cvs/mutt/mutt/globals.h,v
retrieving revision 3.27
diff -u -r3.27 globals.h
--- globals.h   22 Dec 2005 17:46:40 -0000      3.27
+++ globals.h   9 Jan 2006 13:24:27 -0000
@@ -139,6 +139,8 @@
 WHERE char *CurrentFolder;
 WHERE char *LastFolder;
 
+WHERE HASH *Groups;
+
 WHERE LIST *AutoViewList INITVAL(0);
 WHERE LIST *AlternativeOrderList INITVAL(0);
 WHERE LIST *AttachAllow INITVAL(0);
Index: group.c
===================================================================
RCS file: group.c
diff -N group.c
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ group.c     9 Jan 2006 13:24:27 -0000
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2006 Thomas Roessler <roessler@xxxxxxxxxxxxxxxxxx>
+ * 
+ *     This program is free software; you can redistribute it and/or modify
+ *     it under the terms of the GNU General Public License as published by
+ *     the Free Software Foundation; either version 2 of the License, or
+ *     (at your option) any later version.
+ * 
+ *     This program is distributed in the hope that it will be useful,
+ *     but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *     GNU General Public License for more details.
+ * 
+ *     You should have received a copy of the GNU General Public License
+ *     along with this program; if not, write to the Free Software
+ *     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  
02110-1301, USA.
+ */ 
+
+#if HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "mutt.h"
+#include "mapping.h"
+#include "mutt_curses.h"
+#include "mutt_regex.h"
+#include "history.h"
+#include "keymap.h"
+#include "mbyte.h"
+#include "charset.h"
+#include "mutt_crypt.h"
+#include "mutt_idna.h"
+
+#include <ctype.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/utsname.h>
+#include <errno.h>
+#include <sys/wait.h>
+
+group_t *mutt_pattern_group (const char *k)
+{
+  group_t *p;
+  
+  if (!k)
+    return 0;
+  
+  if (!(p = hash_find (Groups, k)))
+  {
+    dprint (2, (debugfile, "mutt_pattern_group: Creating group %s.\n", k));
+    p = safe_calloc (1, sizeof (group_t));
+    p->name = safe_strdup (k);
+    hash_insert (Groups, p->name, p, 0);
+  }
+  
+  return p;
+}
+
+void mutt_group_add_adrlist (group_t *g, ADDRESS *a)
+{
+  ADDRESS **p;
+
+  if (!g)
+    return;
+  if (!a)
+    return;
+  
+  for (p = &g->as; *p; p = &((*p)->next))
+    ;
+  
+  *p = rfc822_cpy_adr (a);
+}
+
+int mutt_group_match (group_t *g, const char *s)
+{
+  ADDRESS *ap;
+  
+  
+  if (s && g)
+  {
+    if (mutt_match_rx_list (s, g->rs))
+      return 1;
+    for (ap = g->as; ap; ap = ap->next)
+      if (ap->mailbox && !mutt_strcasecmp (s, ap->mailbox))
+       return 1;
+  }
+  return 0;
+}
+
Index: init.c
===================================================================
RCS file: /cvs/mutt/mutt/init.c,v
retrieving revision 3.47
diff -u -r3.47 init.c
--- init.c      16 Dec 2005 18:49:40 -0000      3.47
+++ init.c      9 Jan 2006 13:24:28 -0000
@@ -134,7 +134,7 @@
          (ch == '#' && !(flags & M_TOKEN_COMMENT)) ||
          (ch == '=' && (flags & M_TOKEN_EQUAL)) ||
          (ch == ';' && !(flags & M_TOKEN_SEMICOLON)) ||
-         ((flags & M_TOKEN_PATTERN) && strchr ("~=!|", ch)))
+         ((flags & M_TOKEN_PATTERN) && strchr ("~$=!|", ch)))
        break;
     }
 
@@ -652,14 +652,40 @@
 
 static int parse_alternates (BUFFER *buf, BUFFER *s, unsigned long data, 
BUFFER *err)
 {
+  short first = 1;
+  group_t *group = NULL;
+
   _alternates_clean();
   do
   {
     mutt_extract_token (buf, s, 0);
+    
+    if (first && !mutt_strcasecmp (buf->data, "-group"))
+    {
+      if (!MoreArgs (s))
+      {
+       strfcpy (err->data, _("-group: no group name"), err->dsize);
+       return -1;
+      }
+      
+      mutt_extract_token (buf, s, 0);
+      group = mutt_pattern_group (buf->data);
+
+      if (!MoreArgs (s))
+       break;
+      
+      mutt_extract_token (buf, s, 0);
+    }
+    
+    first = 0;
+
     remove_from_rx_list (&UnAlternates, buf->data);
 
     if (add_to_rx_list (&Alternates, buf->data, REG_ICASE, err) != 0)
       return -1;
+    /* XXX -- unclean encapsulation */
+    if (group && add_to_rx_list (&group->rs, buf->data, REG_ICASE, err) != 0)
+      return -1;
   }
   while (MoreArgs (s));
 
@@ -780,13 +806,40 @@
 
 static int parse_lists (BUFFER *buf, BUFFER *s, unsigned long data, BUFFER 
*err)
 {
+  short first = 1;
+  group_t *group = NULL;
+
   do
   {
     mutt_extract_token (buf, s, 0);
+    
+    if (first && !mutt_strcasecmp (buf->data, "-group"))
+    {
+      if (!MoreArgs (s))
+      {
+       strfcpy (err->data, _("-group: no group name"), err->dsize);
+       return -1;
+      }
+      
+      mutt_extract_token (buf, s, 0);
+      group = mutt_pattern_group (buf->data);
+
+      if (!MoreArgs (s))
+       break;
+      
+      mutt_extract_token (buf, s, 0);
+    }
+    
+    first = 0;
+    
     remove_from_rx_list (&UnMailLists, buf->data);
     
     if (add_to_rx_list (&MailLists, buf->data, REG_ICASE, err) != 0)
       return -1;
+    
+    /* XXX -- unclean encapsulation */
+    if (group && add_to_rx_list (&group->rs, buf->data, REG_ICASE, err) != 0)
+      return -1;
   }
   while (MoreArgs (s));
 
@@ -1077,9 +1130,32 @@
 
 static int parse_subscribe (BUFFER *buf, BUFFER *s, unsigned long data, BUFFER 
*err)
 {
+  short first = 1;
+  group_t *group = NULL;
+  
   do
   {
     mutt_extract_token (buf, s, 0);
+
+    if (first && !mutt_strcasecmp (buf->data, "-group"))
+    {
+      if (!MoreArgs (s))
+      {
+       strfcpy (err->data, _("-group: no group name"), err->dsize);
+       return -1;
+      }
+      
+      mutt_extract_token (buf, s, 0);
+      group = mutt_pattern_group (buf->data);
+
+      if (!MoreArgs (s))
+       break;
+      
+      mutt_extract_token (buf, s, 0);
+    }
+    
+    first = 0;
+    
     remove_from_rx_list (&UnMailLists, buf->data);
     remove_from_rx_list (&UnSubscribedLists, buf->data);
 
@@ -1087,6 +1163,9 @@
       return -1;
     if (add_to_rx_list (&SubscribedLists, buf->data, REG_ICASE, err) != 0)
       return -1;
+    /* XXX -- unclean encapsulation */
+    if (group && add_to_rx_list (&group->rs, buf->data, REG_ICASE, err) != 0)
+      return -1;
   }
   while (MoreArgs (s));
 
@@ -1161,6 +1240,7 @@
   ALIAS *tmp = Aliases;
   ALIAS *last = NULL;
   char *estr = NULL;
+  group_t *group = NULL;
   
   if (!MoreArgs (s))
   {
@@ -1173,6 +1253,27 @@
   dprint (2, (debugfile, "parse_alias: First token is '%s'.\n",
              buf->data));
   
+  if (!mutt_strcasecmp (buf->data, "-group")) 
+  {
+    if (!MoreArgs (s))
+    {
+      strfcpy (err->data, _("-group: no group name"), err->dsize);
+      return (-1);
+    }
+    
+    mutt_extract_token (buf, s, 0);
+    
+    group = mutt_pattern_group (buf->data);
+    
+    if (!MoreArgs (s))
+    {
+      strfcpy (err->data, _("alias: no address"), err->dsize);
+      return (-1);
+    }
+    
+    mutt_extract_token (buf, s, 0);
+  }
+  
   /* check to see if an alias with this name already exists */
   for (; tmp; tmp = tmp->next)
   {
@@ -1202,7 +1303,9 @@
   mutt_extract_token (buf, s, M_TOKEN_QUOTE | M_TOKEN_SPACE | 
M_TOKEN_SEMICOLON);
   dprint (2, (debugfile, "parse_alias: Second token is '%s'.\n",
              buf->data));
+
   tmp->addr = mutt_parse_adrlist (tmp->addr, buf->data);
+
   if (last)
     last->next = tmp;
   else
@@ -1213,6 +1316,11 @@
              estr, tmp->name);
     return -1;
   }
+
+  if (group)
+    mutt_group_add_adrlist (group, tmp->addr);
+
+
 #ifdef DEBUG
   if (debuglevel >= 2) 
   {
@@ -2541,6 +2649,8 @@
   err.data = error;
   err.dsize = sizeof (error);
 
+  Groups = hash_create (1031);
+  
   /* 
    * XXX - use something even more difficult to predict?
    */
Index: mutt.h
===================================================================
RCS file: /cvs/mutt/mutt/mutt.h,v
retrieving revision 3.62
diff -u -r3.62 mutt.h
--- mutt.h      12 Dec 2005 17:24:11 -0000      3.62
+++ mutt.h      9 Jan 2006 13:24:28 -0000
@@ -798,18 +798,30 @@
   M_MATCH_FULL_ADDRESS = 1
 } pattern_exec_flag;
 
+typedef struct group_t
+{
+  ADDRESS *as;
+  RX_LIST *rs;
+  char *name;
+} group_t;
+
 typedef struct pattern_t
 {
   short op;
   unsigned int not : 1;
   unsigned int alladdr : 1;
   unsigned int stringmatch : 1;
+  unsigned int groupmatch : 1;
   int min;
   int max;
   struct pattern_t *next;
   struct pattern_t *child;             /* arguments to logical op */
-  char *str;
-  regex_t *rx;
+  union 
+  {
+    regex_t *rx;
+    group_t *g;
+    char *str;
+  } p;
 } pattern_t;
 
 typedef struct
Index: pattern.c
===================================================================
RCS file: /cvs/mutt/mutt/pattern.c,v
retrieving revision 3.27
diff -u -r3.27 pattern.c
--- pattern.c   24 Nov 2005 12:50:27 -0000      3.27
+++ pattern.c   9 Jan 2006 13:24:29 -0000
@@ -278,19 +278,24 @@
 
   if (pat->stringmatch)
   {
-    pat->str = safe_strdup (buf.data);
+    pat->p.str = safe_strdup (buf.data);
+    FREE (&buf.data);
+  }
+  else if (pat->groupmatch)
+  {
+    pat->p.g = mutt_pattern_group (buf.data);
     FREE (&buf.data);
   }
   else
   {
-    pat->rx = safe_malloc (sizeof (regex_t));
-    r = REGCOMP (pat->rx, buf.data, REG_NEWLINE | REG_NOSUB | mutt_which_case 
(buf.data));
+    pat->p.rx = safe_malloc (sizeof (regex_t));
+    r = REGCOMP (pat->p.rx, buf.data, REG_NEWLINE | REG_NOSUB | 
mutt_which_case (buf.data));
     FREE (&buf.data);
     if (r)
     {
-      regerror (r, pat->rx, err->data, err->dsize);
-      regfree (pat->rx);
-      FREE (&pat->rx);
+      regerror (r, pat->p.rx, err->data, err->dsize);
+      regfree (pat->p.rx);
+      FREE (&pat->p.rx);
       return (-1);
     }
   }
@@ -697,9 +702,11 @@
 static int patmatch (const pattern_t* pat, const char* buf)
 {
   if (pat->stringmatch)
-    return !strstr (buf, pat->str);
+    return !strstr (buf, pat->p.str);
+  else if (pat->groupmatch)
+    return !mutt_group_match (pat->p.g, buf);
   else
-    return regexec (pat->rx, buf, 0, NULL, 0);
+    return regexec (pat->p.rx, buf, 0, NULL, 0);
 }
 
 static struct pattern_flags *lookup_tag (char tag)
@@ -739,12 +746,16 @@
     tmp = *pat;
     *pat = (*pat)->next;
 
-    if (tmp->rx)
+    if (tmp->stringmatch)
+      FREE (&tmp->p.str);
+    else if (tmp->groupmatch)
+      tmp->p.g = NULL;
+    else if (tmp->p.rx)
     {
-      regfree (tmp->rx);
-      FREE (&tmp->rx);
+      regfree (tmp->p.rx);
+      FREE (&tmp->p.rx);
     }
-    FREE (&tmp->str);
+
     if (tmp->child)
       mutt_pattern_free (&tmp->child);
     FREE (&tmp);
@@ -808,6 +819,7 @@
        not = 0;
        alladdr = 0;
        break;
+      case '$':
       case '=':
       case '~':
        if (implicit && or)
@@ -825,6 +837,7 @@
        tmp->not = not;
        tmp->alladdr = alladdr;
         tmp->stringmatch = (*ps.dptr == '=') ? 1 : 0;
+        tmp->groupmatch  = (*ps.dptr == '$') ? 1 : 0;
        not = 0;
        alladdr = 0;
 
@@ -1164,7 +1177,7 @@
    * equivalences?
    */
   
-  if (!strchr (s, '~') && !strchr (s, '=')) /* yup, so spoof a real request */
+  if (!strchr (s, '~') && !strchr (s, '=') && !strchr (s, '$')) /* yup, so 
spoof a real request */
   {
     /* convert old tokens into the new format */
     if (ascii_strcasecmp ("all", s) == 0 ||
Index: protos.h
===================================================================
RCS file: /cvs/mutt/mutt/protos.h,v
retrieving revision 3.42
diff -u -r3.42 protos.h
--- protos.h    20 Dec 2005 17:50:47 -0000      3.42
+++ protos.h    9 Jan 2006 13:24:29 -0000
@@ -72,6 +72,7 @@
 void mutt_FormatString (char *, size_t, const char *, format_t *, unsigned 
long, format_flag);
 void mutt_parse_content_type (char *, BODY *);
 void mutt_generate_boundary (PARAMETER **);
+void mutt_group_add_adrlist (group_t *, ADDRESS *);
 void mutt_delete_parameter (const char *attribute, PARAMETER **p);
 void mutt_set_parameter (const char *, const char *, PARAMETER **);
 
@@ -144,6 +145,8 @@
 
 const char *mutt_fqdn(short);
 
+group_t *mutt_pattern_group (const char *);
+
 REGEXP *mutt_compile_regexp (const char *, int);
 
 void mutt_account_hook (const char* url);
@@ -291,6 +294,7 @@
 #define mutt_get_password(A,B,C) mutt_get_field_unbuffered(A,B,C,M_PASS)
 int mutt_get_postponed (CONTEXT *, HEADER *, HEADER **, char *, size_t);
 int mutt_get_tmp_attachment (BODY *);
+int mutt_group_match (group_t *g, const char *s);
 int mutt_index_menu (void);
 int mutt_invoke_sendmail (ADDRESS *, ADDRESS *, ADDRESS *, ADDRESS *, const 
char *, int);
 int mutt_is_autoview (BODY *, const char *);
Index: imap/imap.c
===================================================================
RCS file: /cvs/mutt/mutt/imap/imap.c,v
retrieving revision 3.77
diff -u -r3.77 imap.c
--- imap/imap.c 3 Jan 2006 17:35:18 -0000       3.77
+++ imap/imap.c 9 Jan 2006 13:24:29 -0000
@@ -1657,13 +1657,13 @@
         mutt_buffer_addstr (buf, "HEADER ");
 
         /* extract header name */
-        if (! (delim = strchr (pat->str, ':')))
+        if (! (delim = strchr (pat->p.str, ':')))
         {
-          mutt_error (_("Header search without header name: %s"), pat->str);
+          mutt_error (_("Header search without header name: %s"), pat->p.str);
           return -1;
         }
         *delim = '\0';
-        imap_quote_string (term, sizeof (term), pat->str);
+        imap_quote_string (term, sizeof (term), pat->p.str);
         mutt_buffer_addstr (buf, term);
         mutt_buffer_addch (buf, ' ');
         
@@ -1676,12 +1676,12 @@
         break;
       case M_BODY:
         mutt_buffer_addstr (buf, "BODY ");
-        imap_quote_string (term, sizeof (term), pat->str);
+        imap_quote_string (term, sizeof (term), pat->p.str);
         mutt_buffer_addstr (buf, term);
         break;
       case M_WHOLE_MSG:
         mutt_buffer_addstr (buf, "TEXT ");
-        imap_quote_string (term, sizeof (term), pat->str);
+        imap_quote_string (term, sizeof (term), pat->p.str);
         mutt_buffer_addstr (buf, term);
         break;
     }

Attachment: pgpUxcoA7kxIO.pgp
Description: PGP signature