3
0
mirror of https://github.com/jlu5/PyLink.git synced 2024-12-28 05:32:51 +01:00

utils: add match_text(), general glob matching function

In preparation for ircmatch removal (#636)
This commit is contained in:
James Lu 2019-06-21 12:11:46 -07:00
parent e25a5df4db
commit b7d93fe86a
2 changed files with 92 additions and 0 deletions

View File

@ -187,6 +187,70 @@ class UtilsTestCase(unittest.TestCase):
utils.parse_duration("4s3d")
utils.parse_duration("1m5w")
def test_match_text(self):
f = utils.match_text # glob, target
# Base cases
self.assertTrue(f("", ""))
self.assertFalse(f("test", ""))
self.assertFalse(f("", "abcdef"))
self.assertFalse(f("", "*")) # specified the wrong way
self.assertFalse(f("", "?"))
self.assertTrue(f("foo", "foo"))
self.assertFalse(f("foo", "bar"))
self.assertFalse(f("foo", "food"))
# Test use of *
self.assertTrue(f("*", "b"))
self.assertTrue(f("*", "abc"))
self.assertTrue(f("*", ""))
self.assertTrue(f("*!*@*", "nick!user@host"))
self.assertTrue(f("*@*", "rick!user@lost"))
self.assertTrue(f("ni*!*@*st", "nick!user@roast"))
self.assertFalse(f("nick!*abcdef*@*st*", "nick!user@roast"))
self.assertTrue(f("*!*@*.overdrive.pw", "abc!def@abc.users.overdrive.pw"))
# Test use of ?
self.assertTrue(f("?", "b"))
self.assertFalse(f("?", "abc"))
self.assertTrue(f("Guest?????!???irc@users.overdrive.pw", "Guest12567!webirc@users.overdrive.pw"))
self.assertFalse(f("Guest????!webirc@users.overdrive.pw", "Guest23457!webirc@users.overdrive.pw"))
def test_match_text_complex(self):
f = utils.match_text # glob, target
# Test combination of * and ?
for glob in {"*?", "?*"}:
self.assertTrue(f(glob, "a"))
self.assertTrue(f(glob, "ab"))
self.assertFalse(f(glob, ""))
self.assertTrue(f("ba*??*ll", "basketball"))
self.assertFalse(f("ba*??*ll", "ball"))
self.assertFalse(f("ba*??*ll", "basketballs"))
self.assertTrue(f("**", "fooBarBaz"))
self.assertTrue(f("*?*?*?*", "cat"))
self.assertTrue(f("*??****?*", "cat"))
self.assertFalse(f("*??****?*?****", "MAP"))
def test_match_text_casemangle(self):
f = utils.match_text # glob, target, manglefunc
# We are case insensitive by default
self.assertTrue(f("Test", "TEST"))
self.assertTrue(f("ALPHA*", "alphabet"))
# But we can override this preference
self.assertFalse(f("Test", "TEST", None))
self.assertFalse(f("*for*", "BEForE", None))
self.assertTrue(f("*corn*", "unicorns", None))
# Or specify some other filter func
self.assertTrue(f('005', '5', lambda s: s.zfill(3)))
self.assertTrue(f('*0*', '14', lambda s: s.zfill(6)))
self.assertFalse(f('*9*', '14', lambda s: s.zfill(13)))
self.assertTrue(f('*chin*', 'machine', str.upper))
if __name__ == '__main__':
unittest.main()

View File

@ -12,6 +12,7 @@ import os
import collections
import argparse
import ipaddress
import functools
from .log import log
from . import world, conf, structures
@ -821,3 +822,30 @@ def parse_duration(text):
raise ValueError("Failed to parse duration string %r" % text)
return result
@functools.lru_cache(maxsize=1024)
def _glob2re(glob):
"""Converts an IRC-style glob to a regular expression."""
patt = ['^']
for char in glob:
if char == '*' and patt[-1] != '*': # Collapse ** into *
patt.append('.*')
elif char == '?':
patt.append('.')
else:
patt.append(re.escape(char))
patt.append('$')
return ''.join(patt)
def match_text(glob, text, filterfunc=str.lower):
"""
Returns whether glob matches text. If filterfunc is specified, run filterfunc on glob and text
before preforming matches.
"""
if filterfunc:
glob = filterfunc(glob)
text = filterfunc(text)
return re.match(_glob2re(glob), text)