Skip to content

Commit 06add14

Browse files
Exireldgw
andcommitted
plugin: remove support for regex in command name
Co-authored-by: dgw <[email protected]>
1 parent 13237dc commit 06add14

File tree

3 files changed

+67
-109
lines changed

3 files changed

+67
-109
lines changed

sopel/plugin.py

Lines changed: 28 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -582,17 +582,16 @@ def command(*command_list: str) -> Callable:
582582
583583
.. note::
584584
585-
You can use a regular expression for the command name(s), but this is
586-
**not recommended** since version 7.1. For backward compatibility,
587-
this behavior will be kept until version 8.0.
585+
The command name will be escaped for use in a regular expression.
586+
As such it is not possible to use something like ``.command\\d+`` to
587+
catch something like ``.command1`` or ``.command2``.
588588
589-
Regex patterns are confusing for your users; please don't use them in
590-
command names!
589+
You have several options at your disposal to replace a regex in the
590+
command name:
591591
592-
If you still want to use a regex pattern, please use the :func:`rule`
593-
decorator instead. For extra arguments and subcommands based on a regex
594-
pattern, you should handle these inside your decorated function, by
595-
using the ``trigger`` object.
592+
* use a command alias
593+
* parse the arguments with your own regex within your plugin callable
594+
* use a :func:`rule` instead
596595
597596
"""
598597
def add_attribute(function):
@@ -629,15 +628,18 @@ def nickname_command(*command_list: str) -> Callable:
629628
630629
.. note::
631630
632-
You can use a regular expression for the command name(s), but this is
633-
**not recommended** since version 7.1. For backward compatibility,
634-
this behavior will be kept until version 8.0.
631+
The command name will be escaped to be used in a regex command. As such
632+
it is not possible to use something like ``command\\d+`` to catch
633+
something like ``Bot: command1`` or ``Bot: command2``.
635634
636-
Regex patterns are confusing for your users; please don't use them in
637-
command names!
635+
You have several options at your disposal to replace a regex in the
636+
command name:
638637
639-
If you need to use a regex pattern, please use the :func:`rule`
640-
decorator instead, with the ``$nick`` variable::
638+
* use a command alias
639+
* parse the arguments with your own regex within your plugin callable
640+
* use a :func:`rule`
641+
642+
The :func:`rule` can be used with a ``$nick`` variable::
641643
642644
@rule(r'$nick .*')
643645
# Would trigger on anything starting with "$nickname[:,]? ",
@@ -679,15 +681,18 @@ def action_command(*command_list: str) -> Callable:
679681
680682
.. note::
681683
682-
You can use a regular expression for the command name(s), but this is
683-
**not recommended** since version 7.1. For backward compatibility,
684-
this behavior will be kept until version 8.0.
684+
The command name will be escaped for use in a regular expression.
685+
As such it is not possible to use something like ``/me command\\d+``
686+
to catch something like ``/me command1`` or ``/me command2``.
687+
688+
You have several options at your disposal to replace a regex in the
689+
command name:
685690
686-
Regex patterns are confusing for your users; please don't use them in
687-
command names!
691+
* use a command alias
692+
* parse the arguments with your own regex within your plugin callable
693+
* use a :func:`rule`
688694
689-
If you need to use a regex pattern, please use the :func:`rule`
690-
decorator instead, with the :func:`ctcp` decorator::
695+
The :func:`rule` must be used with the :func:`ctcp` decorator::
691696
692697
@rule(r'hello!?')
693698
@ctcp('ACTION')

sopel/plugins/rules.py

Lines changed: 6 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1249,41 +1249,6 @@ def has_alias(self, name):
12491249
"""
12501250
return name in self._aliases
12511251

1252-
def escape_name(self, name):
1253-
"""Escape the provided name if needed.
1254-
1255-
.. note::
1256-
1257-
Until now, Sopel has allowed command name to be regex pattern.
1258-
It was mentioned in the documentation without much details, and
1259-
there were no tests for it.
1260-
1261-
In order to ensure backward compatibility with previous versions of
1262-
Sopel, we make sure to escape command name only when it's needed.
1263-
1264-
**It is not recommended to use a regex pattern for your command
1265-
name. This feature will be removed in Sopel 8.0.**
1266-
1267-
"""
1268-
if set('.^$*+?{}[]\\|()') & set(name):
1269-
# the name contains a regex pattern special character
1270-
# we assume the user knows what they are doing
1271-
try:
1272-
# make sure it compiles properly
1273-
# (nobody knows what they are doing)
1274-
re.compile(name)
1275-
except re.error as error:
1276-
original_name = name
1277-
name = re.escape(name)
1278-
LOGGER.warning(
1279-
'Command name "%s" is an invalid regular expression '
1280-
'and will be replaced by "%s": %s',
1281-
original_name, name, error)
1282-
else:
1283-
name = re.escape(name)
1284-
1285-
return name
1286-
12871252
@abc.abstractmethod
12881253
def get_rule_regex(self):
12891254
"""Make the rule regex for this named rule.
@@ -1403,8 +1368,8 @@ def get_rule_regex(self):
14031368
14041369
to create a compiled regex to return.
14051370
"""
1406-
name = [self.escape_name(self._name)]
1407-
aliases = [self.escape_name(alias) for alias in self._aliases]
1371+
name = [re.escape(self._name)]
1372+
aliases = [re.escape(alias) for alias in self._aliases]
14081373
pattern = r'|'.join(name + aliases)
14091374

14101375
# Escape all whitespace with a single backslash.
@@ -1525,8 +1490,8 @@ def get_rule_regex(self):
15251490
15261491
to create a compiled regex to return.
15271492
"""
1528-
name = [self.escape_name(self._name)]
1529-
aliases = [self.escape_name(alias) for alias in self._aliases]
1493+
name = [re.escape(self._name)]
1494+
aliases = [re.escape(alias) for alias in self._aliases]
15301495
pattern = r'|'.join(name + aliases)
15311496

15321497
return _compile_pattern(
@@ -1610,8 +1575,8 @@ def get_rule_regex(self):
16101575
16111576
to create a compiled regex to return.
16121577
"""
1613-
name = [self.escape_name(self._name)]
1614-
aliases = [self.escape_name(alias) for alias in self._aliases]
1578+
name = [re.escape(self._name)]
1579+
aliases = [re.escape(alias) for alias in self._aliases]
16151580
pattern = r'|'.join(name + aliases)
16161581
pattern = self.PATTERN_TEMPLATE.format(command=pattern)
16171582
return re.compile(pattern, re.IGNORECASE | re.VERBOSE)

test/plugins/test_plugins_rules.py

Lines changed: 33 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -2125,10 +2125,7 @@ def handler(wrapped, trigger):
21252125
assert result.group(1) == 'reverse'
21262126

21272127

2128-
def test_command_from_callable_regex_pattern(mockbot):
2129-
# TODO: this test must FAIL for Sopel 8.0
2130-
# Command name as regex pattern will be removed in Sopel 8.0
2131-
2128+
def test_command_from_callable_escaped_regex_pattern(mockbot):
21322129
# prepare callable
21332130
@plugin.commands('main .*')
21342131
def handler(wrapped, trigger):
@@ -2139,21 +2136,23 @@ def handler(wrapped, trigger):
21392136
# create rule from a cleaned callable
21402137
rule = rules.Command.from_callable(mockbot.settings, handler)
21412138

2142-
# match on ".main anything"
2139+
# does not match on ".main anything"
21432140
line = ':[email protected] PRIVMSG #sopel :.main anything'
21442141
pretrigger = trigger.PreTrigger(mockbot.nick, line)
21452142
results = list(rule.match(mockbot, pretrigger))
21462143

2144+
assert not results, 'Regex commands are not allowed since Sopel 8.0'
2145+
2146+
# match on ".main .*"
2147+
line = ':[email protected] PRIVMSG #sopel :.main .*'
2148+
pretrigger = trigger.PreTrigger(mockbot.nick, line)
2149+
results = list(rule.match(mockbot, pretrigger))
2150+
21472151
assert len(results) == 1, (
2148-
'Exactly 1 command must match; MUST fail for Sopel 8.0')
2152+
'Command name must be escaped to get an exact match')
21492153
result = results[0]
2150-
assert result.group(0) == '.main anything'
2151-
assert result.group(1) == 'main anything'
2152-
assert result.group(2) is None
2153-
assert result.group(3) is None
2154-
assert result.group(4) is None
2155-
assert result.group(5) is None
2156-
assert result.group(6) is None
2154+
assert result.group(0) == '.main .*'
2155+
assert result.group(1) == 'main .*'
21572156

21582157

21592158
def test_command_from_callable_invalid(mockbot):
@@ -2169,21 +2168,6 @@ def handler(wrapped, trigger):
21692168
rules.Command.from_callable(mockbot.settings, handler)
21702169

21712170

2172-
def test_command_escape_name():
2173-
rule = rules.Command('hello', r'\.', plugin='testplugin')
2174-
2175-
assert rule.escape_name('hello') == 'hello'
2176-
assert rule.escape_name('hello world') == r'hello\ world'
2177-
assert rule.escape_name(r'hello\ world') == r'hello\ world', (
2178-
'Valid pattern must not be escaped')
2179-
assert rule.escape_name(r'.*') == r'.*', (
2180-
'Valid pattern must not be escaped')
2181-
assert rule.escape_name(r'a[bc]d') == r'a[bc]d', (
2182-
'Valid pattern must not be escaped')
2183-
assert rule.escape_name(r'hello(') == r'hello\(', (
2184-
'Invalid pattern must be escaped')
2185-
2186-
21872171
# -----------------------------------------------------------------------------
21882172
# tests for :class:`sopel.plugins.rules.NickCommand`
21892173

@@ -2444,10 +2428,6 @@ def handler(wrapped, trigger):
24442428

24452429

24462430
def test_nick_command_from_callable_regex_pattern(mockbot):
2447-
# TODO: this test must FAIL for Sopel 8.0
2448-
# Command name as regex pattern will be removed in Sopel 8.0
2449-
2450-
# prepare callable
24512431
@plugin.nickname_commands('do .*')
24522432
def handler(wrapped, trigger):
24532433
wrapped.reply('Hi!')
@@ -2457,16 +2437,21 @@ def handler(wrapped, trigger):
24572437
# create rule from a cleaned callable
24582438
rule = rules.NickCommand.from_callable(mockbot.settings, handler)
24592439

2460-
# match on ".main anything"
2440+
# does not match on ".do anything"
24612441
line = ':[email protected] PRIVMSG #sopel :TestBot: do anything'
24622442
pretrigger = trigger.PreTrigger(mockbot.nick, line)
24632443
results = list(rule.match(mockbot, pretrigger))
24642444

2465-
assert len(results) == 1, (
2466-
'Exactly 1 command must match; MUST fail for Sopel 8.0')
2445+
assert not results, 'Regex commands are not allowed since Sopel 8.0'
2446+
2447+
# match on ".do .*"
2448+
line = ':[email protected] PRIVMSG #sopel :TestBot: do .*'
2449+
pretrigger = trigger.PreTrigger(mockbot.nick, line)
2450+
results = list(rule.match(mockbot, pretrigger))
2451+
assert len(results) == 1, 'Exactly 1 command must match'
24672452
result = results[0]
2468-
assert result.group(0) == 'TestBot: do anything'
2469-
assert result.group(1) == 'do anything'
2453+
assert result.group(0) == 'TestBot: do .*'
2454+
assert result.group(1) == 'do .*'
24702455
assert result.group(2) is None
24712456
assert result.group(3) is None
24722457
assert result.group(4) is None
@@ -2649,9 +2634,6 @@ def handler(wrapped, trigger):
26492634

26502635

26512636
def test_action_command_from_callable_regex_pattern(mockbot):
2652-
# TODO: this test must FAIL for Sopel 8.0
2653-
# Command name as regex pattern will be removed in Sopel 8.0
2654-
26552637
# prepare callable
26562638
@plugin.action_commands('do .*')
26572639
def handler(wrapped, trigger):
@@ -2662,16 +2644,22 @@ def handler(wrapped, trigger):
26622644
# create rule from a cleaned callable
26632645
rule = rules.ActionCommand.from_callable(mockbot.settings, handler)
26642646

2665-
# match on ".main anything"
2647+
# does not match on ".do anything"
26662648
line = ':[email protected] PRIVMSG #sopel :\x01ACTION do anything\x01'
26672649
pretrigger = trigger.PreTrigger(mockbot.nick, line)
26682650
results = list(rule.match(mockbot, pretrigger))
26692651

2670-
assert len(results) == 1, (
2671-
'Exactly 1 command must match; MUST fail for Sopel 8.0')
2652+
assert not results, 'Regex commands are not allowed since Sopel 8.0'
2653+
2654+
# match on ".do .*"
2655+
line = ':[email protected] PRIVMSG #sopel :\x01ACTION do .*\x01'
2656+
pretrigger = trigger.PreTrigger(mockbot.nick, line)
2657+
results = list(rule.match(mockbot, pretrigger))
2658+
2659+
assert len(results) == 1, 'Exactly 1 command must match'
26722660
result = results[0]
2673-
assert result.group(0) == 'do anything'
2674-
assert result.group(1) == 'do anything'
2661+
assert result.group(0) == 'do .*'
2662+
assert result.group(1) == 'do .*'
26752663
assert result.group(2) is None
26762664
assert result.group(3) is None
26772665
assert result.group(4) is None

0 commit comments

Comments
 (0)