Merge branch 'matthiasc/for-master' into 'master'

fnmatch: Support case-folding

See merge request GNOME/gtk!3628
This commit is contained in:
Matthias Clasen 2021-06-04 04:43:20 +00:00
commit 036dd60a4e
5 changed files with 171 additions and 132 deletions

View File

@ -36,14 +36,14 @@
#include <glib.h>
static gunichar
get_char (const char **str)
get_char (const char **str,
gboolean casefold)
{
gunichar c = g_utf8_get_char (*str);
*str = g_utf8_next_char (*str);
#ifdef G_PLATFORM_WIN32
c = g_unichar_tolower (c);
#endif
if (casefold)
c = g_unichar_tolower (c);
return c;
}
@ -56,13 +56,14 @@ get_char (const char **str)
static gunichar
get_unescaped_char (const char **str,
gboolean *was_escaped)
gboolean *was_escaped,
gboolean casefold)
{
gunichar c = get_char (str);
gunichar c = get_char (str, casefold);
*was_escaped = DO_ESCAPE && c == '\\';
if (*was_escaped)
c = get_char (str);
c = get_char (str, casefold);
return c;
}
@ -74,7 +75,8 @@ static gboolean
gtk_fnmatch_intern (const char *pattern,
const char *string,
gboolean component_start,
gboolean no_leading_period)
gboolean no_leading_period,
gboolean casefold)
{
const char *p = pattern, *n = string;
@ -82,8 +84,8 @@ gtk_fnmatch_intern (const char *pattern,
{
const char *last_n = n;
gunichar c = get_char (&p);
gunichar nc = get_char (&n);
gunichar c = get_char (&p, casefold);
gunichar nc = get_char (&n, casefold);
switch (c)
{
@ -97,7 +99,7 @@ gtk_fnmatch_intern (const char *pattern,
break;
case '\\':
if (DO_ESCAPE)
c = get_char (&p);
c = get_char (&p, casefold);
if (nc != c)
return FALSE;
break;
@ -108,9 +110,9 @@ gtk_fnmatch_intern (const char *pattern,
{
const char *last_p;
for (last_p = p, c = get_char (&p);
for (last_p = p, c = get_char (&p, casefold);
c == '?' || c == '*';
last_p = p, c = get_char (&p))
last_p = p, c = get_char (&p, casefold))
{
if (c == '?')
{
@ -120,7 +122,7 @@ gtk_fnmatch_intern (const char *pattern,
return FALSE;
else
{
last_n = n; nc = get_char (&n);
last_n = n; nc = get_char (&n, casefold);
}
}
}
@ -138,17 +140,17 @@ gtk_fnmatch_intern (const char *pattern,
}
if (DO_ESCAPE && c == '\\')
c = get_char (&p);
c = get_char (&p, casefold);
for (p = last_p; nc != '\0';)
{
if ((c == '[' || nc == c) &&
gtk_fnmatch_intern (p, last_n, component_start, no_leading_period))
gtk_fnmatch_intern (p, last_n, component_start, no_leading_period, casefold))
return TRUE;
component_start = (nc == G_DIR_SEPARATOR);
last_n = n;
nc = get_char (&n);
nc = get_char (&n, casefold);
}
return FALSE;
@ -170,7 +172,7 @@ gtk_fnmatch_intern (const char *pattern,
if (not)
++p;
c = get_unescaped_char (&p, &was_escaped);
c = get_unescaped_char (&p, &was_escaped, casefold);
for (;;)
{
register gunichar cstart = c, cend = c;
@ -178,15 +180,15 @@ gtk_fnmatch_intern (const char *pattern,
/* [ (unterminated) loses. */
return FALSE;
c = get_unescaped_char (&p, &was_escaped);
c = get_unescaped_char (&p, &was_escaped, casefold);
if (!was_escaped && c == '-' && *p != ']')
{
cend = get_unescaped_char (&p, &was_escaped);
cend = get_unescaped_char (&p, &was_escaped, casefold);
if (cend == '\0')
return FALSE;
c = get_char (&p);
c = get_char (&p, casefold);
}
if (nc >= cstart && nc <= cend)
@ -208,7 +210,7 @@ gtk_fnmatch_intern (const char *pattern,
/* [... (unterminated) loses. */
return FALSE;
c = get_unescaped_char (&p, &was_escaped);
c = get_unescaped_char (&p, &was_escaped, casefold);
}
if (not)
return FALSE;
@ -246,113 +248,8 @@ gtk_fnmatch_intern (const char *pattern,
gboolean
_gtk_fnmatch (const char *pattern,
const char *string,
gboolean no_leading_period)
gboolean no_leading_period,
gboolean casefold)
{
return gtk_fnmatch_intern (pattern, string, TRUE, no_leading_period);
return gtk_fnmatch_intern (pattern, string, TRUE, no_leading_period, casefold);
}
#undef FNMATCH_TEST_CASES
#ifdef FNMATCH_TEST_CASES
#define TEST(pat, str, no_leading_period, result) \
g_assert (_gtk_fnmatch ((pat), (str), (no_leading_period)) == result)
int main (int argc, char **argv)
{
TEST ("[a-]", "-", TRUE, TRUE);
TEST ("a", "a", TRUE, TRUE);
TEST ("a", "b", TRUE, FALSE);
/* Test what ? matches */
TEST ("?", "a", TRUE, TRUE);
TEST ("?", ".", TRUE, FALSE);
TEST ("a?", "a.", TRUE, TRUE);
TEST ("a/?", "a/b", TRUE, TRUE);
TEST ("a/?", "a/.", TRUE, FALSE);
TEST ("?", "/", TRUE, FALSE);
/* Test what * matches */
TEST ("*", "a", TRUE, TRUE);
TEST ("*", ".", TRUE, FALSE);
TEST ("a*", "a.", TRUE, TRUE);
TEST ("a/*", "a/b", TRUE, TRUE);
TEST ("a/*", "a/.", TRUE, FALSE);
TEST ("*", "/", TRUE, FALSE);
/* Range tests */
TEST ("[ab]", "a", TRUE, TRUE);
TEST ("[ab]", "c", TRUE, FALSE);
TEST ("[^ab]", "a", TRUE, FALSE);
TEST ("[!ab]", "a", TRUE, FALSE);
TEST ("[^ab]", "c", TRUE, TRUE);
TEST ("[!ab]", "c", TRUE, TRUE);
TEST ("[a-c]", "b", TRUE, TRUE);
TEST ("[a-c]", "d", TRUE, FALSE);
TEST ("[a-]", "-", TRUE, TRUE);
TEST ("[]]", "]", TRUE, TRUE);
TEST ("[^]]", "a", TRUE, TRUE);
TEST ("[!]]", "a", TRUE, TRUE);
/* Various unclosed ranges */
TEST ("[ab", "a", TRUE, FALSE);
TEST ("[a-", "a", TRUE, FALSE);
TEST ("[ab", "c", TRUE, FALSE);
TEST ("[a-", "c", TRUE, FALSE);
TEST ("[^]", "a", TRUE, FALSE);
/* Ranges and special no-wildcard matches */
TEST ("[.]", ".", TRUE, FALSE);
TEST ("a[.]", "a.", TRUE, TRUE);
TEST ("a/[.]", "a/.", TRUE, FALSE);
TEST ("[/]", "/", TRUE, FALSE);
TEST ("[^/]", "a", TRUE, TRUE);
/* Basic tests of * (and combinations of * and ?) */
TEST ("a*b", "ab", TRUE, TRUE);
TEST ("a*b", "axb", TRUE, TRUE);
TEST ("a*b", "axxb", TRUE, TRUE);
TEST ("a**b", "ab", TRUE, TRUE);
TEST ("a**b", "axb", TRUE, TRUE);
TEST ("a**b", "axxb", TRUE, TRUE);
TEST ("a*?*b", "ab", TRUE, FALSE);
TEST ("a*?*b", "axb", TRUE, TRUE);
TEST ("a*?*b", "axxb", TRUE, TRUE);
/* Test of *[range] */
TEST ("a*[cd]", "ac", TRUE, TRUE);
TEST ("a*[cd]", "axc", TRUE, TRUE);
TEST ("a*[cd]", "axx", TRUE, FALSE);
TEST ("a/[.]", "a/.", TRUE, FALSE);
TEST ("a*[.]", "a/.", TRUE, FALSE);
/* Test of UTF-8 */
TEST ("ä", "ä", TRUE, TRUE); /* TEST ("ä", "ä", TRUE); */
TEST ("?", "ä", TRUE, TRUE); /* TEST ("?", "ä", TRUE); */
TEST ("", "äö", TRUE, TRUE); /* TEST ("*ö", "äö", TRUE); */
TEST ("", "ääö", TRUE, TRUE); /* TEST ("*ö", "ääö", TRUE); */
TEST ("[ä]", "ä", TRUE, TRUE); /* TEST ("[ä]", "ä", TRUE); */
TEST ("[ä-ö]", "é", TRUE, TRUE); /* TEST ("[ä-ö]", "é", TRUE); */
TEST ("[ä-ö]", "a", TRUE, FALSE); /* TEST ("[ä-ö]", "a", FALSE); */
#ifdef DO_ESCAPE
/* Tests of escaping */
TEST ("\\\\", "\\", TRUE, TRUE);
TEST ("\\?", "?", TRUE, TRUE);
TEST ("\\?", "a", TRUE, FALSE);
TEST ("\\*", "*", TRUE, TRUE);
TEST ("\\*", "a", TRUE, FALSE);
TEST ("\\[a-b]", "[a-b]", TRUE, TRUE);
TEST ("[\\\\]", "\\", TRUE, TRUE);
TEST ("[\\^a]", "a", TRUE, TRUE);
TEST ("[a\\-c]", "b", TRUE, FALSE);
TEST ("[a\\-c]", "-", TRUE, TRUE);
TEST ("[a\\]", "a", TRUE, FALSE);
#endif /* DO_ESCAPE */
return 0;
}
#endif /* FNMATCH_TEST_CASES */

View File

@ -804,7 +804,7 @@ gtk_file_filter_match (GtkFilter *filter,
display_name = g_file_info_get_display_name (info);
if (display_name)
{
if (_gtk_fnmatch (rule->u.pattern, display_name, FALSE))
if (_gtk_fnmatch (rule->u.pattern, display_name, FALSE, FALSE))
return TRUE;
}
}

View File

@ -61,7 +61,8 @@ const char * _gtk_get_data_prefix (void);
gboolean _gtk_fnmatch (const char *pattern,
const char *string,
gboolean no_leading_period);
gboolean no_leading_period,
gboolean casefold);
char * _gtk_get_lc_ctype (void);

140
testsuite/gtk/fnmatch.c Normal file
View File

@ -0,0 +1,140 @@
#include <gtk/gtk.h>
#include "gtk/gtkprivate.h"
#if defined(G_OS_WIN32) || defined(G_WITH_CYGWIN)
#define DO_ESCAPE 0
#else
#define DO_ESCAPE 1
#endif
typedef struct {
const char *pat;
const char *str;
gboolean no_leading_period;
gboolean ci;
gboolean result;
} TestCase;
static TestCase tests[] = {
{ "[a-]", "-", TRUE, FALSE, TRUE },
{ "a", "a", TRUE, FALSE, TRUE },
{ "a", "b", TRUE, FALSE, FALSE },
/* Test what ? matches */
{ "?", "a", TRUE, FALSE, TRUE },
{ "?", ".", TRUE, FALSE, FALSE },
{ "a?", "a.", TRUE, FALSE, TRUE },
{ "a/?", "a/b", TRUE, FALSE, TRUE },
{ "a/?", "a/.", TRUE, FALSE, FALSE },
{ "?", "/", TRUE, FALSE, FALSE },
/* Test what * matches */
{ "*", "a", TRUE, FALSE, TRUE },
{ "*", ".", TRUE, FALSE, FALSE },
{ "a*", "a.", TRUE, FALSE, TRUE },
{ "a/*", "a/b", TRUE, FALSE, TRUE },
{ "a/*", "a/.", TRUE, FALSE, FALSE },
{ "*", "/", TRUE, FALSE, FALSE },
/* Range tests */
{ "[ab]", "a", TRUE, FALSE, TRUE },
{ "[ab]", "c", TRUE, FALSE, FALSE },
{ "[^ab]", "a", TRUE, FALSE, FALSE },
{ "[!ab]", "a", TRUE, FALSE, FALSE },
{ "[^ab]", "c", TRUE, FALSE, TRUE },
{ "[!ab]", "c", TRUE, FALSE, TRUE },
{ "[a-c]", "b", TRUE, FALSE, TRUE },
{ "[a-c]", "d", TRUE, FALSE, FALSE },
{ "[a-]", "-", TRUE, FALSE, TRUE },
{ "[]]", "]", TRUE, FALSE, TRUE },
{ "[^]]", "a", TRUE, FALSE, TRUE },
{ "[!]]", "a", TRUE, FALSE, TRUE },
/* Various unclosed ranges */
{ "[ab", "a", TRUE, FALSE, FALSE },
{ "[a-", "a", TRUE, FALSE, FALSE },
{ "[ab", "c", TRUE, FALSE, FALSE },
{ "[a-", "c", TRUE, FALSE, FALSE },
{ "[^]", "a", TRUE, FALSE, FALSE },
/* Ranges and special no-wildcard matches */
{ "[.]", ".", TRUE, FALSE, FALSE },
{ "a[.]", "a.", TRUE, FALSE, TRUE },
{ "a/[.]", "a/.", TRUE, FALSE, FALSE },
{ "[/]", "/", TRUE, FALSE, FALSE },
{ "[^/]", "a", TRUE, FALSE, TRUE },
/* Basic tests of * (and combinations of * and ?) */
{ "a*b", "ab", TRUE, FALSE, TRUE },
{ "a*b", "axb", TRUE, FALSE, TRUE },
{ "a*b", "axxb", TRUE, FALSE, TRUE },
{ "a**b", "ab", TRUE, FALSE, TRUE },
{ "a**b", "axb", TRUE, FALSE, TRUE },
{ "a**b", "axxb", TRUE, FALSE, TRUE },
{ "a*?*b", "ab", TRUE, FALSE, FALSE },
{ "a*?*b", "axb", TRUE, FALSE, TRUE },
{ "a*?*b", "axxb", TRUE, FALSE, TRUE },
/* Test of *[range] */
{ "a*[cd]", "ac", TRUE, FALSE, TRUE },
{ "a*[cd]", "axc", TRUE, FALSE, TRUE },
{ "a*[cd]", "axx", TRUE, FALSE, FALSE },
{ "a/[.]", "a/.", TRUE, FALSE, FALSE },
{ "a*[.]", "a/.", TRUE, FALSE, FALSE },
/* Test of UTF-8 */
{ "ä", "ä", TRUE, FALSE, TRUE },
{ "?", "ä", TRUE, FALSE, TRUE },
{ "", "äö", TRUE, FALSE, TRUE },
{ "", "ääö", TRUE, FALSE, TRUE },
{ "[ä]", "ä", TRUE, FALSE, TRUE },
{ "[ä-ö]", "é", TRUE, FALSE, TRUE },
{ "[ä-ö]", "a", TRUE, FALSE, FALSE },
/* ci patterns */
{ "*.txt", "a.TXT", TRUE, TRUE, TRUE },
{ "*.txt", "a.TxT", TRUE, TRUE, TRUE },
{ "*.txt", "a.txT", TRUE, TRUE, TRUE },
{ "", "äÖ", TRUE, TRUE, TRUE },
#ifdef DO_ESCAPE
/* Tests of escaping */
{ "\\\\", "\\", TRUE, FALSE, TRUE },
{ "\\?", "?", TRUE, FALSE, TRUE },
{ "\\?", "a", TRUE, FALSE, FALSE },
{ "\\*", "*", TRUE, FALSE, TRUE },
{ "\\*", "a", TRUE, FALSE, FALSE },
{ "\\[a-b]", "[a-b]", TRUE, FALSE, TRUE },
{ "[\\\\]", "\\", TRUE, FALSE, TRUE },
{ "[\\^a]", "a", TRUE, FALSE, TRUE },
{ "[a\\-c]", "b", TRUE, FALSE, FALSE },
{ "[a\\-c]", "-", TRUE, FALSE, TRUE },
{ "[a\\]", "a", TRUE, FALSE, FALSE },
#endif /* DO_ESCAPE */
};
static void
test_fnmatch (gconstpointer data)
{
const TestCase *test = data;
g_assert_true (_gtk_fnmatch (test->pat, test->str, test->no_leading_period, test->ci) == test->result);
}
int
main (int argc, char *argv[])
{
(g_test_init) (&argc, &argv, NULL);
for (int i = 0; i < G_N_ELEMENTS (tests); i++)
{
char *path = g_strdup_printf ("/fnmatch/test%d", i);
g_test_add_data_func (path, &tests[i], test_fnmatch);
}
return g_test_run ();
}

View File

@ -118,6 +118,7 @@ internal_tests = [
{ 'name': 'rbtree' },
{ 'name': 'timsort' },
{ 'name': 'texthistory' },
{ 'name': 'fnmatch' },
]
# Tests that are expected to fail