diff --git a/doc/protocol.rst b/doc/protocol.rst index f0b477bd1..e2425e250 100644 --- a/doc/protocol.rst +++ b/doc/protocol.rst @@ -243,6 +243,43 @@ applies `Unicode normalization `__ and converts all punctuation to ASCII equivalents if MPD was compiled with `ICU `__ support. +Explicit case-sensitivity [#since_0_24]_ +---------------------------------------- + +.. note:: The following variants of filter operators override the default case sensitivity + that is command dependant with explicit case sensitivity. + +.. list-table:: Explicitly case-sensitive operators + :widths: 33 33 33 + + * - Explicitly case-sensitive + - Explicitly case-insensitive + - Equivalent command dependant + + * - ``eq_cs`` + - ``eq_ci`` + - ``==`` + + * - ``!eq_cs`` + - ``!eq_ci`` + - ``!=`` + + * - ``contains_cs`` + - ``contains_ci`` + - ``contains`` + + * - ``!contains_cs`` + - ``!contains_ci`` + - ``!contains`` + + * - ``starts_with_cs`` + - ``starts_with_ci`` + - ``starts_with`` + + * - ``!starts_with_cs`` + - ``!starts_with_ci`` + - ``!starts_with`` + Prior to MPD 0.21, the syntax looked like this:: find TYPE VALUE diff --git a/src/song/Filter.cxx b/src/song/Filter.cxx index 047389da9..8f7dbb2c6 100644 --- a/src/song/Filter.cxx +++ b/src/song/Filter.cxx @@ -190,6 +190,39 @@ ExpectQuoted(const char *&s) return {buffer, length}; } +/** + * Operator definition used to parse the operator + * from the command and create the StringFilter + * if it matched the operator prefix. + */ +struct OperatorDef { + const char *prefix; + bool fold_case; + bool negated; + StringFilter::Position position; +}; + +/** + * Pre-defined operators with explicit case-sensitivity. + */ +static constexpr std::array operators = { + // operator prefix fold case negated position + OperatorDef { "contains_cs ", false, false, StringFilter::Position::ANYWHERE }, + OperatorDef { "!contains_cs ", false, true, StringFilter::Position::ANYWHERE }, + OperatorDef { "contains_ci ", true, false, StringFilter::Position::ANYWHERE }, + OperatorDef { "!contains_ci ", true, true, StringFilter::Position::ANYWHERE }, + + OperatorDef { "starts_with_cs ", false, false, StringFilter::Position::PREFIX }, + OperatorDef { "!starts_with_cs ", false, true, StringFilter::Position::PREFIX }, + OperatorDef { "starts_with_ci ", true, false, StringFilter::Position::PREFIX }, + OperatorDef { "!starts_with_ci ", true, true, StringFilter::Position::PREFIX }, + + OperatorDef { "eq_cs ", false, false, StringFilter::Position::FULL }, + OperatorDef { "!eq_cs ", false, true, StringFilter::Position::FULL }, + OperatorDef { "eq_ci ", true, false, StringFilter::Position::FULL }, + OperatorDef { "!eq_ci ", true, true, StringFilter::Position::FULL }, +}; + /** * Parse a string operator and its second operand and convert it to a * #StringFilter. @@ -199,6 +232,17 @@ ExpectQuoted(const char *&s) static StringFilter ParseStringFilter(const char *&s, bool fold_case) { + for (auto& op: operators) { + if (auto after_prefix = StringAfterPrefixIgnoreCase(s, op.prefix)) { + s = StripLeft(after_prefix); + return StringFilter( + ExpectQuoted(s), + op.fold_case, + op.position, + op.negated); + } + } + if (auto after_contains = StringAfterPrefixIgnoreCase(s, "contains ")) { s = StripLeft(after_contains); auto value = ExpectQuoted(s);