Skip to content

Commit

Permalink
sq: rename to split_pattern_repl; add docs
Browse files Browse the repository at this point in the history
  • Loading branch information
lukaszachy committed May 14, 2024
1 parent 5d502aa commit aa39def
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 16 deletions.
17 changes: 17 additions & 0 deletions docs/features.rst
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,23 @@ attribute, matching regular expressions are replaced by an empty
string. For dictionaries it's possible to provide list of keys
which should be removed.

Substitution of current values can be done by appending a ``~`` suffix
to the key name. The pattern and replacement parameters need to be provided
as values in the form of ``<d>PATTERN<d>REPLACEMENT<d>``, where ``<d>`` is delimiter
which can be any character however such character cannot be then used withing PATTERN
and REPLACEMENT text as escaping isn't supported. These input can be either a string or
list of strings.

The `re.sub`__ is used to do the substitution thus all feaures of ``re.Pattern`` can
be used (named groups, backreferencing...).

In the fmf file it is better to use single quotes ``'`` as they do not need such intensive escaping::

require~: ';^foo;foo-ng;'
recommend~:
- '/python2-/python3-/'

__ https://docs.python.org/3/library/re.html#re.sub

Elasticity
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down
4 changes: 2 additions & 2 deletions fmf/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,10 +191,10 @@ def _merge_plus(self, data, key, value, prepend=False):
key, self.name, str(error)))

def _merge_regexp(self, data, key, value):
"""FIXME"""
""" Handle substitution of current values """
if isinstance(value, str):
value = [value]
for pattern, repl in [utils.BETTER_NAME(v) for v in value]:
for pattern, repl in [utils.split_pattern_repl(v) for v in value]:
if isinstance(data[key], list):
data[key] = [re.sub(pattern, repl, original) for original in data[key]]
elif isinstance(data[key], str):
Expand Down
30 changes: 20 additions & 10 deletions fmf/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -901,19 +901,29 @@ class PatternRepl(NamedTuple):
repl: str


def BETTER_NAME(source):
""" Splits sed-like input into pattern/repl pair suitable for re.sub """
def split_pattern_repl(source):
"""
Splits pattern/repl string input into parts
Format of the input:
<delimiter><PATTERN><delimiter><REPL><delimiter>
Delimiter set by the first character of the input and this character cannot be used
in the PATTERN or REPL text. Escaping is not supported.
"""

try:
if source[0] != source[-1] != '/':
raise FormatError("'{0}' has to start and end with '/'.".format(source))
delimiter = source[0]
if source[-1] != delimiter:
raise FormatError("'{0}' has to end with '{1}'.".format(source, delimiter))
except IndexError:
raise FormatError("'{0}' has to start and end with '/'.".format(source))

split = re.split(r'(?<!\\)/', source[1:-1])
if len(split) != 2:
try:
pattern, repl = source[1:-1].split(delimiter)
except ValueError:
raise FormatError("'{0}' can't be split in two parts".format(source))
# Unescape backslash
split = [s.replace('\\/', '/') for s in split]
if split[0] == '':

if not pattern:
raise FormatError("Pattern cannot be empty: '{0}'.".format(source))
return PatternRepl(pattern=split[0], repl=split[1])
return PatternRepl(pattern=pattern, repl=repl)
9 changes: 5 additions & 4 deletions tests/unit/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -438,10 +438,11 @@ def test_sort(self):

class TestBETTERNAME:
def test_invalid(self):
for s in ["", "/", ";/;", ";;;", "/a/b/c", "/a/b/c/d"]:
for s in ["", "/", ";/;", ";;;", "/a/b/c", "/a/b/c/d",
"/x;y/", "/a/a;"]:
with pytest.raises(utils.FormatError):
utils.BETTER_NAME(s)
utils.split_pattern_repl(s)

def test_simple(self):
assert utils.BETTER_NAME('/a/b/') == utils.PatternRepl('a', 'b')
assert utils.BETTER_NAME('/ac\\/dc/rose/') == utils.PatternRepl('ac/dc', 'rose')
assert utils.split_pattern_repl('/a/b/') == utils.PatternRepl('a', 'b')
assert utils.split_pattern_repl(';ac/dc;rose;') == utils.PatternRepl('ac/dc', 'rose')

0 comments on commit aa39def

Please sign in to comment.