]> granicus.if.org Git - neomutt/commitdiff
Add subjectrx command to replace matching subjects with something else.
authorDavid Champion <dgc@bikeshed.us>
Tue, 24 Jan 2017 03:01:50 +0000 (19:01 -0800)
committerRichard Russon <rich@flatcap.org>
Fri, 10 Feb 2017 03:32:55 +0000 (03:32 +0000)
This lets you define regular expressions-replacement pairs for subject
display.  When a Subject: matches the regular expression, the replacement
value will be displayed instead in the message index.  Backreferences
are supported.

This is especially nice for simplifying subjects that are overly wordy,
such as mailing list posts (with [Listname] tags, etc), mail from
ticketing systems or bug trackers, etc.  It lets you reduce clutter in
your mutt display without altering the messages themselves.

doc/manual.xml.head
globals.h
hdrline.c
init.c
init.h
mutt.h
muttlib.c

index c9d2c37bab1f72209f07b564a3a6afb0a79aa903..da9504aac28dd04079755b68006a63ffb17566c8 100644 (file)
@@ -7112,6 +7112,72 @@ to your inbox, and it is fully compatible with the new keywords code.
 
 </sect1>
 
+<sect1 id="display-munging">
+<title>Display Munging</title>
+
+<para>
+Working within the confines of a console or terminal window, it is
+often useful to be able to modify certain information elements in a
+non-destructive way -- to change how they display, without changing
+the stored value of the information itself.  This is especially so of
+message subjects, which may often be polluted with extraneous metadata
+that either is reproduced elsewhere, or is of secondary interest.
+</para>
+
+<cmdsynopsis>
+<command>subjectrx</command>
+<arg choice="plain">
+<replaceable class="parameter">pattern</replaceable>
+</arg>
+<arg choice="plain">
+<replaceable class="parameter">replacement</replaceable>
+</arg>
+
+<command>unsubjectrx</command>
+<arg choice="plain">
+<replaceable class="parameter">pattern</replaceable>
+</arg>
+</cmdsynopsis>
+
+<para>
+<literal>subjectrx</literal> specifies a regular expression
+<quote>pattern</quote> which, if detected in a message subject, causes
+the subject to be replaced with the <quote>replacement</quote> value.
+The replacement is subject to substitutions in the same way as for the
+<link linkend="spam">spam</link> command: <literal>%L</literal> for the text
+to the left of the match, <literal>%R</literal> for text to the right of the
+match, and <literal>%1</literal> for the first subgroup in the match (etc).
+If you simply want to erase the match, set it to <quote>%L%R</quote>.
+Any number of <literal>subjectrx</literal> commands may coexist.
+</para>
+
+<para>
+Note this well: the <quote>replacement</quote> value replaces the
+entire subject, not just the match!
+</para>
+
+<para>
+<literal>unsubjectrx</literal> removes a given subjectrx from the substitution
+list.
+</para>
+
+<example id="ex-subjectrx">
+<title>Subject Munging</title>
+<screen>
+# Erase [rt #12345] tags from Request Tracker (RT) e-mails
+subjectrx '\[rt #[0-9]+\] *' '%L%R'
+
+# Servicedesk is another RT that sends more complex subjects.
+# Keep the ticket number.
+subjectrx '\[servicedesk #([0-9]+)\] ([^.]+)\.([^.]+) - (new|open|pending|update) - ' '%L[#%1] %R'
+
+# Strip out annoying [listname] prefixes in subjects
+subjectrx '\[[^\]]*\]:? *' '%L%R'
+</screen>
+</example>
+
+</sect1>
+
 <sect1 id="new-mail">
 <title>New Mail Detection</title>
 
@@ -15325,6 +15391,23 @@ The following are the commands understood by Mutt:
 </cmdsynopsis>
 </listitem>
 
+<listitem>
+<cmdsynopsis>
+<command><link linkend="display-munging">subjectrx</link></command>
+<arg choice="plain">
+<replaceable class="parameter">pattern</replaceable>
+</arg>
+<arg choice="plain">
+<replaceable class="parameter">format</replaceable>
+</arg>
+
+<command><link linkend="display-munging">unsubjectrx</link></command>
+<arg choice="plain">
+<replaceable class="parameter">pattern</replaceable>
+</arg>
+</cmdsynopsis>
+</listitem>
+
 <listitem>
 <cmdsynopsis>
 <command><link linkend="subscribe">subscribe</link></command>
index 4326fcec0ff3d592fc5d20dc2342d8174a043f8a..6263d9fa2f2adde85ad1e50b00f62b1f7c9ece77 100644 (file)
--- a/globals.h
+++ b/globals.h
@@ -207,6 +207,7 @@ WHERE RX_LIST *SubscribedLists INITVAL(0);
 WHERE RX_LIST *UnSubscribedLists INITVAL(0);
 WHERE REPLACE_LIST *SpamList INITVAL(0);
 WHERE RX_LIST *NoSpamList INITVAL(0);
+WHERE REPLACE_LIST *SubjectRxList INITVAL(0);
 
 
 /* bit vector for boolean variables */
index 9242709170956886c9aaa3d43615b74e904bb29e..77e58e28e70f38f7b84131e382ac6d83de52b553 100644 (file)
--- a/hdrline.c
+++ b/hdrline.c
@@ -403,6 +403,22 @@ char *get_nth_wchar (mbchar_table *table, int index)
   return table->chars[index];
 }
 
+static char *apply_subject_mods (ENVELOPE *env)
+{
+  if (env == NULL)
+    return NULL;
+
+  if (SubjectRxList == NULL)
+    return env->subject;
+
+  if (env->subject == NULL || *env->subject == '\0')
+    return env->disp_subj = NULL;
+
+  env->disp_subj = mutt_apply_replace(NULL, 0, env->subject, SubjectRxList);
+  return env->disp_subj;
+}
+
+
 /* %a = address of author
  * %A = reply-to address (if present; otherwise: address of author
  * %b = filename of the originating folder
@@ -929,25 +945,33 @@ hdr_format_str (char *dest,
       break;
 
     case 's':
-      
-      if (flags & MUTT_FORMAT_TREE && !hdr->collapsed)
       {
-       if (flags & MUTT_FORMAT_FORCESUBJ)
+       char *subj;
+        if (hdr->env->disp_subj)
+         subj = hdr->env->disp_subj;
+       else if (SubjectRxList)
+         subj = apply_subject_mods(hdr->env);
+       else
+         subj = hdr->env->subject;
+       if (flags & MUTT_FORMAT_TREE && !hdr->collapsed)
+       {
+         if (flags & MUTT_FORMAT_FORCESUBJ)
+         {
+           colorlen = add_index_color (dest, destlen, flags, MT_COLOR_INDEX_SUBJECT);
+           mutt_format_s (dest + colorlen, destlen - colorlen, "", NONULL (subj));
+           add_index_color (dest + colorlen, destlen - colorlen, flags, MT_COLOR_INDEX);
+           snprintf (buf2, sizeof (buf2), "%s%s", hdr->tree, dest);
+           mutt_format_s_tree (dest, destlen, prefix, buf2);
+         }
+         else
+           mutt_format_s_tree (dest, destlen, prefix, hdr->tree);
+       }
+       else
        {
          colorlen = add_index_color (dest, destlen, flags, MT_COLOR_INDEX_SUBJECT);
-         mutt_format_s (dest + colorlen, destlen - colorlen, "", NONULL (hdr->env->subject));
+         mutt_format_s (dest + colorlen, destlen - colorlen, prefix, NONULL (subj));
          add_index_color (dest + colorlen, destlen - colorlen, flags, MT_COLOR_INDEX);
-         snprintf (buf2, sizeof (buf2), "%s%s", hdr->tree, dest);
-         mutt_format_s_tree (dest, destlen, prefix, buf2);
        }
-       else
-         mutt_format_s_tree (dest, destlen, prefix, hdr->tree);
-      }
-      else
-      {
-       colorlen = add_index_color (dest, destlen, flags, MT_COLOR_INDEX_SUBJECT);
-       mutt_format_s (dest + colorlen, destlen - colorlen, prefix, NONULL (hdr->env->subject));
-       add_index_color (dest + colorlen, destlen - colorlen, flags, MT_COLOR_INDEX);
       }
       break;
 
diff --git a/init.c b/init.c
index 0e14f0dda4fca779ee6b90521e605547f60205c1..90632c95cd96bb8558cd722ceb83b045bab07bbe 100644 (file)
--- a/init.c
+++ b/init.c
@@ -884,6 +884,88 @@ static int parse_unalternates (BUFFER *buf, BUFFER *s, unsigned long data, BUFFE
   return 0;
 }
 
+static int parse_replace_list (BUFFER *buf, BUFFER *s, unsigned long data, BUFFER *err)
+{
+  REPLACE_LIST **list = (REPLACE_LIST **)data;
+  BUFFER templ;
+
+  memset(&templ, 0, sizeof(templ));
+
+  /* First token is a regexp. */
+  if (!MoreArgs(s))
+  {
+    strfcpy(err->data, _("not enough arguments"), err->dsize);
+    return -1;
+  }
+  mutt_extract_token(buf, s, 0);
+
+  /* Second token is a replacement template */
+  if (!MoreArgs(s))
+  {
+    strfcpy(err->data, _("not enough arguments"), err->dsize);
+    return -1;
+  }
+  mutt_extract_token(&templ, s, 0);
+
+  if (add_to_replace_list(list, buf->data, templ.data, err) != 0) {
+    FREE(&templ.data);
+    return -1;
+  }
+  FREE(&templ.data);
+
+  return 0;
+}
+
+static int parse_unreplace_list (BUFFER *buf, BUFFER *s, unsigned long data, BUFFER *err)
+{
+  REPLACE_LIST **list = (REPLACE_LIST **)data;
+
+  /* First token is a regexp. */
+  if (!MoreArgs(s))
+  {
+    strfcpy(err->data, _("not enough arguments"), err->dsize);
+    return -1;
+  }
+
+  mutt_extract_token(buf, s, 0);
+  remove_from_replace_list(list, buf->data);
+  return 0;
+}
+
+
+static void clear_subject_mods (void)
+{
+  int i;
+  if (Context && Context->msgcount) 
+  {
+    for (i = 0; i < Context->msgcount; i++)
+      FREE(&Context->hdrs[i]->env->disp_subj);
+  }
+}
+
+
+static int parse_subjectrx_list (BUFFER *buf, BUFFER *s, unsigned long data, BUFFER *err)
+{
+  int rc;
+
+  rc = parse_replace_list(buf, s, data, err);
+  if (rc == 0)
+    clear_subject_mods();
+  return rc;
+}
+
+
+static int parse_unsubjectrx_list (BUFFER *buf, BUFFER *s, unsigned long data, BUFFER *err)
+{
+  int rc;
+
+  rc = parse_unreplace_list(buf, s, data, err);
+  if (rc == 0)
+    clear_subject_mods();
+  return rc;
+}
+
+
 static int parse_spam_list (BUFFER *buf, BUFFER *s, unsigned long data, BUFFER *err)
 {
   BUFFER templ;
diff --git a/init.h b/init.h
index 9375adadb22564c8cc94d5629c2716df7da7203b..f43e1f4fe73ca21c27339174ca9560f10eac6352 100644 (file)
--- a/init.h
+++ b/init.h
@@ -4412,6 +4412,10 @@ static int parse_unsubscribe (BUFFER *, BUFFER *, unsigned long, BUFFER *);
 static int parse_attachments (BUFFER *, BUFFER *, unsigned long, BUFFER *);
 static int parse_unattachments (BUFFER *, BUFFER *, unsigned long, BUFFER *);
 
+static int parse_replace_list (BUFFER *, BUFFER *, unsigned long, BUFFER *);
+static int parse_unreplace_list (BUFFER *, BUFFER *, unsigned long, BUFFER *);
+static int parse_subjectrx_list (BUFFER *, BUFFER *, unsigned long, BUFFER *);
+static int parse_unsubjectrx_list (BUFFER *, BUFFER *, unsigned long, BUFFER *);
 static int parse_alternates (BUFFER *, BUFFER *, unsigned long, BUFFER *);
 static int parse_unalternates (BUFFER *, BUFFER *, unsigned long, BUFFER *);
 
@@ -4505,6 +4509,8 @@ const struct command_t Commands[] = {
   { "shutdown-hook",   mutt_parse_hook,        MUTT_SHUTDOWNHOOK | MUTT_GLOBALHOOK },
   { "startup-hook",    mutt_parse_hook,        MUTT_STARTUPHOOK | MUTT_GLOBALHOOK },
   { "subscribe",       parse_subscribe,        0 },
+  { "subjectrx",       parse_subjectrx_list,   UL &SubjectRxList },
+  { "unsubjectrx",     parse_unsubjectrx_list, UL &SubjectRxList },
   { "timeout-hook",    mutt_parse_hook,        MUTT_TIMEOUTHOOK | MUTT_GLOBALHOOK },
   { "toggle",          parse_set,              MUTT_SET_INV },
   { "unalias",         parse_unalias,          0 },
diff --git a/mutt.h b/mutt.h
index 3440021e7ecb9029e822e905cc1ed59bb4172f82..0b95c8fd09fc4fec34b73bfafceafc68ab126a98 100644 (file)
--- a/mutt.h
+++ b/mutt.h
@@ -714,6 +714,7 @@ typedef struct envelope
   char *list_post;             /* this stores a mailto URL, or nothing */
   char *subject;
   char *real_subj;             /* offset of the real subject */
+  char *disp_subj;             /* display subject (modified copy of subject) */
   char *message_id;
   char *supersedes;
   char *date;
index ec751846c7cecba161afb3d8259ffecb9313d6c0..9a875a10a0ec2bf142d8aeb22028f5ddcf9d8d90 100644 (file)
--- a/muttlib.c
+++ b/muttlib.c
@@ -805,6 +805,7 @@ void mutt_free_envelope (ENVELOPE **p)
   FREE (&(*p)->list_post);
   FREE (&(*p)->subject);
   /* real_subj is just an offset to subject and shouldn't be freed */
+  FREE (&(*p)->disp_subj);
   FREE (&(*p)->message_id);
   FREE (&(*p)->supersedes);
   FREE (&(*p)->date);
@@ -862,8 +863,10 @@ void mutt_merge_envelopes(ENVELOPE* base, ENVELOPE** extra)
   {
     base->subject = (*extra)->subject;
     base->real_subj = (*extra)->real_subj;
+    base->disp_subj = (*extra)->disp_subj;
     (*extra)->subject = NULL;
     (*extra)->real_subj = NULL;
+    (*extra)->disp_subj = NULL;
   }
   /* spam and user headers should never be hashed, and the new envelope may
     * have better values. Use new versions regardless. */