[exim-cvs] Add expansion item for sorting lists

Góra strony
Delete this message
Reply to this message
Autor: Exim Git Commits Mailing List
Data:  
Dla: exim-cvs
Temat: [exim-cvs] Add expansion item for sorting lists
Gitweb: http://git.exim.org/exim.git/commitdiff/ac4ef9bdcad6bb41c919f6d0b76fdef717ab5e18
Commit:     ac4ef9bdcad6bb41c919f6d0b76fdef717ab5e18
Parent:     be36e5721725253b7529899884d7fe8ecd5120b9
Author:     Jeremy Harris <jgh146exb@???>
AuthorDate: Sat Sep 6 21:10:17 2014 +0100
Committer:  Jeremy Harris <jgh146exb@???>
CommitDate: Sat Sep 6 21:45:44 2014 +0100


    Add expansion item for sorting lists
---
 doc/doc-docbook/spec.xfpt    |   31 +++++++++
 doc/doc-txt/ChangeLog        |    2 +
 src/src/expand.c             |  141 ++++++++++++++++++++++++++++++++++++++++++
 test/scripts/0000-Basic/0002 |    7 ++
 test/stdout/0002             |    7 ++
 5 files changed, 188 insertions(+), 0 deletions(-)


diff --git a/doc/doc-docbook/spec.xfpt b/doc/doc-docbook/spec.xfpt
index 69009e9..df63ad6 100644
--- a/doc/doc-docbook/spec.xfpt
+++ b/doc/doc-docbook/spec.xfpt
@@ -9698,6 +9698,37 @@ the regular expression from string expansion.



+.new
+.vitem &*${sort{*&<&'string'&>&*}{*&<&'comparator'&>&*}{*&<&'extractor'&>&*}}*&
+.cindex sorting a list
+.cindex list sorting
+After expansion, <&'string'&> is interpreted as a list, colon-separated by
+default, but the separator can be changed in the usual way.
+The <&'comparator'&> argument is interpreted as the operator
+of a two-argument expansion condition.
+The numeric operators plus ge, gt, le, lt (and ~i variants) are supported.
+The comparison should return true when applied to two values
+if the first value should sort before the second value.
+The <&'extractor'&> expansion is applied repeatedly to elements of the list,
+the element being placed in &$item$&,
+to give values for comparison.
+
+The item result is a sorted list,
+with the original list separator,
+of the list elements (in full) of the original.
+
+Examples:
+.code
+${sort{3:2:1:4}{<}{$item}}
+.endd
+sorts a list of numbers, and
+.code
+${sort {$lookup dnsdb{>:,,mx=example.com}} {<} {${listextract{1}{<,$item}}}}
+.endd
+will sort an MX lookup into priority order.
+.wen
+
+
.vitem &*${substr{*&<&'string1'&>&*}{*&<&'string2'&>&*}{*&<&'string3'&>&*}}*&
.cindex "&%substr%& expansion item"
.cindex "substring extraction"
diff --git a/doc/doc-txt/ChangeLog b/doc/doc-txt/ChangeLog
index c8e6ccf..49211bc 100644
--- a/doc/doc-txt/ChangeLog
+++ b/doc/doc-txt/ChangeLog
@@ -30,6 +30,8 @@ JH/02 Add EXPERIMENTAL_DANE, allowing for using the DNS as trust-anchor for

JH/03 Support secondary-separator specifier for MX, SRV, TLSA lookups.

+JH/04 Add ${sort {list}{condition}{extractor}} expansion item.
+

Exim version 4.84
-----------------
diff --git a/src/src/expand.c b/src/src/expand.c
index a929e93..8e94c3e 100644
--- a/src/src/expand.c
+++ b/src/src/expand.c
@@ -127,6 +127,7 @@ static uschar *item_table[] = {
US"reduce",
US"run",
US"sg",
+ US"sort",
US"substr",
US"tr" };

@@ -152,6 +153,7 @@ enum {
EITEM_REDUCE,
EITEM_RUN,
EITEM_SG,
+ EITEM_SORT,
EITEM_SUBSTR,
EITEM_TR };

@@ -5633,6 +5635,145 @@ while (*s != 0)
       continue;
       }


+    case EITEM_SORT:
+      {
+      int sep = 0;
+      uschar *srclist, *cmp, *xtract;
+      uschar *srcitem;
+      uschar *dstlist = NULL;
+      uschar *dstkeylist = NULL;
+      uschar * tmp;
+      uschar *save_iterate_item = iterate_item;
+
+      while (isspace(*s)) s++;
+      if (*s++ != '{') goto EXPAND_FAILED_CURLY;
+
+      srclist = expand_string_internal(s, TRUE, &s, skipping, TRUE, &resetok);
+      if (!srclist) goto EXPAND_FAILED;
+      if (*s++ != '}') goto EXPAND_FAILED_CURLY;
+
+      while (isspace(*s)) s++;
+      if (*s++ != '{') goto EXPAND_FAILED_CURLY;
+
+      cmp = expand_string_internal(s, TRUE, &s, skipping, FALSE, &resetok);
+      if (!cmp) goto EXPAND_FAILED;
+      if (*s++ != '}') goto EXPAND_FAILED_CURLY;
+
+      while (isspace(*s)) s++;
+      if (*s++ != '{') goto EXPAND_FAILED_CURLY;
+
+      xtract = s;
+      tmp = expand_string_internal(s, TRUE, &s, TRUE, TRUE, &resetok);
+      if (!tmp) goto EXPAND_FAILED;
+      xtract = string_copyn(xtract, s - xtract);
+
+      if (*s++ != '}') goto EXPAND_FAILED_CURLY;
+                            /*{*/
+      if (*s++ != '}')
+        {                        /*{*/
+        expand_string_message = US"missing } at end of \"sort\"";
+        goto EXPAND_FAILED;
+        }
+
+      if (skipping) continue;
+
+      while ((srcitem = string_nextinlist(&srclist, &sep, NULL, 0)))
+        {
+    uschar * dstitem;
+    uschar * newlist = NULL;
+    uschar * newkeylist = NULL;
+    uschar * srcfield;
+
+        DEBUG(D_expand) debug_printf("%s: $item = \"%s\"\n", name, srcitem);
+
+    /* extract field for comparisons */
+    iterate_item = srcitem;
+    if (  !(srcfield = expand_string_internal(xtract, FALSE, NULL, FALSE,
+                      TRUE, &resetok))
+       || !*srcfield)
+      {
+      expand_string_message = string_sprintf(
+          "field-extract in sort: \"%s\"", xtract);
+      goto EXPAND_FAILED;
+      }
+
+    /* Insertion sort */
+
+    /* copy output list until new-item < list-item */
+    while ((dstitem = string_nextinlist(&dstlist, &sep, NULL, 0)))
+      {
+      uschar * dstfield;
+      uschar * expr;
+      BOOL before;
+
+      /* field for comparison */
+      if (!(dstfield = string_nextinlist(&dstkeylist, &sep, NULL, 0)))
+        goto sort_mismatch;
+
+      /* build and run condition string */
+      expr = string_sprintf("%s{%s}{%s}", cmp, srcfield, dstfield);
+
+      DEBUG(D_expand) debug_printf("%s: cond = \"%s\"\n", name, expr);
+      if (!eval_condition(expr, &resetok, &before))
+        {
+        expand_string_message = string_sprintf("comparison in sort: %s",
+        expr);
+        goto EXPAND_FAILED;
+        }
+
+      if (before)
+        {
+        /* New-item sorts before this dst-item.  Append new-item,
+        then dst-item, then remainder of dst list. */
+
+        newlist = string_append_listele(newlist, sep, srcitem);
+        newkeylist = string_append_listele(newkeylist, sep, srcfield);
+        srcitem = NULL;
+
+        newlist = string_append_listele(newlist, sep, dstitem);
+        newkeylist = string_append_listele(newkeylist, sep, dstfield);
+
+        while ((dstitem = string_nextinlist(&dstlist, &sep, NULL, 0)))
+          {
+          if (!(dstfield = string_nextinlist(&dstkeylist, &sep, NULL, 0)))
+        goto sort_mismatch;
+          newlist = string_append_listele(newlist, sep, dstitem);
+          newkeylist = string_append_listele(newkeylist, sep, dstfield);
+          }
+
+        break;
+        }
+
+      newlist = string_append_listele(newlist, sep, dstitem);
+      newkeylist = string_append_listele(newkeylist, sep, dstfield);
+      }
+
+    /* If we ran out of dstlist without consuming srcitem, append it */
+    if (srcitem)
+      {
+      newlist = string_append_listele(newlist, sep, srcitem);
+      newkeylist = string_append_listele(newkeylist, sep, srcfield);
+      }
+
+    dstlist = newlist;
+    dstkeylist = newkeylist;
+
+        DEBUG(D_expand) debug_printf("%s: dstlist = \"%s\"\n", name, dstlist);
+        DEBUG(D_expand) debug_printf("%s: dstkeylist = \"%s\"\n", name, dstkeylist);
+    }
+
+      if (dstlist)
+    yield = string_cat(yield, &size, &ptr, dstlist, Ustrlen(dstlist));
+
+      /* Restore preserved $item */
+      iterate_item = save_iterate_item;
+      continue;
+
+      sort_mismatch:
+    expand_string_message = US"Internal error in sort (list mismatch)";
+    goto EXPAND_FAILED;
+      }
+


     /* If ${dlfunc } support is configured, handle calling dynamically-loaded
     functions, unless locked out at this time. Syntax is ${dlfunc{file}{func}}
diff --git a/test/scripts/0000-Basic/0002 b/test/scripts/0000-Basic/0002
index 51dc6ae..bcfacde 100644
--- a/test/scripts/0000-Basic/0002
+++ b/test/scripts/0000-Basic/0002
@@ -82,6 +82,13 @@ listextract: ${listextract{-5}{a:b:c:d}}
 listextract: ${listextract{ 5}{a:b:c:d}{}{fail}}
 listextract: ${listextract{ 5}{a:b:c:d}{}fail}


+sort: ${sort{3:2:1:4}{<}{$item}}
+sort: ${sort {<, 3,2,1,4}{>}{$item}}
+sort: ${sort{c:B:a:aa}{lti}{$item}}
+sort: ${sort{666 r99.ex.com:10 smtp.ex.com:100 r2.ex.com}{<}{${sg {$item}{([0-9]*).*\$}{\$1}}}}
+sort: ${sort{666,r99.ex.com:10,smtp.ex.com:100,r2.ex.com}{<}{${listextract{1}{<,$item}}}}
+sort: "${sort{}{<}{$item}}"
+
# Tests with iscntrl() and illegal separators

map: ${map{<\n a\n\nb\nc}{'$item'}}
diff --git a/test/stdout/0002 b/test/stdout/0002
index 7200bf3..a3706ee 100644
--- a/test/stdout/0002
+++ b/test/stdout/0002
@@ -71,6 +71,13 @@
> listextract: fail
> Failed: "extract" failed and "fail" requested
>

+> sort: 1:2:3:4
+> sort: 4,3,2,1
+> sort: a:aa:B:c
+> sort: 10 smtp.ex.com:100 r2.ex.com:666 r99.ex.com
+> sort: 10,smtp.ex.com:100,r2.ex.com:666,r99.ex.com
+> sort: ""
+>
> # Tests with iscntrl() and illegal separators
>
> map: 'a'