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)
|
||||
* protocol
|
||||
- operator "=~" matches a regular expression
|
||||
* decoder
|
||||
- ffmpeg: require FFmpeg 3.1 or later
|
||||
- ffmpeg: fix broken sound with certain codecs
|
||||
|
|
|
@ -157,6 +157,11 @@ of:
|
|||
and are case-sensitive; the `search`
|
||||
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
|
||||
(relative to the music directory).
|
||||
|
||||
|
|
|
@ -67,6 +67,7 @@ For example, the following installs a fairly complete list of build dependencies
|
|||
.. code-block:: none
|
||||
|
||||
apt install g++ \
|
||||
libpcre3-dev \
|
||||
libmad0-dev libmpg123-dev libid3tag0-dev \
|
||||
libflac-dev libvorbis-dev libopus-dev \
|
||||
libadplug-dev libaudiofile-dev libsndfile1-dev libfaad-dev \
|
||||
|
|
|
@ -315,6 +315,7 @@ subdir('src/lib/gcrypt')
|
|||
subdir('src/lib/wrap')
|
||||
subdir('src/lib/nfs')
|
||||
subdir('src/lib/oss')
|
||||
subdir('src/lib/pcre')
|
||||
subdir('src/lib/pulse')
|
||||
subdir('src/lib/sndio')
|
||||
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('iconv', type: 'feature', description: 'Use iconv() for character set conversion')
|
||||
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('yajl', type: 'feature', description: 'libyajl for YAML support')
|
||||
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)
|
||||
{
|
||||
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] == '=')
|
||||
negated = true;
|
||||
else if (s[0] != '=' || s[1] != '=')
|
||||
|
|
|
@ -31,6 +31,11 @@ StringFilter::MatchWithoutNegation(const char *s) const noexcept
|
|||
assert(s != nullptr);
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_PCRE
|
||||
if (regex)
|
||||
return regex->Match(s);
|
||||
#endif
|
||||
|
||||
if (fold_case) {
|
||||
return substring
|
||||
? fold_case.IsIn(s)
|
||||
|
|
|
@ -23,7 +23,12 @@
|
|||
#include "lib/icu/Compare.hxx"
|
||||
#include "util/Compiler.h"
|
||||
|
||||
#ifdef HAVE_PCRE
|
||||
#include "lib/pcre/UniqueRegex.hxx"
|
||||
#endif
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
class StringFilter {
|
||||
std::string value;
|
||||
|
@ -33,6 +38,10 @@ class StringFilter {
|
|||
*/
|
||||
IcuCompare fold_case;
|
||||
|
||||
#ifdef HAVE_PCRE
|
||||
std::shared_ptr<UniqueRegex> regex;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Search for substrings instead of matching the whole string?
|
||||
*/
|
||||
|
@ -53,6 +62,21 @@ public:
|
|||
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 {
|
||||
return value;
|
||||
}
|
||||
|
@ -70,7 +94,9 @@ public:
|
|||
}
|
||||
|
||||
const char *GetOperator() const noexcept {
|
||||
return negated ? "!=" : "==";
|
||||
return IsRegex()
|
||||
? (negated ? "!~" : "=~")
|
||||
: (negated ? "!=" : "==");
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
|
|
|
@ -19,6 +19,7 @@ song_dep = declare_dependency(
|
|||
link_with: song,
|
||||
dependencies: [
|
||||
icu_dep,
|
||||
pcre_dep,
|
||||
tag_dep,
|
||||
util_dep,
|
||||
],
|
||||
|
|
Loading…
Reference in New Issue