From f4593c8c3011cda04a6c89af05259aa19a4d2ea4 Mon Sep 17 00:00:00 2001 From: Joseph Davies Date: Mon, 13 Jul 2015 17:07:05 -0400 Subject: [PATCH] Plasma attribute parsing script --- korman/plasma_attributes.py | 125 ++++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 korman/plasma_attributes.py diff --git a/korman/plasma_attributes.py b/korman/plasma_attributes.py new file mode 100644 index 0000000..a647ff2 --- /dev/null +++ b/korman/plasma_attributes.py @@ -0,0 +1,125 @@ +import re +import ast + +# We want to grab all of the ptAttributes initialized at the start of every +# script. We could use the Abstract Syntax Tree parser... except that if we +# try to use that on a script written for a substantially different version of +# Python (e.g., parsing Path of the Shell scripts in the Py 3.4 interpreter), +# we'll get a nice *boom*. I don't want to write a full parser, so we'll +# compromise. +# +# Here's what we're going to do: +# Using a regex we'll collect all of the assignments that occur using the +# ptAttrib initializers, and then process them. Some arguments can contain +# lists and tuples. We can't use literal_eval since the function call can also +# contain keyword arguments, which from its point of view is an expression. +# Writing a full parser in Python's regex is not going to happen, so we'll lump +# them all together and throw them at an AST visitor to do the heavy lifting +# and give us back a nice data structure. + + +# This is the regex we'll be using to find all of the ptAttrib assignments. +# It captures commented-out assignments too, but the AST parser will wisely +# recognize them as comments and won't trouble us with them. +ptAttribFunction = "(#*\w+?\s*?=\s*?ptAttrib[^()]+?\s*?\(.+\).*\s*?)" +funcregex = re.compile(ptAttribFunction) + + +class PlasmaAttributeVisitor(ast.NodeVisitor): + def __init__(self): + self._attributes = dict() + + def visit_Module(self, node): + # Filter out anything that isn't an assignment, and make a nice list. + assigns = [x for x in node.body if isinstance(x, ast.Assign)] + for assign in assigns: + # We only want: + # - assignments with targets + # - that are taking a function call (the ptAttrib Constructor) + # - whose name starts with ptAttrib + if (len(assign.targets) == 1 + and isinstance(assign.value, ast.Call) + and hasattr(assign.value.func, "id") + and assign.value.func.id.startswith("ptAttrib")): + # Start pulling apart that delicious information + ptVar = assign.targets[0].id + ptType = assign.value.func.id + ptArgs = [] + for arg in assign.value.args: + value = self.visit(arg) + ptArgs.append(value) + + # Some scripts use dynamic ptAttribs (see: dsntKILightMachine) + # which only have an index. We don't want those. + if len(ptArgs) > 1: + # Add the common arguments as named items. + self._attributes[ptArgs[0]] = {"name": ptVar, "type": ptType, "desc": ptArgs[1]} + # Add the class-specific arguments under the 'args' item. + if ptArgs[2:]: + self._attributes[ptArgs[0]]["args"] = ptArgs[2:] + + # Add the keyword arguments, if any. + if assign.value.keywords: + for keyword in assign.value.keywords: + self._attributes[ptArgs[0]][keyword.arg] = self.visit(keyword.value) + return self.generic_visit(node) + + def visit_Name(self, node): + return(node.id) + + def visit_Num(self, node): + return(node.n) + + def visit_Str(self, node): + return(node.s) + + def visit_List(self, node): + elts = [] + for x in node.elts: + elts.append(self.visit(x)) + return elts + + def visit_Tuple(self, node): + elts = [] + for x in node.elts: + elts.append(self.visit(x)) + return tuple(elts) + + def generic_visit(self, node): + ast.NodeVisitor.generic_visit(self, node) + + +def get_attributes(scriptFile): + """Scan the file for assignments matching our regex, let our visitor parse them, and return the + file's ptAttribs, if any.""" + attribs = None + with open(scriptFile) as script: + results = funcregex.findall(script.read()) + if results: + # We'll fake the ptAttribs being all alone in a module... + assigns = ast.parse("\n".join(results)) + v = PlasmaAttributeVisitor() + v.visit(assigns) + if v._attributes: + attribs = v._attributes + return attribs + +if __name__ == "__main__": + import glob + import json + import os + import sys + + if len(sys.argv) != 2: + print("Specify a path containing Plasma Python!") + else: + readpath = sys.argv[1] + files = glob.glob(os.path.join(readpath, "*.py")) + ptAttribs = {} + for scriptFile in files: + attribs = get_attributes(scriptFile) + if attribs: + ptAttribs[os.path.basename(scriptFile)] = attribs + + jsonout = open("attribs.json", "w") + jsonout.write(json.dumps(ptAttribs, sort_keys=True, indent=2))