common: Add inverted scope match rules to ignores
authorShane Synan <digitalcircuit36939@gmail.com>
Thu, 1 Mar 2018 05:54:10 +0000 (23:54 -0600)
committerManuel Nickschas <sputnick@quassel-irc.org>
Wed, 6 Jun 2018 17:15:14 +0000 (19:15 +0200)
Add support for inverted scope matching subrules to ignore entries.
A match now only happens when the string does NOT match ANY inverted
rules and matches AT LEAST one normal rule, unless no normal rules
exist (implicit wildcard match).

Example:
> #quassel*; #foobar; !#quasseldroid

Matches "#foobar" and anything "#quassel...", except for
"#quasseldroid"

Example:
> !#quassel*; !#foobar

Matches anything except for "#foobar" or anything "#quassel..."

src/common/ignorelistmanager.cpp
src/common/ignorelistmanager.h
src/qtui/settingspages/ignorelisteditdlg.ui

index 8919b67..1599cde 100644 (file)
@@ -162,15 +162,57 @@ IgnoreListManager::StrictnessType IgnoreListManager::_match(const QString &msgCo
 
 bool IgnoreListManager::scopeMatch(const QString &scopeRule, const QString &string) const
 {
 
 bool IgnoreListManager::scopeMatch(const QString &scopeRule, const QString &string) const
 {
-    foreach(QString rule, scopeRule.split(";")) {
-        QRegExp ruleRx = QRegExp(rule.trimmed());
-        ruleRx.setCaseSensitivity(Qt::CaseInsensitive);
-        ruleRx.setPatternSyntax(QRegExp::Wildcard);
-        if (ruleRx.exactMatch(string)) {
-            return true;
+    // A match happens when the string does NOT match ANY inverted rules and matches AT LEAST one
+    // normal rule, unless no normal rules exist (implicit wildcard match).  This gives inverted
+    // rules higher priority regardless of ordering.
+    //
+    // TODO: After switching to Qt 5, use of this should be split into two parts, one part that
+    // would generate compiled QRegularExpressions for match/inverted match, regenerating it on any
+    // rule changes, and another part that would check each message against these compiled rules.
+
+    // Keep track if any matches are found
+    bool matches = false;
+    // Keep track if normal rules and inverted rules are found, allowing for implicit wildcard
+    bool normalRuleFound = false, invertedRuleFound = false;
+
+    // Split each scope rule by separator, ignoring empty parts
+    foreach(QString rule, scopeRule.split(";", QString::SkipEmptyParts)) {
+        // Trim whitespace from the start/end of the rule
+        rule = rule.trimmed();
+        // Ignore empty rules
+        if (rule.isEmpty())
+            continue;
+
+        // Check if this is an inverted rule (starts with '!')
+        if (rule.startsWith("!")) {
+            // Inverted rule found
+            invertedRuleFound = true;
+
+            // Take the reminder of the string
+            QRegExp ruleRx(rule.mid(1), Qt::CaseInsensitive);
+            ruleRx.setPatternSyntax(QRegExp::Wildcard);
+            if (ruleRx.exactMatch(string)) {
+                // Matches an inverted rule, full rule cannot match
+                return false;
+            }
+        } else {
+            // Normal rule found
+            normalRuleFound = true;
+
+            QRegExp ruleRx(rule, Qt::CaseInsensitive);
+            ruleRx.setPatternSyntax(QRegExp::Wildcard);
+            if (ruleRx.exactMatch(string)) {
+                // Matches a normal rule, full rule might match
+                matches = true;
+                // Continue checking in case other inverted rules negate this
+            }
         }
     }
         }
     }
-    return false;
+    // No inverted rules matched, okay to match normally
+    // Return true if...
+    // ...we found a normal match
+    // ...implicit wildcard: we had inverted rules (that didn't match) and no normal rules
+    return matches || (invertedRuleFound && !normalRuleFound);
 }
 
 
 }
 
 
index 3a813da..ef86d5e 100644 (file)
@@ -147,7 +147,19 @@ public slots:
 
 protected:
     void setIgnoreList(const QList<IgnoreListItem> &ignoreList) { _ignoreList = ignoreList; }
 
 protected:
     void setIgnoreList(const QList<IgnoreListItem> &ignoreList) { _ignoreList = ignoreList; }
-    bool scopeMatch(const QString &scopeRule, const QString &string) const; // scopeRule is a ';'-separated list, string is a network/channel-name
+
+    //! Check if a scope rule matches a string
+    /** Checks that the string does NOT match ANY inverted rules (prefixed by '!'), then checks that
+     * it matches AT LEAST one normal (non-inverted) rule.
+     *
+     * If only inverted rules are specified, it'll match so long as the string does not match any
+     * inverted rules (implicit wildcard).
+     *
+     * \param scopeRule  A ';'-separated list of wildcard expressions, prefix of '!' inverts subrule
+     * \param string     String to test, e.g. network/channel name
+     * \return True if matches, otherwise false
+     */
+    bool scopeMatch(const QString &scopeRule, const QString &string) const;
 
     StrictnessType _match(const QString &msgContents, const QString &msgSender, Message::Type msgType, const QString &network, const QString &bufferName);
 
 
     StrictnessType _match(const QString &msgContents, const QString &msgSender, Message::Type msgType, const QString &network, const QString &bufferName);
 
index 8938b8e..5cf6070 100644 (file)
@@ -184,9 +184,16 @@ Whenever you disable/delete the ignore rule, the messages are shown again.&lt;/p
 &lt;p&gt;A scope rule is a semicolon separated list of either &lt;i&gt;network&lt;/i&gt; or &lt;i&gt;channel&lt;/i&gt; names.&lt;/p&gt;
 &lt;p&gt;&lt;i&gt;Example:&lt;/i&gt;
 &lt;br /&gt;
 &lt;p&gt;A scope rule is a semicolon separated list of either &lt;i&gt;network&lt;/i&gt; or &lt;i&gt;channel&lt;/i&gt; names.&lt;/p&gt;
 &lt;p&gt;&lt;i&gt;Example:&lt;/i&gt;
 &lt;br /&gt;
-&lt;i&gt;#quassel*; #foobar&lt;/i&gt;
+&lt;i&gt;#quassel*; #foobar; !#quasseldroid&lt;/i&gt;
 &lt;br /&gt;
 &lt;br /&gt;
-would match on #foobar and on any channel starting with &lt;i&gt;#quassel&lt;/i&gt;&lt;/p&gt;</string>
+would match on #foobar and on any channel starting with &lt;i&gt;#quassel&lt;/i&gt; except for &lt;i&gt;#quasseldroid&lt;/i&gt;
+&lt;br /&gt;
+&lt;p&gt;If only inverted names are specified, it will match anything except for what's specified (implicit wildcard).&lt;/p&gt;
+&lt;p&gt;&lt;i&gt;Example:&lt;/i&gt;
+&lt;br /&gt;
+&lt;i&gt;!#quassel*; !#foobar&lt;/i&gt;
+&lt;br /&gt;
+would match anything except for #foobar or any channel starting with &lt;i&gt;#quassel&lt;/i&gt;&lt;/p&gt;</string>
         </property>
        </widget>
       </item>
         </property>
        </widget>
       </item>