song/StringFilter: support regular expressions with "=~" and "!~"
This feature requires `libpcre`.
This commit is contained in:
parent
fee75dc766
commit
72184dccfc
2
NEWS
2
NEWS
|
@ -1,4 +1,6 @@
|
||||||
ver 0.21.2 (not yet released)
|
ver 0.21.2 (not yet released)
|
||||||
|
* protocol
|
||||||
|
- operator "=~" matches a regular expression
|
||||||
* decoder
|
* decoder
|
||||||
- ffmpeg: require FFmpeg 3.1 or later
|
- ffmpeg: require FFmpeg 3.1 or later
|
||||||
- ffmpeg: fix broken sound with certain codecs
|
- ffmpeg: fix broken sound with certain codecs
|
||||||
|
|
|
@ -157,6 +157,11 @@ of:
|
||||||
and are case-sensitive; the `search`
|
and are case-sensitive; the `search`
|
||||||
commands specify a sub string and ignore case.
|
commands specify a sub string and ignore case.
|
||||||
|
|
||||||
|
- ``(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
|
||||||
|
:file:`libpcre`)
|
||||||
|
|
||||||
- ``(file == 'VALUE')``: match the full song URI
|
- ``(file == 'VALUE')``: match the full song URI
|
||||||
(relative to the music directory).
|
(relative to the music directory).
|
||||||
|
|
||||||
|
|
|
@ -67,6 +67,7 @@ For example, the following installs a fairly complete list of build dependencies
|
||||||
.. code-block:: none
|
.. code-block:: none
|
||||||
|
|
||||||
apt install g++ \
|
apt install g++ \
|
||||||
|
libpcre3-dev \
|
||||||
libmad0-dev libmpg123-dev libid3tag0-dev \
|
libmad0-dev libmpg123-dev libid3tag0-dev \
|
||||||
libflac-dev libvorbis-dev libopus-dev \
|
libflac-dev libvorbis-dev libopus-dev \
|
||||||
libadplug-dev libaudiofile-dev libsndfile1-dev libfaad-dev \
|
libadplug-dev libaudiofile-dev libsndfile1-dev libfaad-dev \
|
||||||
|
|
|
@ -315,6 +315,7 @@ subdir('src/lib/gcrypt')
|
||||||
subdir('src/lib/wrap')
|
subdir('src/lib/wrap')
|
||||||
subdir('src/lib/nfs')
|
subdir('src/lib/nfs')
|
||||||
subdir('src/lib/oss')
|
subdir('src/lib/oss')
|
||||||
|
subdir('src/lib/pcre')
|
||||||
subdir('src/lib/pulse')
|
subdir('src/lib/pulse')
|
||||||
subdir('src/lib/sndio')
|
subdir('src/lib/sndio')
|
||||||
subdir('src/lib/sqlite')
|
subdir('src/lib/sqlite')
|
||||||
|
|
|
@ -176,6 +176,7 @@ option('expat', type: 'feature', description: 'Expat XML support')
|
||||||
option('icu', type: 'feature', description: 'Use libicu for Unicode')
|
option('icu', type: 'feature', description: 'Use libicu for Unicode')
|
||||||
option('iconv', type: 'feature', description: 'Use iconv() for character set conversion')
|
option('iconv', type: 'feature', description: 'Use iconv() for character set conversion')
|
||||||
option('libwrap', type: 'feature', description: 'libwrap support')
|
option('libwrap', type: 'feature', description: 'libwrap support')
|
||||||
|
option('pcre', type: 'feature', description: 'Enable regular expression support (using libpcre)')
|
||||||
option('sqlite', type: 'feature', description: 'SQLite database support (for stickers)')
|
option('sqlite', type: 'feature', description: 'SQLite database support (for stickers)')
|
||||||
option('yajl', type: 'feature', description: 'libyajl for YAML support')
|
option('yajl', type: 'feature', description: 'libyajl for YAML support')
|
||||||
option('zlib', type: 'feature', description: 'zlib support (for database compression)')
|
option('zlib', type: 'feature', description: 'zlib support (for database compression)')
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2007-2018 Content Management AG
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* author: Max Kellermann <mk@cm4all.com>
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions
|
||||||
|
* are met:
|
||||||
|
*
|
||||||
|
* - Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* - Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the
|
||||||
|
* distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||||
|
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||||
|
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef REGEX_POINTER_HXX
|
||||||
|
#define REGEX_POINTER_HXX
|
||||||
|
|
||||||
|
#include "util/StringView.hxx"
|
||||||
|
#include "util/Compiler.h"
|
||||||
|
|
||||||
|
#include <pcre.h>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
class RegexPointer {
|
||||||
|
protected:
|
||||||
|
pcre *re = nullptr;
|
||||||
|
pcre_extra *extra = nullptr;
|
||||||
|
|
||||||
|
unsigned n_capture = 0;
|
||||||
|
|
||||||
|
public:
|
||||||
|
constexpr bool IsDefined() const noexcept {
|
||||||
|
return re != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
gcc_pure
|
||||||
|
bool Match(StringView s) const noexcept {
|
||||||
|
/* we don't need the data written to ovector, but PCRE can
|
||||||
|
omit internal allocations if we pass a buffer to
|
||||||
|
pcre_exec() */
|
||||||
|
std::array<int, 16> ovector;
|
||||||
|
return pcre_exec(re, extra, s.data, s.size,
|
||||||
|
0, 0, &ovector.front(), ovector.size()) >= 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,71 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2007-2018 Content Management AG
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* author: Max Kellermann <mk@cm4all.com>
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions
|
||||||
|
* are met:
|
||||||
|
*
|
||||||
|
* - Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* - Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the
|
||||||
|
* distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||||
|
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||||
|
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "UniqueRegex.hxx"
|
||||||
|
#include "util/RuntimeError.hxx"
|
||||||
|
|
||||||
|
void
|
||||||
|
UniqueRegex::Compile(const char *pattern, bool anchored, bool capture,
|
||||||
|
bool caseless)
|
||||||
|
{
|
||||||
|
constexpr int default_options = PCRE_DOTALL|PCRE_NO_AUTO_CAPTURE|PCRE_UTF8;
|
||||||
|
|
||||||
|
int options = default_options;
|
||||||
|
if (anchored)
|
||||||
|
options |= PCRE_ANCHORED;
|
||||||
|
if (capture)
|
||||||
|
options &= ~PCRE_NO_AUTO_CAPTURE;
|
||||||
|
if (caseless)
|
||||||
|
options |= PCRE_CASELESS;
|
||||||
|
|
||||||
|
const char *error_string;
|
||||||
|
int error_offset;
|
||||||
|
re = pcre_compile(pattern, options, &error_string, &error_offset, nullptr);
|
||||||
|
if (re == nullptr)
|
||||||
|
throw FormatRuntimeError("Error in regex at offset %d: %s",
|
||||||
|
error_offset, error_string);
|
||||||
|
|
||||||
|
int study_options = 0;
|
||||||
|
#ifdef PCRE_CONFIG_JIT
|
||||||
|
study_options |= PCRE_STUDY_JIT_COMPILE;
|
||||||
|
#endif
|
||||||
|
extra = pcre_study(re, study_options, &error_string);
|
||||||
|
if (extra == nullptr && error_string != nullptr) {
|
||||||
|
pcre_free(re);
|
||||||
|
re = nullptr;
|
||||||
|
throw FormatRuntimeError("Regex study error: %s", error_string);
|
||||||
|
}
|
||||||
|
|
||||||
|
int n;
|
||||||
|
if (capture && pcre_fullinfo(re, extra, PCRE_INFO_CAPTURECOUNT, &n) == 0)
|
||||||
|
n_capture = n;
|
||||||
|
}
|
|
@ -0,0 +1,79 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2007-2018 Content Management AG
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* author: Max Kellermann <mk@cm4all.com>
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions
|
||||||
|
* are met:
|
||||||
|
*
|
||||||
|
* - Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* - Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the
|
||||||
|
* distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||||
|
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||||
|
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef UNIQUE_REGEX_HXX
|
||||||
|
#define UNIQUE_REGEX_HXX
|
||||||
|
|
||||||
|
#include "RegexPointer.hxx"
|
||||||
|
#include "util/StringView.hxx"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include <pcre.h>
|
||||||
|
|
||||||
|
class UniqueRegex : public RegexPointer {
|
||||||
|
public:
|
||||||
|
UniqueRegex() = default;
|
||||||
|
|
||||||
|
UniqueRegex(const char *pattern, bool anchored, bool capture,
|
||||||
|
bool caseless) {
|
||||||
|
Compile(pattern, anchored, capture, caseless);
|
||||||
|
}
|
||||||
|
|
||||||
|
UniqueRegex(UniqueRegex &&src) noexcept:RegexPointer(src) {
|
||||||
|
src.re = nullptr;
|
||||||
|
src.extra = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
~UniqueRegex() noexcept {
|
||||||
|
pcre_free(re);
|
||||||
|
#ifdef PCRE_CONFIG_JIT
|
||||||
|
pcre_free_study(extra);
|
||||||
|
#else
|
||||||
|
pcre_free(extra);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
UniqueRegex &operator=(UniqueRegex &&src) {
|
||||||
|
using std::swap;
|
||||||
|
swap<RegexPointer>(*this, src);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Throws std::runtime_error on error.
|
||||||
|
*/
|
||||||
|
void Compile(const char *pattern, bool anchored, bool capture,
|
||||||
|
bool caseless);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,21 @@
|
||||||
|
pcre_dep = dependency('libpcre', required: get_option('pcre'))
|
||||||
|
conf.set('HAVE_PCRE', pcre_dep.found())
|
||||||
|
if not pcre_dep.found()
|
||||||
|
subdir_done()
|
||||||
|
endif
|
||||||
|
|
||||||
|
pcre = static_library(
|
||||||
|
'pcre',
|
||||||
|
'UniqueRegex.cxx',
|
||||||
|
include_directories: inc,
|
||||||
|
dependencies: [
|
||||||
|
pcre_dep,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
pcre_dep = declare_dependency(
|
||||||
|
link_with: pcre,
|
||||||
|
dependencies: [
|
||||||
|
pcre_dep,
|
||||||
|
],
|
||||||
|
)
|
|
@ -207,6 +207,20 @@ static StringFilter
|
||||||
ParseStringFilter(const char *&s, bool fold_case)
|
ParseStringFilter(const char *&s, bool fold_case)
|
||||||
{
|
{
|
||||||
bool negated = false;
|
bool negated = false;
|
||||||
|
|
||||||
|
#ifdef HAVE_PCRE
|
||||||
|
if ((s[0] == '!' || s[0] == '=') && s[1] == '~') {
|
||||||
|
negated = s[0] == '!';
|
||||||
|
s = StripLeft(s + 2);
|
||||||
|
auto value = ExpectQuoted(s);
|
||||||
|
StringFilter f(std::move(value), fold_case, false, negated);
|
||||||
|
f.SetRegex(std::make_shared<UniqueRegex>(f.GetValue().c_str(),
|
||||||
|
false, false,
|
||||||
|
fold_case));
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (s[0] == '!' && s[1] == '=')
|
if (s[0] == '!' && s[1] == '=')
|
||||||
negated = true;
|
negated = true;
|
||||||
else if (s[0] != '=' || s[1] != '=')
|
else if (s[0] != '=' || s[1] != '=')
|
||||||
|
|
|
@ -31,6 +31,11 @@ StringFilter::MatchWithoutNegation(const char *s) const noexcept
|
||||||
assert(s != nullptr);
|
assert(s != nullptr);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAVE_PCRE
|
||||||
|
if (regex)
|
||||||
|
return regex->Match(s);
|
||||||
|
#endif
|
||||||
|
|
||||||
if (fold_case) {
|
if (fold_case) {
|
||||||
return substring
|
return substring
|
||||||
? fold_case.IsIn(s)
|
? fold_case.IsIn(s)
|
||||||
|
|
|
@ -23,7 +23,12 @@
|
||||||
#include "lib/icu/Compare.hxx"
|
#include "lib/icu/Compare.hxx"
|
||||||
#include "util/Compiler.h"
|
#include "util/Compiler.h"
|
||||||
|
|
||||||
|
#ifdef HAVE_PCRE
|
||||||
|
#include "lib/pcre/UniqueRegex.hxx"
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
class StringFilter {
|
class StringFilter {
|
||||||
std::string value;
|
std::string value;
|
||||||
|
@ -33,6 +38,10 @@ class StringFilter {
|
||||||
*/
|
*/
|
||||||
IcuCompare fold_case;
|
IcuCompare fold_case;
|
||||||
|
|
||||||
|
#ifdef HAVE_PCRE
|
||||||
|
std::shared_ptr<UniqueRegex> regex;
|
||||||
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Search for substrings instead of matching the whole string?
|
* Search for substrings instead of matching the whole string?
|
||||||
*/
|
*/
|
||||||
|
@ -53,6 +62,21 @@ public:
|
||||||
return value.empty();
|
return value.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IsRegex() const noexcept {
|
||||||
|
#ifdef HAVE_PCRE
|
||||||
|
return !!regex;
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef HAVE_PCRE
|
||||||
|
template<typename R>
|
||||||
|
void SetRegex(R &&_regex) noexcept {
|
||||||
|
regex = std::forward<R>(_regex);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
const auto &GetValue() const noexcept {
|
const auto &GetValue() const noexcept {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
@ -70,7 +94,9 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *GetOperator() const noexcept {
|
const char *GetOperator() const noexcept {
|
||||||
return negated ? "!=" : "==";
|
return IsRegex()
|
||||||
|
? (negated ? "!~" : "=~")
|
||||||
|
: (negated ? "!=" : "==");
|
||||||
}
|
}
|
||||||
|
|
||||||
gcc_pure
|
gcc_pure
|
||||||
|
|
|
@ -19,6 +19,7 @@ song_dep = declare_dependency(
|
||||||
link_with: song,
|
link_with: song,
|
||||||
dependencies: [
|
dependencies: [
|
||||||
icu_dep,
|
icu_dep,
|
||||||
|
pcre_dep,
|
||||||
tag_dep,
|
tag_dep,
|
||||||
util_dep,
|
util_dep,
|
||||||
],
|
],
|
||||||
|
|
Loading…
Reference in New Issue