mirror of https://github.com/H-uru/korman.git
1 changed files with 125 additions and 0 deletions
@ -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)) |
Loading…
Reference in new issue