Fix alias regexes

This commit is contained in:
Griatch 2021-04-25 16:14:43 +02:00
parent ae9578e1de
commit a10a297c55
3 changed files with 93 additions and 49 deletions

View file

@ -1291,9 +1291,10 @@ Custom arg markers
$N argument position (1-99)
"""
_RE_OR = re.compile(r"(?<!\\)\|")
_RE_NICK_RE_ARG = re.compile(r"arg([1-9][0-9]?)")
_RE_NICK_ARG = re.compile(r"\\(\$)([1-9][0-9]?)")
_RE_NICK_TEMPLATE_ARG = re.compile(r"(\$)([1-9][0-9]?)")
_RE_NICK_RAW_ARG = re.compile(r"(\$)([1-9][0-9]?)")
_RE_NICK_SPACE = re.compile(r"\\ ")
@ -1301,7 +1302,7 @@ class NickTemplateInvalid(ValueError):
pass
def initialize_nick_templates(pattern, out_template, pattern_is_regex=False):
def initialize_nick_templates(pattern, replacement, pattern_is_regex=False):
"""
Initialize the nick templates for matching and remapping a string.
@ -1310,11 +1311,12 @@ def initialize_nick_templates(pattern, out_template, pattern_is_regex=False):
be parsed for shell patterns into a regex, unless `pattern_is_regex`
is `True`, in which case it must be an already valid regex string. In
this case, instead of `$N`, numbered arguments must instead be given
as matching groups named as `argN`, such as `(?P<arg1>.+?)`. Such
groups can then also be made optional using e.g. `{?P<arg1>.*?}`.
out_template (str): The template to be used to replace the string
as matching groups named as `argN`, such as `(?P<arg1>.+?)`.
replacement (str): The template to be used to replace the string
matched by the pattern. This can contain `$N` markers and is never
parsed into a regex.
pattern_is_regex (bool): If set, `pattern` is a full regex string
instead of containing shell patterns.
Returns:
regex, template (str): Regex to match against strings and template
@ -1325,35 +1327,47 @@ def initialize_nick_templates(pattern, out_template, pattern_is_regex=False):
evennia.typecalasses.attributes.NickTemplateInvalid: If the in/out
template does not have a matching number of `$args`.
Example:
in->out-template: `grin $1 -> emote gives a wicked grin to $1.`
Examples:
- `pattern` (shell syntax): `"grin $1"`
- `pattern` (regex): `"grin (?P<arg1.+?>)"`
- `replacement`: `"emote gives a wicked grin to $1"`
"""
# create the regex from the pattern
if pattern_is_regex:
# Explicit regex given from the onset - this already contains argN groups
pattern_regex_string = pattern + r"\Z"
# Note that for a regex we can't validate in the way we do for the shell
# pattern, since you may have complex OR statements or optional arguments.
# Explicit regex given from the onset - this already contains argN
# groups. we need to split out any | - separated parts so we can
# attach the line-break/ending extras all regexes require.
pattern_regex_string = r"|".join(
or_part + r"(?:[\n\r]*?)\Z"
for or_part in _RE_OR.split(pattern))
else:
# regex generated by parsing shell pattern syntax - convert $N to argN groups
# Shell pattern syntax - convert $N to argN groups
# for the shell pattern we make sure we have matching $N on both sides
pattern_args = [match.group(1) for match in _RE_NICK_RAW_ARG.finditer(pattern)]
replacement_args = [
match.group(1) for match in _RE_NICK_RAW_ARG.finditer(replacement)]
if set(pattern_args) != set(replacement_args):
# We don't have the same amount of argN/$N tags in input/output.
raise NickTemplateInvalid("Nicks: Both in/out-templates must contain the same $N tags.")
# generate regex from shell pattern
pattern_regex_string = fnmatch.translate(pattern)
pattern_regex_string = _RE_NICK_SPACE.sub(r"\\s+", pattern_regex_string)
pattern_regex_string = _RE_NICK_ARG.sub(
lambda m: "(?P<arg%s>.+?)" % m.group(2), pattern_regex_string)
# we must account for a possible line break coming over the wire
pattern_regex_string = pattern_regex_string[:-2] + r"(?:[\n\r]*?)\Z"
# count and compare the args-nums in pattern and replacement for validation
pattern_args = [match.group(1) for match in _RE_NICK_RE_ARG.finditer(pattern_regex_string)]
replacement_args = [match.group(2) for match in _RE_NICK_TEMPLATE_ARG.finditer(out_template)]
if set(pattern_args) != set(replacement_args):
# We don't have the same amount of argN/$N tags in input/output.
raise NickTemplateInvalid("Nicks: Both in/out-templates must contain the same $N tags.")
# we must account for a possible line break coming over the wire
pattern_regex_string = pattern_regex_string[:-2] + r"(?:[\n\r]*?)\Z"
# map the replacement to match the arg1 group-names, to make replacement easy
template_string = _RE_NICK_TEMPLATE_ARG.sub(lambda m: "{arg%s}" % m.group(2), out_template)
replacement_string = _RE_NICK_RAW_ARG.sub(lambda m: "{arg%s}" % m.group(2), replacement)
return pattern_regex_string, template_string
return pattern_regex_string, replacement_string
def parse_nick_template(string, template_regex, outtemplate):
@ -1366,12 +1380,16 @@ def parse_nick_template(string, template_regex, outtemplate):
initialize_nick_template.
outtemplate (str): The template to which to map the matches
produced by the template_regex. This should have $1, $2,
etc to match the regex.
etc to match the template-regex. Un-found $N-markers (possible if
the regex has optional matching groups) are replaced with empty
strings.
"""
match = template_regex.match(string)
if match:
return True, outtemplate.format(**match.groupdict())
matchdict = {key: value if value is not None else ""
for key, value in match.groupdict().items()}
return True, outtemplate.format(**matchdict)
return False, string

View file

@ -177,23 +177,38 @@ class TestTags(EvenniaTest):
class TestNickHandler(EvenniaTest):
"""
Test the nick handler mechanisms.
Test the nick handler replacement.
"""
@parameterized.expand([
# ("gr $1 $2 at $3", "emote with a $1 smile, $2 grins at $3.", False,
# "gr happy Foo at Bar", "emote with a happy smile, Foo grins at Bar."),
# ("gr (?P<arg1>.+?) (?P<arg2>.+?) at (?P<arg3>.+?)",
# "emote with a $1 smile, $2 grins at $3.", True,
# "gr happy Foo at Bar", "emote with a happy smile, Foo grins at Bar."),
("groo $1", "channel groo = $1", True,
# shell syntax
("gr $1 $2 at $3", "emote with a $1 smile, $2 grins at $3.", False,
"gr happy Foo at Bar", "emote with a happy smile, Foo grins at Bar."),
# regex syntax
("gr (?P<arg1>.+?) (?P<arg2>.+?) at (?P<arg3>.+?)",
"emote with a $1 smile, $2 grins at $3.", True,
"gr happy Foo at Bar", "emote with a happy smile, Foo grins at Bar."),
# channel-style syntax
("groo $1", "channel groo = $1", False,
"groo Hello world", "channel groo = Hello world"),
(r"groo\s*?(?P<arg1>.*?)", "channel groo = $1", False,
(r"groo\s*?|groo\s+?(?P<arg1>.+?)", "channel groo = $1", True,
"groo Hello world", "channel groo = Hello world"),
(r"groo\s*?(?P<arg1>.*?)", "channel groo = $1", False,
(r"groo\s*?|groo\s+?(?P<arg1>.+?)", "channel groo = $1", True,
"groo ", "channel groo = "),
(r"groo\s*?(?P<arg1>.*?)", "channel groo = $1", False,
(r"groo\s*?|groo\s*?(?P<arg1>.+?)", "channel groo = $1", True,
"groo", "channel groo = "),
(r"groo\s*?|groo\s+?(?P<arg1>.+?)", "channel groo = $1", True,
"grooHello world", "grooHello world"), # not matched - this is correct!
# optional, space-separated arguments
(r"groo\s*?|groo\s+?(?P<arg1>.+?)(?:\s+?(?P<arg2>.+?)){0,1}",
"channel groo = $1 and $2", True,
"groo Hello World", "channel groo = Hello and World"),
(r"groo\s*?|groo\s+?(?P<arg1>.+?)(?:\s+?(?P<arg2>.+?)){0,1}",
"channel groo = $1 and $2", True,
"groo Hello", "channel groo = Hello and "),
(r"groo\s*?|groo\s+?(?P<arg1>.+?)(?:\s+?(?P<arg2>.+?)){0,1}",
"channel groo = $1 and $2", True,
"groo", "channel groo = and "), # $1/$2 replaced by ''
])
def test_nick_parsing(self, pattern, replacement, pattern_is_regex,
inp_string, expected_replaced):
@ -201,6 +216,7 @@ class TestNickHandler(EvenniaTest):
Setting up nick patterns and make sure they replace as expected.
"""
# from evennia import set_trace;set_trace()
self.char1.nicks.add(pattern, replacement,
category="inputline", pattern_is_regex=pattern_is_regex)
actual_replaced = self.char1.nicks.nickreplace(inp_string)