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=None): 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 45 # Find imports and discover if they are really used to define the 46 # special prefix. 47 48 if isinstance(node, compiler.ast.Import): 49 prefix = process_import(node, root_node) or prefix 50 51 # Identify suitable names and add replacement nodes. 52 53 elif isinstance(node, compiler.ast.CallFunc): 54 process_callfunc(node, prefix) 55 elif isinstance(node, compiler.ast.Getattr): 56 process_getattr(node, prefix, root_node) 57 else: 58 process_nodes(node, prefix) 59 60 def process_import(node, parent): 61 62 """ 63 Process the Import 'node' searching for the special incantation required to 64 set the prefix. Remove compliant nodes from their 'parent' node. If no new 65 prefix is found, return None. 66 """ 67 68 for name, alias in node.names: 69 if name == "libxml2macro": 70 71 # Remove this node from its parent. 72 73 parent.nodes.remove(node) 74 return alias 75 76 return None 77 78 def process_callfunc(node, prefix): 79 80 """ 81 Process the CallFunc 'node' searching for special names with the given 82 'prefix'. 83 """ 84 85 # Check the prefix. 86 87 if prefix is None: 88 return 89 90 # Check the target. 91 92 target = node.node 93 if isinstance(target, compiler.ast.Getattr): 94 if process_getattr(target, prefix, node): 95 96 # Process all sibling arguments of the new first argument, too. 97 98 process_callfunc_args(node, prefix, start_index=1) 99 100 else: 101 process_callfunc_args(node, prefix) 102 103 else: 104 process_callfunc_args(node, prefix) 105 106 def process_callfunc_args(node, prefix, start_index=0): 107 108 """ 109 Process the arguments of the given CallFunc 'node' searching for special 110 names with the given 'prefix'. The optional 'start_index' is used to 111 indicate the first argument to be processed. 112 """ 113 114 for index in range(start_index, len(node.args)): 115 arg = node.args[index] 116 if isinstance(arg, compiler.ast.Getattr): 117 process_getattr(arg, prefix, node, index=index) 118 119 def process_getattr(node, prefix, parent, index=None): 120 121 """ 122 Process the Getattr 'node' searching for special names with the given 123 'prefix', using the given 'parent' to add transformed expressions in place 124 of the original ones. 125 126 The optional 'index' is used when arguments of CallFunc nodes are being 127 processed and where a replacement of such arguments is occurring. 128 """ 129 130 # Check the prefix. 131 132 if prefix is None: 133 return 134 135 # Detected cases: 136 # node.attr plus node.attr.attr 137 # (obj.node).attr plus (obj.node).attr.attr 138 # Note that the deep cases are dealt with first using a recursive call. 139 140 if getattr_has_prefix(node, prefix) or \ 141 isinstance(node.expr, compiler.ast.Getattr) and propagated_prefix(process_getattr(node.expr, prefix, node)): 142 143 # Replace CallFunc plus Getattr occurrences: 144 # node.attr(args) -> Node_attr(node, args) 145 # fn(node.attr) -> fn(Node_attr(node)) 146 147 if isinstance(parent, compiler.ast.CallFunc): 148 149 # If this node is not an argument, transform the call. 150 151 if index is None: 152 parent.node = compiler.ast.Name("Node_%s" % node.attrname) 153 parent.args.insert(0, node.expr) 154 155 else: 156 replacement = compiler.ast.CallFunc( 157 compiler.ast.Name("Node_%s" % node.attrname), 158 [node.expr] 159 ) 160 parent.args[index] = replacement 161 162 # Replace plain Getattr nodes: 163 # node.attr -> Node_attr(node) 164 # NOTE: Nasty but necessary rewiring of the parent node required. 165 166 else: 167 replacement = compiler.ast.CallFunc( 168 compiler.ast.Name("Node_%s" % node.attrname), 169 [node.expr] 170 ) 171 for key, value in parent.__dict__.items(): 172 # Detect lists. 173 if hasattr(value, "__len__") and node in value: 174 index = value.index(node) 175 value[index] = replacement 176 elif value is node: 177 parent.__dict__[key] = replacement 178 179 # Propagate whether the kind of result might need transforming itself. 180 181 return node.attrname 182 183 else: 184 process_nodes(node, prefix) 185 return None 186 187 def propagated_prefix(attrname): 188 189 """ 190 Return whether the given 'attrname' used in a transformation should be 191 considered significant at the parent level. 192 """ 193 194 return attrname in ("ownerElement", "ownerDocument") 195 196 def getattr_has_prefix(node, prefix): 197 198 """ 199 Determine whether the given Getattr 'node' employs the special 'prefix' in a 200 number of ways. 201 """ 202 203 # Check the expression as a simple name: 204 # node.attr 205 206 if isinstance(node.expr, compiler.ast.Name) and node.expr.name.startswith(prefix): 207 return 1 208 209 # Check the attribute name of child expressions: 210 # (obj.node).attr 211 212 elif isinstance(node.expr, compiler.ast.Getattr) and node.expr.attrname.startswith(prefix): 213 return 1 214 else: 215 return 0 216 217 def include_import(module): 218 219 """ 220 Include an import statement in 'module' to make the macro library available. 221 """ 222 223 module.node.nodes.insert(0, compiler.ast.From("libxml2dom.macrolib", [("*", None)])) 224 225 def process_file(filename): 226 227 """ 228 Process the module given by the specified 'filename'. The optional special 229 'prefix' marks those variables to be processed. 230 """ 231 232 # Open the module as an AST. 233 234 module = compiler.parseFile(filename) 235 236 # Find references to special variables. 237 238 process_nodes(module) 239 240 # Add necessary imports. 241 242 include_import(module) 243 244 # Write the module. 245 246 write_module(module, filename, os.path.splitext(filename)[0] + ".pyc") 247 return module 248 249 if __name__ == "__main__": 250 import sys 251 if len(sys.argv) < 2: 252 print "libxml2macro.py <module-filename>" 253 sys.exit(1) 254 process_file(sys.argv[1]) 255 256 # vim: tabstop=4 expandtab shiftwidth=4