tests for macro running and parsing

This commit is contained in:
sezanzeb 2020-11-28 18:27:28 +01:00
parent 8d23593c89
commit ace421e84a
2 changed files with 87 additions and 38 deletions

View File

@ -42,20 +42,23 @@ import time
import re import re
import random import random
try:
from rich.traceback import install
install(show_locals=True)
except ImportError:
pass
from keymapper.logger import logger from keymapper.logger import logger
logger.setLevel(5)
class Macro: class Macro:
"""Supports chaining and preparing actions.""" """Supports chaining and preparing actions."""
def __init__(self): def __init__(self, handler):
"""Create a macro instance that can be populated with tasks.
Parameters
----------
handler : func
A function that accepts keycodes as the first parameter and the
key-press state as the second. 1 for down and 0 for up. The
macro will write to this function once executed with `.run()`.
"""
self.tasks = [] self.tasks = []
self.handler = handler
def run(self): def run(self):
"""Run the macro.""" """Run the macro."""
@ -75,9 +78,9 @@ class Macro:
modifier : str modifier : str
macro : Macro macro : Macro
""" """
# TODO press modifier down self.tasks.append(lambda: self.handler(modifier, 1))
self.tasks.append(macro.run) self.tasks.append(macro.run)
# TODO release modifier self.tasks.append(lambda: self.handler(modifier, 0))
return self return self
def repeat(self, repeats, macro): def repeat(self, repeats, macro):
@ -94,8 +97,8 @@ class Macro:
def keycode(self, character): def keycode(self, character):
"""Write the character.""" """Write the character."""
# TODO write character self.tasks.append(lambda: self.handler(character, 1))
self.tasks.append(lambda: print(character)) self.tasks.append(lambda: self.handler(character, 0))
return self return self
def wait(self, min, max=None): def wait(self, min, max=None):
@ -105,7 +108,7 @@ class Macro:
return self return self
def parse(macro): def parse(macro, handler):
"""parse and generate a Macro that can be run as often as you want. """parse and generate a Macro that can be run as often as you want.
Parameters Parameters
@ -114,13 +117,13 @@ def parse(macro):
"r(3, k(a).w(10))" "r(3, k(a).w(10))"
"r(2, k(a).k(-)).k(b)" "r(2, k(a).k(-)).k(b)"
"w(1000).m(SHIFT_L, r(2, k(a))).w(10, 20).k(b)" "w(1000).m(SHIFT_L, r(2, k(a))).w(10, 20).k(b)"
handler : func
A function that accepts keycodes as the first parameter and the
key-press state as the second. 1 for down and 0 for up. The
macro will write to this function once executed with `.run()`.
""" """
try: # simpler function prototype and docstring than parse_recurse
return parse_recurse(macro) return parse_recurse(macro, handler)
except Exception as e:
logger.error(e)
# parsing unsuccessful
return None
def extract_params(inner): def extract_params(inner):
@ -154,13 +157,15 @@ def extract_params(inner):
return params return params
def parse_recurse(macro, macro_instance=None, depth=0): def parse_recurse(macro, handler, macro_instance=None, depth=0):
"""Handle a subset of the macro, e.g. one parameter or function call. """Handle a subset of the macro, e.g. one parameter or function call.
Parameters Parameters
---------- ----------
macro : string macro : string
Just like parse Just like parse
handler : function
passed to Macro constructors
macro_instance : Macro or None macro_instance : Macro or None
A macro instance to add tasks to A macro instance to add tasks to
depth : int depth : int
@ -170,8 +175,16 @@ def parse_recurse(macro, macro_instance=None, depth=0):
# please make a pull request. Because it probably is. # please make a pull request. Because it probably is.
# not using eval for security reasons ofc. And this syntax doesn't need # not using eval for security reasons ofc. And this syntax doesn't need
# string quotes for its params. # string quotes for its params.
# If this gets more complicated than that I'd rather make a macro
# editor GUI and store them as json.
assert isinstance(macro, str)
assert callable(handler)
assert isinstance(depth, int)
if macro_instance is None: if macro_instance is None:
macro_instance = Macro() macro_instance = Macro(handler)
else:
assert isinstance(macro_instance, Macro)
macro = macro.strip() macro = macro.strip()
logger.spam('%sinput %s', ' ' * depth, macro) logger.spam('%sinput %s', ' ' * depth, macro)
@ -221,7 +234,7 @@ def parse_recurse(macro, macro_instance=None, depth=0):
logger.spam('%scalls %s with %s', space, call, string_params) logger.spam('%scalls %s with %s', space, call, string_params)
# evaluate the params # evaluate the params
params = [ params = [
parse_recurse(param.strip(), None, depth + 1) parse_recurse(param.strip(), handler, None, depth + 1)
for param in string_params for param in string_params
] ]
@ -232,7 +245,7 @@ def parse_recurse(macro, macro_instance=None, depth=0):
if len(macro) > position and macro[position] == '.': if len(macro) > position and macro[position] == '.':
chain = macro[position + 1:] chain = macro[position + 1:]
logger.spam('%sfollowed by %s', space, chain) logger.spam('%sfollowed by %s', space, chain)
parse_recurse(chain, macro_instance, depth) parse_recurse(chain, handler, macro_instance, depth)
return macro_instance return macro_instance
else: else:
@ -242,10 +255,3 @@ def parse_recurse(macro, macro_instance=None, depth=0):
except ValueError: except ValueError:
pass pass
return macro return macro
parse("k(1).k(2).k(3)").run()
parse("r(1, k(2))").run()
parse("r(3, k(a).w(10))").run()
parse("r(2, k(a).k(-)).k(b)").run()
parse("w(1000).m(SHIFT_L, r(2, k(a))).w(10, 20).k(b)").run()

View File

@ -21,24 +21,67 @@
import unittest import unittest
from keymapper.dev.macros import Macro, k, m, r, w from keymapper.dev.macros import parse
class TestMacros(unittest.TestCase): class TestMacros(unittest.TestCase):
def setUp(self):
self.result = []
self.handler = lambda char, value: self.result.append((char, value))
def tearDown(self):
self.result = []
def test_0(self):
parse('k(1)', self.handler).run()
self.assertListEqual(self.result, [(1, 1), (1, 0)])
def test_1(self): def test_1(self):
r(3, k('a').w(200)).run() parse('k(1).k(a).k(3)', self.handler).run()
self.assertListEqual(self.result, [
(1, 1), (1, 0),
('a', 1), ('a', 0),
(3, 1), (3, 0),
])
def test_2(self): def test_2(self):
r(2, k('a').k('-')).k('b').run() parse('r(1, k(k))', self.handler).run()
self.assertListEqual(self.result, [
('k', 1), ('k', 0),
])
def test_3(self): def test_3(self):
w(400).m('SHIFT_L', r(2, k('a'))).w(10).k('b').run() parse('r(3, k(m).w(200))', self.handler).run()
self.assertListEqual(self.result, [
('m', 1), ('m', 0),
('m', 1), ('m', 0),
('m', 1), ('m', 0),
])
def test_4(self): def test_4(self):
parse(' r(2,\nk(\rr ).k(-\n )).k(m) ', self.handler).run()
self.assertListEqual(self.result, [
('r', 1), ('r', 0),
('-', 1), ('-', 0),
('r', 1), ('r', 0),
('-', 1), ('-', 0),
('m', 1), ('m', 0),
])
def test_5(self):
parse('w(400).r(2,m(w,\rr(2,\tk(r))).w(10).k(k))', self.handler).run()
expected = [('w', 1)]
expected += [('r', 1), ('r', 0)] * 2
expected += [('w', 0)]
expected += [('k', 1), ('k', 0)]
expected *= 2
self.assertListEqual(self.result, expected)
def test_6(self):
# prints nothing without .run # prints nothing without .run
k('a').r(3, k('b')) parse('k(a).r(3, k(b))', self.handler)
self.assertListEqual(self.result, [])
if __name__ == '__main__':
if __name__ == "__main__":
unittest.main() unittest.main()