From 4296cc07481b68582efd7616048319b5daf03b9b Mon Sep 17 00:00:00 2001 From: nick Date: Mon, 31 Oct 2022 13:47:17 -0400 Subject: [PATCH] deleted --- src/tfscript/__init__.py | 33 --- src/tfscript/cli.py | 180 ----------- src/tfscript/tftypes.py | 624 --------------------------------------- src/tfscript/verify.py | 105 ------- src/tfscript/writing.py | 149 ---------- 5 files changed, 1091 deletions(-) delete mode 100644 src/tfscript/__init__.py delete mode 100644 src/tfscript/cli.py delete mode 100644 src/tfscript/tftypes.py delete mode 100644 src/tfscript/verify.py delete mode 100644 src/tfscript/writing.py diff --git a/src/tfscript/__init__.py b/src/tfscript/__init__.py deleted file mode 100644 index b679ab8..0000000 --- a/src/tfscript/__init__.py +++ /dev/null @@ -1,33 +0,0 @@ -""" This has only one function. It mostly exists to allow imports of things like tftypes """ - -from copy import deepcopy -from tfscript.tftypes import Double - -def makeCFG(bindList, default=False): - - if default: - # Write to defaultDict instead of condDict - Double.condDict = Double.defaultDict - else: - Double.condDict = deepcopy(Double.defaultDict) - - ret = '' - - for bind in bindList: - ret += bind.toTF2() - - # 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 - if default or Double.condDict != Double.defaultDict: - # ==, and by extension !=, does in fact check - # for dictionary equality in keys and values - for key, toggles in Double.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'bind {key} "+{key}_toggles"\n' - ) - - return ret diff --git a/src/tfscript/cli.py b/src/tfscript/cli.py deleted file mode 100644 index 368eb6a..0000000 --- a/src/tfscript/cli.py +++ /dev/null @@ -1,180 +0,0 @@ -''' -Command line module for making Team Fortress 2 macro scripts from -YAML source code. -''' - -__all__ = ['parseFile'] -__author__ = 'Nicholas Hope (dict, dict): - '''Parse, verify, and do the conversion.''' - config = yaml.safe_load(inputFile) - - # See verify.py - config, defaults = verify.verifyConfig(config) - if 'warnings' in config: - for cclass, messages in config.pop('warnings').items(): - print(f'Warning in {cclass}:', file=stderr) - for msg in messages: - print(f' {msg}', file=stderr) - - if 'errors' in config: - for cclass, messages in config['errors'].items(): - print(f'Error in {cclass}:', file=stderr) - for msg in messages: - print(f' {msg}', file=stderr) - return None, None - else: - return config, defaults - -def parseConfig(config, defaults): - '''With validated data structure, write out all the files.''' - global args - global targetDir - - if isdir(targetDir) == False: - mkdir(targetDir) - if args.debug: - print( f'DEBUG: Created directory {targetDir}', file=stderr) - - tempsAndReals = {} - - if defaults is not None: - config.update({'default': defaults}) - - for class_ in config: - stringToWrite = makeCFG( - config[class_], - default=(class_ == 'default') - ) - replaceDict = writing.writeOutput(stringToWrite, class_, args) - tempsAndReals.update(replaceDict) - - return tempsAndReals - -def parseCLI(): - # Handle command line - parser = argparse.ArgumentParser( - description='Parse YAML file and produce TF2 config script.' - ) - parser.add_argument( '-d', '--debug', action='store_true', - help='Enable debugging messages.') - parser.add_argument( '-n', '--dry-run', action='store_true', - help='Parse input file, but don\'t write anything.') - parser.add_argument( '-f', '--force', action='store_true', - help='Force tfscript to continue until catastrophic failure') - parser.add_argument( '-D', '--directory', action='store', type=str, - help='Change output directory') - # warnings - parseWarnNames = [ - 'implicit-release', 'implicit-off', - 'implicit-primary', 'implicit-secondary', - 'implicit' - ] - for warnName in parseWarnNames: - splitWarnName = ' '.join(warnName.split('-')) - parser.add_argument( '-W' + warnName, action='store_true', - help=f'Generate warning on {splitWarnName} creation') - # positional argument: first non-hyphenated argument is input file - parser.add_argument( 'infile', type=argparse.FileType('r'), - help='File containing YAML to convert.') - return parser - -def getTargetDir(systemName): - if systemName == 'Darwin': - if float( '.'.join( GetOSRelease().split('.')[0:2] ) ) >= 10.15: - warn( - 'As of macOS Catalina (v10.15), 32-bit applications' - + ' like TF2 do not run. tfscript will run, but you can\'t run TF2' - + ' on this system', - category=RuntimeWarning ) - return None - return expanduser('~/Library/Application Support/Steam') - - elif systemName == 'Windows': - # oh god why do we have to use the registry - accessReg = ConnectRegistry(None, HKEY_LOCAL_MACHINE) - accessKey = OpenKey(accessReg, 'SOFTWARE\\WOW6432Node\\Valve\\Steam') - keyNum = 0 - while True: - try: - accessSubkeyName, data, _ = EnumValue(accessKey, keyNum) - if accessSubkeyName == 'InstallPath': - return data - except EnvironmentError: - break - keyNum += 1 - return None - - elif systemName == 'Linux': - return expanduser('~/.local/Steam') - - elif systemName == 'Java': - warn('Java-based OSes are not supported yet by tfscript.', category=RuntimeWarning) - - return None - -def main() -> int: - ''' Command line interface. ''' - global args - global targetDir - parser = parseCLI() - - args = parser.parse_args() - - systemName = GetOSName() - if args.directory is not None: - 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') + dirsep - elif args.force: - # Unsupported OS but -f specified - if args.debug: - print('DEBUG: forced to continue, output set to current directory', file=stderr) - targetDir = '.' - else: - # Unsupported OS and not forced to continue - return 2 - - config, defaults = parseFile(args.infile) - if config is None: - return 2 - - fileNames = parseConfig(config, defaults) - fileList = writing.replaceFiles(targetDir, fileNames, args) - defaultsGiven = (defaults is not None) - writing.appendToActuals(targetDir, fileList, defaultsGiven, args) - - return 0 - -if __name__ == '__main__': - exit(main()) diff --git a/src/tfscript/tftypes.py b/src/tfscript/tftypes.py deleted file mode 100644 index e4b8fd4..0000000 --- a/src/tfscript/tftypes.py +++ /dev/null @@ -1,624 +0,0 @@ -validKeyList = [ - # top row - 'escape', 'f1', 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9', 'f10', 'f11', 'f12', - # keyboard - '`', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '=', 'backspace', - 'tab', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\\', - 'capslock', 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'semicolon', '\'', 'enter', - 'shift', 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/', 'rshift', - 'ctrl', 'lwin', 'alt', 'space', 'rwin', 'ralt', 'rctrl', - # mouse - 'mouse1', 'mouse2', 'mouse3', 'mouse4', 'mouse5', 'mwheelup', 'mwheeldown', - # 8 of the 9 keys to the upper-right (PrtScn can't be bound) - 'scrolllock', 'numlock', - 'ins', 'home', 'pgup', - 'del', 'end', 'pgdn', - # arrows - 'uparrow', 'downarrow', - 'leftarrow', 'rightarrow' -] - -popErrors = (AttributeError, KeyError, TypeError) - -class Bind(object): - ''' - Parent class for all bind types. - Verifies key, creates local variables - ''' - bindTypes = [] - instances = {} - - def __init__(self, key='', fields={}, /,*, parent=None): - if parent is None: - self.alias = False - self.key = str(key) - self.fields = fields - self.errors = [] - self.warnings = [] - self.TargetType = None - else: - self.alias = parent.alias - self.key = parent.key - self.fields = parent.fields - self.errors = parent.errors - self.warnings = parent.warnings - - # redefined for each unique type, default just verifies key - # and some other universal fields like alias and finds targetType - self.verify() - - if type(self) is Bind: - # not using isinstance(), because all subclasses are also instances - # of bind. - return - - if len(self.fields) > 0: - # verify function should remove all fields relavent to the bind. - # Any extras are errors - - self.warnings.append(f'extra fields in "{self.key}":') - if isinstance(self.fields, str): - # iterating over a str returns each character, - # making meaningless error messages - self.warnings.append(f' "{self.fields}"') - else: - for field in self.fields: - self.warnings.append(f' "{field}"') - if len(self.errors) == 0: - # no errors, add new instance to the list of instances - try: - self.instances[type(self)].append(self) - except KeyError: - self.instances[type(self)] = [self] - - def verify(self): - self.alias = self.optional('alias', default=False) - if not isinstance(self.alias, bool): - self.err( - f'`alias` should be "yes" or "no", not "{self.alias}"' - ) - - try: - typeName, self.key = self.key.split(' ', 1) - # all types start with a capital - typeName = typeName.lower().capitalize() - if not self.alias: - # don't mess with alias names - self.key = self.key.lower() - except ValueError: - # catastrophic error: either no type or no key, assume no type - self.errors.append(f'could not find type in "{self.key}"') - return - - for type_ in self.bindTypes: - if typeName == type_.__name__: - self.TargetType = type_ - break - - if self.TargetType is None: - self.errors.append( - f'"{typeName}" is not a valid type for "{self.key}"' - ) - - if (not self.alias) and (self.key not in validKeyList): - self.errors.append(f'invalid key name: "{self.key}"') - - def optional(self, name, /,*, default=None): - try: - return self.fields.pop(name) - except popErrors: - return default - - def cmdListFrom(self, name, /,*, default=None): - result = self.fields.pop(name) - if isinstance(result, str): - return result.split(';') - elif isinstance(result, list): - return result - else: - return default - - def toTargetType(self): - if self.TargetType is None: - # do nothing - return self - # cast to targetType, "inheriting" stuff from self - bind = self.TargetType(parent=self) - return bind - - def err(self, message): - self.errors.append( - f'{type(self).__name__.lower()} "{self.key}" {message}' - ) - - def warn(self, message): - self.warnings.append( - f'{type(self).__name__.lower()} "{self.key}" {message}' - ) - - -class Impulse(Bind): - def verify(self): - self.command: list = None - if not isinstance(self.fields, dict): - self.fields = {'command': self.fields} - - try: - self.command = self.cmdListFrom( - 'command', - default=self.fields - ) - if self.command is None: - self.err('`command` field must be string or list') - except popErrors: - self.err('requires `command` field') - - def toTF2(self) -> str: - if self.alias: - bindOrAlias = 'alias' - else: - bindOrAlias = 'bind' - - allInstructions = self.shortcut(self.command) - instruction = ';'.join(allInstructions) - - return f'{bindOrAlias} {self.key} "{instruction}"\n' - - def shortcut(self, instList): - for i, instruction in enumerate(instList): - try: - cmd, restOfCmd = instruction.split(' ', 1) - except ValueError: - # no spaces in cmd - cmd, restOfCmd = instruction, '' - - simpleSCs = { - 'primary': 'slot1', - 'secondary': 'slot2', - 'melee': 'slot3' - } - # if is shortcut, change - cmd = simpleSCs.get(cmd, cmd) - - if cmd == 'voice': - cmd = 'voicemenu' - restOfCmd = self.expandVoice(restOfCmd) - - elif cmd == 'build' or cmd == 'destroy': - restOfCmd = self.expandBuildings(restOfCmd) - - elif cmd == 'loadout' and restOfCmd.isalpha(): - cmd = 'load_itempreset' - try: - loadoutNum = ['a','b','c','d'].index(restOfCmd.lower()) - restOfCmd = str(loadoutNum) - except ValueError: - # not a load_itempreset shortcut - pass - - elif cmd == 'unalias': - cmd = 'alias' - # adding empty arg to indicate unaliasing - restOfCmd += ' ' - - if restOfCmd != '': - cmd += ' ' + restOfCmd - instList[i] = cmd - - return instList - - def expandVoice(self, keyword): - keyword = keyword.lower() - - allLists = ( - ('medic', 'thanks', 'go', 'move up', 'go left', 'go right', 'yes', 'no', 'pass to me'), - ('incoming', 'spy', 'sentry ahead', 'teleporter here', 'dispenser here', 'sentry here', 'activate uber', 'uber ready'), - ('help', 'battle cry', 'cheers', 'jeers', 'positive', 'negative', 'nice shot', 'good job'), - ) - - for menu, voiceList in enumerate(allLists): - for selection, shortcut in enumerate(voiceList): - if keyword == shortcut: - return f'{menu} {selection}' - - def expandBuildings(self, building): - buildingNums = { - 'dispenser': '0 0', - 'entrance': '1 0', - 'exit': '1 1', - 'sentry': '2 0' - } - return buildingNums.get(building, building) - - -class Hold(Bind): - def verify(self): - self.press: list = None - self.release: list = None - if not isinstance(self.fields, dict): - self.fields = {'press': self.fields} - - # verify press - try: - self.press = self.cmdListFrom('press') - if self.press is None: - self.err('`press` field must be string or list') - except popErrors: - self.err('requires `press` field') - - # verify release - try: - self.release = self.cmdListFrom('release') - if self.release is None: - self.err('`release` field must be string or list') - except popErrors: - if self.press is None: - return - self.warn('has no `release`, creating one') - # no release specified, do -action for each item in press - self.release = [] - for cmd in self.press: - if cmd[0] == '+': - self.release.append('-' + cmd[1:]) - - def toTF2(self) -> str: - if self.alias: - bindOrAlias = 'alias' - else: - bindOrAlias = 'bind' - holdStr = f'hold_{self.key}' - - # Making impulse instances from self.press and .release - # allows them to share the shortcuts - pressObj = Impulse('+' + holdStr, self.press) - pressObj.alias = True - pressStr = pressObj.toTF2() - - releaseObj = Impulse('-' + holdStr, self.release) - releaseObj.alias = True - releaseStr = releaseObj.toTF2() - - if self.alias: - # if alias, do this to avoid activating - # and never deactivating - minuskey = '-' + self.key - self.key = '+' + self.key - - code = ( - pressStr + releaseStr - + f'{bindOrAlias} {self.key} "+{holdStr}"\n' - ) - if self.alias: - code += f'alias {minuskey} "-{holdStr}"\n' - return code - - -class Toggle(Bind): - def verify(self): - self.on : list = None - self.off: list = None - if not isinstance(self.fields, dict): - self.fields = {'on': self.fields} - - # verify on - try: - self.on = self.cmdListFrom('on') - if self.on is None: - self.err(f'`on` field must be string or list') - except popErrors: - self.err('requires `on` field') - - # verify off - try: - self.off = self.cmdListFrom('off') - if self.off is None: - self.err(f'`off` field must be string or list') - except popErrors: - # no off specified, do -action for each item in on - self.off = [] - if self.on is None: - return - for cmd in self.on: - if cmd[0] == '+': - self.off.append('-' + cmd[1:]) - - def toTF2(self) -> str: - if self.alias: - bindOrAlias = 'alias' - else: - bindOrAlias = 'bind' - toggleStr = f'toggle_{self.key}' - onStr = f'{toggleStr}_on' - offStr = f'{toggleStr}_off' - - onObj = Impulse(onStr, self.on) - onObj.alias = True - toggleOn = onObj.toTF2() - # remove starting/trailing " and \n - toggleOn = toggleOn[:-2] - - offObj = Impulse(offStr, self.off) - offObj.alias = True - toggleOff = offObj.toTF2()[:-2] - - return ( - f'{toggleOn}; alias {toggleStr} {offStr}"\n' - + f'{toggleOff}; alias {toggleStr} {onStr}"\n' - + f'alias {toggleStr} "{onStr}"\n' - + f'{bindOrAlias} {self.key} "{toggleStr}"\n' - ) - - -class Double(Bind): - defaultDict = {} - condDict = {} - bindNames = [] - - def verify(self): - self.primStr = f'{self.key}_primary' - self.secondStr = f'{self.key}_secondary' - self.isToggle = False - self.cancelBoth = False - - self.primary: Bind = None - self.secondary: Bind = None - self.condition: str = None - self.type: str = None - - # toggler - try: - self.condition = self.fields.pop('condition') - if self.condition not in validKeyList: - self.err(f'has invalid `condition` field: "{self.condition}"') - except popErrors: - self.err('requires `condition` field') - - self.isToggle = self.optional('toggle', default=False) - if not isinstance(self.isToggle, bool): - self.err( - '`toggle` field should be "yes" or "no",' - + f' not "{self.isToggle}"' - ) - - # type - try: - self.type = self.fields.pop('type').lower() - if self.type not in self.bindNames: - # catastrophic: invalid type - self.err(f'has invalid type: "{self.type}"') - return - except popErrors: - # catastrophic: no type given - self.err('requires `type` field') - return - - # cancel mode, must happend after type has been inferred - cancel = self.optional('cancel', default='released') - - if not isinstance(cancel, str): - self.err(f'`cancel` field must be "released" or "both"') - else: - if cancel == 'both': - if self.type == 'hold': - self.cancelBoth = True - else: - self.err( - '`cancel` field only affects "hold",' - + f' not "{self.type}"' - ) - elif cancel == 'released': - self.cancelBoth = False - else: - self.err( - '`cancel` field must be "released"' - + f' or "both", not "{cancel}"' - ) - - try: - self.primary = self.getSection('primary', self.primStr) - except popErrors: - self.primary = None - - try: - self.secondary = self.getSection('secondary', self.secondStr) - except popErrors: - self.secondary = None - - if self.primary is self.secondary is None: - self.err('has neither primary nor secondary') - - def getSection(self, popName, key, /) -> Bind: - section = self.fields.pop(popName) - bind = Bind(f'{self.type} {key}', section) - bind = bind.toTargetType() - - bind.errors.remove(f'invalid key name: "{key}"') - self.prettifyList(bind.errors, key) - self.errors.extend(bind.errors) - self.prettifyList(bind.warnings, key) - self.warnings.extend(bind.warnings) - - return bind - - def prettifyList(self, strList, origStr): - repStr = ' '.join(origStr.split('_', 1)) - for i, cmd in enumerate(strList): - strList[i] = cmd.replace(origStr, repStr) - - def toTF2(self) -> str: - # Get code for primary and secondary actions. - # alias=true so the toTF2() method aliases - # them instead of binding them - self.primary.alias = True - mainCode = self.primary.toTF2() - self.secondary.alias = True - altCode = self.secondary.toTF2() - - # Make code to switch between the two actions - if self.cancelBoth: - mainCode, altCode = self.getCancelCode(mainCode, altCode) - - if self.type == 'hold': - self.primStr = '+hold_' + self.primStr - self.secondStr = '+hold_' + self.secondStr - - shiftStr = f'shift_{self.key}' - shiftCode = self.getChangeCode(shiftStr) - self.addToCondDict(shiftStr) - - return mainCode + altCode + shiftCode - - def getChangeCode(self, shift): - if self.alias: - bindOrAlias = 'alias' - else: - bindOrAlias = 'bind' - code = ( - f'alias +{shift} "{bindOrAlias} {self.key} {self.secondStr}"\n' - + f'alias -{shift} "{bindOrAlias} {self.key} {self.primStr}"\n' - ) - - if self.isToggle: - toggleObj = Toggle(shift, f'+{shift}') - toggleObj.alias = True # so it aliases instead of binding - code += toggleObj.toTF2() - else: - code += f'{bindOrAlias} {self.key} "{self.primStr}"\n' - return code - - def addToCondDict(self, shiftStr): - if self.isToggle: - changeStr = shiftStr - else: - changeStr = '+' + shiftStr - restoreStr = '-' + shiftStr - - if self.condition not in self.condDict: - # if not already present, make dict for key - self.condDict.update( { - self.condition: { - 'change_keys': [], - 'restore_keys': [] - } - } ) - - self.condDict[self.condition]['change_keys'].append(changeStr) - if self.isToggle == False: - self.condDict[self.condition]['restore_keys'].append(restoreStr) - - def getCancelCode(self, mainCode, altCode) -> (str, str): - # code to cancel both if either is released - # it copies the - statement from each to both. - # if it just extracted the name of the - statement, - # you'd end up with each recursively calling the other - - mainLines = mainCode.splitlines() - mainMinusLine = mainLines[1] - mainMinusName = mainMinusLine.split(' ')[1] - # second arg, without first or last quote - mainMinusStr = mainMinusLine.split(' ', 2)[2][1:-1] - - altLines = altCode.splitlines() - altMinusLine = altLines[1] - altMinusName = altMinusLine.split(' ')[1] - # same as above - altMinusStr = altMinusLine.split(' ', 2)[2][1:-1] - - # remove duplicate - actions - mainMinusSet = set(mainMinusStr.split(';')) - altMinusSet = set(altMinusStr.split(';')) - allCancels = mainMinusSet | altMinusSet - allCancelStr = ';'.join(allCancels) - - altMinusLineStart = ' '.join(altMinusLine.split(' ')[:2]) - altLines[1] = altMinusLineStart + f' "{allCancelStr}"' - altLines.insert(3, f'alias -{self.secondStr} "{altMinusName}"') - mainMinusLineStart = ' '.join(mainMinusLine.split(' ')[:2]) - mainLines[1] = mainMinusLineStart + f' "{allCancelStr}"' - mainLines.insert(3, f'alias -{self.primStr} "{mainMinusName}"') - - return ( - '\n'.join(mainLines) + '\n', - '\n'.join(altLines) + '\n' - ) - - -class Repeat(Bind): - def verify(self): - self.interval = None - self.command = None - - try: - intervalStr = str(self.fields.pop('interval')) - self.interval = int(intervalStr) - if self.interval <= 0: - self.err('`interval` must be greater than 0') - except (KeyError, TypeError): - self.err('requires `interval` field') - except ValueError: - self.err(f'has invalid `interval`: "{self.interval}"') - except AttributeError: - self.err(f'requires `interval` field') - - try: - self.command = self.fields.pop('command') - if not isinstance(self.command, (str, list)): - self.err('`command` must be string or list') - self.command = None - except popErrors: - self.err('requires `command` field') - - def toTF2(self) -> str: - # commented-out placeholder - return f'// repeat {self.key}\n' - - -class Literal(Bind): - def verify(self): - self.text = '' - self.run = False - if not isinstance(self.fields, dict): - self.fields = {'text': self.fields} - - if not self.alias: - try: - # keyname should be invalid, remove the error - self.errors.remove( - f'invalid key name: "{self.key}"' - ) - except ValueError: - # if not invalid key, indicate as such - self.warn('should not use a key as a label') - - if 'run' in self.fields: - self.run = self.fields.pop('run') - if not isinstance(self.run, bool): - self.errors.append( - f'`run` should be "yes" or "no", not "{self.run}"' - ) - if not self.alias: - self.warn('`run` specified without alias') - - try: - self.text = self.fields.pop('text') - except KeyError: - self.err('requires `text` field') - - if isinstance(self.text, str): - self.text = self.text.split(';') - elif not isinstance(self.text, list): - self.err('argument must be of string or list') - - def toTF2(self) -> str: - result = ';'.join(self.text) - if self.alias: - result = f'alias {self.key} "{result}"' - if self.run: - result += f'\n{self.key}' - return result + '\n' - -# This is at the bottom because it has to happen after -# all inheritances have been completed - -Bind.bindTypes = Bind.__subclasses__() -Double.bindNames = [ bind.__name__.lower() for bind in Bind.bindTypes ] \ No newline at end of file diff --git a/src/tfscript/verify.py b/src/tfscript/verify.py deleted file mode 100644 index aabfc43..0000000 --- a/src/tfscript/verify.py +++ /dev/null @@ -1,105 +0,0 @@ -"""Verify all the things that could go wrong.""" - -from tfscript import tftypes - -def verifyConfig(cfg: dict) -> (dict, dict): - verifiedConfig = {} - - errors = {} - warnings = {} - - # Do defaults first - defaults = [] - - classList = [ - 'default', - 'scout', - 'soldier', - 'pyro', - ('demo','demoman'), - ('engi','engineer'), - ('heavy','heavyweapons'), - 'medic', - 'sniper', - 'spy' - ] - - for isclass, class_ in enumerate(classList): - - classCFG = None - className = class_ - - if isinstance(class_, str) and class_ in cfg: - classCFG = cfg.pop(class_) - elif isinstance(class_, tuple): - for tupClass in class_: - if tupClass in cfg: - classCFG = cfg.pop(tupClass) - className = class_[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 - - classBinds = [] - errMessages = [] - warnMessages = [] - for key, data in classCFG.items(): - bind = tftypes.Bind(key, data) - - bind = bind.toTargetType() - if isclass: - classBinds.append(bind) - else: - defaults.append(bind) - - errMessages.extend(bind.errors) - warnMessages.extend(bind.warnings) - - if len(errMessages) > 0: - errors.update( {className: errMessages} ) - if len(warnMessages) > 0: - warnings.update( {className: warnMessages} ) - - verifiedConfig.update({className: classBinds}) - - # Turn list into only strings by expanding tuples - for i, class_ in enumerate(classList): - if isinstance(class_, tuple): - classList.insert(i+1, class_[1]) - classList.insert(i+1, class_[0]) - classList.pop(i) - - globalErrors = [] - for remainingClass in cfg: - if remainingClass not in classList: - globalErrors.append(f'"{remainingClass}" is not a valid class') - else: - otherName = findTwin(remainingClass) - 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}) - if len(warnings) > 0: - verifiedConfig.update({'warnings': warnings}) - - return verifiedConfig, defaults - -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 \ No newline at end of file diff --git a/src/tfscript/writing.py b/src/tfscript/writing.py deleted file mode 100644 index 2b939a9..0000000 --- a/src/tfscript/writing.py +++ /dev/null @@ -1,149 +0,0 @@ -from sys import stderr -from os.path import exists -from tempfile import NamedTemporaryFile -from shutil import move as moveRobust - -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=stderr) - - FilNedLen = len(str(filesNeeded)) - # extra 4 bytes is leeway - 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. - 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=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})', end='\n\n', file=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=stderr) - else: - # using shutil.move() because it can move files across disk drives on windows - moveRobust( tmpName, f'{targetDir}{realName}' ) - 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): - if defaultsGiven: - classList = [ - "scout", - "soldier", - "pyro", - "demo", - "engi", - "heavy", - "medic", - "sniper", - "spy" - ] - 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]}' - addCallIfUncalled(execStr, targetDir, currFile, args) - -def addCallIfUncalled(execStr, targetDir, fileName, args): - realFilePath = targetDir + getRealName(fileName) - - realExists = exists(realFilePath) - - # creates if it doesn't exist, so must come after the exists() call - cfgFile = open(realFilePath, 'a+') - if not realExists: - if args.debug: - print( f"DEBUG: Created {realFilePath}" ) - cfgFile.write(execStr + '\n') - - elif not strInFile(execStr, cfgFile): - cfgFile.write('\n' + execStr + '\n') - - cfgFile.close() - -def onlyFirsts(fileList): - for i, fileName in enumerate(fileList): - noExtension = fileName.split('.')[0] - number = int(noExtension.split('_')[2]) - if number != 1: - fileList.pop(i) - - return fileList - -def getRealName(fileName): - className = fileName.split('_')[0] - targetNames = { - "demo": "demoman", - "heavy": "heavyweapons", - "engi": "engineer", - "default": "autoexec" - } - if className in targetNames: - className = targetNames[className] - - return className + '.cfg' - -def strInFile(execStr, f): - # 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 - if execStr == line: - return True - - return False \ No newline at end of file