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