tfscript/src/tfscript/verify.py

162 lines
5.1 KiB
Python

"""Verify all the things that could go wrong."""
def verifyConfig(cfg: dict):
# Do aliases first
aliasErrors = []
aliases = None
if "aliases" in cfg:
aliases = cfg.pop("aliases")
for key, data in aliases.items():
errMessages = validBind(key, data, alias = True)
if len(errMessages) > 0:
for msg in errMessages:
aliasErrors.append(f"Error in aliases: {msg}")
errors = []
for cclass in cfg:
for key, data in cfg[cclass].items():
errMessages = validBind(key, data)
if len(errMessages) > 0:
for msg in errMessages:
errors.append(f"Error in {cclass}: {msg}")
errors += aliasErrors
if len(errors) > 0:
cfg.update({"errors": errors})
return cfg
def validBind(key, data, alias = False) -> list:
"""Check for valid key and valid binding"""
ret = []
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)
extras = dataCopy.keys()
if len(extras) > 0:
extrasString = "\n\t".join(extras)
ret.append(f'Unused fields in "{key}":\n\t{extrasString}')
return ret
validKeyList = [
'\'', '=', ',', '-', '[', '\\', ']', '`', '.', '/',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
'u', 'v', 'w', 'x', 'y', 'z',
'mouse1', 'mouse2', 'mouse3', 'mouse4', 'mouse5',
'shift', 'capslock', 'ctrl', 'semicolon', 'space', 'enter',
'backspace'
]
def validKey(key):
"""determines if the key is a valid key"""
key = str(key).lower()
return key in validKeyList
def validBindType(key, data: dict):
"""
Checks if `key` has a valid bind type,
like 'double' or 'hold'
"""
validType = False
types = [
"impulse",
"hold",
"toggle",
"double",
"repeat"
]
errMsgs = []
for potentialType in data.keys():
if potentialType in types:
validType = True
data, errMsgs = removeRelaventFields(data, potentialType)
break
if not validType:
errMsgs.append(f'Key "{key}" has no known bind type')
return data, errMsgs
def removeRelaventFields(data, bindType):
errMsgs = []
# 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):
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")
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:
# Nasty bit of recursion to validate the action.
# 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
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
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}"')
elif bindType == "repeat":
content = data.pop("repeat")
unit = "s"
if "unit" in content:
# Set unit if provided
unitStr = str(content["unit"]).lower()
if unitStr in ["t", "ticks"]:
unit = "t"
elif unitStr not in ["s", "seconds"]:
# If not seconds or ticks, error
errMsgs.append(f"Invalid interval unit {unitStr}")
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")
return data, errMsgs