From 5eeb88e2b7de9587b35992573235c2fab4f9f241 Mon Sep 17 00:00:00 2001 From: John Morrison Date: Fri, 22 Apr 2022 19:16:30 +0100 Subject: [PATCH] Minimum necessary to add index and get capability and tests --- kmk/keys.py | 33 ++++--- tests/test_kmk_keys.py | 195 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 217 insertions(+), 11 deletions(-) diff --git a/kmk/keys.py b/kmk/keys.py index cf7fc5f..0751398 100644 --- a/kmk/keys.py +++ b/kmk/keys.py @@ -21,10 +21,6 @@ ALL_NUMBERS = '1234567890' ALL_NUMBER_ALIASES = tuple(f'N{x}' for x in ALL_NUMBERS) -class InfiniteLoopDetected(Exception): - pass - - # this is a bit of an FP style thing - combining a pipe operator a-la F# with # a bootleg Maybe monad to clean up these make_key sequences def left_pipe_until_some(candidate, functor, *args_iter): @@ -61,13 +57,30 @@ def maybe_make_consumer_key(candidate, code, names): return make_consumer_key(code=code, names=names) -class KeyAttrDict(AttrDict): - def __getattr__(self, key, depth=0): - if depth > 1: - raise InfiniteLoopDetected() +class KeyAttrDict: + __cache = {} + def __setitem__(self, key, value): + if DEBUG_OUTPUT: + print(f'__setitem__ {key}, {value}') + self.__cache.__setitem__(key, value) + + def __getattr__(self, key): + if DEBUG_OUTPUT: + print(f'__getattr__ {key}') + return self.__getitem__(key) + + def get(self, key, default=None): try: - return super(KeyAttrDict, self).__getattr__(key) + return self.__getitem__(key) + except Exception: + return default + + def __getitem__(self, key): + if DEBUG_OUTPUT: + print(f'__getitem__ {key}') + try: + return self.__cache[key] except Exception: pass @@ -365,7 +378,7 @@ class KeyAttrDict(AttrDict): if not maybe_key: raise ValueError('Invalid key') - return self.__getattr__(key, depth=depth + 1) + return self.__cache[key] # Global state, will be filled in throughout this file, and diff --git a/tests/test_kmk_keys.py b/tests/test_kmk_keys.py index 7a646db..a8c5125 100644 --- a/tests/test_kmk_keys.py +++ b/tests/test_kmk_keys.py @@ -1,6 +1,6 @@ import unittest -from kmk.keys import KC, Key, ModifierKey +from kmk.keys import KC, Key, ModifierKey, KeyAttrDict, make_key from tests.keyboard_test import KeyboardTest @@ -97,5 +97,198 @@ class TestKmkKeys(unittest.TestCase): assert not isinstance(KC.RALT(KC.Q), ModifierKey) +import unittest + +from kmk.keys import KC, KeyAttrDict, make_key + + +class TestKeys_dot(unittest.TestCase): + def setUp(self): + global KC + KC = KeyAttrDict() + + def test_expected_code_uppercase(self): + assert 4 == KC.A.code + + def test_expected_code_lowercase(self): + assert 4 == KC.a.code + + def test_case_ignored_alpha(self): + upper_key = KC.A + lower_key = KC.a + assert upper_key is lower_key + + def test_case_requested_order_irrelevant(self): + lower_key = KC.a + upper_key = KC.A + assert upper_key is lower_key + + def test_secondary_name(self): + primary_key = KC.NO + secondary_key = KC.XXXXXXX + assert primary_key is secondary_key + + def test_invalid_key_upper(self): + with self.assertRaises(ValueError): + KC.INVALID_KEY + + def test_invalid_key_lower(self): + with self.assertRaises(ValueError): + KC.invalid_key + + def test_custom_key(self): + created = make_key( + KC.N2.code, + names=( + 'EURO', + '€', + ), + has_modifiers={KC.LSFT.code, KC.ROPT.code}, + ) + assert created is KC.get('EURO') + assert created is KC.get('€') + + def test_match_exactly_case(self): + created = make_key(names=('ThIs_Is_A_StRaNgE_kEy',)) + assert created is KC.get('ThIs_Is_A_StRaNgE_kEy') + + +class TestKeys_index(unittest.TestCase): + def setUp(self): + global KC + KC = KeyAttrDict() + + def test_expected_code_uppercase(self): + assert 4 == KC['A'].code + + def test_expected_code_lowercase(self): + assert 4 == KC['a'].code + + def test_case_ignored_alpha(self): + upper_key = KC['A'] + lower_key = KC['a'] + assert upper_key is lower_key + + def test_case_requested_order_irrelevant(self): + lower_key = KC['a'] + upper_key = KC['A'] + assert upper_key is lower_key + + def test_invalid_key_upper(self): + with self.assertRaises(ValueError): + KC['NOT_A_VALID_KEY'] + + def test_invalid_key_lower(self): + with self.assertRaises(ValueError): + KC['not_a_valid_key'] + + def test_custom_key(self): + created = make_key( + KC['N2'].code, + names=( + 'EURO', + '€', + ), + has_modifiers={KC['LSFT'].code, KC['ROPT'].code}, + ) + assert created is KC['EURO'] + assert created is KC['€'] + + def test_match_exactly_case(self): + created = make_key(names=('ThIs_Is_A_StRaNgE_kEy',)) + assert created is KC['ThIs_Is_A_StRaNgE_kEy'] + + +class TestKeys_get(unittest.TestCase): + def setUp(self): + global KC + KC = KeyAttrDict() + + def test_expected_code_uppercase(self): + assert 4 == KC.get('A').code + + def test_expected_code_lowercase(self): + assert 4 == KC.get('a').code + + def test_case_ignored_alpha(self): + upper_key = KC.get('A') + lower_key = KC.get('a') + assert upper_key is lower_key + + def test_case_requested_order_irrelevant(self): + lower_key = KC.get('a') + upper_key = KC.get('A') + assert upper_key is lower_key + + def test_secondary_name(self): + primary_key = KC.NO + secondary_key = KC.XXXXXXX + assert primary_key is secondary_key + + def test_invalid_key_upper(self): + assert KC.get('INVALID_KEY') is None + + def test_invalid_key_lower(self): + assert KC.get('not_a_valid_key') is None + + def test_custom_key(self): + created = make_key( + KC.get('N2').code, + names=( + 'EURO', + '€', + ), + has_modifiers={KC.get('LSFT').code, KC.get('ROPT').code}, + ) + assert created is KC.get('EURO') + assert created is KC.get('€') + + def test_match_exactly_case(self): + created = make_key(names=('ThIs_Is_A_StRaNgE_kEy',)) + assert created is KC.get('ThIs_Is_A_StRaNgE_kEy') + + +# Some of these test appear silly, but they're testing we get the +# same, single, instance back when requested through KC and that +# order of request doesn't matter +class TestKeys_instances(unittest.TestCase): + def setUp(self): + global KC + KC = KeyAttrDict() + + def test_make_key_new_instance(self): + key1 = make_key(code=1) + key2 = make_key(code=1) + assert key1 is not key2 + assert key1.code == key2.code + + def test_index_is_index(self): + assert KC['A'] is KC['A'] + + def test_index_is_dot(self): + assert KC['A'] is KC.A + + def test_index_is_get(self): + assert KC['A'] is KC.get('A') + + def test_dot_is_dot(self): + assert KC.A is KC.A + + def test_dot_is_index(self): + assert KC.A is KC['A'] + + def test_dot_is_get(self): + assert KC.A is KC.get('A') + + def test_get_is_get(self): + assert KC.get('A') is KC.get('A') + + def test_get_is_index(self): + assert KC.get('A') is KC['A'] + + def test_get_is_dot(self): + assert KC.get('A') is KC.A + + if __name__ == '__main__': unittest.main()