Just, a ton of work. Aded literals

class_based_refactor
Nicholas Hope 2022-10-02 12:12:33 -04:00
parent 0bbca21fea
commit f0dac0d1a1
1 changed files with 265 additions and 157 deletions

View File

@ -1,3 +1,5 @@
import re
validKeyList = [
# top row
'escape', 'f1', 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9', 'f10', 'f11', 'f12',
@ -18,7 +20,9 @@ validKeyList = [
'leftarrow', 'rightarrow'
]
class bind(object):
popErrors = (AttributeError, KeyError, TypeError)
class Bind(object):
'''
Parent class for all bind types.
Verifies key, creates local variables
@ -26,18 +30,26 @@ class bind(object):
bindTypes = []
instances = {}
def __init__(self, key, fields):
self.alias = False
self.key = key
self.fields = fields
self.errors = []
self.targetType = None
def __init__(self, key='', fields={}, *, parent=None):
if parent is None:
self.alias = False
self.key = str(key)
self.fields = fields
self.errors = []
self.warnings = []
self.TargetType = None
else:
self.alias = parent.alias
self.key = parent.key
self.fields = parent.fields
self.errors = parent.errors
self.warnings = parent.warnings
# redefined for each unique type, default just verifies key
# and some other universal fields like alias and finds targetType
self.verify()
if type(self) is bind:
if type(self) is Bind:
# not using isinstance(), because all subclasses are also instances
# of bind.
return
@ -46,15 +58,15 @@ class bind(object):
# verify function should remove all fields relavent to the bind.
# Any extras are errors
self.errors.append(f'extra fields in "{self.key}":')
self.warnings.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}"')
self.warnings.append(f' "{self.fields}"')
else:
for field in self.fields:
self.errors.append(f' "{field}"')
elif len(self.errors) == 0:
self.warnings.append(f' "{field}"')
if len(self.errors) == 0:
# no errors, add new instance to the list of instances
try:
self.instances[type(self)].append(self)
@ -62,20 +74,20 @@ class bind(object):
self.instances[type(self)] = [self]
def verify(self):
try:
self.alias = self.fields.pop('alias')
if not isinstance(self.alias, bool):
self.errors.append(f'alias should be "yes" or "no", not "{self.alias}"')
self.errors.append(
f'alias should be "yes" or "no", not "{self.alias}"'
)
self.alias = False
except (KeyError, AttributeError, TypeError):
# KeyError, if dict but no alias field;
# AttributeError, if no pop method;
# TypeError, if pop method won't take str() as arg
except popErrors:
self.alias = False
try:
typeName, self.key = self.key.split(' ', 1)
# all types start with a capital
typeName = typeName.lower().capitalize()
if not self.alias:
# don't mess with alias names
self.key = self.key.lower()
@ -84,33 +96,39 @@ class bind(object):
self.errors.append(f'could not find type in "{self.key}"')
return
for type_ in self.bindTypes:
if typeName == type_.__name__:
self.TargetType = type_
break
if self.TargetType is None:
self.errors.append(
f'"{typeName}" is not a valid type for "{self.key}"'
)
if (not self.alias) and (self.key not in validKeyList):
self.errors.append(f'invalid key name: "{self.key}"')
for type_ in self.bindTypes:
if typeName == type_.__name__:
self.targetType = type_
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:
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)
bind.alias = self.alias
return self
# cast to targetType, "inheriting" stuff from self
bind = self.TargetType(parent=self)
return bind
def err(self, message):
self.errors.append(f'{type(self).__name__} "{self.key}" {message}')
self.errors.append(
f'{type(self).__name__.lower()} "{self.key}" {message}'
)
def warn(self, message):
self.warnings.append(
f'{type(self).__name__.lower()} "{self.key}" {message}'
)
class impulse(bind):
class Impulse(Bind):
def verify(self):
self.command = None
if not isinstance(self.fields, dict):
@ -118,15 +136,14 @@ class impulse(bind):
try:
self.command = self.fields.pop('command')
if isinstance(self.command, str):
self.command = self.command.split(';')
elif not isinstance(self.command, list):
self.err('`command` field must be argument of string or list')
self.command = None
except KeyError:
self.err('requires `command` field')
if isinstance(self.command, str):
self.command = self.command.split(';')
elif not isinstance(self.command, list):
self.err('`command` field must be argument of string or list')
self.command = None
def toTF2(self) -> str:
if self.alias:
@ -165,7 +182,8 @@ class impulse(bind):
elif cmd == 'build' or cmd == 'destroy':
restOfCmd = self.expandBuildings(restOfCmd)
elif cmd == 'load_itempreset' and restOfCmd.isalpha():
elif cmd == 'loadout' and restOfCmd.isalpha():
cmd = 'load_itempreset'
try:
restOfCmd = restOfCmd.lower()
restOfCmd = str(['a','b','c','d'].index(restOfCmd))
@ -173,6 +191,11 @@ class impulse(bind):
# not a load_itempreset shortcut
pass
elif cmd == 'unalias':
cmd = 'alias'
# adding empty arg to indicate unaliasing
restOfCmd += ' '
if restOfCmd != '':
cmd += ' ' + restOfCmd
instList[i] = cmd
@ -205,7 +228,7 @@ class impulse(bind):
return num
class hold(bind):
class Hold(Bind):
def verify(self):
self.press = None
self.release = None
@ -215,31 +238,32 @@ class hold(bind):
# verify press
try:
self.press = self.fields.pop('press')
if isinstance(self.press, str):
self.press = self.press.split(';')
elif not isinstance(self.press, list):
self.err('`press` field must be string or list')
self.press = None
except KeyError:
self.err('requires `press` field')
if isinstance(self.press, str):
self.press = self.press.split(';')
elif not isinstance(self.press, list):
self.err('`press` field must be string or list')
self.press = None
if self.press is None:
return
# verify release
try:
self.release = self.fields.pop('release')
except KeyError:
if isinstance(self.release, str):
self.release = self.release.split(';')
elif not isinstance(self.release, list):
self.err('`release` field must be string or list')
self.release = None
except popErrors:
self.warn('has no `release`, creating one')
# no release specified, do -action for each item in press
self.release = []
for cmd in self.press:
if cmd[0] == '+':
self.release.append('-' + cmd[1:])
if isinstance(self.release, str):
self.release = self.release.split(';')
elif not isinstance(self.release, list):
self.err('"release" field must be string or list')
self.release = None
def toTF2(self) -> str:
if self.alias:
bindOrAlias = 'alias'
@ -249,11 +273,11 @@ class hold(bind):
# Making impulse instances from self.press and .release
# allows them to share the shortcuts
pressObj = impulse(f'+{holdStr}', self.press)
pressObj = Impulse(f'+{holdStr}', self.press)
pressObj.alias = True
pressStr = pressObj.toTF2()
releaseObj = impulse(f'-{holdStr}', self.release)
releaseObj = Impulse(f'-{holdStr}', self.release)
releaseObj.alias = True
releaseStr = releaseObj.toTF2()
@ -262,10 +286,13 @@ class hold(bind):
# and never deactivating
self.key = '+' + self.key
return pressStr + releaseStr + f'{bindOrAlias} {self.key} "+{holdStr}"\n'
return (
pressStr + releaseStr
+ f'{bindOrAlias} {self.key} "+{holdStr}"\n'
)
class toggle(bind):
class Toggle(Bind):
def verify(self):
self.on = None
self.off = None
@ -275,31 +302,30 @@ class toggle(bind):
# verify on
try:
self.on = self.fields.pop('on')
if isinstance(self.on, str):
self.on = self.on.split(';')
elif not isinstance(self.on, list):
self.err('`on` field must be string or list')
self.on = None
except KeyError:
self.err('requires `on` field')
if isinstance(self.on, str):
self.on = self.on.split(';')
elif not isinstance(self.on, list):
self.err('`on` field must be string or list')
self.on = None
if self.on is None:
return
# verify off
try:
self.off = self.fields.pop('off')
except KeyError:
if isinstance(self.off, str):
self.off = self.off.split(';')
elif not isinstance(self.off, list):
self.err('`off` field must be string or list')
except popErrors:
# no off specified, do -action for each item in on
self.off = []
for cmd in self.on:
if cmd[0] == '+':
self.off.append('-' + cmd[1:])
if isinstance(self.off, str):
self.off = self.off.split(';')
elif not isinstance(self.off, list):
self.err('`off` field must be string or list')
self.off = None
def toTF2(self) -> str:
if self.alias:
bindOrAlias = 'alias'
@ -309,27 +335,28 @@ class toggle(bind):
onStr = f'{toggleStr}_on'
offStr = f'{toggleStr}_off'
onObj = impulse(onStr, self.on)
onObj = Impulse(onStr, self.on)
onObj.alias = True
toggleOn = onObj.toTF2()[
# remove trailing " and \n
:-2
]
toggleOn = onObj.toTF2()
# remove starting/trailing " and \n
toggleOn = toggleOn[:-2]
offObj = impulse(offStr, self.off)
offObj = Impulse(offStr, self.off)
offObj.alias = True
toggleOff = offObj.toTF2()[:-2]
return \
f'{toggleOn}; alias {toggleStr} {offStr}"\n' +\
f'{toggleOff}; alias {toggleStr} {onStr}"\n' +\
f'alias {toggleStr} "{onStr}"\n' +\
f'{bindOrAlias} {self.key} "{toggleStr}"\n'
return (
f'{toggleOn}; alias {toggleStr} {offStr}"\n'
+ f'{toggleOff}; alias {toggleStr} {onStr}"\n'
+ f'alias {toggleStr} "{onStr}"\n'
+ f'{bindOrAlias} {self.key} "{toggleStr}"\n'
)
class double(bind):
class Double(Bind):
defaultDict = {}
condDict = {}
bindNames = []
def verify(self):
self.primary = None
@ -345,114 +372,152 @@ class double(bind):
self.type = None
# either 'released' (default) or 'both'
self.cancel = 'released'
self.cancelBoth = False
# toggler
try:
self.condition = self.fields.pop('condition')
if self.condition not in validKeyList:
self.err(f'has invalid `condition` field: "{self.condition}"')
except KeyError:
except popErrors:
self.err('requires `condition` field')
if 'toggle' in self.fields:
try:
self.isToggle = self.fields.pop('toggle')
if not isinstance(self.isToggle, bool):
self.err(f'`toggle` field should be "yes" or "no", not "{self.isToggle}"')
self.err(
'`toggle` field should be "yes" or "no", '
+ f'not "{self.isToggle}"'
)
except popErrors:
self.isToggle = False
# type
try:
self.type = self.fields.pop('type')
if self.type not in [ type_.__name__ for type_ in self.bindTypes ]:
self.type = self.fields.pop('type').lower()
if self.type not in self.bindNames:
# catastrophic: invalid type
self.err(f'has invalid type: "{self.type}"')
return
except KeyError:
except popErrors:
# catastrophic: no type given
self.err('requires `type` field')
return
# cancel mode, must happend after type has been inferred
if 'cancel' in self.fields:
self.cancel = self.fields.pop('cancel')
if self.cancel in ('released', 'both'):
if self.cancel == 'both' and self.type != 'hold':
self.err(f'`cancel` field only affects "hold", not "{self.type}"')
elif isinstance(self.cancel, str):
self.err(f'`cancel` field must be "released" or "both", not "{self.cancel}"')
try:
cancel = self.fields.pop('cancel')
if not isinstance(cancel, str):
self.err(f'`cancel` field must be "released" or "both"')
else:
self.err(f'`cancel` field must be argument of "released" or "both"')
if cancel == 'both':
if self.type == 'hold':
self.cancelBoth = True
else:
self.err(
'`cancel` field only affects "hold", '
+ f'not "{self.type}"'
)
elif cancel != 'released':
self.err(
'`cancel` field must be "released" '
+ f'or "both", not "{cancel}"'
)
except popErrors:
cancel = 'released'
# primary action
try:
mainSection = self.fields.pop('primary')
mainBind = Bind(f'{self.type} {self.primStr}', mainSection)
mainBind = mainBind.toTargetType()
mainAction = bind(f'{self.type} {self.primStr}', mainSection)
self.primary = mainAction.toTargetType()
except KeyError:
self.errors.extend(mainBind.errors)
self.warnings.extend(mainBind.warnings)
self.errors.remove(f'invalid key name: "{self.primStr}"')
self.primary = mainBind
except popErrors:
self.err('requires `primary` field')
# secondary action
try:
altSection = self.fields.pop('secondary')
altBind = Bind(f'{self.type} {self.secondStr}', altSection)
altBind = altBind.toTargetType()
altBind = bind(f'{self.type} {self.secondStr}', altSection)
self.secondary = altBind.toTargetType()
except KeyError:
self.errors.extend(altBind.errors)
self.warnings.extend(altBind.warnings)
self.errors.remove(f'invalid key name: "{self.secondStr}"')
self.secondary = altBind
except popErrors:
self.err('requires `secondary` field')
def toTF2(self) -> str:
if self.alias:
bindOrAlias = 'alias'
else:
bindOrAlias = 'bind'
# Get code for primary and secondary actions
def toTF2(self) -> str:
# Get code for primary and secondary actions.
# alias=true so the toTF2() method aliases
# them instead of binding them
self.primary.alias = True
mainCode = self.primary.toTF2()
self.secondary.alias = True
altCode = self.secondary.toTF2()
# Make code to switch between the two actions
pShiftStr = f'+shift_{self.key}'
mShiftStr = f'-shift_{self.key}'
if self.cancel == 'both':
mainCode, altCode = self.cancelBoth(mainCode, altCode)
if self.cancelBoth:
mainCode, altCode = self.getCancelCode(mainCode, altCode)
if self.type == 'hold':
self.primStr = '+' + self.primStr
self.secondStr = '+' + self.secondStr
result = mainCode + altCode +\
f'alias {pShiftStr} "{bindOrAlias} {self.key} {self.secondStr}"\n' +\
f'alias {mShiftStr} "{bindOrAlias} {self.key} {self.primStr}"\n'+\
f'{bindOrAlias} {self.key} "{self.primStr}"\n'
shiftStr = f'shift_{self.key}'
shiftCode = self.getChangeCode(shiftStr)
self.addToCondDict(shiftStr)
try:
# If the condition key (like 'mouse4') already has toggles,
# just append another toggle string
changes = self.condDict[self.condition]['change_keys']
restores = self.condDict[self.condition]['restore_keys']
return mainCode + altCode + shiftCode
if pShiftStr not in changes:
# not already in changes
changes.append(pShiftStr)
restores.append(mShiftStr)
def getChangeCode(self, shift):
if self.alias:
bindOrAlias = 'alias'
else:
bindOrAlias = 'bind'
code = (
f'alias +{shift} "{bindOrAlias} {self.key} {self.secondStr}"\n'
+ f'alias -{shift} "{bindOrAlias} {self.key} {self.primStr}"\n'
)
except KeyError:
# If the condition key doesn't already exist, make it
if self.isToggle:
toggleObj = Toggle(shift, f'+{shift}')
toggleObj.alias = True # so it aliases instead of binding
code += toggleObj.toTF2()
else:
code += f'{bindOrAlias} {self.key} "{self.primStr}"\n'
return code
def addToCondDict(self, shiftStr):
if self.isToggle:
changeStr = shiftStr
else:
changeStr = '+' + shiftStr
restoreStr = '-' + shiftStr
if self.condition not in self.condDict:
# if not already present, make dict for key
self.condDict.update( {
self.condition: {
'change_keys': [ pShiftStr ],
'restore_keys': [ mShiftStr ],
'alias': self.alias
'change_keys': [],
'restore_keys': []
}
} )
return result
self.condDict[self.condition]['change_keys'].append(changeStr)
if self.isToggle == False:
self.condDict[self.condition]['restore_keys'].append(restoreStr)
def cancelBoth(self, mainCode, altCode) -> (str, str):
def getCancelCode(self, mainCode, altCode) -> (str, str):
# code to cancel both if either is released
# it copies the - statement from each to both.
# if it just extracted the name of the - statement,
@ -469,18 +534,13 @@ class double(bind):
altMinusStr = altMinusLine.split(' ', 2)[2][1:-1]
# remove duplicate - actions
mainMinusList = set(mainMinusStr.split(';'))
altMinusList = set(altMinusStr.split(';'))
uniqMain = mainMinusList.difference(altMinusList)
uniqAlt = altMinusList.difference(mainMinusList)
mainMinusSet = set(mainMinusStr.split(';'))
altMinusSet = set(altMinusStr.split(';'))
allCancels = mainMinusSet | altMinusSet
allCancelStr = ';'.join(allCancels)
mainMinusStr = ';'.join(uniqMain)
altMinusStr = ';'.join(uniqAlt)
if not uniqMain.issuperset(uniqAlt):
# main has things alt doesn't
mainLines[1] = mainLines[1][:-1] + f';{altMinusStr}"'
if not uniqAlt.issuperset(uniqMain):
altLines[1] = altLines[1][:-1] + f';{mainMinusStr}"'
altLines[1] = altLines[1][:-1] + f';{allCancelStr}"'
mainLines[1] = mainLines[1][:-1] + f';{allCancelStr}"'
return (
'\n'.join(mainLines) + '\n',
@ -488,7 +548,7 @@ class double(bind):
)
class repeat(bind):
class Repeat(Bind):
def verify(self):
self.interval = None
self.command = None
@ -497,25 +557,73 @@ class repeat(bind):
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')
self.err('`interval` must be greater than 0')
except (KeyError, TypeError):
self.err('requires `interval` field')
except ValueError:
self.err(f'has invalid number of ticks: "{self.interval}"')
self.err(f'has invalid `interval`: "{self.interval}"')
except AttributeError:
self.err(f'requires `interval` field')
try:
self.command = self.fields.pop('command')
if not isinstance(self.command, (str, list)):
self.err('command must be string or list')
self.err('`command` must be string or list')
self.command = None
except KeyError:
self.err('requires command')
except popErrors:
self.err('requires `command` field')
def toTF2(self) -> str:
# commented-out placeholder
return f'// repeat {self.key}\n'
class Literal(Bind):
def verify(self):
self.text = ''
self.run = False
if not isinstance(self.fields, dict):
self.fields = {'text': self.fields}
if not self.alias:
try:
# keyname should be invalid, remove the error
self.errors.remove(
f'invalid key name: "{self.key}"'
)
except ValueError:
# if not invalid key, indicate as such
self.warn('should not use a key as a label')
if 'run' in self.fields:
self.run = self.fields.pop('run')
if not isinstance(self.run, bool):
self.errors.append(
f'`run` should be "yes" or "no", not "{self.run}"'
)
if not self.alias:
self.warn('`run` specified without alias')
try:
self.text = self.fields.pop('text')
except KeyError:
self.err('requires `text` field')
if isinstance(self.text, str):
self.text = self.text.split(';')
elif not isinstance(self.text, list):
self.err('argument must be of string or list')
def toTF2(self) -> str:
result = ';'.join(self.text)
if self.alias:
result = f'alias {self.key} "{result}"'
if self.run:
result += f'\n{self.key}'
return result + '\n'
# This is at the bottom because it has to happen after
# all inheritances have been completed
bind.bindTypes = bind.__subclasses__()
Bind.bindTypes = Bind.__subclasses__()
Double.bindNames = [ bind.__name__.lower() for bind in Bind.bindTypes ]