From 0d64ad9b54bfcae89342353867d418d6c6df52f9 Mon Sep 17 00:00:00 2001 From: Nicholas Hope Date: Fri, 19 Aug 2022 21:34:45 -0400 Subject: [PATCH] added new way to format files --- src/tfscript/__init__.py | 168 +++++++++++++++++++++++++-------------- src/tfscript/cli.py | 14 +--- src/tfscript/verify.py | 93 +++++++++++++++------- 3 files changed, 175 insertions(+), 100 deletions(-) diff --git a/src/tfscript/__init__.py b/src/tfscript/__init__.py index 7ed279d..34efd24 100644 --- a/src/tfscript/__init__.py +++ b/src/tfscript/__init__.py @@ -4,7 +4,6 @@ condDict = {} defaultDict = {} bindOrAlias = "bind" -from json import dumps from copy import deepcopy def makeCFG(cfg, default=False): @@ -32,12 +31,15 @@ def makeCFG(cfg, default=False): # Doubles are weird. All of the toggles got put into a dictionary. # This takes all of the nested dictionaries and turns them into the right string - for key, toggles in condDict.items(): - plusToggleStr = ';'.join(toggles["plus_toggles"]) - minusToggleStr = ';'.join(toggles["minus_toggles"]) - ret += f'alias +{key}_toggles "{plusToggleStr}"\n' +\ - f'alias -{key}_toggles "{minusToggleStr}"\n' +\ - f'{bindOrAlias} {key} "+{key}_toggles"\n' + if condDict != defaultDict: + # ==, and by extension !=, does in fact check + # for dictionary equality in keys and values + for key, toggles in condDict.items(): + onCondPress = ';'.join(toggles["change_keys"]) + onCondRelease = ';'.join(toggles["restore_keys"]) + ret += f'alias +{key}_toggles "{onCondPress}"\n' +\ + f'alias -{key}_toggles "{onCondRelease}"\n' +\ + f'{bindOrAlias} {key} "+{key}_toggles"\n' del condDict # free deep copy @@ -57,6 +59,22 @@ def typeOf(dictIn): return t def branch(keyName, bindContent): + """ Using the provided keyName and content, call the correct function """ + + """ + Terser syntax, ex. + impulse e: + + instead of + e: + impulse: + + """ + splitKey = keyName.split(' ', 1) + if len(splitKey) > 1: + keyName = splitKey[1] + bindContent = {splitKey[0]: bindContent} + bindType = typeOf(bindContent) bindContent = bindContent.pop(bindType) @@ -80,41 +98,46 @@ def branch(keyName, bindContent): def impulse(key, instruction): global bindOrAlias - if isinstance(instruction, list): - instruction = ';'.join(instruction) - - allInstructions = [] + if isinstance(instruction, dict): + instruction = instruction["command"] - for indivCmd in instruction.split(';'): - allInstructions.append(impulseShortcuts(indivCmd)) + if not isinstance(instruction, list): + instruction = instruction.split(';') - instruction = ';'.join(allInstructions) + instuction = impulseShortcuts(instruction) + instruction = ';'.join(instruction) return f'{bindOrAlias} {key} "{instruction}"\n' -def impulseShortcuts(instruction): - splitCommand = instruction.split(' ') - cmd = splitCommand[0] - shortcuts = { - "primary": "slot1", - "secondary": "slot2", - "melee": "slot3" - } - if cmd in shortcuts: - for sc, expansion in shortcuts.items(): - if cmd == sc: - splitCommand[0] = expansion - break - instruction = ' '.join(splitCommand) +def impulseShortcuts(instList): + for i, instruction in enumerate(instList): + splitCmd = instruction.split(' ') + cmd = splitCmd[0] + restOfCmd = ' '.join(splitCmd[1:]) + shortcuts = { + "primary": "slot1", + "secondary": "slot2", + "melee": "slot3" + } + if cmd in shortcuts: + cmd = shortcuts[cmd] - restOfCmd = ' '.join(splitCommand[1:]) - if cmd == "voice": - instruction = voice(restOfCmd) + if cmd == "voice": + cmd = "voicemenu" + restOfCmd = voice(restOfCmd) - elif cmd == "build" or cmd == "destroy": - instruction = f"{cmd} " + expandBuildings(restOfCmd) - - return instruction + elif cmd == "build" or cmd == "destroy": + restOfCmd = expandBuildings(restOfCmd) + + elif cmd == "load_itempreset" and restOfCmd.isalpha(): + restOfCmd = restOfCmd.lower() + restOfCmd = ['a','b','c','d'].index(restOfCmd) + + if restOfCmd != "": + cmd += ' ' + restOfCmd + instList[i] = cmd + + return instList def voice(keyword): keyword = keyword.lower() @@ -128,7 +151,7 @@ def voice(keyword): for menu, voiceList in enumerate(allLists): for selection, shortcut in enumerate(voiceList): if keyword == shortcut: - return f'voicemenu {menu} {selection}' + return f'{menu} {selection}' def expandBuildings(building): buildingNums = { @@ -151,17 +174,15 @@ def simpleHold(key, instruction): def listHold(key, options): global bindOrAlias - pressStr = options["press"] - if isinstance(pressStr, list): - pressStr = ';'.join(pressStr) - - releaseStr = options["release"] - if isinstance(releaseStr, list): - releaseStr = ';'.join(releaseStr) - ret = f'alias +{key}_bind "{pressStr}"\n' +\ - f'alias -{key}_bind "{releaseStr}"\n' +\ - f'{bindOrAlias} {key} "+{key}_bind"\n' + oldBindOrAlias = bindOrAlias + bindOrAlias = 'alias' + ret = impulse(f'+{key}_press', options["press"]) +\ + impulse(f'-{key}_release', options["release"]) + + bindOrAlias = oldBindOrAlias + + ret += f'{bindOrAlias} {key} "+{key}_press"\n' return ret @@ -170,6 +191,8 @@ def toggle(key, instruction): onStr = f'turn_{key}_on' offStr = f'turn_{key}_off' togStr = f'toggle_{key}' + if instruction[0] == '+': + instruction = instruction[1:] ret = f'alias {onStr} "+{instruction}; alias {togStr} {offStr}"\n' +\ f'alias {offStr} "-{instruction}; alias {togStr} {onStr}"\n' +\ @@ -184,8 +207,8 @@ def double(key, options): mainStr = f'{key}_main' altStr = f'{key}_alt' - pTogStr = f'+toggle_{key}' - mTogStr = f'-toggle_{key}' + pShiftStr = f'+shift_{key}' + mShiftStr = f'-shift_{key}' global bindOrAlias oldBindOrAlias = bindOrAlias @@ -195,28 +218,51 @@ def double(key, options): bindOrAlias = oldBindOrAlias ret = recursiveCode +\ - f'alias {pTogStr} "{bindOrAlias} {key} {altStr}"\n' +\ - f'alias {mTogStr} "{bindOrAlias} {key} {mainStr}"\n'+\ + f'alias {pShiftStr} "{bindOrAlias} {key} {altStr}"\n' +\ + f'alias {mShiftStr} "{bindOrAlias} {key} {mainStr}"\n'+\ f'{bindOrAlias} {key} "{mainStr}"\n' + isToggle = ("toggle" in options and options.pop("toggle") == True) + if isToggle: + toggleStr = toggle(key, pShiftStr) + condName = options["condition"] global condDict if condName in condDict: # If the condition key (like "mouse4") already has toggles, # just append another toggle string - plusToggles = condDict[condName]["plus_toggles"] - minusToggles = condDict[condName]["minus_toggles"] - if pTogStr not in plusToggles: - plusToggles.append(pTogStr) - minusToggles.append(mTogStr) + changes = condDict[condName]["change_keys"] + restores = condDict[condName]["restore_keys"] + + if isToggle: + # "toggle: true" specified, add the toggle string + if toggleStr not in changes: + changes.append(toggleStr) + if pShiftStr in changes: + # If key already has normal shift, remove it + changes.remove(pShiftStr) + changes.remove(mShiftStr) + + elif pShiftStr not in changes: + # not toggle, not already in changes + changes.append(pShiftStr) + restores.append(mShiftStr) else: # If the condition key doesn't already exist, make it - condDict.update( { - condName: { - "plus_toggles": [ pTogStr ], - "minus_toggles": [ mTogStr ] - } - } ) + if isToggle: + condDict.update( { + condName: { + "change_keys": [ toggleStr ], + "restore_keys": [ ] + } + } ) + else: + condDict.update( { + condName: { + "change_keys": [ pShiftStr ], + "restore_keys": [ mShiftStr ] + } + } ) return ret diff --git a/src/tfscript/cli.py b/src/tfscript/cli.py index 71987ae..506f93a 100644 --- a/src/tfscript/cli.py +++ b/src/tfscript/cli.py @@ -11,7 +11,7 @@ __copyright__ = "Copyright © 2022 Nicholas Hope. See LICENSE for details." # Standard libraries from sys import stderr -from os import mkdir +from os import mkdir, sep as dirsep from os.path import isdir, expanduser, normpath import argparse from warnings import warn @@ -134,20 +134,12 @@ def main() -> int: systemName = GetOSName() if args.directory is not None: - targetDir = normpath(args.directory) - if systemName == "Windows": - targetDir += '\\' - else: - targetDir += '/' + targetDir = normpath(args.directory) + dirsep else: targetDir = getTargetDir(systemName) if targetDir is not None: # Supported OS: add steamapps path - targetDir += normpath("/steamapps/common/Team Fortress 2/tf/cfg") - if systemName == "Windows": - targetDir += '\\' - else: - targetDir += '/' + targetDir += normpath("/steamapps/common/Team Fortress 2/tf/cfg") + dirsep elif args.force: # Unsupported OS but -f specified if args.debug: diff --git a/src/tfscript/verify.py b/src/tfscript/verify.py index 0ee9d5f..84de8ee 100644 --- a/src/tfscript/verify.py +++ b/src/tfscript/verify.py @@ -1,4 +1,6 @@ """Verify all the things that could go wrong.""" +from copy import deepcopy + def verifyConfig(cfg: dict) -> (dict, dict): verifiedConfig = {} @@ -76,7 +78,7 @@ def verifyConfig(cfg: dict) -> (dict, dict): globalErrors.append(f'Conflicting names for section: "{remainingClass}" and "{otherName}"') if len(globalErrors) > 0: - errors.update( {"file": globalErrors} ) + errors.update({"file": globalErrors}) if len(errors) > 0: verifiedConfig.update({"errors": errors}) @@ -86,15 +88,17 @@ def verifyConfig(cfg: dict) -> (dict, dict): def validBind(key, data, alias = False) -> list: """Check for valid key and valid binding""" ret = [] + splitKey = key.split(' ') + if len(splitKey) > 1: + key = splitKey[1] + data = {splitKey[0]: data} + if (not alias and not validKey(key)): ret.append(f'Invalid key "{key}"') # The values passed to validBindType get mutilated, so a copy must be made - dataCopy = data.copy() - dataCopy, errMsgs = validBindType(key, dataCopy) - if len(errMsgs) > 0: - for msg in errMsgs: - ret.append(msg) + dataCopy, errMsgs = validBindType(key, data) + ret.extend(errMsgs) extras = dataCopy.keys() if len(extras) > 0: @@ -140,33 +144,65 @@ def validBindType(key, data: dict): for potentialType in data.keys(): if potentialType in types: validType = True - data, errMsgs = removeRelaventFields(data, potentialType) break - if not validType: + if validType: + expandSpaceFields(data) + dataCopy = deepcopy(data) + dataCopy, errMsgs = removeRelaventFields(key, dataCopy, potentialType) + else: errMsgs.append(f'Key "{key}" has no known bind type') - return data, errMsgs + return dataCopy, errMsgs -def removeRelaventFields(data, bindType): +def expandSpaceFields(data): + keys = list(data.keys()) + for field in keys: + # if you change a key of a dict, while looping + # over the dict, it causes a RuntimeError. + # Looping as list avoids this + splitField = field.split(' ', 1) + if len(splitField) > 1: + newContent = {splitField[0]: data.pop(field)} + data.update({splitField[1]: newContent}) + field = splitField[1] + content = data[field] + if isinstance(content, dict): + expandSpaceFields(content) + +def removeRelaventFields(key, data, bindType): errMsgs = [] - if "alias" in data: data.pop("alias") - # These types are simple, just the bind type and argument - if bindType in ["impulse", "toggle"]: - data.pop(bindType) + content = data.pop(bindType) + if "alias" in data: + # alias is universal + data.pop("alias") + + if bindType == "impulse": + if isinstance(content, dict): + if "command" not in content: + errMsgs.append('impulse requires `command` argument') + else: + content.pop("command") + + elif bindType == "toggle": + if isinstance(content, dict): + if "begin" not in content: + errMsgs.append("toggle requires `begin` argument") + if "end" not in content: + errMsgs.append("toggle requires `end` argument") + elif not isinstance(content, str): + errMsgs.append(f"toggle must be either single action or begin and end") elif bindType == "hold": - content = data.pop("hold") if isinstance(content, dict): if "press" not in content: - errMsgs.append("If hold is not a single argument, it requires a `press` argument") - elif "release" not in content: - errMsgs.append("If hold is not a single argument, it requires a `release` argument") + errMsgs.append("hold requires `press` argument") + if "release" not in content: + errMsgs.append("hold requires `release` argument") elif not isinstance(content, str): errMsgs.append(f"Hold must be either single action or press and release") elif bindType == "double": - content = data.pop("double") if "primary" not in content: errMsgs.append("Double requires primary action") else: @@ -174,28 +210,29 @@ def removeRelaventFields(data, bindType): # It takes advantage of `alias = True` not verifying the key, # but it isn't an alias, I'm just lazy. errMessages = validBind("primary", content["primary"], alias = True) - if len(errMessages) > 0: - errMsgs += errMessages + errMsgs.extend(errMessages) if "secondary" not in content: errMsgs.append("Double requires secondary action") else: # Same logic as above errMessages = validBind("secondary", content["secondary"], alias = True) - if len(errMessages) > 0: - errMsgs += errMessages + errMsgs.extend(errMessages) if "condition" not in content: errMsgs.append("Double requires condition to toggle") else: # Validate the toggler - key = content["condition"] - if not validKey(key): - errMsgs.append(f'Invalid condition to toggle "{key}"') + condition = content["condition"] + if not validKey(condition): + errMsgs.append(f'Invalid condition to toggle "{condition}"') + + if "cancel both" in content: + cancelBoth = content["cancel both"] + if not isinstance(cancelBoth, bool): + errMsgs.append(f'"cancel both" field in "{key}" makes no sense: "{cancelBoth}"') elif bindType == "repeat": - content = data.pop("repeat") - unit = "s" if "unit" in content: # Set unit if provided