summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorhut <hut@lavabit.com>2010-02-09 10:23:08 +0100
committerhut <hut@lavabit.com>2010-03-09 14:40:18 +0100
commitd8cd2b7573a43d94ac28a83d3ff4ffca14cde11a (patch)
tree66041eba74f955d29da9f56c3bdd9c5211f4a047
parenta32c6a1f417d249beaa02c416a790642bd94a96d (diff)
downloadranger-d8cd2b7573a43d94ac28a83d3ff4ffca14cde11a.tar.gz
started new key parser
-rw-r--r--test/tc_newkeys.py331
1 files changed, 331 insertions, 0 deletions
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()