1 #!/usr/bin/env python 2 3 import compiler 4 import marshal, imp, os, stat, struct 5 6 # Originally from Analysis/Writers/Python. 7 8 def write_module(module, source_filename, target_filename, syntax_check=1): 9 10 """ 11 Write the given 'module', associated with the given 'source_filename', to a 12 file with the given 'target_filename'. The optional 'syntax_check' flag (set 13 by default) ensures that the module is syntactically correct. 14 """ 15 16 if syntax_check: 17 compiler.syntax.check(module) 18 compiler.misc.set_filename(source_filename, module) 19 generator = compiler.pycodegen.ModuleCodeGenerator(module) 20 f = open(target_filename, "wb") 21 f.write(get_header(source_filename)) 22 marshal.dump(generator.getCode(), f) 23 f.close() 24 25 def get_header(filename): 26 27 "Taken from compiler.pycodegen. Prepare the compiled module header." 28 29 MAGIC = imp.get_magic() 30 mtime = os.stat(filename)[stat.ST_MTIME] 31 mtime = struct.pack('<i', mtime) 32 return MAGIC + mtime 33 34 # Processing functions. 35 36 def process_nodes(root_node, prefix): 37 38 """ 39 Under the 'root_node', process all suitable expression nodes which employ 40 special names using the given 'prefix'. 41 """ 42 43 for node in root_node.getChildNodes(): 44 # Identify suitable names and add such nodes 45 if isinstance(node, compiler.ast.CallFunc): 46 process_callfunc(node, prefix) 47 elif isinstance(node, compiler.ast.Getattr): 48 process_getattr(node, prefix, root_node) 49 else: 50 process_nodes(node, prefix) 51 52 def process_callfunc(node, prefix): 53 54 """ 55 Process the CallFunc 'node' searching for special names with the given 56 'prefix'. 57 """ 58 59 # Check the target. 60 target = node.node 61 if isinstance(target, compiler.ast.Getattr): 62 if process_getattr(target, prefix, node): 63 64 # Process all sibling arguments of the new first argument, too. 65 66 process_callfunc_args(node, prefix, start_index=1) 67 68 else: 69 process_callfunc_args(node, prefix) 70 71 else: 72 process_callfunc_args(node, prefix) 73 74 def process_callfunc_args(node, prefix, start_index=0): 75 76 """ 77 Process the arguments of the given CallFunc 'node' searching for special 78 names with the given 'prefix'. The optional 'start_index' is used to 79 indicate the first argument to be processed. 80 """ 81 82 for index in range(start_index, len(node.args)): 83 arg = node.args[index] 84 if isinstance(arg, compiler.ast.Getattr): 85 process_getattr(arg, prefix, node, index=index) 86 87 def process_getattr(node, prefix, parent, index=None): 88 89 """ 90 Process the Getattr 'node' searching for special names with the given 91 'prefix', using the given 'parent' to add transformed expressions in place 92 of the original ones. 93 94 The optional 'index' is used when arguments of CallFunc nodes are being 95 processed and where a replacement of such arguments is occurring. 96 """ 97 98 # Detected cases: 99 # node.attr plus node.attr.attr 100 # (obj.node).attr plus (obj.node).attr.attr 101 # Note that the deep cases are dealt with first using a recursive call. 102 103 if getattr_has_prefix(node, prefix) or \ 104 isinstance(node.expr, compiler.ast.Getattr) and propagated_prefix(process_getattr(node.expr, prefix, node)): 105 106 # Replace CallFunc plus Getattr occurrences: 107 # node.attr(args) -> Node_attr(node, args) 108 # fn(node.attr) -> fn(Node_attr(node)) 109 110 if isinstance(parent, compiler.ast.CallFunc): 111 112 # If this node is not an argument, transform the call. 113 114 if index is None: 115 parent.node = compiler.ast.Name("Node_%s" % node.attrname) 116 parent.args.insert(0, node.expr) 117 118 else: 119 replacement = compiler.ast.CallFunc( 120 compiler.ast.Name("Node_%s" % node.attrname), 121 [node.expr] 122 ) 123 parent.args[index] = replacement 124 125 # Replace plain Getattr nodes: 126 # node.attr -> Node_attr(node) 127 # NOTE: Nasty but necessary rewiring of the parent node required. 128 129 else: 130 replacement = compiler.ast.CallFunc( 131 compiler.ast.Name("Node_%s" % node.attrname), 132 [node.expr] 133 ) 134 for key, value in parent.__dict__.items(): 135 # Detect lists. 136 try: 137 if node in value: 138 index = value.index(node) 139 value[index] = replacement 140 except TypeError: 141 if value is node: 142 parent.__dict__[key] = replacement 143 144 # Propagate whether the kind of result might need transforming itself. 145 146 return node.attrname 147 148 else: 149 process_nodes(node, prefix) 150 return None 151 152 def propagated_prefix(attrname): 153 154 """ 155 Return whether the given 'attrname' used in a transformation should be 156 considered significant at the parent level. 157 """ 158 159 return attrname in ("ownerElement", "ownerDocument") 160 161 def getattr_has_prefix(node, prefix): 162 163 """ 164 Determine whether the given Getattr 'node' employs the special 'prefix' in a 165 number of ways. 166 """ 167 168 # Check the expression as a simple name: 169 # node.attr 170 if isinstance(node.expr, compiler.ast.Name) and node.expr.name.startswith(prefix): 171 return 1 172 # Check the attribute name of child expressions: 173 # (obj.node).attr 174 elif isinstance(node.expr, compiler.ast.Getattr) and node.expr.attrname.startswith(prefix): 175 return 1 176 else: 177 return 0 178 179 def process_file(filename, prefix="x2_"): 180 181 """ 182 Process the module given by the specified 'filename'. The optional special 183 'prefix' marks those variables to be processed. 184 """ 185 186 # Open the module as an AST. 187 188 module = compiler.parseFile(filename) 189 190 # Find references to special variables. 191 192 process_nodes(module, prefix) 193 194 # Write the module. 195 196 write_module(module, filename, os.path.splitext(filename)[0] + ".pyc") 197 return module 198 199 if __name__ == "__main__": 200 import sys 201 if len(sys.argv) < 2: 202 print "libxml2macro.py <module-filename>" 203 sys.exit(1) 204 process_file(sys.argv[1]) 205 206 # vim: tabstop=4 expandtab shiftwidth=4