Compare commits

...

4 Commits

Author SHA1 Message Date
Nicholas Hope 30cc02012e Merge branch 'main' of https://git.paco.to/nick/tfscript into main 2022-08-15 13:46:47 -04:00
Nicholas Hope 90f39b2737 New alias system 2022-08-15 13:41:09 -04:00
Nicholas Hope 40c8b4ee42 Added nested-double test case 2022-08-15 13:40:22 -04:00
Nicholas Hope 7248acb787 Changed how targetDir works for consistency 2022-08-15 13:40:01 -04:00
5 changed files with 137 additions and 83 deletions

View File

@ -2,26 +2,48 @@
# Used for the conditions in the <double> type
condDict = {}
defaultDict = {}
bindOrAlias = "bind"
from json import dumps
from copy import deepcopy
def makeCFG(cfg, default=False):
global bindOrAlias
global condDict
global defaultDict
bindOrAlias = "bind"
if default:
# Write to defaultDict instead of condDict
condDict = defaultDict
else:
condDict = deepcopy(defaultDict)
def makeCFG(cfg):
condDict.clear()
ret = ''
for key, data in cfg.items():
# I know all of these fields exist because it was verified in verify.py
bindType = firstTypeIn(data.keys())
bindContent = data[bindType]
ret += branch(key, bindContent, bindType)
isAlias = False
if "alias" in data:
isAlias = data.pop("alias")
if isAlias:
bindOrAlias = "alias"
else:
bindOrAlias = "bind"
ret += branch(key, data)
# 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():
ret += f'alias +{key}_toggles "{toggles["plus_toggles"]}"\n' +\
f'alias -{key}_toggles "{toggles["minus_toggles"]}"\n' +\
f'bind {key} "+{key}_toggles"\n'
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'
del condDict # free deep copy
return ret
def firstTypeIn(inputList):
def typeOf(dictIn):
""" Find the first element common to both lists """
types = [
"impulse",
@ -31,10 +53,13 @@ def firstTypeIn(inputList):
"repeat"
]
for t in types:
if t in inputList:
if t in dictIn.keys():
return t
def branch(keyName, bindContent, bindType):
def branch(keyName, bindContent):
bindType = typeOf(bindContent)
bindContent = bindContent.pop(bindType)
if bindType == "impulse":
return impulse(keyName, bindContent)
@ -54,6 +79,7 @@ def branch(keyName, bindContent, bindType):
return repeat(keyName, bindContent)
def impulse(key, instruction):
global bindOrAlias
if isinstance(instruction, list):
instruction = ';'.join(instruction)
@ -64,7 +90,7 @@ def impulse(key, instruction):
instruction = ';'.join(allInstructions)
return f'bind {key} "{instruction}"\n'
return f'{bindOrAlias} {key} "{instruction}"\n'
def impulseShortcuts(instruction):
splitCommand = instruction.split(' ')
@ -116,13 +142,15 @@ def expandBuildings(building):
return num
def simpleHold(key, instruction):
global bindOrAlias
# This isn't quite right, fix later!
if instruction[0] != '+':
return f'bind {key} "+{instruction}"\n'
if instruction[0] == '+' or instruction[0] == '-':
return f'{bindOrAlias} {key} "{instruction}"\n'
else:
return f'bind {key} "{instruction}"\n'
return f'{bindOrAlias} {key} "+{instruction}"\n'
def listHold(key, options):
global bindOrAlias
pressStr = options["press"]
if isinstance(pressStr, list):
pressStr = ';'.join(pressStr)
@ -133,10 +161,12 @@ def listHold(key, options):
ret = f'alias +{key}_bind "{pressStr}"\n' +\
f'alias -{key}_bind "{releaseStr}"\n' +\
f'bind {key} "+{key}_bind"\n'
f'{bindOrAlias} {key} "+{key}_bind"\n'
return ret
def toggle(key, instruction):
global bindOrAlias
onStr = f'turn_{key}_on'
offStr = f'turn_{key}_off'
togStr = f'toggle_{key}'
@ -144,59 +174,47 @@ def toggle(key, instruction):
ret = f'alias {onStr} "+{instruction}; alias {togStr} {offStr}"\n' +\
f'alias {offStr} "-{instruction}; alias {togStr} {onStr}"\n' +\
f'alias {togStr} "{onStr}"\n' +\
f'bind {key} "{togStr}"\n'
f'{bindOrAlias} {key} "{togStr}"\n'
return ret
def double(key, options):
primaryAction = options["primary"]
pBindType = firstTypeIn(primaryAction.keys())
pBindContent = primaryAction[pBindType]
secAction = options["secondary"]
sBindType = firstTypeIn(secAction.keys())
sBindContent = secAction[sBindType]
mainStr = f'{key}_main'
altStr = f'{key}_alt'
togStr = f'toggle_{key}'
pTogStr = f'+toggle_{key}'
mTogStr = f'-toggle_{key}'
recursiveCode = branch(mainStr, pBindContent, pBindType) +\
branch(altStr, sBindContent, sBindType)
global bindOrAlias
oldBindOrAlias = bindOrAlias
bindOrAlias = "alias"
recursiveCode = branch(mainStr, primaryAction) +\
branch(altStr, secAction)
bindOrAlias = oldBindOrAlias
newcode = []
for line in recursiveCode.split('\n'):
# For every line gotten by the recursive call, change all "bind"s to "alias",
# since mainStr and altStr aren't valid bind targes
llist = line.split(' ')
for i in range(len(llist)):
alphanumChars = ''.join(c for c in llist[i] if c.isalnum())
if alphanumChars == 'bind':
if llist[i][0].isalnum():
llist[i] = 'alias'
else:
# If the first character isn't a normal character.
# Almost always because it is a double quote
llist[i] = llist[i][0] + 'alias'
newcode.append(' '.join(llist))
ret = '\n'.join(newcode) +\
f'alias +{togStr} "bind {key} {altStr}"\n' +\
f'alias -{togStr} "bind {key} {mainStr}"\n'+\
f'bind {key} "{mainStr}"\n'
ret = recursiveCode +\
f'alias {pTogStr} "{bindOrAlias} {key} {altStr}"\n' +\
f'alias {mTogStr} "{bindOrAlias} {key} {mainStr}"\n'+\
f'{bindOrAlias} {key} "{mainStr}"\n'
condName = options["condition"]
global condDict
if condName in condDict:
# If the condition key (like "mouse4") already has toggles,
# just append another toggle string (it gets encased in quotes later)
condDict[condName]["plus_toggles"] += f"; +{togStr}"
condDict[condName]["minus_toggles"] += f"; -{togStr}"
# 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)
else:
# If the condition key doesn't already exist, make it with the correct values
# If the condition key doesn't already exist, make it
condDict.update( {
condName: {
"plus_toggles": f"+{togStr}",
"minus_toggles": f"-{togStr}"
"plus_toggles": [ pTogStr ],
"minus_toggles": [ mTogStr ]
}
} )

View File

@ -40,8 +40,10 @@ def parseFile(inputFile) -> (dict, dict):
# See verify.py
config, aliases = verify.verifyConfig(config)
if "errors" in config:
for e in config["errors"]:
print(e, file=stderr)
for cclass, messages in config["errors"].items():
print(f"Error in {cclass}:")
for msg in messages:
print(f" {msg}")
return None, None
else:
return config, aliases
@ -59,7 +61,7 @@ def parseConfig(config, defaults):
tempsAndReals = {}
if defaults is not None:
stringToWrite = tfscript.makeCFG(defaults)
stringToWrite = tfscript.makeCFG(defaults, default=True)
replaceDict = writing.writeOutput(stringToWrite, "default", args)
tempsAndReals.update(replaceDict)
@ -129,10 +131,15 @@ def main() -> int:
parser = parseCLI()
args = parser.parse_args()
systemName = GetOSName()
if args.directory is not None:
targetDir = args.directory
targetDir = normpath(args.directory)
if systemName == "Windows":
targetDir += '\\'
else:
targetDir += '/'
else:
systemName = GetOSName()
targetDir = getTargetDir(systemName)
if targetDir is not None:
# Supported OS: add steamapps path

View File

@ -3,18 +3,22 @@ def verifyConfig(cfg: dict) -> (dict, dict):
verifiedConfig = {}
# Do defaults first
aliasErrors = []
errors = {}
defaults = None
if "default" in cfg:
defaults = cfg.pop("default")
errMessages = []
for key, data in defaults.items():
isAlias = ("alias" in data and data["alias"] == True)
errMessages = validBind(key, data, alias = isAlias)
if len(errMessages) > 0:
for msg in errMessages:
aliasErrors.append(f"Error in defaults: {msg}")
isAlias = False
if "alias" in data:
isAlias = data["alias"]
if not isinstance(isAlias, bool):
errMessages.append(f'"alias" field in "{key}" makes no sense: "{isAlias}"')
errMessages.extend(validBind(key, data, alias = isAlias) )
if len(errMessages) > 0:
errors.update( {"default": errMessages} )
classList = [
"scout",
@ -28,9 +32,6 @@ def verifyConfig(cfg: dict) -> (dict, dict):
"spy"
]
errors = aliasErrors.copy()
for cclass in classList:
classCFG = None
className = cclass
@ -47,33 +48,35 @@ def verifyConfig(cfg: dict) -> (dict, dict):
# It may be less efficient this way, but
# it makes for more descriptive error messages
continue
errMessages = []
for key, data in classCFG.items():
errMessages = []
isAlias = False
if "alias" in data:
isAlias = data["alias"]
if not isinstance(isAlias, bool):
errMessages.append(f'Key "{key}" has alias not set to true or false. Did you accidentally put it in quotes?')
errMessages.append(f'"alias" field in "{key}" makes no sense: "{isAlias}"')
errMessages.extend( validBind(key, data, alias = isAlias) )
if len(errMessages) > 0:
for msg in errMessages:
errors.append(f"Error in {cclass}: {msg}")
if len(errMessages) > 0:
errors.update( {className: errMessages} )
verifiedConfig.update({className: classCFG})
# Turn list into only strings by expanding tuples
for i, clss in enumerate(classList):
if isinstance(clss, tuple):
classList.insert(i+1, clss[0])
classList.insert(i+1, clss[1])
classList.insert(i+1, clss[0])
classList.pop(i)
globalErrors = []
for remainingClass in cfg:
if remainingClass not in classList:
errors.append(f'Error in {remainingClass}: "{remainingClass}" is not a valid class')
globalErrors.append(f'"{remainingClass}" is not a valid class')
else:
otherName = findTwin(remainingClass)
if otherName is not None:
errors.append(f'Error in {remainingClass}: conflicting names for section: "{remainingClass}" and "{otherName}"')
globalErrors.append(f'Conflicting names for section: "{remainingClass}" and "{otherName}"')
if len(globalErrors) > 0:
errors.update( {"file": globalErrors} )
if len(errors) > 0:
verifiedConfig.update({"errors": errors})
@ -95,8 +98,8 @@ def validBind(key, data, alias = False) -> list:
extras = dataCopy.keys()
if len(extras) > 0:
extrasString = "\n\t".join(extras)
ret.append(f'Unused fields in "{key}":\n\t{extrasString}')
extrasString = "\n ".join(extras)
ret.append(f'Unused fields in "{key}":\n {extrasString}')
return ret
@ -108,7 +111,10 @@ validKeyList = [
'u', 'v', 'w', 'x', 'y', 'z',
'mouse1', 'mouse2', 'mouse3', 'mouse4', 'mouse5',
'shift', 'capslock', 'ctrl', 'semicolon', 'space', 'enter',
'backspace'
'backspace',
'scrolllock', 'numlock',
'ins', 'home', 'pgup',
'del', 'end', 'pgdn'
]
def validKey(key):
@ -144,6 +150,7 @@ def validBindType(key, data: dict):
def removeRelaventFields(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)

View File

@ -55,7 +55,7 @@ def writeOutput(data, className, args) -> dict:
outfile.close() # the most-recent tempfile will not have been closed
if args.debug:
print( f'DEBUG: Wrote {bytesWritten} bytes to {className} ({fileNum}/{filesNeeded})', file=stderr)
print( f'DEBUG: Wrote {bytesWritten} bytes to {className} ({fileNum}/{filesNeeded})', end='\n\n', file=stderr)
return namesDict
@ -70,6 +70,10 @@ def replaceFiles(targetDir, fileNames, args):
if args.debug:
print( f'DEBUG: Created {targetDir}{realName}', file=stderr)
if args.debug:
# Break up the debug messages
print(end='\n')
return list(fileNames.values())
def appendToActuals(targetDir, fileList, defaultsGiven, args):
@ -87,6 +91,7 @@ def appendToActuals(targetDir, fileList, defaultsGiven, args):
]
for cclass in classList:
addCallIfUncalled('exec default_script_1', targetDir, cclass, args)
fileList = onlyFirsts(fileList)
for currFile in fileList:
execStr = f'exec {currFile.split(".")[0]}'
@ -98,10 +103,10 @@ def addCallIfUncalled(execStr, targetDir, fileName, args):
realExists = exists(realFilePath)
# creates if it doesn't exist, so must come after the exists() call
cfgFile = open(realFilePath, 'r+')
cfgFile = open(realFilePath, 'a+')
if not realExists:
if args.debug:
print( f"DEBUG: Created file {targetDir}{realFilePath}" )
print( f"DEBUG: Created {realFilePath}" )
cfgFile.write(execStr + '\n')
elif not strInFile(execStr, cfgFile):
@ -132,10 +137,12 @@ def getRealName(fileName):
return className + '.cfg'
def strInFile(execStr, f):
lineList = [ ' '.join(line.split()) for line in f.readlines() ]
# Opened in append mode, so cursor is at the end.
# Must reopen to put cursor at the start.
with open(f.name, 'r') as dupfile:
lineList = [ ' '.join(line.split()) for line in dupfile.readlines() ]
for line in lineList:
# Remove indent and outdent, including trailing newline
print(line)
if execStr == line:
return True

15
tests/nest_test.yaml Normal file
View File

@ -0,0 +1,15 @@
default:
e:
double:
primary:
double:
primary:
impulse: voice medic
secondary:
impulse: voice activate uber
condition: mouse5
secondary:
impulse: voice uber ready
condition: mouse4