From 1fbe47943b4f9e0091751ac475291b802d98226d Mon Sep 17 00:00:00 2001 From: Nicholas Hope Date: Thu, 25 Aug 2022 21:28:12 -0400 Subject: [PATCH] Replacement for types.py --- src/tfscript/tftypes.py | 278 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 278 insertions(+) create mode 100644 src/tfscript/tftypes.py diff --git a/src/tfscript/tftypes.py b/src/tfscript/tftypes.py new file mode 100644 index 0000000..19f51e4 --- /dev/null +++ b/src/tfscript/tftypes.py @@ -0,0 +1,278 @@ +validKeyList = [ + # top row + 'escape', 'f1', 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9', 'f10', 'f11', 'f12', + # keyboard + '`', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '=', 'backspace', + 'tab', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\\', + 'capslock', 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'semicolon', '\'', 'enter', + 'shift', 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/', 'rshift', + 'ctrl', 'lwin', 'alt', 'space', 'rwin', 'ralt', 'rctrl', + # mouse + 'mouse1', 'mouse2', 'mouse3', 'mouse4', 'mouse5', 'mwheelup', 'mwheeldown', + # 8 of the 9 keys to the upper-right (PrtScn can't be bound) + 'scrolllock', 'numlock', + 'ins', 'home', 'pgup', + 'del', 'end', 'pgdn', + # arrows + 'uparrow', 'downarrow', + 'leftarrow', 'rightarrow' +] + +bindTypes = [ + 'impulse', + 'hold', + 'toggle', + 'double', + 'repeat' +] + +class bind: + ''' + Parent class for all bind types. + Verifies key, creates local variables + ''' + + def __init__(self, key, fields): + self.key = key + self.fields = fields + self.errors = [] + self.targetType = None + + # redefined for each unique type, default just verifies key + # and some other universal fields like alias and finds targetType + self.verify() + + if type(self) == bind or len(self.fields) == 0: + return + # verify function should remove all fields relavent to the bind. + # Any extras are errors + + self.errors.append(f'extra fields in "{self.key}":') + if isinstance(self.fields, str): + # iterating over a str returns each character, + # making meaningless error messages + self.errors.append(f' "{self.fields}"') + else: + for field in self.fields: + self.errors.append(f' "{field}"') + + def verify(self): + + try: + typeName, self.key = self.key.split(' ', 1) + self.key = self.key.lower() + except ValueError: + # catastrophic error: no type + self.errors.append(f'could not find type in "{self.key}"') + return + + try: + isalias = self.fields.pop('alias') + if not isinstance(isalias, bool): + self.errors.append(f'alias should be yes or no, not "{isalias}"') + isalias = False + except (KeyError, AttributeError, TypeError): + isalias = False + + if (not isalias) and (self.key not in validKeyList): + self.errors.append(f'invalid key name: "{self.key}"') + + types = { + 'impulse': impulse, + 'hold': hold, + 'toggle': toggle, + 'double': double, + 'repeat': repeat + } + for loopName, typeClass in types.items(): + if loopName == typeName: + self.targetType = typeClass + break + + if self.targetType is None: + self.errors.append(f'could not find type in "{self.key}"') + + def toTargetType(self): + if self.targetType is None: + # do nothing + bind = self + else: + # cast to targetType, extend errors + bind = self.targetType(self.key, self.fields) + bind.errors.extend(self.errors) + return bind + + def err(self, message): + self.errors.append(f'{type(self).__name__} "{self.key}" {message}') + + +class impulse(bind): + def verify(self): + self.command = None + + try: + self.command = self.fields.pop('command') + if not isinstance(self.command, (str, list)): + self.err('command must be string or list') + self.command = None + except (KeyError, AttributeError, TypeError): + self.fields = {'command': self.fields} + self.command = self.fields.pop('command') + if not isinstance(self.command, (str, list)): + self.err('must be command or argument of string or list') + self.command = None + + +class hold(bind): + def verify(self): + self.press = None + self.release = None + if isinstance(self.fields, dict): + # verify press + try: + self.press = self.fields.pop('press') + if not isinstance(self.press, (str, list)): + self.err('press must be string or list') + self.press = None + except KeyError: + self.err('requires press field') + + # verify release + try: + self.release = self.fields.pop('release') + if not isinstance(self.release, (str, list)): + self.err('release must be string or list') + self.release = None + except KeyError: + self.err('requires release field') + + elif isinstance(self.fields, str): + self.fields = {'press': self.fields} + self.press = self.fields.pop('press') + else: + self.err('must be press and release, or argument of string') + + +class toggle(bind): + def verify(self): + self.on = None + self.off = None + if isinstance(self.fields, dict): + # verify on + try: + self.on = self.fields.pop('on') + if not isinstance(self.on, (str, list)): + self.err('on must be string or list') + self.on = None + except KeyError: + self.err('requires on field') + + # verify off + try: + self.off = self.fields.pop('off') + if not isinstance(self.off, (str, list)): + self.err('off must be string or list') + self.off = None + except KeyError: + self.err('requires end field') + + elif isinstance(self.fields, str): + self.fields = {'on': self.fields} + self.on = self.fields.pop('on') + else: + self.err('must be on and end, or argument of string') + + +class double(bind): + defaultDict = {} + condDict = {} + + def verify(self): + self.primary = None + self.secondary = None + self.condition = None + + # name of a bind type + self.type = None + + # either "released" (default) or "both" + self.cancel = 'released' + + # toggler + try: + self.condition = self.fields.pop('condition') + if self.condition not in validKeyList: + self.err(f'has invalid condition to toggle: "{self.condition}"') + except KeyError: + self.err('requires condition to toggle') + + # cancel mode + try: + self.cancel = self.fields.pop('cancel') + if self.cancel not in ['released', 'both']: + self.err(f'cancel must be either "released" or "both", not "{self.cancel}"') + except KeyError: + # maintain default + pass + + # type + try: + self.type = self.fields.pop('type') + if self.type not in bindTypes: + # catastrophic: invalid type + self.err(f'has invalid type: "{self.type}"') + return + except KeyError: + # catastrophic: no type given + self.err('requires type') + return + + # primary action + try: + mainSection = self.fields.pop('primary') + # mainSection.update({'alias': True}) + + mainAction = bind(f'{self.type} primary', mainSection) + if mainAction.targetType is not None: + mainAction = mainAction.targetType(mainAction.key, mainAction.fields) + + self.errors.extend(mainAction.errors) + except KeyError: + self.err('requires primary action') + + # secondary action + try: + altSection = self.fields.pop('secondary') + # altSection.update({'alias': True}) + + altBind = bind(f'{self.type} secondary', altSection) + if altBind.targetType is not None: + altBind = altBind.targetType(altBind.key, altBind.fields) + + self.errors.extend(altBind.errors) + except KeyError: + self.err('requires secondary action') + + +class repeat(bind): + def verify(self): + self.interval = None + self.command = None + + try: + intervalStr = str(self.fields.pop('interval')) + self.interval = int(intervalStr) + if self.interval <= 0: + self.err('interval must be greater than 0') + except KeyError: + self.err('requires interval') + except ValueError: + self.err(f'has invalid number of ticks: "{self.interval}"') + + try: + self.command = self.fields.pop('command') + if not isinstance(self.command, (str, list)): + self.err('command must be string or list') + self.command = None + except KeyError: + self.err('requires command')