Fix alias regexes
This commit is contained in:
parent
ae9578e1de
commit
a10a297c55
3 changed files with 93 additions and 49 deletions
|
|
@ -16,7 +16,6 @@ from evennia.locks.lockhandler import LockException
|
||||||
from evennia.utils import create, logger, utils
|
from evennia.utils import create, logger, utils
|
||||||
from evennia.utils.logger import tail_log_file
|
from evennia.utils.logger import tail_log_file
|
||||||
from evennia.utils.utils import make_iter, class_from_module
|
from evennia.utils.utils import make_iter, class_from_module
|
||||||
from evennia.utils import evmore
|
|
||||||
from evennia.utils.evmenu import ask_yes_no
|
from evennia.utils.evmenu import ask_yes_no
|
||||||
|
|
||||||
COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS)
|
COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS)
|
||||||
|
|
@ -86,10 +85,12 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
||||||
switch_options = (
|
switch_options = (
|
||||||
"list", "all", "history", "sub", "unsub", "mute", "unmute", "alias", "unalias",
|
"list", "all", "history", "sub", "unsub", "mute", "unmute", "alias", "unalias",
|
||||||
"create", "destroy", "desc", "lock", "unlock", "boot", "ban", "unban", "who",)
|
"create", "destroy", "desc", "lock", "unlock", "boot", "ban", "unban", "who",)
|
||||||
|
# disable this in child command classes if wanting on-character channels
|
||||||
|
account_caller = True
|
||||||
|
|
||||||
# note - changing this will invalidate existing aliases in db
|
# note - changing this will invalidate existing aliases in db
|
||||||
# channel_msg_nick_alias = r"{alias}\s*?(?P<arg1>.+?){{0,1}}"
|
# channel_msg_nick_alias = r"{alias}\s*?(?P<arg1>.+?){{0,1}}"
|
||||||
channel_msg_nick_alias = r"{alias} (?P<arg1>.+?)"
|
channel_msg_nick_alias = r"{alias}\s*?|{alias}\s+?(?P<arg1>.+?)"
|
||||||
channel_msg_nick_replacement = "channel {channelname} = $1"
|
channel_msg_nick_replacement = "channel {channelname} = $1"
|
||||||
|
|
||||||
def search_channel(self, channelname, exact=False):
|
def search_channel(self, channelname, exact=False):
|
||||||
|
|
@ -193,13 +194,14 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
||||||
return result, "" if result else f"Were not allowed to subscribe to channel {channel.key}"
|
return result, "" if result else f"Were not allowed to subscribe to channel {channel.key}"
|
||||||
|
|
||||||
def unsub_from_channel(self, channel):
|
def unsub_from_channel(self, channel, **kwargs):
|
||||||
"""
|
"""
|
||||||
Un-Subscribe to a channel. Note that all permissions should
|
Un-Subscribe to a channel. Note that all permissions should
|
||||||
be checked before this step.
|
be checked before this step.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
channel (Channel): The channel to unsub from.
|
channel (Channel): The channel to unsub from.
|
||||||
|
**kwargs: Passed on to nick removal.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
bool, str: True, None if un-connection succeeded. If False,
|
bool, str: True, None if un-connection succeeded. If False,
|
||||||
|
|
@ -210,9 +212,13 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
||||||
if not channel.has_connection(caller):
|
if not channel.has_connection(caller):
|
||||||
return False, f"Not listening to channel {channel.key}."
|
return False, f"Not listening to channel {channel.key}."
|
||||||
# clear nicks
|
# clear aliases
|
||||||
for key_or_alias in self.get_channel_aliases(channel):
|
for key_or_alias in self.get_channel_aliases(channel):
|
||||||
self.remove_alias(key_or_alias)
|
self.remove_alias(key_or_alias, **kwargs)
|
||||||
|
# remove the channel-name alias too
|
||||||
|
msg_alias = self.channel_msg_nick_alias.format(alias=channel.key.lower())
|
||||||
|
caller.nicks.remove(msg_alias, category="inputline", **kwargs)
|
||||||
|
|
||||||
result = channel.disconnect(caller)
|
result = channel.disconnect(caller)
|
||||||
return result, "" if result else f"Could not unsubscribe from channel {channel.key}"
|
return result, "" if result else f"Could not unsubscribe from channel {channel.key}"
|
||||||
|
|
||||||
|
|
@ -240,14 +246,15 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
chan_key = channel.key.lower()
|
chan_key = channel.key.lower()
|
||||||
msg_alias = self.channel_msg_nick_alias.format(alias=alias)
|
# the message-pattern allows us to type the channel on its own without
|
||||||
print("msg_alias", msg_alias)
|
# needing to use the `channel` command explicitly.
|
||||||
|
msg_pattern = self.channel_msg_nick_alias.format(alias=alias)
|
||||||
msg_replacement = self.channel_msg_nick_replacement.format(channelname=chan_key)
|
msg_replacement = self.channel_msg_nick_replacement.format(channelname=chan_key)
|
||||||
|
|
||||||
if chan_key != alias:
|
if chan_key != alias:
|
||||||
self.caller.nicks.add(alias, chan_key, category="channel", **kwargs)
|
self.caller.nicks.add(alias, chan_key, category="channel", **kwargs)
|
||||||
self.caller.nicks.add(msg_alias, msg_replacement, category="inputline",
|
self.caller.nicks.add(msg_pattern, msg_replacement, category="inputline",
|
||||||
regex_pattern=True, **kwargs)
|
pattern_is_regex=True, **kwargs)
|
||||||
|
|
||||||
def remove_alias(self, alias, **kwargs):
|
def remove_alias(self, alias, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
|
@ -270,8 +277,8 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
||||||
"""
|
"""
|
||||||
caller = self.caller
|
caller = self.caller
|
||||||
if caller.nicks.get(alias, category="channel", **kwargs):
|
if caller.nicks.get(alias, category="channel", **kwargs):
|
||||||
msg_alias = self.channel_msg_nick_alias.format(alias=alias)
|
|
||||||
caller.nicks.remove(alias, category="channel", **kwargs)
|
caller.nicks.remove(alias, category="channel", **kwargs)
|
||||||
|
msg_alias = self.channel_msg_nick_alias.format(alias=alias)
|
||||||
caller.nicks.remove(msg_alias, category="inputline", **kwargs)
|
caller.nicks.remove(msg_alias, category="inputline", **kwargs)
|
||||||
return True, ""
|
return True, ""
|
||||||
|
|
||||||
|
|
@ -357,8 +364,7 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
||||||
lockstring = "send:all();listen:all();control:id(%s)" % caller.id
|
lockstring = "send:all();listen:all();control:id(%s)" % caller.id
|
||||||
|
|
||||||
new_chan = create.create_channel(
|
new_chan = create.create_channel(
|
||||||
name, aliases=aliases,desc=description, locks=lockstring,
|
name, aliases=aliases, desc=description, locks=lockstring, typeclass=typeclass)
|
||||||
typeclass=typeclass)
|
|
||||||
new_chan.connect(caller)
|
new_chan.connect(caller)
|
||||||
return new_chan, ""
|
return new_chan, ""
|
||||||
|
|
||||||
|
|
@ -754,13 +760,17 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
||||||
subscribed, available = self.list_channels()
|
subscribed, available = self.list_channels()
|
||||||
if channel in subscribed:
|
if channel in subscribed:
|
||||||
table = self.display_subbed_channels([channel])
|
table = self.display_subbed_channels([channel])
|
||||||
self.msg(
|
inputname = self.raw_cmdname
|
||||||
"\n|wSubscribed to Channel|n "
|
if inputname.lower() != channel.key.lower():
|
||||||
f"(use |w/all|n to see all available)\n{table}")
|
header = f"Channel |w{inputname}|n (alias for {channel.key} channel)"
|
||||||
|
else:
|
||||||
|
header = f"Channel |w{channel.key}|n"
|
||||||
|
self.msg(f"{header}\n(use |w{inputname} <msg>|n to chat and "
|
||||||
|
f"the 'channel' command to customize)\n{table}")
|
||||||
elif channel in available:
|
elif channel in available:
|
||||||
table = self.display_all_channels([], [channel])
|
table = self.display_all_channels([], [channel])
|
||||||
self.msg(
|
self.msg(
|
||||||
"\n|wNot subscribed Channel|n (use /list to "
|
"\n|wNot subscribed to this channel|n (use /list to "
|
||||||
f"show all subscriptions)\n{table}")
|
f"show all subscriptions)\n{table}")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1291,9 +1291,10 @@ Custom arg markers
|
||||||
$N argument position (1-99)
|
$N argument position (1-99)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
_RE_OR = re.compile(r"(?<!\\)\|")
|
||||||
_RE_NICK_RE_ARG = re.compile(r"arg([1-9][0-9]?)")
|
_RE_NICK_RE_ARG = re.compile(r"arg([1-9][0-9]?)")
|
||||||
_RE_NICK_ARG = re.compile(r"\\(\$)([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"\\ ")
|
_RE_NICK_SPACE = re.compile(r"\\ ")
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1301,7 +1302,7 @@ class NickTemplateInvalid(ValueError):
|
||||||
pass
|
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.
|
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`
|
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
|
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
|
this case, instead of `$N`, numbered arguments must instead be given
|
||||||
as matching groups named as `argN`, such as `(?P<arg1>.+?)`. Such
|
as matching groups named as `argN`, such as `(?P<arg1>.+?)`.
|
||||||
groups can then also be made optional using e.g. `{?P<arg1>.*?}`.
|
replacement (str): The template to be used to replace the string
|
||||||
out_template (str): The template to be used to replace the string
|
|
||||||
matched by the pattern. This can contain `$N` markers and is never
|
matched by the pattern. This can contain `$N` markers and is never
|
||||||
parsed into a regex.
|
parsed into a regex.
|
||||||
|
pattern_is_regex (bool): If set, `pattern` is a full regex string
|
||||||
|
instead of containing shell patterns.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
regex, template (str): Regex to match against strings and template
|
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
|
evennia.typecalasses.attributes.NickTemplateInvalid: If the in/out
|
||||||
template does not have a matching number of `$args`.
|
template does not have a matching number of `$args`.
|
||||||
|
|
||||||
Example:
|
Examples:
|
||||||
in->out-template: `grin $1 -> emote gives a wicked grin to $1.`
|
- `pattern` (shell syntax): `"grin $1"`
|
||||||
|
- `pattern` (regex): `"grin (?P<arg1.+?>)"`
|
||||||
|
- `replacement`: `"emote gives a wicked grin to $1"`
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# create the regex from the pattern
|
# create the regex from the pattern
|
||||||
if pattern_is_regex:
|
if pattern_is_regex:
|
||||||
# Explicit regex given from the onset - this already contains argN groups
|
# Note that for a regex we can't validate in the way we do for the shell
|
||||||
pattern_regex_string = pattern + r"\Z"
|
# 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:
|
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 = fnmatch.translate(pattern)
|
||||||
pattern_regex_string = _RE_NICK_SPACE.sub(r"\\s+", pattern_regex_string)
|
pattern_regex_string = _RE_NICK_SPACE.sub(r"\\s+", pattern_regex_string)
|
||||||
pattern_regex_string = _RE_NICK_ARG.sub(
|
pattern_regex_string = _RE_NICK_ARG.sub(
|
||||||
lambda m: "(?P<arg%s>.+?)" % m.group(2), pattern_regex_string)
|
lambda m: "(?P<arg%s>.+?)" % m.group(2), pattern_regex_string)
|
||||||
# we must account for a possible line break coming over the wire
|
# we must account for a possible line break coming over the wire
|
||||||
pattern_regex_string = pattern_regex_string[:-2] + r"(?:[\n\r]*?)\Z"
|
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.")
|
|
||||||
|
|
||||||
# map the replacement to match the arg1 group-names, to make replacement easy
|
# 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):
|
def parse_nick_template(string, template_regex, outtemplate):
|
||||||
|
|
@ -1366,12 +1380,16 @@ def parse_nick_template(string, template_regex, outtemplate):
|
||||||
initialize_nick_template.
|
initialize_nick_template.
|
||||||
outtemplate (str): The template to which to map the matches
|
outtemplate (str): The template to which to map the matches
|
||||||
produced by the template_regex. This should have $1, $2,
|
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)
|
match = template_regex.match(string)
|
||||||
if match:
|
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
|
return False, string
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -177,23 +177,38 @@ class TestTags(EvenniaTest):
|
||||||
|
|
||||||
class TestNickHandler(EvenniaTest):
|
class TestNickHandler(EvenniaTest):
|
||||||
"""
|
"""
|
||||||
Test the nick handler mechanisms.
|
Test the nick handler replacement.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@parameterized.expand([
|
@parameterized.expand([
|
||||||
# ("gr $1 $2 at $3", "emote with a $1 smile, $2 grins at $3.", False,
|
# shell syntax
|
||||||
# "gr happy Foo at Bar", "emote with a happy smile, Foo grins at Bar."),
|
("gr $1 $2 at $3", "emote with a $1 smile, $2 grins at $3.", False,
|
||||||
# ("gr (?P<arg1>.+?) (?P<arg2>.+?) at (?P<arg3>.+?)",
|
"gr happy Foo at Bar", "emote with a happy smile, Foo grins at Bar."),
|
||||||
# "emote with a $1 smile, $2 grins at $3.", True,
|
# regex syntax
|
||||||
# "gr happy Foo at Bar", "emote with a happy smile, Foo grins at Bar."),
|
("gr (?P<arg1>.+?) (?P<arg2>.+?) at (?P<arg3>.+?)",
|
||||||
("groo $1", "channel groo = $1", True,
|
"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"),
|
"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"),
|
"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 = "),
|
"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 = "),
|
"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,
|
def test_nick_parsing(self, pattern, replacement, pattern_is_regex,
|
||||||
inp_string, expected_replaced):
|
inp_string, expected_replaced):
|
||||||
|
|
@ -201,6 +216,7 @@ class TestNickHandler(EvenniaTest):
|
||||||
Setting up nick patterns and make sure they replace as expected.
|
Setting up nick patterns and make sure they replace as expected.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
# from evennia import set_trace;set_trace()
|
||||||
self.char1.nicks.add(pattern, replacement,
|
self.char1.nicks.add(pattern, replacement,
|
||||||
category="inputline", pattern_is_regex=pattern_is_regex)
|
category="inputline", pattern_is_regex=pattern_is_regex)
|
||||||
actual_replaced = self.char1.nicks.nickreplace(inp_string)
|
actual_replaced = self.char1.nicks.nickreplace(inp_string)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue