mirror of
https://github.com/RGBCube/serenity
synced 2025-05-31 02:08:11 +00:00
AK: Move the wildcard-matching implementation to StringUtils
Provide wrappers in the String and StringView classes, and add some tests.
This commit is contained in:
parent
2a30a020c1
commit
055344f346
16 changed files with 147 additions and 62 deletions
|
@ -331,59 +331,7 @@ String String::repeated(char ch, size_t count)
|
||||||
|
|
||||||
bool String::matches(const StringView& mask, CaseSensitivity case_sensitivity) const
|
bool String::matches(const StringView& mask, CaseSensitivity case_sensitivity) const
|
||||||
{
|
{
|
||||||
if (case_sensitivity == CaseSensitivity::CaseInsensitive) {
|
return StringUtils::matches(*this, mask, case_sensitivity);
|
||||||
String this_lower = this->to_lowercase();
|
|
||||||
String mask_lower = String(mask).to_lowercase();
|
|
||||||
return this_lower.match_helper(mask_lower);
|
|
||||||
}
|
|
||||||
|
|
||||||
return match_helper(mask);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool String::match_helper(const StringView& mask) const
|
|
||||||
{
|
|
||||||
if (is_null())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
const char* string_ptr = characters();
|
|
||||||
const char* mask_ptr = mask.characters_without_null_termination();
|
|
||||||
const char* mask_end = mask_ptr + mask.length();
|
|
||||||
|
|
||||||
// Match string against mask directly unless we hit a *
|
|
||||||
while ((*string_ptr) && (mask_ptr < mask_end) && (*mask_ptr != '*')) {
|
|
||||||
if ((*mask_ptr != *string_ptr) && (*mask_ptr != '?'))
|
|
||||||
return false;
|
|
||||||
mask_ptr++;
|
|
||||||
string_ptr++;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* cp = nullptr;
|
|
||||||
const char* mp = nullptr;
|
|
||||||
|
|
||||||
while (*string_ptr) {
|
|
||||||
if ((mask_ptr < mask_end) && (*mask_ptr == '*')) {
|
|
||||||
// If we have only a * left, there is no way to not match.
|
|
||||||
if (++mask_ptr == mask_end)
|
|
||||||
return true;
|
|
||||||
mp = mask_ptr;
|
|
||||||
cp = string_ptr + 1;
|
|
||||||
} else if ((mask_ptr < mask_end) && ((*mask_ptr == *string_ptr) || (*mask_ptr == '?'))) {
|
|
||||||
mask_ptr++;
|
|
||||||
string_ptr++;
|
|
||||||
} else if ((cp != nullptr) && (mp != nullptr)) {
|
|
||||||
mask_ptr = mp;
|
|
||||||
string_ptr = cp++;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle any trailing mask
|
|
||||||
while ((mask_ptr < mask_end) && (*mask_ptr == '*'))
|
|
||||||
mask_ptr++;
|
|
||||||
|
|
||||||
// If we 'ate' all of the mask and the string then we match.
|
|
||||||
return (mask_ptr == mask_end) && !*string_ptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool String::contains(const String& needle) const
|
bool String::contains(const String& needle) const
|
||||||
|
|
|
@ -29,6 +29,7 @@
|
||||||
#include <AK/Forward.h>
|
#include <AK/Forward.h>
|
||||||
#include <AK/RefPtr.h>
|
#include <AK/RefPtr.h>
|
||||||
#include <AK/StringImpl.h>
|
#include <AK/StringImpl.h>
|
||||||
|
#include <AK/StringUtils.h>
|
||||||
#include <AK/StringView.h>
|
#include <AK/StringView.h>
|
||||||
#include <AK/Traits.h>
|
#include <AK/Traits.h>
|
||||||
|
|
||||||
|
@ -108,13 +109,8 @@ public:
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class CaseSensitivity {
|
|
||||||
CaseInsensitive,
|
|
||||||
CaseSensitive,
|
|
||||||
};
|
|
||||||
|
|
||||||
static String repeated(char, size_t count);
|
static String repeated(char, size_t count);
|
||||||
bool matches(const StringView& pattern, CaseSensitivity = CaseSensitivity::CaseInsensitive) const;
|
bool matches(const StringView& mask, CaseSensitivity = CaseSensitivity::CaseInsensitive) const;
|
||||||
|
|
||||||
// FIXME: These should be shared between String and StringView somehow!
|
// FIXME: These should be shared between String and StringView somehow!
|
||||||
int to_int(bool& ok) const;
|
int to_int(bool& ok) const;
|
||||||
|
@ -244,7 +240,6 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool match_helper(const StringView& mask) const;
|
|
||||||
RefPtr<StringImpl> m_impl;
|
RefPtr<StringImpl> m_impl;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
64
AK/StringUtils.cpp
Normal file
64
AK/StringUtils.cpp
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
#include <AK/String.h>
|
||||||
|
#include <AK/StringUtils.h>
|
||||||
|
#include <AK/StringView.h>
|
||||||
|
|
||||||
|
namespace AK {
|
||||||
|
|
||||||
|
namespace StringUtils {
|
||||||
|
|
||||||
|
bool matches(const StringView& str, const StringView& mask, CaseSensitivity case_sensitivity)
|
||||||
|
{
|
||||||
|
if (str.is_null() || mask.is_null())
|
||||||
|
return str.is_null() && mask.is_null();
|
||||||
|
|
||||||
|
if (case_sensitivity == CaseSensitivity::CaseInsensitive) {
|
||||||
|
const String str_lower = String(str).to_lowercase();
|
||||||
|
const String mask_lower = String(mask).to_lowercase();
|
||||||
|
return matches(str_lower, mask_lower, CaseSensitivity::CaseSensitive);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* string_ptr = str.characters_without_null_termination();
|
||||||
|
const char* string_end = string_ptr + str.length();
|
||||||
|
const char* mask_ptr = mask.characters_without_null_termination();
|
||||||
|
const char* mask_end = mask_ptr + mask.length();
|
||||||
|
|
||||||
|
// Match string against mask directly unless we hit a *
|
||||||
|
while ((string_ptr < string_end) && (mask_ptr < mask_end) && (*mask_ptr != '*')) {
|
||||||
|
if ((*mask_ptr != *string_ptr) && (*mask_ptr != '?'))
|
||||||
|
return false;
|
||||||
|
mask_ptr++;
|
||||||
|
string_ptr++;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* cp = nullptr;
|
||||||
|
const char* mp = nullptr;
|
||||||
|
|
||||||
|
while (string_ptr < string_end) {
|
||||||
|
if ((mask_ptr < mask_end) && (*mask_ptr == '*')) {
|
||||||
|
// If we have only a * left, there is no way to not match.
|
||||||
|
if (++mask_ptr == mask_end)
|
||||||
|
return true;
|
||||||
|
mp = mask_ptr;
|
||||||
|
cp = string_ptr + 1;
|
||||||
|
} else if ((mask_ptr < mask_end) && ((*mask_ptr == *string_ptr) || (*mask_ptr == '?'))) {
|
||||||
|
mask_ptr++;
|
||||||
|
string_ptr++;
|
||||||
|
} else if ((cp != nullptr) && (mp != nullptr)) {
|
||||||
|
mask_ptr = mp;
|
||||||
|
string_ptr = cp++;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle any trailing mask
|
||||||
|
while ((mask_ptr < mask_end) && (*mask_ptr == '*'))
|
||||||
|
mask_ptr++;
|
||||||
|
|
||||||
|
// If we 'ate' all of the mask and the string then we match.
|
||||||
|
return (mask_ptr == mask_end) && string_ptr == string_end;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
20
AK/StringUtils.h
Normal file
20
AK/StringUtils.h
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <AK/Forward.h>
|
||||||
|
|
||||||
|
namespace AK {
|
||||||
|
|
||||||
|
enum class CaseSensitivity {
|
||||||
|
CaseInsensitive,
|
||||||
|
CaseSensitive,
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace StringUtils {
|
||||||
|
|
||||||
|
bool matches(const StringView& str, const StringView& mask, CaseSensitivity = CaseSensitivity::CaseInsensitive);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
using AK::CaseSensitivity;
|
|
@ -143,6 +143,11 @@ bool StringView::ends_with(const StringView& str) const
|
||||||
return !memcmp(characters_without_null_termination() + length() - str.length(), str.characters_without_null_termination(), str.length());
|
return !memcmp(characters_without_null_termination() + length() - str.length(), str.characters_without_null_termination(), str.length());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool StringView::matches(const StringView& mask, CaseSensitivity case_sensitivity) const
|
||||||
|
{
|
||||||
|
return StringUtils::matches(*this, mask, case_sensitivity);
|
||||||
|
}
|
||||||
|
|
||||||
StringView StringView::substring_view(size_t start, size_t length) const
|
StringView StringView::substring_view(size_t start, size_t length) const
|
||||||
{
|
{
|
||||||
ASSERT(start + length <= m_length);
|
ASSERT(start + length <= m_length);
|
||||||
|
|
|
@ -28,6 +28,7 @@
|
||||||
|
|
||||||
#include <AK/Forward.h>
|
#include <AK/Forward.h>
|
||||||
#include <AK/StdLibExtras.h>
|
#include <AK/StdLibExtras.h>
|
||||||
|
#include <AK/StringUtils.h>
|
||||||
|
|
||||||
namespace AK {
|
namespace AK {
|
||||||
|
|
||||||
|
@ -65,6 +66,7 @@ public:
|
||||||
bool ends_with(const StringView&) const;
|
bool ends_with(const StringView&) const;
|
||||||
bool starts_with(char) const;
|
bool starts_with(char) const;
|
||||||
bool ends_with(char) const;
|
bool ends_with(char) const;
|
||||||
|
bool matches(const StringView& mask, CaseSensitivity = CaseSensitivity::CaseInsensitive) const;
|
||||||
|
|
||||||
StringView substring_view(size_t start, size_t length) const;
|
StringView substring_view(size_t start, size_t length) const;
|
||||||
Vector<StringView> split_view(char, bool keep_empty = false) const;
|
Vector<StringView> split_view(char, bool keep_empty = false) const;
|
||||||
|
|
|
@ -182,7 +182,7 @@ NonnullRefPtrVector<TestCase> TestSuite::find_cases(const String& search, bool f
|
||||||
{
|
{
|
||||||
NonnullRefPtrVector<TestCase> matches;
|
NonnullRefPtrVector<TestCase> matches;
|
||||||
for (const auto& t : m_cases) {
|
for (const auto& t : m_cases) {
|
||||||
if (!search.is_empty() && !t.name().matches(search, String::CaseSensitivity::CaseInsensitive)) {
|
if (!search.is_empty() && !t.name().matches(search, CaseSensitivity::CaseInsensitive)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ SHARED_TEST_SOURCES = \
|
||||||
../StringImpl.cpp \
|
../StringImpl.cpp \
|
||||||
../StringBuilder.cpp \
|
../StringBuilder.cpp \
|
||||||
../StringView.cpp \
|
../StringView.cpp \
|
||||||
|
../StringUtils.cpp \
|
||||||
../LogStream.cpp \
|
../LogStream.cpp \
|
||||||
../JsonValue.cpp \
|
../JsonValue.cpp \
|
||||||
../JsonParser.cpp \
|
../JsonParser.cpp \
|
||||||
|
|
44
AK/Tests/TestStringUtils.cpp
Normal file
44
AK/Tests/TestStringUtils.cpp
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
#include <AK/StringUtils.h>
|
||||||
|
#include <AK/TestSuite.h>
|
||||||
|
|
||||||
|
TEST_CASE(matches_null)
|
||||||
|
{
|
||||||
|
EXPECT(AK::StringUtils::matches(StringView(), StringView()));
|
||||||
|
|
||||||
|
EXPECT(!AK::StringUtils::matches(StringView(), ""));
|
||||||
|
EXPECT(!AK::StringUtils::matches(StringView(), "*"));
|
||||||
|
EXPECT(!AK::StringUtils::matches(StringView(), "?"));
|
||||||
|
EXPECT(!AK::StringUtils::matches(StringView(), "a"));
|
||||||
|
|
||||||
|
EXPECT(!AK::StringUtils::matches("", StringView()));
|
||||||
|
EXPECT(!AK::StringUtils::matches("a", StringView()));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE(matches_empty)
|
||||||
|
{
|
||||||
|
EXPECT(AK::StringUtils::matches("", ""));
|
||||||
|
|
||||||
|
EXPECT(AK::StringUtils::matches("", "*"));
|
||||||
|
EXPECT(!AK::StringUtils::matches("", "?"));
|
||||||
|
EXPECT(!AK::StringUtils::matches("", "a"));
|
||||||
|
|
||||||
|
EXPECT(!AK::StringUtils::matches("a", ""));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE(matches_case_sensitive)
|
||||||
|
{
|
||||||
|
EXPECT(AK::StringUtils::matches("a", "a", CaseSensitivity::CaseSensitive));
|
||||||
|
EXPECT(!AK::StringUtils::matches("a", "A", CaseSensitivity::CaseSensitive));
|
||||||
|
EXPECT(!AK::StringUtils::matches("A", "a", CaseSensitivity::CaseSensitive));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE(matches_case_insensitive)
|
||||||
|
{
|
||||||
|
EXPECT(!AK::StringUtils::matches("aa", "a"));
|
||||||
|
EXPECT(AK::StringUtils::matches("aa", "*"));
|
||||||
|
EXPECT(!AK::StringUtils::matches("cb", "?a"));
|
||||||
|
EXPECT(AK::StringUtils::matches("adceb", "a*b"));
|
||||||
|
EXPECT(!AK::StringUtils::matches("acdcb", "a*c?b"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_MAIN(StringUtils)
|
|
@ -8,6 +8,7 @@ OBJS = \
|
||||||
../../AK/StringImpl.o \
|
../../AK/StringImpl.o \
|
||||||
../../AK/StringBuilder.o \
|
../../AK/StringBuilder.o \
|
||||||
../../AK/StringView.o \
|
../../AK/StringView.o \
|
||||||
|
../../AK/StringUtils.o \
|
||||||
../../AK/JsonValue.o \
|
../../AK/JsonValue.o \
|
||||||
../../AK/JsonParser.o \
|
../../AK/JsonParser.o \
|
||||||
../../AK/LogStream.o \
|
../../AK/LogStream.o \
|
||||||
|
|
|
@ -8,6 +8,7 @@ OBJS = \
|
||||||
../../AK/StringImpl.o \
|
../../AK/StringImpl.o \
|
||||||
../../AK/StringBuilder.o \
|
../../AK/StringBuilder.o \
|
||||||
../../AK/StringView.o \
|
../../AK/StringView.o \
|
||||||
|
../../AK/StringUtils.o \
|
||||||
../../AK/JsonValue.o \
|
../../AK/JsonValue.o \
|
||||||
../../AK/JsonParser.o \
|
../../AK/JsonParser.o \
|
||||||
../../AK/LogStream.o \
|
../../AK/LogStream.o \
|
||||||
|
|
|
@ -7,6 +7,7 @@ OBJS = \
|
||||||
../AK/StringBuilder.o \
|
../AK/StringBuilder.o \
|
||||||
../AK/StringImpl.o \
|
../AK/StringImpl.o \
|
||||||
../AK/StringView.o \
|
../AK/StringView.o \
|
||||||
|
../AK/StringUtils.o \
|
||||||
../Libraries/LibELF/ELFImage.o \
|
../Libraries/LibELF/ELFImage.o \
|
||||||
../Libraries/LibELF/ELFLoader.o \
|
../Libraries/LibELF/ELFLoader.o \
|
||||||
../Libraries/LibBareMetal/Output/Console.o \
|
../Libraries/LibBareMetal/Output/Console.o \
|
||||||
|
|
|
@ -3,6 +3,7 @@ AK_OBJS = \
|
||||||
../../AK/String.o \
|
../../AK/String.o \
|
||||||
../../AK/StringView.o \
|
../../AK/StringView.o \
|
||||||
../../AK/StringBuilder.o \
|
../../AK/StringBuilder.o \
|
||||||
|
../../AK/StringUtils.o \
|
||||||
../../AK/FileSystemPath.o \
|
../../AK/FileSystemPath.o \
|
||||||
../../AK/URL.o \
|
../../AK/URL.o \
|
||||||
../../AK/JsonValue.o \
|
../../AK/JsonValue.o \
|
||||||
|
|
|
@ -8,6 +8,7 @@ OBJS = \
|
||||||
../../../../AK/StringImpl.o \
|
../../../../AK/StringImpl.o \
|
||||||
../../../../AK/StringBuilder.o \
|
../../../../AK/StringBuilder.o \
|
||||||
../../../../AK/StringView.o \
|
../../../../AK/StringView.o \
|
||||||
|
../../../../AK/StringUtils.o \
|
||||||
../../../../AK/JsonValue.o \
|
../../../../AK/JsonValue.o \
|
||||||
../../../../AK/JsonParser.o \
|
../../../../AK/JsonParser.o \
|
||||||
../../../../AK/LogStream.o \
|
../../../../AK/LogStream.o \
|
||||||
|
|
|
@ -4,6 +4,7 @@ PROGRAM = Generate_CSS_PropertyID_h
|
||||||
|
|
||||||
OBJS = \
|
OBJS = \
|
||||||
Generate_CSS_PropertyID_h.o \
|
Generate_CSS_PropertyID_h.o \
|
||||||
|
../../../../AK/StringUtils.o \
|
||||||
../../../../AK/String.o \
|
../../../../AK/String.o \
|
||||||
../../../../AK/StringImpl.o \
|
../../../../AK/StringImpl.o \
|
||||||
../../../../AK/StringBuilder.o \
|
../../../../AK/StringBuilder.o \
|
||||||
|
|
|
@ -630,7 +630,7 @@ static Vector<String> expand_globs(const StringView& path, const StringView& bas
|
||||||
if (name[0] == '.' && part[0] != '.')
|
if (name[0] == '.' && part[0] != '.')
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (name.matches(part, String::CaseSensitivity::CaseSensitive)) {
|
if (name.matches(part, CaseSensitivity::CaseSensitive)) {
|
||||||
|
|
||||||
StringBuilder nested_base;
|
StringBuilder nested_base;
|
||||||
nested_base.append(new_base);
|
nested_base.append(new_base);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue