Merge branch 'mh/string-list'
authorJunio C Hamano <gitster@pobox.com>
Mon, 17 Sep 2012 22:53:31 +0000 (15:53 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 17 Sep 2012 22:53:31 +0000 (15:53 -0700)
* mh/string-list:
api-string-list.txt: initialize the string_list the easy way
string_list: add a function string_list_longest_prefix()
string_list: add a new function, string_list_remove_duplicates()
string_list: add a new function, filter_string_list()
string_list: add two new functions for splitting strings
string_list: add function string_list_append_nodup()

.gitignore
Documentation/technical/api-string-list.txt
Makefile
string-list.c
string-list.h
t/t0063-string-list.sh [new file with mode: 0755]
test-string-list.c [new file with mode: 0644]
index 68fe464090606b95b40b8b99e2454edb52862abe..a188a82bb1d6ab080c434a7096bd263545169f3c 100644 (file)
 /test-run-command
 /test-sha1
 /test-sigchain
+/test-string-list
 /test-subprocess
 /test-svn-fe
 /common-cmds.h
index 5a0c14fcebfcf4d5cbad4900d062703412c501e1..155ac8cb10d53053eb37bf0ea52d42623d6cb3c9 100644 (file)
@@ -20,8 +20,9 @@ If you need something advanced, you can manually malloc() the `items`
 member (you need this if you add things later) and you should set the
 `nr` and `alloc` members in that case, too.
 
-. Adds new items to the list, using `string_list_append` or
-  `string_list_insert`.
+. Adds new items to the list, using `string_list_append`,
+  `string_list_append_nodup`, `string_list_insert`,
+  `string_list_split`, and/or `string_list_split_in_place`.
 
 . Can check if a string is in the list using `string_list_has_string` or
   `unsorted_string_list_has_string` and get it from the list using
@@ -29,18 +30,23 @@ member (you need this if you add things later) and you should set the
 
 . Can sort an unsorted list using `sort_string_list`.
 
+. Can remove duplicate items from a sorted list using
+  `string_list_remove_duplicates`.
+
 . Can remove individual items of an unsorted list using
   `unsorted_string_list_delete_item`.
 
+. Can remove items not matching a criterion from a sorted or unsorted
+  list using `filter_string_list`.
+
 . Finally it should free the list using `string_list_clear`.
 
 Example:
 
 ----
-struct string_list list;
+struct string_list list = STRING_LIST_INIT_NODUP;
 int i;
 
-memset(&list, 0, sizeof(struct string_list));
 string_list_append(&list, "foo");
 string_list_append(&list, "bar");
 for (i = 0; i < list.nr; i++)
@@ -60,6 +66,22 @@ Functions
 
 * General ones (works with sorted and unsorted lists as well)
 
+`filter_string_list`::
+
+       Apply a function to each item in a list, retaining only the
+       items for which the function returns true.  If free_util is
+       true, call free() on the util members of any items that have
+       to be deleted.  Preserve the order of the items that are
+       retained.
+
+`string_list_longest_prefix`::
+
+       Return the longest string within a string_list that is a
+       prefix (in the sense of prefixcmp()) of the specified string,
+       or NULL if no such prefix exists.  This function does not
+       require the string_list to be sorted (it does a linear
+       search).
+
 `print_string_list`::
 
        Dump a string_list to stdout, useful mainly for debugging purposes. It
@@ -96,11 +118,28 @@ write `string_list_insert(...)->util = ...;`.
        Look up a given string in the string_list, returning the containing
        string_list_item. If the string is not found, NULL is returned.
 
+`string_list_remove_duplicates`::
+
+       Remove all but the first of consecutive entries that have the
+       same string value.  If free_util is true, call free() on the
+       util members of any items that have to be deleted.
+
 * Functions for unsorted lists only
 
 `string_list_append`::
 
-       Append a new string to the end of the string_list.
+       Append a new string to the end of the string_list.  If
+       `strdup_string` is set, then the string argument is copied;
+       otherwise the new `string_list_entry` refers to the input
+       string.
+
+`string_list_append_nodup`::
+
+       Append a new string to the end of the string_list.  The new
+       `string_list_entry` always refers to the input string, even if
+       `strdup_string` is set.  This function can be used to hand
+       ownership of a malloc()ed string to a `string_list` that has
+       `strdup_string` set.
 
 `sort_string_list`::
 
@@ -124,6 +163,25 @@ counterpart for sorted lists, which performs a binary search.
        is set. The third parameter controls if the `util` pointer of the
        items should be freed or not.
 
+`string_list_split`::
+`string_list_split_in_place`::
+
+       Split a string into substrings on a delimiter character and
+       append the substrings to a `string_list`.  If `maxsplit` is
+       non-negative, then split at most `maxsplit` times.  Return the
+       number of substrings appended to the list.
++
+`string_list_split` requires a `string_list` that has `strdup_strings`
+set to true; it leaves the input string untouched and makes copies of
+the substrings in newly-allocated memory.
+`string_list_split_in_place` requires a `string_list` that has
+`strdup_strings` set to false; it splits the input string in place,
+overwriting the delimiter characters with NULs and creating new
+string_list_items that point into the original string (the original
+string must therefore not be modified or freed while the `string_list`
+is in use).
+
+
 Data structures
 ---------------
 
index 56301dc0a80db5085400436bab3346f2dbdd5321..a49d1db2889e7724f48eb81fc1fb3215e8e28a91 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -509,6 +509,7 @@ TEST_PROGRAMS_NEED_X += test-run-command
 TEST_PROGRAMS_NEED_X += test-scrap-cache-tree
 TEST_PROGRAMS_NEED_X += test-sha1
 TEST_PROGRAMS_NEED_X += test-sigchain
+TEST_PROGRAMS_NEED_X += test-string-list
 TEST_PROGRAMS_NEED_X += test-subprocess
 TEST_PROGRAMS_NEED_X += test-svn-fe
 
index d9810aba421cbbc844b45ea5ca713a480a699eb2..c54b816244f69614c67c532d20719081a4b34bd4 100644 (file)
@@ -92,6 +92,23 @@ struct string_list_item *string_list_lookup(struct string_list *list, const char
        return list->items + i;
 }
 
+void string_list_remove_duplicates(struct string_list *list, int free_util)
+{
+       if (list->nr > 1) {
+               int src, dst;
+               for (src = dst = 1; src < list->nr; src++) {
+                       if (!strcmp(list->items[dst - 1].string, list->items[src].string)) {
+                               if (list->strdup_strings)
+                                       free(list->items[src].string);
+                               if (free_util)
+                                       free(list->items[src].util);
+                       } else
+                               list->items[dst++] = list->items[src];
+               }
+               list->nr = dst;
+       }
+}
+
 int for_each_string_list(struct string_list *list,
                         string_list_each_func_t fn, void *cb_data)
 {
@@ -102,6 +119,43 @@ int for_each_string_list(struct string_list *list,
        return ret;
 }
 
+void filter_string_list(struct string_list *list, int free_util,
+                       string_list_each_func_t want, void *cb_data)
+{
+       int src, dst = 0;
+       for (src = 0; src < list->nr; src++) {
+               if (want(&list->items[src], cb_data)) {
+                       list->items[dst++] = list->items[src];
+               } else {
+                       if (list->strdup_strings)
+                               free(list->items[src].string);
+                       if (free_util)
+                               free(list->items[src].util);
+               }
+       }
+       list->nr = dst;
+}
+
+char *string_list_longest_prefix(const struct string_list *prefixes,
+                                const char *string)
+{
+       int i, max_len = -1;
+       char *retval = NULL;
+
+       for (i = 0; i < prefixes->nr; i++) {
+               char *prefix = prefixes->items[i].string;
+               if (!prefixcmp(string, prefix)) {
+                       int len = strlen(prefix);
+                       if (len > max_len) {
+                               retval = prefix;
+                               max_len = len;
+                       }
+               }
+       }
+
+       return retval;
+}
+
 void string_list_clear(struct string_list *list, int free_util)
 {
        if (list->items) {
@@ -148,13 +202,23 @@ void print_string_list(const struct string_list *p, const char *text)
                printf("%s:%p\n", p->items[i].string, p->items[i].util);
 }
 
-struct string_list_item *string_list_append(struct string_list *list, const char *string)
+struct string_list_item *string_list_append_nodup(struct string_list *list,
+                                                 char *string)
 {
+       struct string_list_item *retval;
        ALLOC_GROW(list->items, list->nr + 1, list->alloc);
-       list->items[list->nr].string =
-               list->strdup_strings ? xstrdup(string) : (char *)string;
-       list->items[list->nr].util = NULL;
-       return list->items + list->nr++;
+       retval = &list->items[list->nr++];
+       retval->string = string;
+       retval->util = NULL;
+       return retval;
+}
+
+struct string_list_item *string_list_append(struct string_list *list,
+                                           const char *string)
+{
+       return string_list_append_nodup(
+                       list,
+                       list->strdup_strings ? xstrdup(string) : (char *)string);
 }
 
 static int cmp_items(const void *a, const void *b)
@@ -194,3 +258,56 @@ void unsorted_string_list_delete_item(struct string_list *list, int i, int free_
        list->items[i] = list->items[list->nr-1];
        list->nr--;
 }
+
+int string_list_split(struct string_list *list, const char *string,
+                     int delim, int maxsplit)
+{
+       int count = 0;
+       const char *p = string, *end;
+
+       if (!list->strdup_strings)
+               die("internal error in string_list_split(): "
+                   "list->strdup_strings must be set");
+       for (;;) {
+               count++;
+               if (maxsplit >= 0 && count > maxsplit) {
+                       string_list_append(list, p);
+                       return count;
+               }
+               end = strchr(p, delim);
+               if (end) {
+                       string_list_append_nodup(list, xmemdupz(p, end - p));
+                       p = end + 1;
+               } else {
+                       string_list_append(list, p);
+                       return count;
+               }
+       }
+}
+
+int string_list_split_in_place(struct string_list *list, char *string,
+                              int delim, int maxsplit)
+{
+       int count = 0;
+       char *p = string, *end;
+
+       if (list->strdup_strings)
+               die("internal error in string_list_split_in_place(): "
+                   "list->strdup_strings must not be set");
+       for (;;) {
+               count++;
+               if (maxsplit >= 0 && count > maxsplit) {
+                       string_list_append(list, p);
+                       return count;
+               }
+               end = strchr(p, delim);
+               if (end) {
+                       *end = '\0';
+                       string_list_append(list, p);
+                       p = end + 1;
+               } else {
+                       string_list_append(list, p);
+                       return count;
+               }
+       }
+}
index 0684cb73bfd27846182479a4e192d682a44f201a..5efd07b44e020428326ff32ff258449ec347861f 100644 (file)
@@ -29,6 +29,24 @@ int for_each_string_list(struct string_list *list,
 #define for_each_string_list_item(item,list) \
        for (item = (list)->items; item < (list)->items + (list)->nr; ++item)
 
+/*
+ * Apply want to each item in list, retaining only the ones for which
+ * the function returns true.  If free_util is true, call free() on
+ * the util members of any items that have to be deleted.  Preserve
+ * the order of the items that are retained.
+ */
+void filter_string_list(struct string_list *list, int free_util,
+                       string_list_each_func_t want, void *cb_data);
+
+/*
+ * Return the longest string in prefixes that is a prefix (in the
+ * sense of prefixcmp()) of string, or NULL if no such prefix exists.
+ * This function does not require the string_list to be sorted (it
+ * does a linear search).
+ */
+char *string_list_longest_prefix(const struct string_list *prefixes, const char *string);
+
+
 /* Use these functions only on sorted lists: */
 int string_list_has_string(const struct string_list *list, const char *string);
 int string_list_find_insert_index(const struct string_list *list, const char *string,
@@ -38,11 +56,64 @@ struct string_list_item *string_list_insert_at_index(struct string_list *list,
                                                     int insert_at, const char *string);
 struct string_list_item *string_list_lookup(struct string_list *list, const char *string);
 
+/*
+ * Remove all but the first of consecutive entries with the same
+ * string value.  If free_util is true, call free() on the util
+ * members of any items that have to be deleted.
+ */
+void string_list_remove_duplicates(struct string_list *sorted_list, int free_util);
+
+
 /* Use these functions only on unsorted lists: */
+
+/*
+ * Add string to the end of list.  If list->strdup_string is set, then
+ * string is copied; otherwise the new string_list_entry refers to the
+ * input string.
+ */
 struct string_list_item *string_list_append(struct string_list *list, const char *string);
+
+/*
+ * Like string_list_append(), except string is never copied.  When
+ * list->strdup_strings is set, this function can be used to hand
+ * ownership of a malloc()ed string to list without making an extra
+ * copy.
+ */
+struct string_list_item *string_list_append_nodup(struct string_list *list, char *string);
+
 void sort_string_list(struct string_list *list);
 int unsorted_string_list_has_string(struct string_list *list, const char *string);
 struct string_list_item *unsorted_string_list_lookup(struct string_list *list,
                                                     const char *string);
+
 void unsorted_string_list_delete_item(struct string_list *list, int i, int free_util);
+
+/*
+ * Split string into substrings on character delim and append the
+ * substrings to list.  The input string is not modified.
+ * list->strdup_strings must be set, as new memory needs to be
+ * allocated to hold the substrings.  If maxsplit is non-negative,
+ * then split at most maxsplit times.  Return the number of substrings
+ * appended to list.
+ *
+ * Examples:
+ *   string_list_split(l, "foo:bar:baz", ':', -1) -> ["foo", "bar", "baz"]
+ *   string_list_split(l, "foo:bar:baz", ':', 0) -> ["foo:bar:baz"]
+ *   string_list_split(l, "foo:bar:baz", ':', 1) -> ["foo", "bar:baz"]
+ *   string_list_split(l, "foo:bar:", ':', -1) -> ["foo", "bar", ""]
+ *   string_list_split(l, "", ':', -1) -> [""]
+ *   string_list_split(l, ":", ':', -1) -> ["", ""]
+ */
+int string_list_split(struct string_list *list, const char *string,
+                     int delim, int maxsplit);
+
+/*
+ * Like string_list_split(), except that string is split in-place: the
+ * delimiter characters in string are overwritten with NULs, and the
+ * new string_list_items point into string (which therefore must not
+ * be modified or freed while the string_list is in use).
+ * list->strdup_strings must *not* be set.
+ */
+int string_list_split_in_place(struct string_list *list, char *string,
+                              int delim, int maxsplit);
 #endif /* STRING_LIST_H */
diff --git a/t/t0063-string-list.sh b/t/t0063-string-list.sh
new file mode 100755 (executable)
index 0000000..41c8826
--- /dev/null
@@ -0,0 +1,121 @@
+#!/bin/sh
+#
+# Copyright (c) 2012 Michael Haggerty
+#
+
+test_description='Test string list functionality'
+
+. ./test-lib.sh
+
+test_split () {
+       cat >expected &&
+       test_expect_success "split $1 at $2, max $3" "
+               test-string-list split '$1' '$2' '$3' >actual &&
+               test_cmp expected actual &&
+               test-string-list split_in_place '$1' '$2' '$3' >actual &&
+               test_cmp expected actual
+       "
+}
+
+test_longest_prefix () {
+       test "$(test-string-list longest_prefix "$1" "$2")" = "$3"
+}
+
+test_no_longest_prefix () {
+       test_must_fail test-string-list longest_prefix "$1" "$2"
+}
+
+test_split "foo:bar:baz" ":" "-1" <<EOF
+3
+[0]: "foo"
+[1]: "bar"
+[2]: "baz"
+EOF
+
+test_split "foo:bar:baz" ":" "0" <<EOF
+1
+[0]: "foo:bar:baz"
+EOF
+
+test_split "foo:bar:baz" ":" "1" <<EOF
+2
+[0]: "foo"
+[1]: "bar:baz"
+EOF
+
+test_split "foo:bar:baz" ":" "2" <<EOF
+3
+[0]: "foo"
+[1]: "bar"
+[2]: "baz"
+EOF
+
+test_split "foo:bar:" ":" "-1" <<EOF
+3
+[0]: "foo"
+[1]: "bar"
+[2]: ""
+EOF
+
+test_split "" ":" "-1" <<EOF
+1
+[0]: ""
+EOF
+
+test_split ":" ":" "-1" <<EOF
+2
+[0]: ""
+[1]: ""
+EOF
+
+test_expect_success "test filter_string_list" '
+       test "x-" = "x$(test-string-list filter - y)" &&
+       test "x-" = "x$(test-string-list filter no y)" &&
+       test yes = "$(test-string-list filter yes y)" &&
+       test yes = "$(test-string-list filter no:yes y)" &&
+       test yes = "$(test-string-list filter yes:no y)" &&
+       test y1:y2 = "$(test-string-list filter y1:y2 y)" &&
+       test y2:y1 = "$(test-string-list filter y2:y1 y)" &&
+       test "x-" = "x$(test-string-list filter x1:x2 y)"
+'
+
+test_expect_success "test remove_duplicates" '
+       test "x-" = "x$(test-string-list remove_duplicates -)" &&
+       test "x" = "x$(test-string-list remove_duplicates "")" &&
+       test a = "$(test-string-list remove_duplicates a)" &&
+       test a = "$(test-string-list remove_duplicates a:a)" &&
+       test a = "$(test-string-list remove_duplicates a:a:a:a:a)" &&
+       test a:b = "$(test-string-list remove_duplicates a:b)" &&
+       test a:b = "$(test-string-list remove_duplicates a:a:b)" &&
+       test a:b = "$(test-string-list remove_duplicates a:b:b)" &&
+       test a:b:c = "$(test-string-list remove_duplicates a:b:c)" &&
+       test a:b:c = "$(test-string-list remove_duplicates a:a:b:c)" &&
+       test a:b:c = "$(test-string-list remove_duplicates a:b:b:c)" &&
+       test a:b:c = "$(test-string-list remove_duplicates a:b:c:c)" &&
+       test a:b:c = "$(test-string-list remove_duplicates a:a:b:b:c:c)" &&
+       test a:b:c = "$(test-string-list remove_duplicates a:a:a:b:b:b:c:c:c)"
+'
+
+test_expect_success "test longest_prefix" '
+       test_no_longest_prefix - '' &&
+       test_no_longest_prefix - x &&
+       test_longest_prefix "" x "" &&
+       test_longest_prefix x x x &&
+       test_longest_prefix "" foo "" &&
+       test_longest_prefix : foo "" &&
+       test_longest_prefix f foo f &&
+       test_longest_prefix foo foobar foo &&
+       test_longest_prefix foo foo foo &&
+       test_no_longest_prefix bar foo &&
+       test_no_longest_prefix bar:bar foo &&
+       test_no_longest_prefix foobar foo &&
+       test_longest_prefix foo:bar foo foo &&
+       test_longest_prefix foo:bar bar bar &&
+       test_longest_prefix foo::bar foo foo &&
+       test_longest_prefix foo:foobar foo foo &&
+       test_longest_prefix foobar:foo foo foo &&
+       test_longest_prefix foo: bar "" &&
+       test_longest_prefix :foo bar ""
+'
+
+test_done
diff --git a/test-string-list.c b/test-string-list.c
new file mode 100644 (file)
index 0000000..5e9631f
--- /dev/null
@@ -0,0 +1,123 @@
+#include "cache.h"
+#include "string-list.h"
+
+/*
+ * Parse an argument into a string list.  arg should either be a
+ * ':'-separated list of strings, or "-" to indicate an empty string
+ * list (as opposed to "", which indicates a string list containing a
+ * single empty string).  list->strdup_strings must be set.
+ */
+void parse_string_list(struct string_list *list, const char *arg)
+{
+       if (!strcmp(arg, "-"))
+               return;
+
+       (void)string_list_split(list, arg, ':', -1);
+}
+
+void write_list(const struct string_list *list)
+{
+       int i;
+       for (i = 0; i < list->nr; i++)
+               printf("[%d]: \"%s\"\n", i, list->items[i].string);
+}
+
+void write_list_compact(const struct string_list *list)
+{
+       int i;
+       if (!list->nr)
+               printf("-\n");
+       else {
+               printf("%s", list->items[0].string);
+               for (i = 1; i < list->nr; i++)
+                       printf(":%s", list->items[i].string);
+               printf("\n");
+       }
+}
+
+int prefix_cb(struct string_list_item *item, void *cb_data)
+{
+       const char *prefix = (const char *)cb_data;
+       return !prefixcmp(item->string, prefix);
+}
+
+int main(int argc, char **argv)
+{
+       if (argc == 5 && !strcmp(argv[1], "split")) {
+               struct string_list list = STRING_LIST_INIT_DUP;
+               int i;
+               const char *s = argv[2];
+               int delim = *argv[3];
+               int maxsplit = atoi(argv[4]);
+
+               i = string_list_split(&list, s, delim, maxsplit);
+               printf("%d\n", i);
+               write_list(&list);
+               string_list_clear(&list, 0);
+               return 0;
+       }
+
+       if (argc == 5 && !strcmp(argv[1], "split_in_place")) {
+               struct string_list list = STRING_LIST_INIT_NODUP;
+               int i;
+               char *s = xstrdup(argv[2]);
+               int delim = *argv[3];
+               int maxsplit = atoi(argv[4]);
+
+               i = string_list_split_in_place(&list, s, delim, maxsplit);
+               printf("%d\n", i);
+               write_list(&list);
+               string_list_clear(&list, 0);
+               free(s);
+               return 0;
+       }
+
+       if (argc == 4 && !strcmp(argv[1], "filter")) {
+               /*
+                * Retain only the items that have the specified prefix.
+                * Arguments: list|- prefix
+                */
+               struct string_list list = STRING_LIST_INIT_DUP;
+               const char *prefix = argv[3];
+
+               parse_string_list(&list, argv[2]);
+               filter_string_list(&list, 0, prefix_cb, (void *)prefix);
+               write_list_compact(&list);
+               string_list_clear(&list, 0);
+               return 0;
+       }
+
+       if (argc == 3 && !strcmp(argv[1], "remove_duplicates")) {
+               struct string_list list = STRING_LIST_INIT_DUP;
+
+               parse_string_list(&list, argv[2]);
+               string_list_remove_duplicates(&list, 0);
+               write_list_compact(&list);
+               string_list_clear(&list, 0);
+               return 0;
+       }
+
+       if (argc == 4 && !strcmp(argv[1], "longest_prefix")) {
+               /* arguments: <colon-separated-prefixes>|- <string> */
+               struct string_list prefixes = STRING_LIST_INIT_DUP;
+               int retval;
+               const char *prefix_string = argv[2];
+               const char *string = argv[3];
+               const char *match;
+
+               parse_string_list(&prefixes, prefix_string);
+               match = string_list_longest_prefix(&prefixes, string);
+               if (match) {
+                       printf("%s\n", match);
+                       retval = 0;
+               }
+               else
+                       retval = 1;
+               string_list_clear(&prefixes, 0);
+               return retval;
+       }
+
+       fprintf(stderr, "%s: unknown function name: %s\n", argv[0],
+               argv[1] ? argv[1] : "(there was none)");
+       return 1;
+}