Skip to content

Commit

Permalink
Support escaping | and & in utils.filter()
Browse files Browse the repository at this point in the history
Add support for escaping boolean operators using the back slash
character `\` to allow for example regular expression grouping.
  • Loading branch information
psss committed Jun 4, 2024
1 parent 673c83c commit fe38c42
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 3 deletions.
17 changes: 14 additions & 3 deletions fmf/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,13 @@ def filter(filter, data, sensitive=True, regexp=False, name=None):
tag: Tier1 | tag: Tier2 | tag: Tier3
category: Sanity, Security & tag: -destructive
Use the back slash character ``\\`` to escape the boolean
operators if you need to specify them as part of the filter
expressions::
tag: Tier(1\\|2)
text: Q\\&A
Note that multiple comma-separated values can be used as a syntactic
sugar to shorten the filter notation::
Expand Down Expand Up @@ -322,7 +329,10 @@ def check_clause(clause):
# E.g. clause = 'tag: A, B & tag: C & tag: -D'
# Split into individual literals by dimension
literals = dict()
for literal in re.split(r"\s*&\s*", clause):

for literal in re.split(r"\s*(?<!\\)&\s*", clause):
# Remove the possible escaping
literal = re.sub(r"\\&", "&", literal)
# E.g. literal = 'tag: A, B'
# Check whether the literal matches dimension:value format
matched = re.match(r"^([^:]*)\s*:\s*(.*)$", literal)
Expand Down Expand Up @@ -364,8 +374,9 @@ def check_clause(clause):
data = lowered

# At least one clause must be true
return any([check_clause(clause)
for clause in re.split(r"\s*\|\s*", filter)])
return any([
check_clause(re.sub(r"\\\|", "|", clause))
for clause in re.split(r"\s*(?<!\\)\|\s*", filter)])

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Logging
Expand Down
27 changes: 27 additions & 0 deletions tests/unit/test_utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import os
import queue
import re
import shutil
import threading
import time
Expand Down Expand Up @@ -118,6 +119,32 @@ def test_unicode(self):
assert filter("tag: ťip", {"tag": ["ťip"]}) is True
assert filter("tag: -ťop", {"tag": ["ťip"]}) is True

def test_escape_or(self):
""" Escaping the | operator """

# Escaped
assert filter(r"category: (Sanity\|Security)", self.data, regexp=True) is True
assert filter(r"tag: Tier(1\|2)", self.data, regexp=True) is True

# Unescaped
with pytest.raises(re.error):
assert filter(r"category: (Sanity|Security)", self.data, regexp=True) is True
with pytest.raises(re.error):
assert filter(r"tag: Tier(1|2)", self.data, regexp=True) is True

def test_escape_and(self):
""" Escaping the & operator """
self.data["text"] = "Q&A"

# Escaped
assert filter(r"text: Q\&A", self.data) is True
assert filter(r"text: Q\&A", self.data) is True

# Unescaped
assert filter(r"text: Q&A", self.data, name="foo") is False
with pytest.raises(utils.FilterError):
filter(r"text: foo & bar", self.data)


class TestPluralize:
""" Function pluralize() """
Expand Down

0 comments on commit fe38c42

Please sign in to comment.