Add ${listextract {n}{list}...}
authorJeremy Harris <jgh146exb@wizmail.org>
Sun, 27 Oct 2013 15:18:44 +0000 (15:18 +0000)
committerJeremy Harris <jgh146exb@wizmail.org>
Sun, 22 Dec 2013 17:38:14 +0000 (17:38 +0000)
doc/doc-docbook/spec.xfpt
doc/doc-txt/ChangeLog
src/src/expand.c
test/scripts/0000-Basic/0002
test/stdout/0002

index 17bdcc943a6b3a2bc3a668a5a5fc8971a7a2efe4..f29c38722ccb6e90a9461553b28b6e04e11c6ce2 100644 (file)
@@ -9181,6 +9181,44 @@ of <&'string2'&>, whichever is the shorter. Do not confuse &%length%& with
 &%strlen%&, which gives the length of a string.
 
 
+.vitem "&*${listextract{*&<&'number'&>&*}&&&
+        {*&<&'string1'&>&*}{*&<&'string2'&>&*}{*&<&'string3'&>&*}}*&"
+.cindex "expansion" "extracting list elements by number"
+.cindex "&%listextract%&" "extract list elements by number"
+.cindex "list" "extracting elements by number"
+The <&'number'&> argument must consist entirely of decimal digits,
+apart from an optional leading minus,
+and leading and trailing white space (which is ignored).
+
+After expansion, <&'string1'&> is interpreted as a list, colon-separated by
+default, but the separator can be changed in the usual way.
+
+The first field of the list is numbered one.
+If the number is negative, the fields are
+counted from the end of the list, with the rightmost one numbered -1.
+The numbered element of the list is extracted and placed in &$value$&,
+then <&'string2'&> is expanded as the result.
+
+If the modulus of the
+number is zero or greater than the number of fields in the string,
+the result is the expansion of <&'string3'&>.
+
+For example:
+.code
+${listextract{2}{x:42:99}}
+.endd
+yields &"42"&, and
+.code
+${listextract{-3}{<, x,42,99,& Mailer,,/bin/bash}{result: $value}}
+.endd
+yields &"result: 99"&.
+
+If {<&'string3'&>} is omitted, an empty string is used for string3.
+If {<&'string2'&>} is also omitted, the value that was
+extracted is used.
+You can use &`fail`& instead of {<&'string3'&>} as in a string extract.
+
+
 .vitem "&*${lookup{*&<&'key'&>&*}&~*&<&'search&~type'&>&*&~&&&
         {*&<&'file'&>&*}&~{*&<&'string1'&>&*}&~{*&<&'string2'&>&*}}*&"
 This is the first of one of two different types of lookup item, which are both
index 12369e4346d7f7e4ccaca8316c1a478ec4cf7e7d..e2b33cf9195f7a74c4aeeaae1c3a6b53c318d80e 100644 (file)
@@ -27,6 +27,8 @@ TL/02 Experimental Proxy Protocol support: allows a proxied SMTP connection
       to extract and use the src ip:port in logging and expansions as if it
       were a direct connection from the outside internet.
 
+JH/02 Add ${listextract {number}{list}{success}{fail}}.
+
 
 Exim version 4.82
 -----------------
index bf1f82c6facacd332de02338667a7bcf2662a7d9..325b05178890d13bc544204155860d908cea090c 100644 (file)
@@ -110,6 +110,7 @@ static uschar *item_table[] = {
   US"hmac",
   US"if",
   US"length",
+  US"listextract",
   US"lookup",
   US"map",
   US"nhash",
@@ -133,6 +134,7 @@ enum {
   EITEM_HMAC,
   EITEM_IF,
   EITEM_LENGTH,
+  EITEM_LISTEXTRACT,
   EITEM_LOOKUP,
   EITEM_MAP,
   EITEM_NHASH,
@@ -1131,6 +1133,22 @@ return fieldtext;
 }
 
 
+static uschar *
+expand_getlistele (int field, uschar *list)
+{
+uschar * tlist= list;
+int sep= 0;
+uschar dummy;
+
+if(field<0)
+{
+  for(field++; string_nextinlist(&tlist, &sep, &dummy, 1); ) field++;
+  sep= 0;
+}
+if(field==0) return NULL;
+while(--field>0 && (string_nextinlist(&list, &sep, &dummy, 1))) ;
+return string_nextinlist(&list, &sep, NULL, 0);
+}
 
 /*************************************************
 *        Extract a substring from a string       *
@@ -5149,7 +5167,7 @@ while (*s != 0)
       for (i = 0; i < j; i++)
         {
         while (isspace(*s)) s++;
-        if (*s == '{')
+        if (*s == '{')                                                 /*}*/
           {
           sub[i] = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok);
           if (sub[i] == NULL) goto EXPAND_FAILED;              /*{*/
@@ -5230,6 +5248,99 @@ while (*s != 0)
       continue;
       }
 
+    /* return the Nth item from a list */
+
+    case EITEM_LISTEXTRACT:
+      {
+      int i;
+      int field_number = 1;
+      uschar *save_lookup_value = lookup_value;
+      uschar *sub[2];
+      int save_expand_nmax =
+        save_expand_strings(save_expand_nstring, save_expand_nlength);
+
+      /* Read the field & list arguments */
+
+      for (i = 0; i < 2; i++)
+        {
+        while (isspace(*s)) s++;
+        if (*s != '{')                                 /*}*/
+         goto EXPAND_FAILED_CURLY;
+
+       sub[i] = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok);
+       if (!sub[i])     goto EXPAND_FAILED;            /*{*/
+       if (*s++ != '}') goto EXPAND_FAILED_CURLY;
+
+       /* After removal of leading and trailing white space, the first
+       argument must be numeric and nonempty. */
+
+       if (i == 0)
+         {
+         int len;
+         int x = 0;
+         uschar *p = sub[0];
+
+         while (isspace(*p)) p++;
+         sub[0] = p;
+
+         len = Ustrlen(p);
+         while (len > 0 && isspace(p[len-1])) len--;
+         p[len] = 0;
+
+         if (!*p && !skipping)
+           {
+           expand_string_message = US"first argument of \"listextract\" must "
+             "not be empty";
+           goto EXPAND_FAILED;
+           }
+
+         if (*p == '-')
+           {
+           field_number = -1;
+           p++;
+           }
+         while (*p && isdigit(*p)) x = x * 10 + *p++ - '0';
+         if (*p)
+           {
+           expand_string_message = US"first argument of \"listextract\" must "
+             "be numeric";
+           goto EXPAND_FAILED;
+           }
+         field_number *= x;
+         }
+        }
+
+      /* Extract the numbered element into $value. If
+      skipping, just pretend the extraction failed. */
+
+      lookup_value = skipping? NULL : expand_getlistele(field_number, sub[1]);
+
+      /* If no string follows, $value gets substituted; otherwise there can
+      be yes/no strings, as for lookup or if. */
+
+      switch(process_yesno(
+               skipping,                     /* were previously skipping */
+               lookup_value != NULL,         /* success/failure indicator */
+               save_lookup_value,            /* value to reset for string2 */
+               &s,                           /* input pointer */
+               &yield,                       /* output pointer */
+               &size,                        /* output size */
+               &ptr,                         /* output current point */
+               US"extract",                  /* condition type */
+              &resetok))
+        {
+        case 1: goto EXPAND_FAILED;          /* when all is well, the */
+        case 2: goto EXPAND_FAILED_CURLY;    /* returned value is 0 */
+        }
+
+      /* All done - restore numerical variables. */
+
+      restore_expand_strings(save_expand_nmax, save_expand_nstring,
+        save_expand_nlength);
+
+      continue;
+      }
+
     /* Handle list operations */
 
     case EITEM_FILTER:
index b924a0934b85f47b40692b984191f26a7468e3e8..367d558b3a98038a47993a4c38ca118d00fa60f4 100644 (file)
@@ -75,6 +75,13 @@ listcount: ${listcount:}
 listcount: ${listcount:<;a;b;c}
 listcount: ${listcount:${listnamed:dlist}}
 
+listextract: ${listextract{ 2}{a:b:c:d}}
+listextract: ${listextract{-2}{<,a,b,c,d}{X${value}X}}
+listextract: ${listextract{ 5}{a:b:c:d}}
+listextract: ${listextract{-5}{a:b:c:d}}
+listextract: ${listextract{ 5}{a:b:c:d}{}{fail}}
+listextract: ${listextract{ 5}{a:b:c:d}{}fail}
+
 # Tests with iscntrl() and illegal separators
 
 map: ${map{<\n a\n\nb\nc}{'$item'}}
index 1cf6a5b843562069fcff5b55d0e897fced9220c1..e6270977b96ff2a9b3b1d510ae12309b8736a28e 100644 (file)
 > listcount: 3
 > listcount: 2
 > 
+> listextract: b
+> listextract: XcX
+> listextract: 
+> listextract: 
+> listextract: fail
+> Failed: "extract" failed and "fail" requested
+> 
 > # Tests with iscntrl() and illegal separators
 > 
 > map: 'a'