Allow to match multiple patterns (#100).
authorDaniel Carl <danielcarl@gmx.de>
Sat, 6 Sep 2014 22:35:06 +0000 (00:35 +0200)
committerDaniel Carl <danielcarl@gmx.de>
Sat, 6 Sep 2014 22:35:06 +0000 (00:35 +0200)
Change the matching functions to not end at the NUL of the pattern. In stead
we give a pattern length parameter. This allows to process multiple patterns
without the need to put NUL-bytes into it or to allocate memory for the parts
to match.

src/autocmd.c
src/util.c
src/util.h
tests/test-util.c

index ddd8b23..d2741d6 100644 (file)
@@ -57,7 +57,6 @@ static GSList *get_group(const char *name);
 static guint get_event_bits(const char *name);
 static void rebuild_used_bits(void);
 static char *get_next_word(char **line);
-static gboolean wildmatch(char *patterns, const char *uri);
 static AuGroup *new_group(const char *name);
 static void free_group(AuGroup *group);
 static AutoCmd *new_autocmd(const char *excmd, const char *pattern);
@@ -201,7 +200,7 @@ gboolean autocmd_add(char *name, gboolean delete)
             }
             /* skip if pattern does not match - we check the pattern against
              * another pattern */
-            if (!wildmatch(pattern, cmd->pattern)) {
+            if (!util_wildmatch_multi(pattern, cmd->pattern)) {
                 continue;
             }
             /* remove the bits from the item */
@@ -271,7 +270,7 @@ gboolean autocmd_run(const char *group, AuEvent event, const char *uri)
             }
             /* check pattern only if uri was given */
             /* skip if pattern does not match */
-            if (uri && !wildmatch(cmd->pattern, uri)) {
+            if (uri && !util_wildmatch_multi(cmd->pattern, uri)) {
                 continue;
             }
             /* run the command */
@@ -398,36 +397,6 @@ static char *get_next_word(char **line)
     return word;
 }
 
-/**
- * Check if given uri matches one of the patterns given as comma separated
- * string.
- */
-static gboolean wildmatch(char *patterns, const char *uri)
-{
-    char *cur, *start;
-    gboolean matched;
-
-    for (cur = start = patterns; *cur; cur++) {
-        /* iterate through the patterns until the next ',' */
-        if (*cur == ',') {
-            *cur    = '\0';
-            matched = util_wildmatch(start, uri);
-            *cur    = ',';
-
-            /* return if the first match is found */
-            if (matched) {
-                return true;
-            }
-
-            /* set the start right after the ',' */
-            start = cur + 1;
-        }
-    }
-
-    /* still no match found - check the last pattern */
-    return util_wildmatch(start, uri);
-}
-
 static AuGroup *new_group(const char *name)
 {
     AuGroup *new = g_slice_new(AuGroup);
index 7101bca..92d5d26 100644 (file)
@@ -28,7 +28,8 @@
 
 extern VbCore vb;
 
-static int match_list(const char *pattern, const char *subject);
+static gboolean match(const char *pattern, int patlen, const char *subject);
+static gboolean match_list(const char *pattern, int patlen, const char *subject);
 
 
 char *util_get_config_dir(void)
@@ -510,6 +511,42 @@ gboolean util_parse_expansion(const char **input, GString *str, int flags,
     return expanded;
 }
 
+/**
+ * Like util_wildmatch, but allow to use a list of patterns,
+ */
+gboolean util_wildmatch_multi(const char *pattern, const char *subject)
+{
+    const char *end;
+    int braces, patlen;
+
+    /* loop through all pattens */
+    for (; *pattern; pattern = (*end == ',' ? end + 1 : end)) {
+        /* find end of the pattern - but be careful with comma in curly braces */
+        braces = 0;
+        for (end = pattern; *end && (*end != ',' || braces || *(end - 1) == '\\'); ++end) {
+            if (*end == '{') {
+                braces++;
+            } else if (*end == '}') {
+                braces--;
+            }
+        }
+        /* ignore single comma */
+        if (*pattern == *end) {
+            continue;
+        }
+        /* calculate the length of the pattern */
+        patlen = end - pattern;
+
+        /* if this pattern matches - return */
+        if (match(pattern, patlen, subject)) {
+            return true;
+        }
+    }
+
+    /* empty pattern matches only on empty subject */
+    return !*subject;
+}
+
 /**
  * Compares given string against also given pattern.
  *
@@ -521,11 +558,20 @@ gboolean util_parse_expansion(const char **input, GString *str, int flags,
  * *?{}      these chars must always be escaped by '\' to match them literally
  */
 gboolean util_wildmatch(const char *pattern, const char *subject)
+{
+    return match(pattern, strlen(pattern), subject);
+}
+
+/**
+ * Compares given subject string against the given pattern.
+ * The pattern needs not to bee NUL terminated.
+ */
+static gboolean match(const char *pattern, int patlen, const char *subject)
 {
     int i;
     char sl, pl;
 
-    while (*pattern) {
+    while (patlen > 0) {
         switch (*pattern) {
             case '?':
                 /* '?' matches a single char except of / and subject end */
@@ -537,14 +583,14 @@ gboolean util_wildmatch(const char *pattern, const char *subject)
             case '*':
                 /* easiest case - the '*' ist the last char in pattern - this
                  * will always match */
-                if (!pattern[1]) {
+                if (patlen == 1) {
                     return true;
                 }
                 /* Try to match as much as possible. Try to match the complete
                  * uri, if that fails move forward in uri and check for a
                  * match. */
                 i = strlen(subject);
-                while (i >= 0 && !util_wildmatch(pattern + 1, subject + i)) {
+                while (i >= 0 && !match(pattern + 1, patlen - 1, subject + i)) {
                     i--;
                 }
                 return i >= 0;
@@ -555,12 +601,13 @@ gboolean util_wildmatch(const char *pattern, const char *subject)
 
             case '{':
                 /* possible {foo,bar} pattern */
-                return match_list(pattern, subject);
+                return match_list(pattern, patlen, subject);
 
             case '\\':
                 /* '\' escapes next special char */
                 if (strchr("*?{}", pattern[1])) {
                     pattern++;
+                    patlen--;
                     if (*pattern != *subject) {
                         return false;
                     }
@@ -584,6 +631,7 @@ gboolean util_wildmatch(const char *pattern, const char *subject)
         }
         /* do another loop run with next pattern and subject char */
         pattern++;
+        patlen--;
         subject++;
     }
 
@@ -591,16 +639,26 @@ gboolean util_wildmatch(const char *pattern, const char *subject)
     return !*subject;
 }
 
-static int match_list(const char *pattern, const char *subject)
+/**
+ * Matches pattern starting with '{'.
+ * This function can process also on none null terminated pattern.
+ */
+static gboolean match_list(const char *pattern, int patlen, const char *subject)
 {
+    int endlen;
     const char *end, *s;
 
+    end    = pattern;
+    endlen = patlen;
     /* finde the next none escaped '}' */
-    for (end = pattern; *end && *end != '}'; end++) {
+    while (endlen > 0 && *end != '}') {
         /* if escape char - move pointer one additional step */
         if (*end == '\\') {
             end++;
+            endlen--;
         }
+        end++;
+        endlen--;
     }
 
     if (!*end) {
@@ -610,29 +668,34 @@ static int match_list(const char *pattern, const char *subject)
 
     s = subject;
     end++;      /* skip over } */
+    endlen--;
     pattern++;  /* skip over { */
+    patlen--;
     while (true) {
         switch (*pattern) {
             case ',':
-                if (util_wildmatch(end, s)) {
+                if (match(end, endlen, s)) {
                     return true;
                 }
                 s = subject;
                 pattern++;
+                patlen--;
                 break;
 
             case '}':
-                return util_wildmatch(end, s);
+                return match(end, endlen, s);
 
             case '\\':
                 if (pattern[1] == ',' || pattern[1] == '}' || pattern[1] == '{') {
-                    pattern += 1;
+                    pattern++;
+                    patlen--;
                 }
                 /* fall through */
 
             default:
                 if (*pattern == *s) {
                     pattern++;
+                    patlen--;
                     s++;
                 } else {
                     /* this item of the list does not match - move forward to
@@ -642,12 +705,15 @@ static int match_list(const char *pattern, const char *subject)
                         /* if escape char is found - skip next char */
                         if (*pattern == '\\') {
                             pattern++;
+                            patlen--;
                         }
                         pattern++;
+                        patlen--;
                     }
                     /* found ',' skip over it to check the next list item */
                     if (*pattern == ',') {
                         pattern++;
+                        patlen--;
                     }
                 }
         }
index ad6a770..938d789 100644 (file)
@@ -49,6 +49,7 @@ char *util_build_path(const char *path, const char *dir);
 char *util_expand(const char *src, int expflags);
 gboolean util_parse_expansion(const char **input, GString *str, int flags,
     const char *quoteable);
+gboolean util_wildmatch_multi(const char *pattern, const char *subject);
 gboolean util_wildmatch(const char *pattern, const char *string);
 gboolean util_fill_completion(GtkListStore *store, const char *input, GList *src);
 
index 38b7bf2..b89d620 100644 (file)
@@ -248,6 +248,23 @@ static void test_wildmatch_complete(void)
     g_assert_true(util_wildmatch("http{s,}://{fanglingsu.,}github.{io,com}/*vimb/", "https://github.com/fanglingsu/vimb/"));
 }
 
+static void test_wildmatch_multi(void)
+{
+    /* check if sinlge pattern matching works */
+    g_assert_true(util_wildmatch_multi("", ""));
+    g_assert_true(util_wildmatch_multi("single", "single"));
+    g_assert_true(util_wildmatch_multi("s*e", "single"));
+
+    g_assert_true(util_wildmatch_multi("foo,b{a,o,}r,ba?", "foo"));
+    g_assert_true(util_wildmatch_multi("foo,b{a,o,}r,ba?", "bar"));
+    g_assert_true(util_wildmatch_multi("foo,b{a,o,}r,ba?", "bor"));
+    g_assert_true(util_wildmatch_multi("foo,b{a,o,}r,ba?", "br"));
+    g_assert_true(util_wildmatch_multi("foo,b{a,o,}r,ba?", "baz"));
+    g_assert_true(util_wildmatch_multi("foo,b{a,o,}r,ba?", "bat"));
+
+    g_assert_false(util_wildmatch_multi("foo,b{a,o,}r,ba?", "foo,"));
+}
+
 int main(int argc, char *argv[])
 {
     g_test_init(&argc, &argv, NULL);
@@ -264,6 +281,7 @@ int main(int argc, char *argv[])
     g_test_add_func("/test-util/wildmatch-wildcard", test_wildmatch_wildcard);
     g_test_add_func("/test-util/wildmatch-curlybraces", test_wildmatch_curlybraces);
     g_test_add_func("/test-util/wildmatch-complete", test_wildmatch_complete);
+    g_test_add_func("/test-util/wildmatch-multi", test_wildmatch_multi);
 
     return g_test_run();
 }