got to where i was at the start of the refactor

class_based_refactor
Nicholas Hope 2022-09-17 14:09:51 -04:00
parent 3f9f35cced
commit 5014161b02
1 changed files with 263 additions and 120 deletions

View File

@ -18,19 +18,13 @@ validKeyList = [
'leftarrow', 'rightarrow' 'leftarrow', 'rightarrow'
] ]
bindTypes = [ class bind(object):
'impulse',
'hold',
'toggle',
'double',
'repeat'
]
class bind:
''' '''
Parent class for all bind types. Parent class for all bind types.
Verifies key, creates local variables Verifies key, creates local variables
''' '''
bindTypes = []
instances = {}
def __init__(self, key, fields): def __init__(self, key, fields):
self.alias = False self.alias = False
@ -43,19 +37,29 @@ class bind:
# and some other universal fields like alias and finds targetType # and some other universal fields like alias and finds targetType
self.verify() self.verify()
if type(self) == bind or len(self.fields) == 0: if type(self) is bind:
# not using isinstance(), because all subclasses are also instances
# of bind.
return 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 len(self.fields) > 0:
if isinstance(self.fields, str): # verify function should remove all fields relavent to the bind.
# iterating over a str returns each character, # Any extras are errors
# making meaningless error messages
self.errors.append(f' "{self.fields}"') self.errors.append(f'extra fields in "{self.key}":')
else: if isinstance(self.fields, str):
for field in self.fields: # iterating over a str returns each character,
self.errors.append(f' "{field}"') # making meaningless error messages
self.errors.append(f' "{self.fields}"')
else:
for field in self.fields:
self.errors.append(f' "{field}"')
elif len(self.errors) == 0:
# no errors, add new instance to the list of instances
try:
self.instances[type(self)].append(self)
except KeyError:
self.instances[type(self)] = [self]
def verify(self): def verify(self):
@ -63,14 +67,14 @@ class bind:
typeName, self.key = self.key.split(' ', 1) typeName, self.key = self.key.split(' ', 1)
self.key = self.key.lower() self.key = self.key.lower()
except ValueError: except ValueError:
# catastrophic error: no type # catastrophic error: either no type or no key, assume no type
self.errors.append(f'could not find type in "{self.key}"') self.errors.append(f'could not find type in "{self.key}"')
return return
try: try:
self.alias = self.fields.pop('alias') self.alias = self.fields.pop('alias')
if not isinstance(self.alias, bool): 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 self.alias = False
except (KeyError, AttributeError, TypeError): except (KeyError, AttributeError, TypeError):
self.alias = False self.alias = False
@ -78,16 +82,9 @@ class bind:
if (not self.alias) and (self.key not in validKeyList): if (not self.alias) and (self.key not in validKeyList):
self.errors.append(f'invalid key name: "{self.key}"') self.errors.append(f'invalid key name: "{self.key}"')
types = { for type_ in self.bindTypes:
'impulse': impulse, if typeName == type_.__name__:
'hold': hold, self.targetType = type_
'toggle': toggle,
'double': double,
'repeat': repeat
}
for loopName, typeClass in types.items():
if loopName == typeName:
self.targetType = typeClass
break break
if self.targetType is None: if self.targetType is None:
@ -101,6 +98,7 @@ class bind:
# cast to targetType, extend errors # cast to targetType, extend errors
bind = self.targetType(self.key, self.fields) bind = self.targetType(self.key, self.fields)
bind.errors.extend(self.errors) bind.errors.extend(self.errors)
bind.alias = self.alias
return bind return bind
def err(self, message): def err(self, message):
@ -110,18 +108,19 @@ class bind:
class impulse(bind): class impulse(bind):
def verify(self): def verify(self):
self.command = None self.command = None
if not isinstance(self.fields, dict):
self.fields = {'command': self.fields}
try: try:
self.command = self.fields.pop('command') self.command = self.fields.pop('command')
except (KeyError, AttributeError, TypeError): except KeyError:
self.fields = {'command': self.fields} self.err('requires `command` field')
self.command = self.fields.pop('command')
if isinstance(self.command, str): if isinstance(self.command, str):
self.command = self.command.split(';') self.command = self.command.split(';')
elif not isinstance(self.command, list): elif not isinstance(self.command, list):
self.err('must be command or argument of string or list') self.err('`command` field must be argument of string or list')
self.command = None self.command = None
def toTF2(self) -> str: def toTF2(self) -> str:
@ -144,9 +143,9 @@ class impulse(bind):
cmd, restOfCmd = instruction, '' cmd, restOfCmd = instruction, ''
simpleSCs = { simpleSCs = {
"primary": "slot1", 'primary': 'slot1',
"secondary": "slot2", 'secondary': 'slot2',
"melee": "slot3" 'melee': 'slot3'
} }
try: try:
cmd = simpleSCs[cmd] cmd = simpleSCs[cmd]
@ -154,14 +153,14 @@ class impulse(bind):
# not a shortcut # not a shortcut
pass pass
if cmd == "voice": if cmd == 'voice':
cmd = "voicemenu" cmd = 'voicemenu'
restOfCmd = self.expandVoice(restOfCmd) restOfCmd = self.expandVoice(restOfCmd)
elif cmd == "build" or cmd == "destroy": elif cmd == 'build' or cmd == 'destroy':
restOfCmd = self.expandBuildings(restOfCmd) restOfCmd = self.expandBuildings(restOfCmd)
elif cmd == "load_itempreset" and restOfCmd.isalpha(): elif cmd == 'load_itempreset' and restOfCmd.isalpha():
try: try:
restOfCmd = restOfCmd.lower() restOfCmd = restOfCmd.lower()
restOfCmd = ['a','b','c','d'].index(restOfCmd) restOfCmd = ['a','b','c','d'].index(restOfCmd)
@ -169,7 +168,7 @@ class impulse(bind):
# not a load_itempreset shortcut # not a load_itempreset shortcut
pass pass
if restOfCmd != "": if restOfCmd != '':
cmd += ' ' + restOfCmd cmd += ' ' + restOfCmd
instList[i] = cmd instList[i] = cmd
@ -179,9 +178,9 @@ class impulse(bind):
keyword = keyword.lower() keyword = keyword.lower()
allLists = ( allLists = (
("medic", "thanks", "go", "move up", "go left", "go right", "yes", "no", "pass to me"), ('medic', 'thanks', 'go', 'move up', 'go left', 'go right', 'yes', 'no', 'pass to me'),
("incoming", "spy", "sentry ahead", "teleporter here", "dispenser here", "sentry here", "activate uber", "uber ready"), ('incoming', 'spy', 'sentry ahead', 'teleporter here', 'dispenser here', 'sentry here', 'activate uber', 'uber ready'),
("help", "battle cry", "cheers", "jeers", "positive", "negative", "nice shot", "good job"), ('help', 'battle cry', 'cheers', 'jeers', 'positive', 'negative', 'nice shot', 'good job'),
) )
for menu, voiceList in enumerate(allLists): for menu, voiceList in enumerate(allLists):
@ -191,10 +190,10 @@ class impulse(bind):
def expandBuildings(self, building): def expandBuildings(self, building):
buildingNums = { buildingNums = {
"dispenser": "0 0", 'dispenser': '0 0',
"entrance": "1 0", 'entrance': '1 0',
"exit": "1 1", 'exit': '1 1',
"sentry": "2 0" 'sentry': '2 0'
} }
for shortBuild, num in buildingNums.items(): for shortBuild, num in buildingNums.items():
if building == shortBuild: if building == shortBuild:
@ -205,60 +204,122 @@ class hold(bind):
def verify(self): def verify(self):
self.press = None self.press = None
self.release = None self.release = None
if isinstance(self.fields, dict): if not 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.fields = {'press': self.fields}
# verify press
try:
self.press = self.fields.pop('press') self.press = self.fields.pop('press')
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
# verify release
try:
self.release = self.fields.pop('release')
except KeyError:
# 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'
else: else:
self.err('must be press and release, or argument of string') bindOrAlias = 'bind'
holdStr = f'hold_{self.key}'
# Making impulse instances from self.press and .release
# allows them to share the shortcuts
pressObj = impulse(f'+{holdStr}', self.press)
pressObj.alias = True
pressStr = pressObj.toTF2()
releaseObj = impulse(f'-{holdStr}', self.release)
releaseObj.alias = True
releaseStr = releaseObj.toTF2()
if self.alias:
# if alias, do this to avoid activating
# and never deactivating
self.key = '+' + self.key
return pressStr + releaseStr + f'{bindOrAlias} {self.key} "+{holdStr}"\n'
class toggle(bind): class toggle(bind):
def verify(self): def verify(self):
self.on = None self.on = None
self.off = None self.off = None
if isinstance(self.fields, dict): if not 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.fields = {'on': self.fields}
# verify on
try:
self.on = self.fields.pop('on') self.on = self.fields.pop('on')
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
# verify off
try:
self.off = self.fields.pop('off')
except KeyError:
# 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'
else: else:
self.err('must be on and end, or argument of string') bindOrAlias = 'bind'
toggleStr = f'toggle_{self.key}'
onStr = f'{toggleStr}_on'
offStr = f'{toggleStr}_off'
onObj = impulse(onStr, self.on)
onObj.alias = True
toggleOn = onObj.toTF2()[
# remove trailing " and \n
:-2
]
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'
class double(bind): class double(bind):
@ -267,69 +328,142 @@ class double(bind):
def verify(self): def verify(self):
self.primary = None self.primary = None
self.primStr = f'{self.key}_primary'
self.secondary = None self.secondary = None
self.secondStr = f'{self.key}_secondary'
self.condition = None self.condition = None
self.isToggle = False
# name of a bind type # name of a bind type
self.type = None self.type = None
# either "released" (default) or "both" # either 'released' (default) or 'both'
self.cancel = 'released' self.cancel = 'released'
# toggler # toggler
try: try:
self.condition = self.fields.pop('condition') self.condition = self.fields.pop('condition')
if self.condition not in validKeyList: if self.condition not in validKeyList:
self.err(f'has invalid condition to toggle: "{self.condition}"') self.err(f'has invalid `condition` field: "{self.condition}"')
except KeyError: except KeyError:
self.err('requires condition to toggle') self.err('requires `condition` field')
# cancel mode if 'toggle' in self.fields:
try: self.isToggle = self.fields.pop('toggle')
self.cancel = self.fields.pop('cancel') if not isinstance(self.isToggle, bool):
if self.cancel not in ['released', 'both']: self.err(f'`toggle` field should be "yes" or "no", not "{self.isToggle}"')
self.err(f'cancel must be either "released" or "both", not "{self.cancel}"')
except KeyError:
# maintain default
pass
# type # type
try: try:
self.type = self.fields.pop('type') self.type = self.fields.pop('type')
if self.type not in bindTypes: if self.type not in [ type_.__name__ for type_ in self.bindTypes ]:
# catastrophic: invalid type # catastrophic: invalid type
self.err(f'has invalid type: "{self.type}"') self.err(f'has invalid type: "{self.type}"')
return return
except KeyError: except KeyError:
# catastrophic: no type given # catastrophic: no type given
self.err('requires type') self.err('requires `type` field')
return return
# cancel mode
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}"')
else:
self.err(f'`cancel` field must be argument of "released" or "both"')
# primary action # primary action
try: try:
mainSection = self.fields.pop('primary') mainSection = self.fields.pop('primary')
# mainSection.update({'alias': True})
mainAction = bind(f'{self.type} primary', mainSection) mainAction = bind(f'{self.type} {self.primStr}', mainSection)
if mainAction.targetType is not None: self.primary = mainAction.toTargetType()
mainAction = mainAction.targetType(mainAction.key, mainAction.fields)
self.errors.extend(mainAction.errors)
except KeyError: except KeyError:
self.err('requires primary action') self.err('requires `primary` field')
# secondary action # secondary action
try: try:
altSection = self.fields.pop('secondary') altSection = self.fields.pop('secondary')
# altSection.update({'alias': True})
altBind = bind(f'{self.type} secondary', altSection) altBind = bind(f'{self.type} {self.secondStr}', altSection)
if altBind.targetType is not None: self.secondary = altBind.toTargetType()
altBind = altBind.targetType(altBind.key, altBind.fields)
self.errors.extend(altBind.errors)
except KeyError: except KeyError:
self.err('requires secondary action') self.err('requires `secondary` field')
def toTF2(self) -> str:
if self.alias:
bindOrAlias = 'alias'
else:
bindOrAlias = 'bind'
# Get code for primary and secondary actions
self.primary.alias = True
mainCode = self.primary.toTF2()
self.secondary.alias = True
altCode = self.secondary.toTF2()
# Make code to toggle between the two actions
pShiftStr = f'+shift_{self.key}'
mShiftStr = f'-shift_{self.key}'
if self.cancel == 'both':
# 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,
# you'd end up with each recursively calling the other
mainLines = mainCode.splitlines()
mainMinusLine = mainLines[1]
# second arg, without first quote
mainMinusStr = mainMinusLine.split(' ', 2)[2][1:]
altLines = altCode.splitlines()
altMinusLine = altLines[1]
# same as above
altMinusStr = altMinusLine.split(' ', 2)[2][1:]
mainLines[1] = mainLines[1][:-1] + f';{altMinusStr}'
altLines[1] = altLines[1][:-1] + f';{mainMinusStr}'
mainCode = '\n'.join(mainLines) + '\n'
altCode = '\n'.join(altLines) + '\n'
if self.type == 'hold':
self.primStr = '+' + self.primStr
self.secondStr = '+' + self.secondStr
result = mainCode + altCode +\
f'alias {pShiftStr} "{bindOrAlias} {self.key} {self.primStr}"\n' +\
f'alias {mShiftStr} "{bindOrAlias} {self.key} {self.secondStr}"\n'+\
f'{bindOrAlias} {self.key} "{self.primStr}"\n'
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']
if pShiftStr not in changes:
# not already in changes
changes.append(pShiftStr)
restores.append(mShiftStr)
except KeyError:
# If the condition key doesn't already exist, make it
self.condDict.update( {
self.condition: {
'change_keys': [ pShiftStr ],
'restore_keys': [ mShiftStr ],
'alias': self.alias
}
} )
return result
class repeat(bind): class repeat(bind):
@ -354,3 +488,12 @@ class repeat(bind):
self.command = None self.command = None
except KeyError: except KeyError:
self.err('requires command') self.err('requires command')
def toTF2(self) -> str:
# commented-out placeholder
return f'// repeat {self.key}\n'
# This is at the bottom because it has to happen after
# all inheritances have been completed
bind.bindTypes = bind.__subclasses__()