Author: fw Date: 2010-05-07 18:50:02 +0000 (Fri, 07 May 2010) New Revision: 14622 Added: lib/python/regexpcase.py Log: lib/python/regexpcase.py: dispatching on regular expressions Added: lib/python/regexpcase.py ==================================================================--- lib/python/regexpcase.py (rev 0) +++ lib/python/regexpcase.py 2010-05-07 18:50:02 UTC (rev 14622) @@ -0,0 +1,127 @@ +# regexpcase.py -- Python module for regexp-based dispatching +# Copyright (C) 2009, 2010 Florian Weimer <fw at deneb.enyo.de> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +import re + +class RegexpCase(object): + def __init__(self, rules, prefix=None, suffix=None, default=None): + offset = 0 + probes = [] + maycall = default is None or callable(default) + + # We use a single regular expression and use special probe + # captures to figure out which one has actually matched. + # Hopefully, the regular expression engine will make this run + # fast. + for (regexp, action) in rules: + compiled = re.compile(regexp) + probes.append((offset, offset + 1, offset + compiled.groups + 1, + action)) + offset += compiled.groups + 1 + if action is not None: + maycall = maycall and callable(action) + self.probes = tuple(probes) + self.maycall = maycall + + if not self.probes: + raise ValueError("empty rule list") + if prefix is None: + prefix = "^(?:(" + else: + if re.compile(prefix).groups > 0: + raise ValueError("prefix must not contain captures") + prefix = "^(?:" + prefix + ")(?:(" + + if suffix is None: + suffix = "))$" + else: + if re.compile(suffix).groups > 0: + raise ValueError("suffix must not contain captures") + suffix = "))(?:" + suffix + ")$" + + self.regexp = re.compile( + prefix + '')|(''.join(regexp for (regexp, action) in rules) + + suffix) + + self.default = default + + def match(self, key): + match = self.regexp.match(key) + if match is None: + return (None, self.default) + groups = match.groups() + for (probe, i, j, action) in self.probes: + if groups[probe] is not None: + return (groups[i:j], action) + raise AssertionError("pattern and offset list incongruent") + + def __getitem__(self, key): + return self.match(key)[1] + + def __call__(self, key, *args): + if not self.maycall: + raise TypeError, "not all actions are callable" + (groups, action) = self.match(key) + if action is None: + return None + if groups is None: + groups = key + return action(groups, *args) + +def rule(regexp): + """Add a regular expression to the function, for the rule list""" + return lambda f: (regexp, f) + +if __name__ == "__main__": + import unittest + + class TestRegexpCase(unittest.TestCase): + def testempty(self): + self.assertRaises(ValueError, RegexpCase, ()) + self.assertRaises(ValueError, RegexpCase, (), prefix="foo") + self.assertRaises(ValueError, RegexpCase, (), suffix="foo") + self.assertRaises(ValueError, RegexpCase, (), default="foo") + self.assertRaises(ValueError, RegexpCase, (("two", 2)), + prefix="(f)oo") + self.assertRaises(ValueError, RegexpCase, (("two", 2)), + suffix="(f)oo") + + def teststrings(self): + rc = RegexpCase((("two", 2), + ("three", 3), + ("five", 5))) + self.assertEqual(2, rc["two"]) + self.assertEqual(3, rc["three"]) + self.assertEqual(5, rc["five"]) + self.assertEqual(None, rc["seven"]) + self.assertEquals((None, None), rc.match("seven")) + self.assertRaises(TypeError, rc.__call__, ()) + + def testcallstrings(self): + rc = RegexpCase((("(two)", lambda groups, x: (groups, x)), + ("three", lambda groups, x: (groups, x)), + ("f(i)v(e)", lambda groups, x : (groups, x)))) + self.assertEqual((("two",), -2), rc("two", -2)) + self.assertEqual(((), -3), rc("three", -3)) + self.assertEqual((tuple("ie"), -5), rc("five", -5)) + self.assertEqual(None, rc("seven", -1)) + def testcallstringsdefault(self): + rc = RegexpCase([("f(i)v(e)", lambda groups, x : (groups, x))], + default=lambda key, x: (key, x)) + self.assertEqual(("seven", -1), rc("seven", -1)) + + unittest.main()