Add starts_with to filter expressions
This commit is contained in:
parent
512cd7b0de
commit
868a06eaf9
|
@ -196,6 +196,8 @@ of:
|
|||
- ``(TAG contains 'VALUE')`` checks if the given value is a substring
|
||||
of the tag value.
|
||||
|
||||
- ``(TAG starts_with 'VALUE')`` checks if the tag value starts with the given value.
|
||||
|
||||
- ``(TAG =~ 'VALUE')`` and ``(TAG !~ 'VALUE')`` use a Perl-compatible
|
||||
regular expression instead of doing a simple string comparison.
|
||||
(This feature is only available if :program:`MPD` was compiled with
|
||||
|
|
|
@ -112,3 +112,32 @@ IcuCompare::IsIn(const char *haystack) const noexcept
|
|||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool
|
||||
IcuCompare::StartsWith(const char *haystack) const noexcept
|
||||
{
|
||||
#ifdef HAVE_ICU_CASE_FOLD
|
||||
return StringIsEqual(IcuCaseFold(haystack).c_str(),
|
||||
needle.c_str(), strlen(needle.c_str()));
|
||||
#elif defined(_WIN32)
|
||||
if (needle == nullptr)
|
||||
/* the MultiByteToWideChar() call in the constructor
|
||||
has failed, so let's always fail the comparison */
|
||||
return false;
|
||||
|
||||
try {
|
||||
auto w_haystack = MultiByteToWideChar(CP_UTF8, haystack);
|
||||
return FindNLSStringEx(LOCALE_NAME_INVARIANT,
|
||||
FIND_STARTSWITH|NORM_IGNORECASE,
|
||||
w_haystack.c_str(), -1,
|
||||
needle.c_str(), -1,
|
||||
nullptr,
|
||||
nullptr, nullptr, 0) == 0;
|
||||
} catch (...) {
|
||||
/* MultiByteToWideChar() has failed */
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
return strncmp(haystack, needle.c_str(), strlen(needle.c_str()));
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -72,6 +72,9 @@ public:
|
|||
|
||||
[[gnu::pure]]
|
||||
bool IsIn(const char *haystack) const noexcept;
|
||||
|
||||
[[gnu::pure]]
|
||||
bool StartsWith(const char *haystack) const noexcept;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -91,7 +91,7 @@ SongFilter::SongFilter(TagType tag, const char *value, bool fold_case)
|
|||
/* for compatibility with MPD 0.20 and older, "fold_case" also
|
||||
switches on "substring" */
|
||||
and_filter.AddItem(std::make_unique<TagSongFilter>(tag,
|
||||
StringFilter(value, fold_case, fold_case, false)));
|
||||
StringFilter(value, fold_case, fold_case, false, false)));
|
||||
}
|
||||
|
||||
/* this destructor exists here just so it won't get inlined */
|
||||
|
@ -209,13 +209,25 @@ ParseStringFilter(const char *&s, bool fold_case)
|
|||
if (auto after_contains = StringAfterPrefixIgnoreCase(s, "contains ")) {
|
||||
s = StripLeft(after_contains);
|
||||
auto value = ExpectQuoted(s);
|
||||
return {std::move(value), fold_case, true, false};
|
||||
return {std::move(value), fold_case, true, false, false};
|
||||
}
|
||||
|
||||
if (auto after_not_contains = StringAfterPrefixIgnoreCase(s, "!contains ")) {
|
||||
s = StripLeft(after_not_contains);
|
||||
auto value = ExpectQuoted(s);
|
||||
return {std::move(value), fold_case, true, true};
|
||||
return {std::move(value), fold_case, true, false, true};
|
||||
}
|
||||
|
||||
if (auto after_starts_with = StringAfterPrefixIgnoreCase(s, "starts_with ")) {
|
||||
s = StripLeft(after_starts_with);
|
||||
auto value = ExpectQuoted(s);
|
||||
return {std::move(value), fold_case, false, true, false};
|
||||
}
|
||||
|
||||
if (auto after_not_starts_with = StringAfterPrefixIgnoreCase(s, "!starts_with ")) {
|
||||
s = StripLeft(after_not_starts_with);
|
||||
auto value = ExpectQuoted(s);
|
||||
return {std::move(value), fold_case, false, true, true};
|
||||
}
|
||||
|
||||
bool negated = false;
|
||||
|
@ -225,7 +237,7 @@ ParseStringFilter(const char *&s, bool fold_case)
|
|||
negated = s[0] == '!';
|
||||
s = StripLeft(s + 2);
|
||||
auto value = ExpectQuoted(s);
|
||||
StringFilter f(std::move(value), fold_case, false, negated);
|
||||
StringFilter f(std::move(value), fold_case, false, false, negated);
|
||||
f.SetRegex(std::make_shared<UniqueRegex>(f.GetValue().c_str(),
|
||||
false, false,
|
||||
fold_case));
|
||||
|
@ -241,7 +253,7 @@ ParseStringFilter(const char *&s, bool fold_case)
|
|||
s = StripLeft(s + 2);
|
||||
auto value = ExpectQuoted(s);
|
||||
|
||||
return {std::move(value), fold_case, false, negated};
|
||||
return {std::move(value), fold_case, false, false, negated};
|
||||
}
|
||||
|
||||
ISongFilterPtr
|
||||
|
@ -390,6 +402,7 @@ SongFilter::Parse(const char *tag_string, const char *value, bool fold_case)
|
|||
and_filter.AddItem(std::make_unique<UriSongFilter>(StringFilter(value,
|
||||
fold_case,
|
||||
fold_case,
|
||||
false,
|
||||
false)));
|
||||
break;
|
||||
|
||||
|
@ -403,6 +416,7 @@ SongFilter::Parse(const char *tag_string, const char *value, bool fold_case)
|
|||
StringFilter(value,
|
||||
fold_case,
|
||||
fold_case,
|
||||
false,
|
||||
false)));
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -35,11 +35,15 @@ StringFilter::MatchWithoutNegation(const char *s) const noexcept
|
|||
if (fold_case) {
|
||||
return substring
|
||||
? fold_case.IsIn(s)
|
||||
: fold_case == s;
|
||||
: (starts_with
|
||||
? fold_case.StartsWith(s)
|
||||
: fold_case == s);
|
||||
} else {
|
||||
return substring
|
||||
? StringFind(s, value.c_str()) != nullptr
|
||||
: value == s;
|
||||
: (starts_with
|
||||
? StringIsEqual(s, value.c_str(), value.length())
|
||||
: value == s);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -47,16 +47,21 @@ class StringFilter {
|
|||
*/
|
||||
bool substring;
|
||||
|
||||
/**
|
||||
* Search for substrings instead of matching the whole string?
|
||||
*/
|
||||
bool starts_with;
|
||||
|
||||
bool negated;
|
||||
|
||||
public:
|
||||
template<typename V>
|
||||
StringFilter(V &&_value, bool _fold_case, bool _substring, bool _negated)
|
||||
StringFilter(V &&_value, bool _fold_case, bool _substring, bool _starts_with, bool _negated)
|
||||
:value(std::forward<V>(_value)),
|
||||
fold_case(_fold_case
|
||||
? IcuCompare(value)
|
||||
: IcuCompare()),
|
||||
substring(_substring), negated(_negated) {}
|
||||
substring(_substring), starts_with(_starts_with), negated(_negated) {}
|
||||
|
||||
bool empty() const noexcept {
|
||||
return value.empty();
|
||||
|
@ -98,7 +103,9 @@ public:
|
|||
? (negated ? "!~" : "=~")
|
||||
: (substring
|
||||
? (negated ? "!contains" : "contains")
|
||||
: (negated ? "!=" : "=="));
|
||||
: (starts_with
|
||||
? (negated ? "!starts_with" : "starts_with")
|
||||
: (negated ? "!=" : "==")));
|
||||
}
|
||||
|
||||
[[gnu::pure]]
|
||||
|
|
|
@ -33,7 +33,7 @@ InvokeFilter(const TagSongFilter &f, const Tag &tag) noexcept
|
|||
TEST(TagSongFilter, Basic)
|
||||
{
|
||||
const TagSongFilter f(TAG_TITLE,
|
||||
StringFilter("needle", false, false, false));
|
||||
StringFilter("needle", false, false, false, false));
|
||||
|
||||
EXPECT_TRUE(InvokeFilter(f, MakeTag(TAG_TITLE, "needle")));
|
||||
EXPECT_TRUE(InvokeFilter(f, MakeTag(TAG_TITLE, "foo", TAG_TITLE, "needle")));
|
||||
|
@ -54,7 +54,7 @@ TEST(TagSongFilter, Basic)
|
|||
TEST(TagSongFilter, Empty)
|
||||
{
|
||||
const TagSongFilter f(TAG_TITLE,
|
||||
StringFilter("", false, false, false));
|
||||
StringFilter("", false, false, false, false));
|
||||
|
||||
EXPECT_TRUE(InvokeFilter(f, MakeTag()));
|
||||
|
||||
|
@ -65,7 +65,7 @@ TEST(TagSongFilter, Empty)
|
|||
TEST(TagSongFilter, Substring)
|
||||
{
|
||||
const TagSongFilter f(TAG_TITLE,
|
||||
StringFilter("needle", false, true, false));
|
||||
StringFilter("needle", false, true, false, false));
|
||||
|
||||
EXPECT_TRUE(InvokeFilter(f, MakeTag(TAG_TITLE, "needle")));
|
||||
EXPECT_TRUE(InvokeFilter(f, MakeTag(TAG_TITLE, "needleBAR")));
|
||||
|
@ -76,10 +76,24 @@ TEST(TagSongFilter, Substring)
|
|||
EXPECT_FALSE(InvokeFilter(f, MakeTag(TAG_TITLE, "eedle")));
|
||||
}
|
||||
|
||||
TEST(TagSongFilter, Startswith)
|
||||
{
|
||||
const TagSongFilter f(TAG_TITLE,
|
||||
StringFilter("needle", false, false, true, false));
|
||||
|
||||
EXPECT_TRUE(InvokeFilter(f, MakeTag(TAG_TITLE, "needle")));
|
||||
EXPECT_TRUE(InvokeFilter(f, MakeTag(TAG_TITLE, "needleBAR")));
|
||||
EXPECT_FALSE(InvokeFilter(f, MakeTag(TAG_TITLE, "FOOneedle")));
|
||||
EXPECT_FALSE(InvokeFilter(f, MakeTag(TAG_TITLE, "FOOneedleBAR")));
|
||||
|
||||
EXPECT_FALSE(InvokeFilter(f, MakeTag()));
|
||||
EXPECT_FALSE(InvokeFilter(f, MakeTag(TAG_TITLE, "eedle")));
|
||||
}
|
||||
|
||||
TEST(TagSongFilter, Negated)
|
||||
{
|
||||
const TagSongFilter f(TAG_TITLE,
|
||||
StringFilter("needle", false, false, true));
|
||||
StringFilter("needle", false, false, false, true));
|
||||
|
||||
EXPECT_TRUE(InvokeFilter(f, MakeTag()));
|
||||
EXPECT_FALSE(InvokeFilter(f, MakeTag(TAG_TITLE, "needle")));
|
||||
|
@ -92,7 +106,7 @@ TEST(TagSongFilter, Negated)
|
|||
TEST(TagSongFilter, EmptyNegated)
|
||||
{
|
||||
const TagSongFilter f(TAG_TITLE,
|
||||
StringFilter("", false, false, true));
|
||||
StringFilter("", false, false, false, true));
|
||||
|
||||
EXPECT_FALSE(InvokeFilter(f, MakeTag()));
|
||||
EXPECT_TRUE(InvokeFilter(f, MakeTag(TAG_TITLE, "foo")));
|
||||
|
@ -104,7 +118,7 @@ TEST(TagSongFilter, EmptyNegated)
|
|||
TEST(TagSongFilter, MultiNegated)
|
||||
{
|
||||
const TagSongFilter f(TAG_TITLE,
|
||||
StringFilter("needle", false, false, true));
|
||||
StringFilter("needle", false, false, false, true));
|
||||
|
||||
EXPECT_TRUE(InvokeFilter(f, MakeTag(TAG_TITLE, "foo", TAG_TITLE, "bar")));
|
||||
EXPECT_FALSE(InvokeFilter(f, MakeTag(TAG_TITLE, "needle", TAG_TITLE, "bar")));
|
||||
|
@ -118,7 +132,7 @@ TEST(TagSongFilter, MultiNegated)
|
|||
TEST(TagSongFilter, Fallback)
|
||||
{
|
||||
const TagSongFilter f(TAG_ALBUM_ARTIST,
|
||||
StringFilter("needle", false, false, false));
|
||||
StringFilter("needle", false, false, false, false));
|
||||
|
||||
EXPECT_TRUE(InvokeFilter(f, MakeTag(TAG_ALBUM_ARTIST, "needle")));
|
||||
EXPECT_TRUE(InvokeFilter(f, MakeTag(TAG_ARTIST, "needle")));
|
||||
|
@ -138,7 +152,7 @@ TEST(TagSongFilter, Fallback)
|
|||
TEST(TagSongFilter, EmptyFallback)
|
||||
{
|
||||
const TagSongFilter f(TAG_ALBUM_ARTIST,
|
||||
StringFilter("", false, false, false));
|
||||
StringFilter("", false, false, false, false));
|
||||
|
||||
EXPECT_TRUE(InvokeFilter(f, MakeTag()));
|
||||
|
||||
|
@ -152,7 +166,7 @@ TEST(TagSongFilter, EmptyFallback)
|
|||
TEST(TagSongFilter, NegatedFallback)
|
||||
{
|
||||
const TagSongFilter f(TAG_ALBUM_ARTIST,
|
||||
StringFilter("needle", false, false, true));
|
||||
StringFilter("needle", false, false, false, true));
|
||||
|
||||
EXPECT_TRUE(InvokeFilter(f, MakeTag()));
|
||||
EXPECT_TRUE(InvokeFilter(f, MakeTag(TAG_ALBUM_ARTIST, "foo")));
|
||||
|
|
Loading…
Reference in New Issue