mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 18:22:45 +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 | ||||
| { | ||||
|     if (case_sensitivity == CaseSensitivity::CaseInsensitive) { | ||||
|         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; | ||||
|     return StringUtils::matches(*this, mask, case_sensitivity); | ||||
| } | ||||
| 
 | ||||
| bool String::contains(const String& needle) const | ||||
|  |  | |||
|  | @ -29,6 +29,7 @@ | |||
| #include <AK/Forward.h> | ||||
| #include <AK/RefPtr.h> | ||||
| #include <AK/StringImpl.h> | ||||
| #include <AK/StringUtils.h> | ||||
| #include <AK/StringView.h> | ||||
| #include <AK/Traits.h> | ||||
| 
 | ||||
|  | @ -108,13 +109,8 @@ public: | |||
|     { | ||||
|     } | ||||
| 
 | ||||
|     enum class CaseSensitivity { | ||||
|         CaseInsensitive, | ||||
|         CaseSensitive, | ||||
|     }; | ||||
| 
 | ||||
|     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!
 | ||||
|     int to_int(bool& ok) const; | ||||
|  | @ -244,7 +240,6 @@ public: | |||
|     } | ||||
| 
 | ||||
| private: | ||||
|     bool match_helper(const StringView& mask) const; | ||||
|     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()); | ||||
| } | ||||
| 
 | ||||
| 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 | ||||
| { | ||||
|     ASSERT(start + length <= m_length); | ||||
|  |  | |||
|  | @ -28,6 +28,7 @@ | |||
| 
 | ||||
| #include <AK/Forward.h> | ||||
| #include <AK/StdLibExtras.h> | ||||
| #include <AK/StringUtils.h> | ||||
| 
 | ||||
| namespace AK { | ||||
| 
 | ||||
|  | @ -65,6 +66,7 @@ public: | |||
|     bool ends_with(const StringView&) const; | ||||
|     bool starts_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; | ||||
|     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; | ||||
|     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; | ||||
|         } | ||||
| 
 | ||||
|  |  | |||
|  | @ -3,6 +3,7 @@ SHARED_TEST_SOURCES = \ | |||
| 	../StringImpl.cpp \
 | ||||
| 	../StringBuilder.cpp \
 | ||||
| 	../StringView.cpp \
 | ||||
| 	../StringUtils.cpp \
 | ||||
| 	../LogStream.cpp \
 | ||||
| 	../JsonValue.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/StringBuilder.o \
 | ||||
|     ../../AK/StringView.o \
 | ||||
|     ../../AK/StringUtils.o \
 | ||||
|     ../../AK/JsonValue.o \
 | ||||
|     ../../AK/JsonParser.o \
 | ||||
|     ../../AK/LogStream.o \
 | ||||
|  |  | |||
|  | @ -8,6 +8,7 @@ OBJS = \ | |||
|     ../../AK/StringImpl.o \
 | ||||
|     ../../AK/StringBuilder.o \
 | ||||
|     ../../AK/StringView.o \
 | ||||
|     ../../AK/StringUtils.o \
 | ||||
|     ../../AK/JsonValue.o \
 | ||||
|     ../../AK/JsonParser.o \
 | ||||
|     ../../AK/LogStream.o \
 | ||||
|  |  | |||
|  | @ -7,6 +7,7 @@ OBJS = \ | |||
|     ../AK/StringBuilder.o \
 | ||||
|     ../AK/StringImpl.o \
 | ||||
|     ../AK/StringView.o \
 | ||||
|     ../AK/StringUtils.o \
 | ||||
|     ../Libraries/LibELF/ELFImage.o \
 | ||||
|     ../Libraries/LibELF/ELFLoader.o \
 | ||||
|     ../Libraries/LibBareMetal/Output/Console.o \
 | ||||
|  |  | |||
|  | @ -3,6 +3,7 @@ AK_OBJS = \ | |||
|     ../../AK/String.o \
 | ||||
|     ../../AK/StringView.o \
 | ||||
|     ../../AK/StringBuilder.o \
 | ||||
|     ../../AK/StringUtils.o \
 | ||||
|     ../../AK/FileSystemPath.o \
 | ||||
|     ../../AK/URL.o \
 | ||||
|     ../../AK/JsonValue.o \
 | ||||
|  |  | |||
|  | @ -8,6 +8,7 @@ OBJS = \ | |||
|     ../../../../AK/StringImpl.o \
 | ||||
|     ../../../../AK/StringBuilder.o \
 | ||||
|     ../../../../AK/StringView.o \
 | ||||
|     ../../../../AK/StringUtils.o \
 | ||||
|     ../../../../AK/JsonValue.o \
 | ||||
|     ../../../../AK/JsonParser.o \
 | ||||
|     ../../../../AK/LogStream.o \
 | ||||
|  |  | |||
|  | @ -4,6 +4,7 @@ PROGRAM = Generate_CSS_PropertyID_h | |||
| 
 | ||||
| OBJS = \
 | ||||
|     Generate_CSS_PropertyID_h.o \
 | ||||
|     ../../../../AK/StringUtils.o \
 | ||||
|     ../../../../AK/String.o \
 | ||||
|     ../../../../AK/StringImpl.o \
 | ||||
|     ../../../../AK/StringBuilder.o \
 | ||||
|  |  | |||
|  | @ -630,7 +630,7 @@ static Vector<String> expand_globs(const StringView& path, const StringView& bas | |||
|             if (name[0] == '.' && part[0] != '.') | ||||
|                 continue; | ||||
| 
 | ||||
|             if (name.matches(part, String::CaseSensitivity::CaseSensitive)) { | ||||
|             if (name.matches(part, CaseSensitivity::CaseSensitive)) { | ||||
| 
 | ||||
|                 StringBuilder nested_base; | ||||
|                 nested_base.append(new_base); | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 howar6hill
						howar6hill