reapack

Package manager for REAPER
Log | Files | Refs | Submodules | README | LICENSE

commit 74d8f21c18e1d4e57691c3915c833f05a09d359b
parent 1c2edf1cea780158d4da814ecc5d44479d3514e8
Author: cfillion <cfillion@users.noreply.github.com>
Date:   Thu,  1 Sep 2016 02:54:21 -0400

filter: implement ^ and $ anchors to match start and end of string

Diffstat:
Msrc/browser.cpp | 2+-
Msrc/filter.cpp | 58+++++++++++++++++++++++++++++++++++++++++++++-------------
Msrc/filter.hpp | 17+++++++++++++++--
Mtest/filter.cpp | 121++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
4 files changed, 162 insertions(+), 36 deletions(-)

diff --git a/src/browser.cpp b/src/browser.cpp @@ -808,7 +808,7 @@ bool Browser::match(const Entry &entry) const const string &author = getValue(AuthorColumn, entry); const string &remote = getValue(RemoteColumn, entry); - return m_filter.match(name + category + author + remote); + return m_filter.match({name, category, author, remote}); } auto Browser::getEntry(const int listIndex) -> Entry * diff --git a/src/filter.cpp b/src/filter.cpp @@ -18,6 +18,7 @@ #include "filter.hpp" #include <boost/algorithm/string.hpp> +#include <unordered_set> using namespace std; @@ -25,49 +26,80 @@ void Filter::set(const string &input) { enum State { Default, DoubleQuote, SingleQuote }; - string buf; + Token token{}; State state = Default; m_input = input; - m_words.clear(); + m_tokens.clear(); auto push = [&] { state = Default; - if(!buf.empty()) { - m_words.push_back(buf); - buf.clear(); + size_t size = token.buf.size(); + + if(!size) + return; + if(size > 1 && token.buf[0] == '^') { + token.flags |= StartAnchorFlag; + token.buf.erase(0, 1); + size--; + } + if(size > 1 && token.buf[size - 1] == '$') { + token.flags |= EndAnchorFlag; + token.buf.erase(size - 1, 1); + size--; } + + m_tokens.emplace_back(token); + token = Token(); }; for(const char c : input) { if(c == '"' && state != SingleQuote) { if(state == DoubleQuote) - push(); + state = Default; else state = DoubleQuote; } else if(c == '\'' && state != DoubleQuote) { if(state == SingleQuote) - push(); + state = Default; else state = SingleQuote; } else if(c == '\x20' && state == Default) push(); else - buf += c; + token.buf += c; } push(); } -bool Filter::match(const string &str) const +bool Filter::match(const vector<string> &rows) const { - for(const string &word : m_words) { - if(!boost::icontains(str, word)) - return false; + unordered_set<const Token *> matches; + + for(const string &str : rows) { + for(const Token &token : m_tokens) { + if(token.match(str)) + matches.insert(&token); + } } - return true; + return matches.size() == m_tokens.size(); +} + +bool Filter::Token::match(const string &str) const +{ + bool match = true; + + if(test(StartAnchorFlag)) + match = match && boost::istarts_with(str, buf); + if(test(EndAnchorFlag)) + match = match && boost::iends_with(str, buf); + + match = match && boost::icontains(str, buf); + + return match; } diff --git a/src/filter.hpp b/src/filter.hpp @@ -29,15 +29,28 @@ public: const std::string get() const { return m_input; } void set(const std::string &); - bool match(const std::string &) const; + bool match(const std::vector<std::string> &) const; Filter &operator=(const std::string &f) { set(f); return *this; } bool operator==(const std::string &f) const { return m_input == f; } bool operator!=(const std::string &f) const { return !(*this == f); } private: + enum Flag { + StartAnchorFlag = 1<<0, + EndAnchorFlag = 1<<1, + }; + + struct Token { + std::string buf; + int flags; + + bool test(Flag f) const { return (flags & f) != 0; } + bool match(const std::string &) const; + }; + std::string m_input; - std::vector<std::string> m_words; + std::vector<Token> m_tokens; }; #endif diff --git a/test/filter.cpp b/test/filter.cpp @@ -8,13 +8,13 @@ static const char *M = "[filter]"; TEST_CASE("basic matching", M) { Filter f; - REQUIRE(f.match("world")); + REQUIRE(f.match({"world"})); f.set("hello"); - REQUIRE(f.match("hello")); - REQUIRE(f.match("HELLO")); - REQUIRE_FALSE(f.match("world")); + REQUIRE(f.match({"hello"})); + REQUIRE(f.match({"HELLO"})); + REQUIRE_FALSE(f.match({"world"})); } TEST_CASE("get/set filter", M) { @@ -23,11 +23,11 @@ TEST_CASE("get/set filter", M) { f.set("hello"); REQUIRE(f.get() == "hello"); - REQUIRE(f.match("hello")); + REQUIRE(f.match({"hello"})); f.set("world"); REQUIRE(f.get() == "world"); - REQUIRE(f.match("world")); + REQUIRE(f.match({"world"})); } TEST_CASE("filter operators", M) { @@ -52,28 +52,28 @@ TEST_CASE("word matching", M) { Filter f; f.set("hello world"); - REQUIRE_FALSE(f.match("hello")); - REQUIRE(f.match("hello world")); - REQUIRE(f.match("helloworld")); - REQUIRE(f.match("hello test world")); + REQUIRE_FALSE(f.match({"hello"})); + REQUIRE(f.match({"hello world"})); + REQUIRE(f.match({"helloworld"})); + REQUIRE(f.match({"hello test world"})); } TEST_CASE("double quote matching", M) { Filter f; f.set("\"hello world\""); - REQUIRE(f.match("hello world")); - REQUIRE_FALSE(f.match("helloworld")); - REQUIRE_FALSE(f.match("hello test world")); + REQUIRE(f.match({"hello world"})); + REQUIRE_FALSE(f.match({"helloworld"})); + REQUIRE_FALSE(f.match({"hello test world"})); } TEST_CASE("single quote matching", M) { Filter f; f.set("'hello world'"); - REQUIRE(f.match("hello world")); - REQUIRE_FALSE(f.match("helloworld")); - REQUIRE_FALSE(f.match("hello test world")); + REQUIRE(f.match({"hello world"})); + REQUIRE_FALSE(f.match({"helloworld"})); + REQUIRE_FALSE(f.match({"hello test world"})); } TEST_CASE("mixing quotes", M) { @@ -82,14 +82,95 @@ TEST_CASE("mixing quotes", M) { SECTION("double in single") { f.set("'hello \"world\"'"); - REQUIRE(f.match("hello \"world\"")); - REQUIRE_FALSE(f.match("hello world")); + REQUIRE(f.match({"hello \"world\""})); + REQUIRE_FALSE(f.match({"hello world"})); } SECTION("single in double") { f.set("\"hello 'world'\""); - REQUIRE(f.match("hello 'world'")); - REQUIRE_FALSE(f.match("hello world")); + REQUIRE(f.match({"hello 'world'"})); + REQUIRE_FALSE(f.match({"hello world"})); } } + +TEST_CASE("start of string", M) { + Filter f; + + SECTION("normal") { + f.set("^hello"); + + REQUIRE(f.match({"hello world"})); + REQUIRE_FALSE(f.match({"puts 'hello world'"})); + } + + SECTION("middle") { + f.set("hel^lo"); + + REQUIRE(f.match({"hel^lo world"})); + REQUIRE_FALSE(f.match({"hello world"})); + } + + SECTION("single") { + f.set("^"); + REQUIRE(f.match({"hel^lo world"})); + REQUIRE_FALSE(f.match({"hello world"})); + } + + SECTION("quote before") { + f.set("'^hello'"); + REQUIRE(f.match({"hello world"})); + REQUIRE_FALSE(f.match({"world hello"})); + } + + SECTION("quote after") { + f.set("^'hello"); + REQUIRE(f.match({"hello world"})); + REQUIRE_FALSE(f.match({"world hello"})); + } +} + +TEST_CASE("end of string", M) { + Filter f; + + SECTION("normal") { + f.set("world$"); + + REQUIRE(f.match({"hello world"})); + REQUIRE_FALSE(f.match({"'hello world'.upcase"})); + } + + SECTION("middle") { + f.set("hel$lo"); + + REQUIRE(f.match({"hel$lo world"})); + REQUIRE_FALSE(f.match({"hello world"})); + } + + SECTION("single") { + f.set("$"); + REQUIRE(f.match({"hel$lo world"})); + REQUIRE_FALSE(f.match({"hello world"})); + } + + SECTION("quote before") { + f.set("'hello'$"); + REQUIRE(f.match({"hello"})); + REQUIRE_FALSE(f.match({"hello world"})); + } + + SECTION("quote after") { + f.set("'hello$'"); + REQUIRE(f.match({"hello"})); + REQUIRE_FALSE(f.match({"hello world"})); + } +} + +TEST_CASE("row matching", M) { + Filter f; + f.set("hello world"); + + REQUIRE_FALSE(f.match({"hello"})); + REQUIRE(f.match({"hello", "world"})); + REQUIRE(f.match({"hello", "test", "world"})); +}