refactored to work with OOP
parent
c3b816c532
commit
7936dd0d10
|
@ -1,5 +1,6 @@
|
||||||
"""Verify all the things that could go wrong."""
|
"""Verify all the things that could go wrong."""
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
|
from tfscript import types
|
||||||
|
|
||||||
def verifyConfig(cfg: dict) -> (dict, dict):
|
def verifyConfig(cfg: dict) -> (dict, dict):
|
||||||
verifiedConfig = {}
|
verifiedConfig = {}
|
||||||
|
@ -7,35 +8,37 @@ def verifyConfig(cfg: dict) -> (dict, dict):
|
||||||
# Do defaults first
|
# Do defaults first
|
||||||
errors = {}
|
errors = {}
|
||||||
|
|
||||||
defaults = None
|
defaults = []
|
||||||
|
|
||||||
if "default" in cfg:
|
if 'default' in cfg:
|
||||||
defaults = cfg.pop("default")
|
defaultCFG = cfg.pop('default')
|
||||||
errMessages = []
|
errMessages = []
|
||||||
for key, data in defaults.items():
|
for key, data in defaultCFG.items():
|
||||||
isAlias = False
|
bind = types.bind(key, data)
|
||||||
if "alias" in data:
|
if bind.targetType is not None:
|
||||||
isAlias = data["alias"]
|
bind = bind.targetType(bind.key, bind.fields)
|
||||||
if not isinstance(isAlias, bool):
|
defaults.append(bind)
|
||||||
errMessages.append(f'"alias" field in "{key}" makes no sense: "{isAlias}"')
|
|
||||||
errMessages.extend(validBind(key, data, alias = isAlias) )
|
errMessages.extend(bind.errors)
|
||||||
if len(errMessages) > 0:
|
|
||||||
errors.update( {"default": errMessages} )
|
if len(errList) > 0:
|
||||||
|
errors.update({'default': errMessages})
|
||||||
|
|
||||||
classList = [
|
classList = [
|
||||||
"scout",
|
'scout',
|
||||||
"soldier",
|
'soldier',
|
||||||
"pyro",
|
'pyro',
|
||||||
("demo","demoman"),
|
('demo','demoman'),
|
||||||
("engi","engineer"),
|
('engi','engineer'),
|
||||||
("heavy","heavyweapons"),
|
('heavy','heavyweapons'),
|
||||||
"medic",
|
'medic',
|
||||||
"sniper",
|
'sniper',
|
||||||
"spy"
|
'spy'
|
||||||
]
|
]
|
||||||
|
|
||||||
for cclass in classList:
|
for cclass in classList:
|
||||||
classCFG = None
|
classCFG = None
|
||||||
|
classBinds = []
|
||||||
className = cclass
|
className = cclass
|
||||||
if isinstance(cclass, str) and cclass in cfg:
|
if isinstance(cclass, str) and cclass in cfg:
|
||||||
classCFG = cfg.pop(cclass)
|
classCFG = cfg.pop(cclass)
|
||||||
|
@ -52,21 +55,23 @@ def verifyConfig(cfg: dict) -> (dict, dict):
|
||||||
continue
|
continue
|
||||||
errMessages = []
|
errMessages = []
|
||||||
for key, data in classCFG.items():
|
for key, data in classCFG.items():
|
||||||
isAlias = False
|
bind = types.bind(key, data)
|
||||||
if "alias" in data:
|
|
||||||
isAlias = data["alias"]
|
if binds.targetType is not None:
|
||||||
if not isinstance(isAlias, bool):
|
bind = bind.targetType(bind.key, bind.fields)
|
||||||
errMessages.append(f'"alias" field in "{key}" makes no sense: "{isAlias}"')
|
classBinds.append(bind)
|
||||||
errMessages.extend( validBind(key, data, alias = isAlias) )
|
|
||||||
|
errMessages.extend(bind.errors)
|
||||||
|
|
||||||
if len(errMessages) > 0:
|
if len(errMessages) > 0:
|
||||||
errors.update( {className: errMessages} )
|
errors.update( {className: errMessages} )
|
||||||
verifiedConfig.update({className: classCFG})
|
verifiedConfig.update({className: classBinds})
|
||||||
|
|
||||||
# Turn list into only strings by expanding tuples
|
# Turn list into only strings by expanding tuples
|
||||||
for i, clss in enumerate(classList):
|
for i, cclass in enumerate(classList):
|
||||||
if isinstance(clss, tuple):
|
if isinstance(cclass, tuple):
|
||||||
classList.insert(i+1, clss[1])
|
classList.insert(i+1, cclass[1])
|
||||||
classList.insert(i+1, clss[0])
|
classList.insert(i+1, cclass[0])
|
||||||
classList.pop(i)
|
classList.pop(i)
|
||||||
|
|
||||||
globalErrors = []
|
globalErrors = []
|
||||||
|
@ -78,189 +83,13 @@ def verifyConfig(cfg: dict) -> (dict, dict):
|
||||||
globalErrors.append(f'Conflicting names for section: "{remainingClass}" and "{otherName}"')
|
globalErrors.append(f'Conflicting names for section: "{remainingClass}" and "{otherName}"')
|
||||||
|
|
||||||
if len(globalErrors) > 0:
|
if len(globalErrors) > 0:
|
||||||
errors.update({"file": globalErrors})
|
errors.update({'file': globalErrors})
|
||||||
|
|
||||||
if len(errors) > 0:
|
if len(errors) > 0:
|
||||||
verifiedConfig.update({"errors": errors})
|
verifiedConfig.update({'errors': errors})
|
||||||
|
|
||||||
return verifiedConfig, defaults
|
return verifiedConfig, defaults
|
||||||
|
|
||||||
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, errMsgs = validBindType(key, data)
|
|
||||||
ret.extend(errMsgs)
|
|
||||||
|
|
||||||
extras = dataCopy.keys()
|
|
||||||
if len(extras) > 0:
|
|
||||||
extrasString = "\n ".join(extras)
|
|
||||||
ret.append(f'Unused fields in "{key}":\n {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',
|
|
||||||
'scrolllock', 'numlock',
|
|
||||||
'ins', 'home', 'pgup',
|
|
||||||
'del', 'end', 'pgdn'
|
|
||||||
]
|
|
||||||
|
|
||||||
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
|
|
||||||
break
|
|
||||||
|
|
||||||
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 dataCopy, errMsgs
|
|
||||||
|
|
||||||
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 = []
|
|
||||||
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")
|
|
||||||
else:
|
|
||||||
if not isinstance(content, str) and not isinstance(content, list):
|
|
||||||
errMsgs.append(f'impulse must be a single string or list')
|
|
||||||
|
|
||||||
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":
|
|
||||||
if isinstance(content, dict):
|
|
||||||
if "press" not in content:
|
|
||||||
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":
|
|
||||||
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)
|
|
||||||
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)
|
|
||||||
errMsgs.extend(errMessages)
|
|
||||||
|
|
||||||
if "condition" not in content:
|
|
||||||
errMsgs.append("Double requires condition to toggle")
|
|
||||||
else:
|
|
||||||
# Validate the toggler
|
|
||||||
condition = content["condition"]
|
|
||||||
if not validKey(condition):
|
|
||||||
errMsgs.append(f'Invalid condition to toggle "{condition}"')
|
|
||||||
|
|
||||||
if "cancel" in content:
|
|
||||||
canType = content["cancel"]
|
|
||||||
if canType not in ["released", "both"]:
|
|
||||||
errMsgs.append(f'"cancel" must be either "released" or "both", not {canType}')
|
|
||||||
|
|
||||||
elif bindType == "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:
|
|
||||||
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):
|
def findTwin(className):
|
||||||
classDict = {
|
classDict = {
|
||||||
"demo": "demoman",
|
"demo": "demoman",
|
||||||
|
|
Loading…
Reference in New Issue