summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--NEWS2
-rw-r--r--doc/protocol.rst5
-rw-r--r--doc/user.rst1
-rw-r--r--meson.build1
-rw-r--r--meson_options.txt1
-rw-r--r--src/lib/pcre/RegexPointer.hxx66
-rw-r--r--src/lib/pcre/UniqueRegex.cxx71
-rw-r--r--src/lib/pcre/UniqueRegex.hxx79
-rw-r--r--src/lib/pcre/meson.build21
-rw-r--r--src/song/Filter.cxx14
-rw-r--r--src/song/StringFilter.cxx5
-rw-r--r--src/song/StringFilter.hxx28
-rw-r--r--src/song/meson.build1
13 files changed, 294 insertions, 1 deletions
diff --git a/NEWS b/NEWS
index 91a932e78..cece79a95 100644
--- a/NEWS
+++ b/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
diff --git a/doc/protocol.rst b/doc/protocol.rst
index b14f12f93..27364b821 100644
--- a/doc/protocol.rst
+++ b/doc/protocol.rst
@@ -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).
diff --git a/doc/user.rst b/doc/user.rst
index 23ed5bcc9..d7773d358 100644
--- a/doc/user.rst
+++ b/doc/user.rst
@@ -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 \
diff --git a/meson.build b/meson.build
index f5457a1f2..47f4b8b39 100644
--- a/meson.build
+++ b/meson.build
@@ -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')
diff --git a/meson_options.txt b/meson_options.txt
index a30f95858..9a31e548e 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -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)')
diff --git a/src/lib/pcre/RegexPointer.hxx b/src/lib/pcre/RegexPointer.hxx
new file mode 100644
index 000000000..7311d0407
--- /dev/null
+++ b/src/lib/pcre/RegexPointer.hxx
@@ -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
diff --git a/src/lib/pcre/UniqueRegex.cxx b/src/lib/pcre/UniqueRegex.cxx
new file mode 100644
index 000000000..4028380ab
--- /dev/null
+++ b/src/lib/pcre/UniqueRegex.cxx
@@ -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;
+}
diff --git a/src/lib/pcre/UniqueRegex.hxx b/src/lib/pcre/UniqueRegex.hxx
new file mode 100644
index 000000000..3be166f18
--- /dev/null
+++ b/src/lib/pcre/UniqueRegex.hxx
@@ -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
diff --git a/src/lib/pcre/meson.build b/src/lib/pcre/meson.build
new file mode 100644
index 000000000..07e83a600
--- /dev/null
+++ b/src/lib/pcre/meson.build
@@ -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,
+ ],
+)
diff --git a/src/song/Filter.cxx b/src/song/Filter.cxx
index 3eaf29e5f..2c47ebb59 100644
--- a/src/song/Filter.cxx
+++ b/src/song/Filter.cxx
@@ -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] != '=')
diff --git a/src/song/StringFilter.cxx b/src/song/StringFilter.cxx
index 1e57b638f..ea3107f68 100644
--- a/src/song/StringFilter.cxx
+++ b/src/song/StringFilter.cxx
@@ -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)
diff --git a/src/song/StringFilter.hxx b/src/song/StringFilter.hxx
index 1cc3f8998..0c7c3f5e8 100644
--- a/src/song/StringFilter.hxx
+++ b/src/song/StringFilter.hxx
@@ -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
diff --git a/src/song/meson.build b/src/song/meson.build
index 71eda8d65..a51c36538 100644
--- a/src/song/meson.build
+++ b/src/song/meson.build
@@ -19,6 +19,7 @@ song_dep = declare_dependency(
link_with: song,
dependencies: [
icu_dep,
+ pcre_dep,
tag_dep,
util_dep,
],