mirror of
https://github.com/Chatterino/chatterino2.git
synced 2024-11-13 19:49:51 +01:00
Add regular expression support to filters (#2225)
This commit is contained in:
parent
278a00a700
commit
5a29198367
|
@ -3,7 +3,7 @@
|
|||
## Unversioned
|
||||
|
||||
- Major: Added clip creation support. You can create clips with `/clip` command, `Alt+X` keybind or `Create a clip` option in split header's context menu. This requires a new authentication scope so re-authentication will be required to use it. (#2271, #2377)
|
||||
- Major: Added "Channel Filters". See https://wiki.chatterino.com/Filters/ for how they work or how to configure them. (#1748, #2083, #2090, #2200)
|
||||
- Major: Added "Channel Filters". See https://wiki.chatterino.com/Filters/ for how they work or how to configure them. (#1748, #2083, #2090, #2200, #2225)
|
||||
- Major: Added Streamer Mode configuration (under `Settings -> General`), where you can select which features of Chatterino should behave differently when you are in Streamer Mode. (#2001, #2316, #2342, #2376)
|
||||
- Major: Color mentions to match the mentioned users. You can disable this by unchecking "Color @usernames" under `Settings -> General -> Advanced (misc.)`. (#1963, #2284)
|
||||
- Minor: Added `/marker` command - similar to webchat, it creates a stream marker. (#2360)
|
||||
|
|
|
@ -255,6 +255,16 @@ ExpressionPtr FilterParser::parseValue()
|
|||
return std::make_unique<ValueExpression>(this->tokenizer_.next(),
|
||||
type);
|
||||
}
|
||||
else if (type == TokenType::REGULAR_EXPRESSION)
|
||||
{
|
||||
auto before = this->tokenizer_.next();
|
||||
// remove quote marks and r/ri
|
||||
bool caseInsensitive = before.startsWith("ri");
|
||||
auto val = before.mid(caseInsensitive ? 3 : 2);
|
||||
val.chop(1);
|
||||
val = val.replace("\\\"", "\"");
|
||||
return std::make_unique<RegexExpression>(val, caseInsensitive);
|
||||
}
|
||||
else if (type == TokenType::LP)
|
||||
{
|
||||
return this->parseParentheses();
|
||||
|
|
|
@ -141,10 +141,18 @@ TokenType Tokenizer::tokenize(const QString &text)
|
|||
return TokenType::STARTS_WITH;
|
||||
else if (text == "endswith")
|
||||
return TokenType::ENDS_WITH;
|
||||
else if (text == "match")
|
||||
return TokenType::MATCH;
|
||||
else if (text == "!")
|
||||
return TokenType::NOT;
|
||||
else
|
||||
{
|
||||
if ((text.startsWith("r\"") || text.startsWith("ri\"")) &&
|
||||
text.back() == '"')
|
||||
{
|
||||
return TokenType::REGULAR_EXPRESSION;
|
||||
}
|
||||
|
||||
if (text.front() == '"' && text.back() == '"')
|
||||
return TokenType::STRING;
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ static const QMap<QString, QString> validIdentifiersMap = {
|
|||
|
||||
// clang-format off
|
||||
static const QRegularExpression tokenRegex(
|
||||
QString("\\\"((\\\\\")|[^\\\"])*\\\"|") + // String literal
|
||||
QString("((r|ri)?\\\")((\\\\\")|[^\\\"])*\\\"|") + // String/Regex literal
|
||||
QString("[\\w\\.]+|") + // Identifier or reserved keyword
|
||||
QString("(<=?|>=?|!=?|==|\\|\\||&&|\\+|-|\\*|\\/|%)+|") + // Operator
|
||||
QString("[\\(\\)]|") + // Parentheses
|
||||
|
|
|
@ -69,6 +69,8 @@ QString tokenTypeToInfoString(TokenType type)
|
|||
return "<starts with>";
|
||||
case ENDS_WITH:
|
||||
return "<ends with>";
|
||||
case MATCH:
|
||||
return "<match>";
|
||||
case NOT:
|
||||
return "<not>";
|
||||
case STRING:
|
||||
|
@ -123,6 +125,33 @@ QString ValueExpression::filterString() const
|
|||
}
|
||||
}
|
||||
|
||||
// RegexExpression
|
||||
|
||||
RegexExpression::RegexExpression(QString regex, bool caseInsensitive)
|
||||
: regexString_(regex)
|
||||
, caseInsensitive_(caseInsensitive)
|
||||
, regex_(QRegularExpression(
|
||||
regex, caseInsensitive ? QRegularExpression::CaseInsensitiveOption
|
||||
: QRegularExpression::NoPatternOption)){};
|
||||
|
||||
QVariant RegexExpression::execute(const ContextMap &) const
|
||||
{
|
||||
return this->regex_;
|
||||
}
|
||||
|
||||
QString RegexExpression::debug() const
|
||||
{
|
||||
return this->regexString_;
|
||||
}
|
||||
|
||||
QString RegexExpression::filterString() const
|
||||
{
|
||||
auto s = this->regexString_;
|
||||
return QString("%1\"%2\"")
|
||||
.arg(this->caseInsensitive_ ? "ri" : "r")
|
||||
.arg(s.replace("\"", "\\\""));
|
||||
}
|
||||
|
||||
// ListExpression
|
||||
|
||||
ListExpression::ListExpression(ExpressionList list)
|
||||
|
@ -334,6 +363,47 @@ QVariant BinaryOperation::execute(const ContextMap &context) const
|
|||
}
|
||||
|
||||
return false;
|
||||
case MATCH: {
|
||||
if (!left.canConvert(QMetaType::QString))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
auto matching = left.toString();
|
||||
|
||||
switch (right.type())
|
||||
{
|
||||
case QVariant::Type::RegularExpression: {
|
||||
return right.toRegularExpression()
|
||||
.match(matching)
|
||||
.hasMatch();
|
||||
}
|
||||
case QVariant::Type::List: {
|
||||
auto list = right.toList();
|
||||
|
||||
// list must be two items
|
||||
if (list.size() != 2)
|
||||
return false;
|
||||
|
||||
// list must be a regular expression and an int
|
||||
if (list.at(0).type() !=
|
||||
QVariant::Type::RegularExpression ||
|
||||
list.at(1).type() != QVariant::Type::Int)
|
||||
return false;
|
||||
|
||||
auto match =
|
||||
list.at(0).toRegularExpression().match(matching);
|
||||
|
||||
// if matched, return nth capture group. Otherwise, return false
|
||||
if (match.hasMatch())
|
||||
return match.captured(list.at(1).toInt());
|
||||
else
|
||||
return false;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
@ -383,6 +453,8 @@ QString BinaryOperation::filterString() const
|
|||
return "startswith";
|
||||
case ENDS_WITH:
|
||||
return "endswith";
|
||||
case MATCH:
|
||||
return "match";
|
||||
default:
|
||||
return QString();
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ enum TokenType {
|
|||
CONTAINS = 27,
|
||||
STARTS_WITH = 28,
|
||||
ENDS_WITH = 29,
|
||||
MATCH = 30,
|
||||
BINARY_END = 49,
|
||||
|
||||
// unary operator
|
||||
|
@ -51,6 +52,7 @@ enum TokenType {
|
|||
STRING = 151,
|
||||
INT = 152,
|
||||
IDENTIFIER = 153,
|
||||
REGULAR_EXPRESSION = 154,
|
||||
|
||||
NONE = 200
|
||||
};
|
||||
|
@ -96,6 +98,21 @@ private:
|
|||
TokenType type_;
|
||||
};
|
||||
|
||||
class RegexExpression : public Expression
|
||||
{
|
||||
public:
|
||||
RegexExpression(QString regex, bool caseInsensitive);
|
||||
|
||||
QVariant execute(const ContextMap &context) const override;
|
||||
QString debug() const override;
|
||||
QString filterString() const override;
|
||||
|
||||
private:
|
||||
QString regexString_;
|
||||
bool caseInsensitive_;
|
||||
QRegularExpression regex_;
|
||||
};
|
||||
|
||||
using ExpressionList = std::vector<std::unique_ptr<Expression>>;
|
||||
|
||||
class ListExpression : public Expression
|
||||
|
|
Loading…
Reference in a new issue