From d8cd2b7573a43d94ac28a83d3ff4ffca14cde11a Mon Sep 17 00:00:00 2001 From: hut Date: Tue, 9 Feb 2010 10:23:08 +0100 Subject: started new key parser --- test/tc_newkeys.py | 331 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 331 insertions(+) create mode 100644 test/tc_newkeys.py diff --git a/test/tc_newkeys.py b/test/tc_newkeys.py new file mode 100644 index 00000000..4b46b0f8 --- /dev/null +++ b/test/tc_newkeys.py @@ -0,0 +1,331 @@ +if __name__ == '__main__': from __init__ import init; init() +from unittest import TestCase, main + +from inspect import isfunction, getargspec +import inspect +from sys import intern + +FUNC = 'func' +DIRECTION = 'direction' +QUANTIFIER = 'n' +MATCH = intern('!!') + +def to_string(i): + """convert a ord'd integer to a string""" + try: + return chr(i) + except ValueError: + return '?' + +def is_ascii_digit(n): + return n >= 48 and n <= 57 + +class Direction(object): + """An object with a down and right method""" + def __init__(self, down=0, right=0): + self.down = down + self.right = right + + def copy(self): + new = type(self)() + new.__dict__.update(self.__dict__) + return new + + def __mul__(self, other): + copy = self.copy() + if other is not None: + copy.down *= other + copy.right *= other + return copy + +class CommandArgs(object): + """The arguments which are passed to a keybinding function""" + def __init__(self, fm, widget, keybuffer): + self.fm = fm + self.wdg = widget + self.keybuffer = keybuffer + self.n = keybuffer.quant1 + self.direction = keybuffer.direction + self.keys = str(keybuffer) + +class KeyBuffer(object): + """The evaluator and storage for pressed keys""" + def __init__(self, keymap, direction_keys): + self.keymap = keymap + self.direction_keys = direction_keys + self.clear() + + def add(self, key): + if self.failure: + return None + assert isinstance(key, int) + assert key >= 0 + + # evaluate first quantifier + if self.level == 0: + if is_ascii_digit(key): + if self.quant1 is None: + self.quant1 = 0 + self.quant1 = self.quant1 * 10 + key - 48 + else: + self.level = 1 + + # evaluate the command and the second quantifier. + # it's possible to jump between them. "x3xj" is equivalent to "xx3j" + if self.level == 1: + try: + self.tree_pointer = self.tree_pointer[key] + except KeyError: + try: + match = self.tree_pointer[MATCH] + except KeyError: + self.failure = True + return None + # self.command = match + if is_ascii_digit(key): + if self.quant2 is None: + self.quant2 = 0 + self.quant2 = self.quant2 * 10 + key - 48 + else: + self.level = 2 + self.tree_pointer = self.direction_keys._tree + else: + try: + match = self.tree_pointer[MATCH] + except KeyError: + pass + else: + self.command = match + if not match.has_direction: + if self.quant2 is not None: + self.direction = self.direction * self.quant2 + self.done = True + + # evaluate direction keys {j,k,gg,pagedown,...} + if self.level == 2: + try: + self.tree_pointer = self.tree_pointer[key] + except KeyError: + self.failure = True + else: + try: + match = self.tree_pointer[MATCH] + except KeyError: + pass + else: + self.direction = match.actions['dir'] * self.quant2 + self.done = True + + + def clear(self): + self.failure = False + self.done = False + self.quant1 = None + self.quant2 = None + self.command = None + self.direction = Direction(down=1) + self.all_keys = [] + self.tree_pointer = self.keymap._tree + self.direction_tree_pointer = self.direction_keys._tree + self.level = 0 + # level 0 = parsing quantifier 1 + # 1 = parsing command or quantifier 2 + # 2 = parsing direction + + def __str__(self): + """returns a concatenation of all characters""" + return "".join(to_string(c) for c in self.all_keys) + + def simulate_press(self, string): + for char in string: + self.add(ord(char)) + if self.done: + return self.command + if self.failure: + break + +class Keymap(object): + """Contains a tree with all the keybindings""" + def __init__(self): + self._tree = dict() + + def add(self, *args, **keywords): + if keywords: + return self.add_binding(*args, **keywords) + firstarg = args[0] + if isfunction(firstarg): + keywords[FUNC] = firstarg + return self.add_binding(*args[1:], **keywords) + def decorator_function(func): + keywords = {FUNC:func} + self.add(*args, **keywords) + return func + return decorator_function + + def _split(self, key): + assert isinstance(key, (tuple, int, str)) + if isinstance(key, tuple): + return key + if isinstance(key, str): + return (ord(k) for k in key) + if isinstance(key, int): + return (key, ) + raise TypeError(key) + + def add_binding(self, *keys, **actions): + assert keys + bind = binding(keys, actions) + + for key in keys: + assert key + chars = tuple(self._split(key)) + tree = self.traverse_tree(chars) + tree[MATCH] = bind + + def traverse_tree(self, generator): + tree = self._tree + for char in generator: + try: + tree = tree[char] + except KeyError: + tree[char] = dict() + tree = tree[char] + except TypeError: + raise TypeError("Attempting to override existing entry") + return tree + + def __getitem__(self, key): + tree = self._tree + for char in self._split(key): + try: + tree = tree[char] + except TypeError: + raise KeyError("trying to enter leaf") + except KeyError: + raise KeyError(str(char) + " not in tree " + str(tree)) + try: + return tree[MATCH] + except KeyError: + raise KeyError(str(char) + " not in tree " + str(tree)) + +class binding(object): + """The keybinding object""" + def __init__(self, keys, actions): + assert hasattr(keys, '__iter__') + assert isinstance(actions, dict) + self.keys = set(keys) + self.actions = actions + try: + self.function = self.actions[FUNC] + except KeyError: + self.function = None + self.has_direction = False + else: + argnames = getargspec(self.function)[0] + try: + self.has_direction = actions['with_direction'] + except KeyError: + self.has_direction = DIRECTION in argnames + + def add_keys(self, keys): + assert isinstance(keys, set) + self.keys |= keys + + def has(self, action): + return action in self.actions + + def action(self, key): + return self.actions[key] + +def n(value): + """ return n or value """ + def fnc(n=None): + if n is None: + return value + return n + return fnc + +def nd(n=1, direction=Direction()): + """ n * direction """ + if n is None: + n = 1 + return n * direction.down + +class Test(TestCase): + """The test cases""" + def test_add(self): + c = Keymap() + c.add(lambda *_: 'lolz', 'aa', 'b') + self.assert_(c['aa'].actions[FUNC](), 'lolz') + @c.add('a', 'c') + def test(): + return 5 + self.assert_(c['b'].actions[FUNC](), 'lolz') + self.assert_(c['c'].actions[FUNC](), 5) + self.assert_(c['a'].actions[FUNC](), 5) + + def test_quantifier(self): + km = Keymap() + directions = Keymap() + kb = KeyBuffer(km, directions) + km.add(n(5), 'd') + match = kb.simulate_press('3d') + self.assertEqual(3, match.function(kb.quant1)) + kb.clear() + match = kb.simulate_press('6223d') + self.assertEqual(6223, match.function(kb.quant1)) + kb.clear() + + def test_direction(self): + km = Keymap() + directions = Keymap() + kb = KeyBuffer(km, directions) + directions.add('j', dir=Direction(down=1)) + directions.add('k', dir=Direction(down=-1)) + km.add(nd, 'd') + km.add('dd', func=nd, with_direction=False) + + match = kb.simulate_press('3d5j') + self.assertEqual(15, match.function(n=kb.quant1, direction=kb.direction)) + kb.clear() + + match = kb.simulate_press('3d5k') + self.assertEqual(-15, match.function(n=kb.quant1, direction=kb.direction)) + kb.clear() + + match = kb.simulate_press('3d5d') + self.assertEqual(15, match.function(n=kb.quant1, direction=kb.direction)) + kb.clear() + + match = kb.simulate_press('3dd') + self.assertEqual(3, match.function(n=kb.quant1, direction=kb.direction)) + kb.clear() + + match = kb.simulate_press('dd') + self.assertEqual(1, match.function(n=kb.quant1, direction=kb.direction)) + kb.clear() + + km.add(nd, 'x') + km.add('xxxx', func=nd, with_direction=False) + + match = kb.simulate_press('xxxxj') + self.assertEqual(1, match.function(n=kb.quant1, direction=kb.direction)) + kb.clear() + + match = kb.simulate_press('xxxxjsomeinvalidchars') + self.assertEqual(1, match.function(n=kb.quant1, direction=kb.direction)) + kb.clear() + + self.assertEqual(None, kb.simulate_press('xxxj')) + kb.clear() + self.assertEqual(None, kb.simulate_press('xxj')) + kb.clear() + self.assertEqual(None, kb.simulate_press('xxkldfjalksdjklsfsldkj')) + kb.clear() + self.assertEqual(None, kb.simulate_press('xyj')) + kb.clear() + self.assertEqual(None, kb.simulate_press('x')) #direction missing + kb.clear() + + +if __name__ == '__main__': main() -- cgit 1.4.1-2-gfad0