Fixed non-funtional code, incorporated new file
parent
cb3c248335
commit
b39528af75
|
@ -123,16 +123,16 @@ def simpleHold(key, instruction):
|
|||
return f'bind {key} "{instruction}"\n'
|
||||
|
||||
def listHold(key, options):
|
||||
press_str = options["press"]
|
||||
if isinstance(press_str, list):
|
||||
press_str = ';'.join(press_str)
|
||||
pressStr = options["press"]
|
||||
if isinstance(pressStr, list):
|
||||
pressStr = ';'.join(pressStr)
|
||||
|
||||
release_str = options["release"]
|
||||
if isinstance(release_str, list):
|
||||
release_str = ';'.join(release_str)
|
||||
releaseStr = options["release"]
|
||||
if isinstance(releaseStr, list):
|
||||
releaseStr = ';'.join(releaseStr)
|
||||
|
||||
ret = f'alias +{key}_bind "{press_str}"\n' +\
|
||||
f'alias -{key}_bind "{release_str}"\n' +\
|
||||
ret = f'alias +{key}_bind "{pressStr}"\n' +\
|
||||
f'alias -{key}_bind "{releaseStr}"\n' +\
|
||||
f'bind {key} "+{key}_bind"\n'
|
||||
return ret
|
||||
|
||||
|
@ -148,29 +148,29 @@ def toggle(key, instruction):
|
|||
return ret
|
||||
|
||||
def double(key, options):
|
||||
prim_action = options["primary"]
|
||||
pBindType = firstTypeIn(prim_action.keys())
|
||||
pBindContent = prim_action[pBindType]
|
||||
primaryAction = options["primary"]
|
||||
pBindType = firstTypeIn(primaryAction.keys())
|
||||
pBindContent = primaryAction[pBindType]
|
||||
|
||||
sec_action = options["secondary"]
|
||||
sBindType = firstTypeIn(sec_action.keys())
|
||||
sBindContent = sec_action[sBindType]
|
||||
secAction = options["secondary"]
|
||||
sBindType = firstTypeIn(secAction.keys())
|
||||
sBindContent = secAction[sBindType]
|
||||
|
||||
main_str = f'{key}_main'
|
||||
alt_str = f'{key}_alt'
|
||||
tog_str = f'toggle_{key}'
|
||||
mainStr = f'{key}_main'
|
||||
altStr = f'{key}_alt'
|
||||
togStr = f'toggle_{key}'
|
||||
|
||||
recursive_code = branch(main_str, pBindContent, pBindType) +\
|
||||
branch(alt_str, sBindContent, sBindType)
|
||||
recursiveCode = branch(mainStr, pBindContent, pBindType) +\
|
||||
branch(altStr, sBindContent, sBindType)
|
||||
|
||||
newcode = []
|
||||
for line in recursive_code.split('\n'):
|
||||
for line in recursiveCode.split('\n'):
|
||||
# For every line gotten by the recursive call, change all "bind"s to "alias",
|
||||
# since main_str and alt_str aren't valid bind targes
|
||||
# since mainStr and altStr aren't valid bind targes
|
||||
llist = line.split(' ')
|
||||
for i in range(len(llist)):
|
||||
alphanum_chars = ''.join(c for c in llist[i] if c.isalnum())
|
||||
if alphanum_chars == 'bind':
|
||||
alphanumChars = ''.join(c for c in llist[i] if c.isalnum())
|
||||
if alphanumChars == 'bind':
|
||||
if llist[i][0].isalnum():
|
||||
llist[i] = 'alias'
|
||||
else:
|
||||
|
@ -180,23 +180,23 @@ def double(key, options):
|
|||
newcode.append(' '.join(llist))
|
||||
|
||||
ret = '\n'.join(newcode) +\
|
||||
f'alias +{tog_str} "bind {key} {alt_str}"\n' +\
|
||||
f'alias -{tog_str} "bind {key} {main_str}"\n'+\
|
||||
f'bind {key} "{main_str}"\n'
|
||||
f'alias +{togStr} "bind {key} {altStr}"\n' +\
|
||||
f'alias -{togStr} "bind {key} {mainStr}"\n'+\
|
||||
f'bind {key} "{mainStr}"\n'
|
||||
|
||||
cond_name = options["condition"]
|
||||
condName = options["condition"]
|
||||
|
||||
if cond_name in 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[cond_name]["plus_toggles"] += f"; +{tog_str}"
|
||||
condDict[cond_name]["minus_toggles"] += f"; -{tog_str}"
|
||||
condDict[condName]["plus_toggles"] += f"; +{togStr}"
|
||||
condDict[condName]["minus_toggles"] += f"; -{togStr}"
|
||||
else:
|
||||
# If the condition key doesn't already exist, make it with the correct values
|
||||
condDict.update( {
|
||||
cond_name: {
|
||||
"plus_toggles": f"+{tog_str}",
|
||||
"minus_toggles": f"-{tog_str}"
|
||||
condName: {
|
||||
"plus_toggles": f"+{togStr}",
|
||||
"minus_toggles": f"-{togStr}"
|
||||
}
|
||||
} )
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ import os
|
|||
import argparse
|
||||
from tempfile import NamedTemporaryFile
|
||||
import yaml
|
||||
from platform import system as OSName, OSRelease
|
||||
from platform import system as GetOSName, release as GetOSRelease
|
||||
|
||||
try:
|
||||
import winreg
|
||||
|
@ -26,79 +26,25 @@ except ModuleNotFoundError:
|
|||
# Local libraries
|
||||
import tfscript
|
||||
from tfscript import verify
|
||||
from tfscript import writing
|
||||
|
||||
args = {}
|
||||
targetDir = ""
|
||||
|
||||
def parseFile(inputFile):
|
||||
def parseFile(inputFile) -> (dict, dict):
|
||||
"""Parse, verify, and do the conversion."""
|
||||
config = yaml.safe_load(inputFile)
|
||||
|
||||
# See verify.py
|
||||
config = tfscript.verify.verifyConfig(config)
|
||||
config, aliases = verify.verifyConfig(config)
|
||||
if "errors" in config:
|
||||
for e in config["errors"]:
|
||||
print(e,file=sys.stderr)
|
||||
print(e, file=sys.stderr)
|
||||
return None, None
|
||||
else:
|
||||
parseConfig(config)
|
||||
return config, aliases
|
||||
|
||||
def writeOutput(data, className) -> dict:
|
||||
"""
|
||||
Write `data' to various files as needed, returning a dict of
|
||||
the temporary file names and their target destination names,
|
||||
not including the target directory
|
||||
"""
|
||||
global args
|
||||
namesDict = {} # return dict
|
||||
|
||||
# Variables
|
||||
lineList = [ l.encode('utf8') for l in data.splitlines() ]
|
||||
fileNum = 1
|
||||
bytesWritten = 0
|
||||
|
||||
# Constants
|
||||
maxFileSize = 2 ** 20 # 1MiB maximum cfg file size
|
||||
filesNeeded = 1 + int( len(data)/maxFileSize )
|
||||
if args.debug:
|
||||
print( f'DEBUG: need {filesNeeded} files for {className}', file=sys.stderr)
|
||||
|
||||
FilNedLen = len(str(filesNeeded))
|
||||
reservedSpace = len(f'{className}_script_{filesNeeded}.cfg') + 4
|
||||
|
||||
# Initialize variables
|
||||
outfile = NamedTemporaryFile(prefix=className, delete=False)
|
||||
# I know % formatting is old-school and pylint hates it,
|
||||
# but "%*d" is the easiest way to left-pad with zeros
|
||||
# without hardcoding a number. The extra 4 bytes is just some leeway
|
||||
namesDict.update({ outfile.name: '%s_script_%0*d.cfg' % (className, FilNedLen, fileNum) })
|
||||
|
||||
while (fileNum <= filesNeeded and len(lineList) > 0):
|
||||
line = lineList.pop(0) + '\n'.encode('utf8')
|
||||
lineLen = len(line) # nice
|
||||
|
||||
if bytesWritten + reservedSpace + lineLen > maxFileSize:
|
||||
outfile.write( ('exec %s_script_%0*d' % (className, FilNedLen, fileNum+1)).encode('utf8') )
|
||||
bytesWritten += reservedSpace
|
||||
if args.debug:
|
||||
print( f'DEBUG: Wrote {bytesWritten} bytes to {className} ({fileNum}/{filesNeeded})', file=sys.stderr)
|
||||
|
||||
outfile.close()
|
||||
outfile = NamedTemporaryFile(prefix=className, delete=False)
|
||||
|
||||
fileNum += 1
|
||||
namesDict.update({ outfile.name: '%s_script_%0*d.cfg' % (className, FilNedLen, fileNum) })
|
||||
bytesWritten = 0
|
||||
|
||||
outfile.write(line)
|
||||
bytesWritten += lineLen
|
||||
|
||||
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=sys.stderr)
|
||||
|
||||
return namesDict
|
||||
|
||||
def parseConfig(config):
|
||||
def parseConfig(config, defaults):
|
||||
"""With validated data structure, write out all the files."""
|
||||
global args
|
||||
global targetDir
|
||||
|
@ -110,20 +56,18 @@ def parseConfig(config):
|
|||
|
||||
tempsAndReals = {}
|
||||
|
||||
if defaults is not None:
|
||||
stringToWrite = tfscript.makeCFG(defaults)
|
||||
replaceDict = writing.writeOutput(stringToWrite, "default", args)
|
||||
tempsAndReals.update(replaceDict)
|
||||
|
||||
for currentClass in config:
|
||||
classDict = config[currentClass]
|
||||
stringToWrite = tfscript.makeCFG(classDict)
|
||||
replaceDict = writeOutput(stringToWrite, currentClass)
|
||||
replaceDict = writing.writeOutput(stringToWrite, currentClass, args)
|
||||
tempsAndReals.update(replaceDict)
|
||||
|
||||
for tmpName, realName in tempsAndReals.items():
|
||||
if args.dry_run:
|
||||
if args.debug:
|
||||
print( f'DEBUG: {tmpName} would be {targetDir}/{realName}.cfg', file=sys.stderr)
|
||||
else:
|
||||
os.replace( tmpName, f'{targetDir}/{realName}' )
|
||||
if args.debug:
|
||||
print( f'DEBUG: Created {targetDir}/{realName}', file=sys.stderr)
|
||||
return tempsAndReals
|
||||
|
||||
def parseCLI():
|
||||
# Handle command line
|
||||
|
@ -143,7 +87,33 @@ def parseCLI():
|
|||
help='File containing YAML to convert.')
|
||||
return parser
|
||||
|
||||
def main():
|
||||
def getTargetDir(systemName):
|
||||
if systemName == "Darwin":
|
||||
if float( '.'.join( GetOSRelease().split('.')[0:2] ) ) >= 10.15:
|
||||
if not args.force:
|
||||
print(
|
||||
"As of macOS Catalina (v10.15), 32-bit applications "
|
||||
"such as tf2 do not work, so tfscript does not function",
|
||||
file=sys.stderr
|
||||
)
|
||||
else:
|
||||
return os.path.expanduser("~/Library/Application Support/Steam/")
|
||||
|
||||
elif systemName == "Windows":
|
||||
# oh god why do we have to use the registry
|
||||
accessReg = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)
|
||||
accessKey = winreg.OpenKey(accessReg, "SOFTWARE\\WOW6432Node\\Valve\\Steam\\")
|
||||
return winreg.QueryValue(accessKey, "InstallPath")
|
||||
|
||||
elif systemName == "Linux":
|
||||
return os.path.expanduser("~/.local/Steam")
|
||||
|
||||
elif systemName == "Java":
|
||||
print("Java-based OSes are not supported yet by tfscript.", file=sys.stderr)
|
||||
|
||||
return None
|
||||
|
||||
def main() -> int:
|
||||
""" Command line interface. """
|
||||
global args
|
||||
global targetDir
|
||||
|
@ -153,31 +123,9 @@ def main():
|
|||
if args.directory is not None:
|
||||
targetDir = args.directory
|
||||
else:
|
||||
systemName = OSName()
|
||||
if systemName == "Darwin":
|
||||
if float( '.'.join(OSRelease().split('.')[0:2]) ) >= 10.15:
|
||||
if not args.force:
|
||||
print(
|
||||
"As of macOS Catalina (v10.15), 32-bit applications "
|
||||
"such as tf2 do not work, so tfscript does not function",
|
||||
file=sys.stderr
|
||||
)
|
||||
else:
|
||||
targetDir = os.path.expanduser("~/Library/Application Support/Steam/")
|
||||
|
||||
elif systemName == "Windows":
|
||||
# oh god why do we have to use the registry
|
||||
accessReg = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)
|
||||
accessKey = winreg.OpenKey(accessReg, "SOFTWARE\\WOW6432Node\\Valve\\Steam\\")
|
||||
targetDir = winreg.QueryValue(accessKey, "InstallPath")
|
||||
|
||||
elif systemName == "Linux":
|
||||
targetDir = os.path.expanduser("~/.local/Steam")
|
||||
|
||||
elif systemName == "Java":
|
||||
print("Java-based OSes are not supported yet by tfscript.", file=sys.stderr)
|
||||
|
||||
if targetDir != "":
|
||||
systemName = GetOSName()
|
||||
targetDir = getTargetDir(systemName)
|
||||
if targetDir is not None:
|
||||
# Supported OS: add steamapps path
|
||||
if targetDir[-1] != '/':
|
||||
targetDir += '/'
|
||||
|
@ -191,7 +139,14 @@ def main():
|
|||
# Unsupported OS and not forced to continue
|
||||
return 2
|
||||
|
||||
parseFile(args.infile)
|
||||
config, defaults = parseFile(args.infile)
|
||||
if config is None:
|
||||
return 2
|
||||
|
||||
fileNames = parseConfig(config, defaults)
|
||||
fileList = writing.replaceFiles(targetDir, fileNames, args)
|
||||
# writing.appendToActuals(targetDir, fileList)
|
||||
|
||||
return 0
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
@ -1,34 +1,84 @@
|
|||
"""Verify all the things that could go wrong."""
|
||||
def verifyConfig(cfg: dict):
|
||||
def verifyConfig(cfg: dict) -> (dict, dict):
|
||||
verifiedConfig = {}
|
||||
|
||||
# Do aliases first
|
||||
# Do defaults first
|
||||
aliasErrors = []
|
||||
|
||||
aliases = None
|
||||
defaults = None
|
||||
|
||||
if "aliases" in cfg:
|
||||
aliases = cfg.pop("aliases")
|
||||
for key, data in aliases.items():
|
||||
errMessages = validBind(key, data, alias = True)
|
||||
if "default" in cfg:
|
||||
defaults = cfg.pop("default")
|
||||
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 aliases: {msg}")
|
||||
aliasErrors.append(f"Error in defaults: {msg}")
|
||||
|
||||
errors = []
|
||||
classList = [
|
||||
"scout",
|
||||
"soldier",
|
||||
"pyro",
|
||||
("demo","demoman"),
|
||||
("engi","engineer"),
|
||||
("heavy","heavyweapons"),
|
||||
"medic",
|
||||
"sniper",
|
||||
"spy"
|
||||
]
|
||||
|
||||
for cclass in cfg:
|
||||
for key, data in cfg[cclass].items():
|
||||
errMessages = validBind(key, data)
|
||||
errors = aliasErrors.copy()
|
||||
|
||||
|
||||
for cclass in classList:
|
||||
classCFG = None
|
||||
className = cclass
|
||||
if isinstance(cclass, str) and cclass in cfg:
|
||||
classCFG = cfg.pop(cclass)
|
||||
elif isinstance(cclass, tuple):
|
||||
for tupClass in cclass:
|
||||
if tupClass in cfg:
|
||||
classCFG = cfg.pop(tupClass)
|
||||
className = cclass[0]
|
||||
break
|
||||
if classCFG is None:
|
||||
# Invalid class, this gets caught later.
|
||||
# It may be less efficient this way, but
|
||||
# it makes for more descriptive error messages
|
||||
continue
|
||||
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.extend( validBind(key, data, alias = isAlias) )
|
||||
if len(errMessages) > 0:
|
||||
for msg in errMessages:
|
||||
errors.append(f"Error in {cclass}: {msg}")
|
||||
verifiedConfig.update({className: classCFG})
|
||||
|
||||
errors += aliasErrors
|
||||
# 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.pop(i)
|
||||
|
||||
for remainingClass in cfg:
|
||||
if remainingClass not in classList:
|
||||
errors.append(f'Error in {remainingClass}: "{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}"')
|
||||
|
||||
if len(errors) > 0:
|
||||
cfg.update({"errors": errors})
|
||||
verifiedConfig.update({"errors": errors})
|
||||
|
||||
return cfg
|
||||
return verifiedConfig, defaults
|
||||
|
||||
def validBind(key, data, alias = False) -> list:
|
||||
"""Check for valid key and valid binding"""
|
||||
|
@ -97,7 +147,7 @@ def removeRelaventFields(data, bindType):
|
|||
# These types are simple, just the bind type and argument
|
||||
if bindType in ["impulse", "toggle"]:
|
||||
data.pop(bindType)
|
||||
|
||||
|
||||
elif bindType == "hold":
|
||||
content = data.pop("hold")
|
||||
if isinstance(content, dict):
|
||||
|
@ -152,10 +202,28 @@ def removeRelaventFields(data, bindType):
|
|||
if "interval" not in content:
|
||||
errMsgs.append("Repeat requires interval")
|
||||
else:
|
||||
interval = content["interval"]
|
||||
if interval < 0:
|
||||
errMsgs.append("Repeat interval cannot be negative")
|
||||
if (unit == "t" and not isinstance(interval, int)):
|
||||
errMsgs.append("Repeat interval must be integer if unit is ticks")
|
||||
intervalStr = content["interval"]
|
||||
if unit == "s":
|
||||
interval = float(intervalStr)
|
||||
else:
|
||||
interval = int(intervalStr)
|
||||
if interval <= 0:
|
||||
errMsgs.append("Repeat interval must be positive")
|
||||
elif interval <= 200/6:
|
||||
errMsgs.append(f"Repeat interval must be greater than 1 tick (approx. {200/6:.3f}s)")
|
||||
|
||||
return data, errMsgs
|
||||
|
||||
def findTwin(className):
|
||||
classDict = {
|
||||
"demo": "demoman",
|
||||
"engi": "engineer",
|
||||
"heavy": "heavyweapons"
|
||||
}
|
||||
for className1, className2 in classDict.items():
|
||||
if className == className1:
|
||||
return className2
|
||||
elif className == className2:
|
||||
return className1
|
||||
|
||||
return None
|
|
@ -0,0 +1,82 @@
|
|||
import os
|
||||
from os.path import exists
|
||||
from tempfile import NamedTemporaryFile
|
||||
|
||||
def writeOutput(data, className, args) -> dict:
|
||||
"""
|
||||
Write `data' to various files as needed, returning a dict of
|
||||
the temporary file names and their target destination names,
|
||||
not including the target directory
|
||||
"""
|
||||
namesDict = {} # return dict
|
||||
|
||||
# Variables
|
||||
lineList = [ l.encode('utf8') for l in data.splitlines() ]
|
||||
fileNum = 1
|
||||
bytesWritten = 0
|
||||
|
||||
# Constants
|
||||
maxFileSize = 2 ** 20 # 1MiB maximum cfg file size
|
||||
filesNeeded = 1 + int( len(data)/maxFileSize )
|
||||
if args.debug:
|
||||
print( f'DEBUG: need {filesNeeded} files for {className}', file=sys.stderr)
|
||||
|
||||
FilNedLen = len(str(filesNeeded))
|
||||
reservedSpace = len(f'{className}_script_{filesNeeded}.cfg') + 4
|
||||
|
||||
# Initialize variables
|
||||
outfile = NamedTemporaryFile(prefix=className, delete=False)
|
||||
# I know % formatting is old-school and pylint hates it,
|
||||
# but "%*d" is the easiest way to left-pad with zeros
|
||||
# without hardcoding a number. The extra 4 bytes is just some leeway
|
||||
namesDict.update({ outfile.name: '%s_script_%0*d.cfg' % (className, FilNedLen, fileNum) })
|
||||
|
||||
while (fileNum <= filesNeeded and len(lineList) > 0):
|
||||
line = lineList.pop(0) + '\n'.encode('utf8')
|
||||
lineLen = len(line) # nice
|
||||
|
||||
if bytesWritten + reservedSpace + lineLen > maxFileSize:
|
||||
outfile.write( ('exec %s_script_%0*d' % (className, FilNedLen, fileNum+1)).encode('utf8') )
|
||||
bytesWritten += reservedSpace
|
||||
if args.debug:
|
||||
print( f'DEBUG: Wrote {bytesWritten} bytes to {className} ({fileNum}/{filesNeeded})', file=sys.stderr)
|
||||
|
||||
outfile.close()
|
||||
outfile = NamedTemporaryFile(prefix=className, delete=False)
|
||||
|
||||
fileNum += 1
|
||||
namesDict.update({ outfile.name: '%s_script_%0*d.cfg' % (className, FilNedLen, fileNum) })
|
||||
bytesWritten = 0
|
||||
|
||||
outfile.write(line)
|
||||
bytesWritten += lineLen
|
||||
|
||||
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=sys.stderr)
|
||||
|
||||
return namesDict
|
||||
|
||||
def replaceFiles(targetDir, fileNames, args):
|
||||
for tmpName, realName in fileNames.items():
|
||||
if args.dry_run:
|
||||
if args.debug:
|
||||
print( f'DEBUG: {tmpName} would be {targetDir}/{realName}.cfg', file=sys.stderr)
|
||||
else:
|
||||
os.replace( tmpName, f'{targetDir}/{realName}' )
|
||||
if args.debug:
|
||||
print( f'DEBUG: Created {targetDir}/{realName}', file=sys.stderr)
|
||||
|
||||
return fileNames.values()
|
||||
|
||||
def execStrInFile(fileName, f):
|
||||
execStr = f'exec {fileName}'
|
||||
|
||||
def appendToActuals(targetDir, fileList):
|
||||
for currFile in fileList:
|
||||
cfgName = targetDir + '/' + open(currFile.split('_')[0] + '.cfg', 'r')
|
||||
if exists(cfgName):
|
||||
if execStrInFile(currFile, cfgFile):
|
||||
pass
|
||||
else:
|
||||
pass
|
Loading…
Reference in New Issue