Skip to content

Commit eff5665

Browse files
authored
Add 'Either' pattern-matching operator (#313)
* Add 'Either' pattern-matching operator It's shorter to write than StrRe for simple cases, and does not require values to be escaped. * Remove incorrect check * Add tests
1 parent e943e2c commit eff5665

File tree

6 files changed

+60
-22
lines changed

6 files changed

+60
-22
lines changed

irctest/patma.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,17 @@ def __repr__(self) -> str:
5252
return f"NotStrRe(r'{self.regexp}')"
5353

5454

55+
@dataclasses.dataclass(frozen=True)
56+
class Either(Operator):
57+
options: str
58+
59+
def __init__(self, *options: Operator):
60+
object.__setattr__(self, "options", options)
61+
62+
def __repr__(self) -> str:
63+
return f"Either({', '.join(map(repr, self.options))})"
64+
65+
5566
@dataclasses.dataclass(frozen=True)
5667
class InsensitiveStr(Operator):
5768
string: str
@@ -116,6 +127,9 @@ def match_string(got: Optional[str], expected: Union[str, Operator, None]) -> bo
116127
elif isinstance(expected, NotStrRe):
117128
if got is None or re.match(expected.regexp + "$", got):
118129
return False
130+
elif isinstance(expected, Either):
131+
if all(not match_string(got, option) for option in expected.options):
132+
return False
119133
elif isinstance(expected, InsensitiveStr):
120134
if got is None or got.lower() != expected.string.lower():
121135
return False

irctest/self_tests/cases.py

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Internal checks of assertion implementations."""
22

3-
from typing import Dict, List, Tuple
3+
from typing import Any, Dict, List, Tuple
44

55
import pytest
66

@@ -11,6 +11,7 @@
1111
ANYLIST,
1212
ANYOPTSTR,
1313
ANYSTR,
14+
Either,
1415
ListRemainder,
1516
NotStrRe,
1617
OptStrRe,
@@ -19,7 +20,7 @@
1920
)
2021

2122
# fmt: off
22-
MESSAGE_SPECS: List[Tuple[Dict, List[str], List[str], List[str]]] = [
23+
MESSAGE_SPECS: List[Tuple[Dict[str, Any], List[str], List[str], List[str]]] = [
2324
(
2425
# the specification:
2526
dict(
@@ -263,6 +264,29 @@
263264
"expected params to match ['nick', '...', OptStrRe(r'[a-zA-Z]+')], got ['nick', '...', '']",
264265
]
265266
),
267+
(
268+
# the specification:
269+
dict(
270+
command="004",
271+
params=[Either("nick", "*", "."), "...", "trailer"], # type: ignore[arg-type]
272+
),
273+
# matches:
274+
[
275+
"004 nick ... trailer",
276+
"004 * ... trailer",
277+
"004 . ... trailer",
278+
],
279+
# and does not match:
280+
[
281+
"004 foo ... trailer",
282+
"004 f ... trailer",
283+
],
284+
# and they each error with:
285+
[
286+
"expected params to match [Either('nick', '*', '.'), '...', 'trailer'], got ['foo', '...', 'trailer']",
287+
"expected params to match [Either('nick', '*', '.'), '...', 'trailer'], got ['f', '...', 'trailer']",
288+
]
289+
),
266290
(
267291
# the specification:
268292
dict(

irctest/server_tests/connection_registration.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from irctest import cases
1111
from irctest.client_mock import ConnectionClosed
1212
from irctest.numerics import ERR_NEEDMOREPARAMS, ERR_PASSWDMISMATCH
13-
from irctest.patma import ANYLIST, ANYSTR, OptStrRe, StrRe
13+
from irctest.patma import ANYLIST, ANYSTR, Either, OptStrRe, StrRe
1414

1515

1616
class PasswordedConnectionRegistrationTestCase(cases.BaseServerTestCase):
@@ -292,7 +292,7 @@ def testEmptyRealname(self):
292292
self.assertMessageMatch(
293293
self.getRegistrationMessage(1),
294294
command=ERR_NEEDMOREPARAMS,
295-
params=[StrRe(r"(\*|foo)"), "USER", ANYSTR],
295+
params=[Either("*", "foo"), "USER", ANYSTR],
296296
)
297297

298298
def testNonutf8Realname(self):

irctest/server_tests/metadata_2.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
from irctest import cases, runner
1212
from irctest.numerics import RPL_METADATASUBOK
13-
from irctest.patma import ANYDICT, ANYLIST, ANYSTR, StrRe
13+
from irctest.patma import ANYDICT, ANYLIST, ANYSTR, Either, StrRe
1414

1515
CLIENT_NICKS = {
1616
1: "foo",
@@ -146,11 +146,11 @@ def assertSetValue(self, client, target, key, value, before_connect=False):
146146
self.sendLine(client, "METADATA {} SET {} :{}".format(target, key, value))
147147

148148
if target == "*":
149-
target = StrRe(r"(\*|" + CLIENT_NICKS[client] + ")")
149+
target = Either("*", CLIENT_NICKS[client])
150150

151151
nick = CLIENT_NICKS[client]
152152
if before_connect:
153-
nick = StrRe(r"(\*|" + CLIENT_NICKS[client] + ")")
153+
nick = Either("*", nick)
154154

155155
self.assertMessageMatch(
156156
self.getMessage(client),
@@ -162,7 +162,7 @@ def assertUnsetValue(self, client, target, key):
162162
self.sendLine(client, "METADATA {} SET {}".format(target, key))
163163

164164
if target == "*":
165-
target = StrRe(r"(\*|" + CLIENT_NICKS[client] + ")")
165+
target = Either("*", CLIENT_NICKS[client])
166166

167167
self.assertMessageMatch(
168168
self.getMessage(client),
@@ -174,11 +174,11 @@ def assertGetValue(self, client, target, key, value, before_connect=False):
174174
self.sendLine(client, "METADATA {} GET {}".format(target, key))
175175

176176
if target == "*":
177-
target = StrRe(r"(\*|" + CLIENT_NICKS[client] + ")")
177+
target = Either("*", CLIENT_NICKS[client])
178178

179179
nick = CLIENT_NICKS[client]
180180
if before_connect:
181-
nick = StrRe(r"(\*|" + CLIENT_NICKS[client] + ")")
181+
nick = Either("*", nick)
182182

183183
(batch_id, messages) = self.getBatchMessages(client)
184184
self.assertEqual(len(messages), 1, fail_msg="Expected one RPL_KEYVALUE")
@@ -192,7 +192,7 @@ def assertValueNotSet(self, client, target, key):
192192
self.sendLine(client, "METADATA {} GET {}".format(target, key))
193193

194194
if target == "*":
195-
target = StrRe(r"(\*|" + CLIENT_NICKS[client] + ")")
195+
target = Either("*", CLIENT_NICKS[client])
196196

197197
(batch_id, messages) = self.getBatchMessages(client)
198198
self.assertEqual(len(messages), 1, fail_msg="Expected one RPL_KEYVALUE")

irctest/server_tests/monitor.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
RPL_MONOFFLINE,
1515
RPL_MONONLINE,
1616
)
17-
from irctest.patma import ANYSTR, StrRe
17+
from irctest.patma import ANYSTR, Either, StrRe
1818

1919

2020
class _BaseMonitorTestCase(cases.BaseServerTestCase):
@@ -191,7 +191,7 @@ def testMonitorForbidsMasks(self):
191191
self.check_server_support()
192192
self.sendLine(1, "MONITOR + *!username@localhost")
193193
self.sendLine(1, "MONITOR + *[email protected]")
194-
expected_command = StrRe(f"({RPL_MONOFFLINE}|{ERR_ERRONEUSNICKNAME})")
194+
expected_command = Either(RPL_MONOFFLINE, ERR_ERRONEUSNICKNAME)
195195
try:
196196
m = self.getMessage(1)
197197
self.assertMessageMatch(m, command=expected_command)

irctest/server_tests/who.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
from irctest import cases, runner
1313
from irctest.numerics import RPL_ENDOFWHO, RPL_WHOREPLY, RPL_WHOSPCRPL, RPL_YOUREOPER
14-
from irctest.patma import ANYSTR, InsensitiveStr, StrRe
14+
from irctest.patma import ANYSTR, Either, InsensitiveStr, StrRe
1515

1616

1717
def realname_regexp(realname):
@@ -60,7 +60,7 @@ def _checkReply(self, reply, flags):
6060
"*", # no chan
6161
StrRe("~?" + self.username),
6262
StrRe(host_re),
63-
StrRe(r"(My.Little.Server|\*)"),
63+
Either("My.Little.Server", "*"),
6464
"coolNick",
6565
flags,
6666
StrRe(realname_regexp(self.realname)),
@@ -76,7 +76,7 @@ def _checkReply(self, reply, flags):
7676
"#chan",
7777
StrRe("~?" + self.username),
7878
StrRe(host_re),
79-
StrRe(r"(My.Little.Server|\*)"),
79+
Either("My.Little.Server", "*"),
8080
"coolNick",
8181
flags + "@",
8282
StrRe(realname_regexp(self.realname)),
@@ -336,7 +336,7 @@ def testWhoChan(self, mask):
336336
"#chan",
337337
StrRe("~?" + self.username),
338338
StrRe(host_re),
339-
StrRe(r"(My.Little.Server|\*)"),
339+
Either("My.Little.Server", "*"),
340340
"coolNick",
341341
"G@",
342342
StrRe(realname_regexp(self.realname)),
@@ -351,7 +351,7 @@ def testWhoChan(self, mask):
351351
"#chan",
352352
ANYSTR,
353353
ANYSTR,
354-
StrRe(r"(My.Little.Server|\*)"),
354+
Either("My.Little.Server", "*"),
355355
"otherNick",
356356
"H",
357357
StrRe("[0-9]+ .*"),
@@ -398,7 +398,7 @@ def testWhoMultiChan(self):
398398
chan,
399399
ANYSTR,
400400
ANYSTR,
401-
StrRe(r"(My.Little.Server|\*)"),
401+
Either("My.Little.Server", "*"),
402402
"coolNick",
403403
ANYSTR,
404404
ANYSTR,
@@ -413,7 +413,7 @@ def testWhoMultiChan(self):
413413
chan,
414414
ANYSTR,
415415
ANYSTR,
416-
StrRe(r"(My.Little.Server|\*)"),
416+
Either("My.Little.Server", "*"),
417417
"otherNick",
418418
ANYSTR,
419419
ANYSTR,
@@ -475,11 +475,11 @@ def _testWhoxFull(self, chars):
475475
params=[
476476
"otherNick",
477477
"123",
478-
StrRe(r"(#chan|\*)"),
478+
Either("#chan", "*"),
479479
StrRe("~?myusernam"),
480480
ANYSTR,
481481
ANYSTR,
482-
StrRe(r"(My.Little.Server|\*)"),
482+
Either("My.Little.Server", "*"),
483483
"coolNick",
484484
StrRe("H@?"),
485485
ANYSTR, # hopcount

0 commit comments

Comments
 (0)