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@43 | 36 | def process_nodes(root_node, prefix): |
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@43 | 44 | # Identify suitable names and add such nodes |
paulb@43 | 45 | if isinstance(node, compiler.ast.CallFunc): |
paulb@43 | 46 | process_callfunc(node, prefix) |
paulb@43 | 47 | elif isinstance(node, compiler.ast.Getattr): |
paulb@43 | 48 | process_getattr(node, prefix, root_node) |
paulb@43 | 49 | else: |
paulb@43 | 50 | process_nodes(node, prefix) |
paulb@43 | 51 | |
paulb@43 | 52 | def process_callfunc(node, prefix): |
paulb@45 | 53 | |
paulb@45 | 54 | """ |
paulb@45 | 55 | Process the CallFunc 'node' searching for special names with the given |
paulb@45 | 56 | 'prefix'. |
paulb@45 | 57 | """ |
paulb@45 | 58 | |
paulb@43 | 59 | # Check the target. |
paulb@43 | 60 | target = node.node |
paulb@43 | 61 | if isinstance(target, compiler.ast.Getattr): |
paulb@45 | 62 | if process_getattr(target, prefix, node): |
paulb@45 | 63 | |
paulb@45 | 64 | # Process all sibling arguments of the new first argument, too. |
paulb@45 | 65 | |
paulb@45 | 66 | process_callfunc_args(node, prefix, start_index=1) |
paulb@45 | 67 | |
paulb@45 | 68 | else: |
paulb@45 | 69 | process_callfunc_args(node, prefix) |
paulb@45 | 70 | |
paulb@43 | 71 | else: |
paulb@45 | 72 | process_callfunc_args(node, prefix) |
paulb@45 | 73 | |
paulb@45 | 74 | def process_callfunc_args(node, prefix, start_index=0): |
paulb@45 | 75 | |
paulb@45 | 76 | """ |
paulb@45 | 77 | Process the arguments of the given CallFunc 'node' searching for special |
paulb@45 | 78 | names with the given 'prefix'. The optional 'start_index' is used to |
paulb@45 | 79 | indicate the first argument to be processed. |
paulb@45 | 80 | """ |
paulb@43 | 81 | |
paulb@45 | 82 | for index in range(start_index, len(node.args)): |
paulb@45 | 83 | arg = node.args[index] |
paulb@45 | 84 | if isinstance(arg, compiler.ast.Getattr): |
paulb@45 | 85 | process_getattr(arg, prefix, node, index=index) |
paulb@45 | 86 | |
paulb@45 | 87 | def process_getattr(node, prefix, parent, index=None): |
paulb@45 | 88 | |
paulb@45 | 89 | """ |
paulb@45 | 90 | Process the Getattr 'node' searching for special names with the given |
paulb@45 | 91 | 'prefix', using the given 'parent' to add transformed expressions in place |
paulb@45 | 92 | of the original ones. |
paulb@45 | 93 | |
paulb@45 | 94 | The optional 'index' is used when arguments of CallFunc nodes are being |
paulb@45 | 95 | processed and where a replacement of such arguments is occurring. |
paulb@45 | 96 | """ |
paulb@45 | 97 | |
paulb@45 | 98 | # Detected cases: |
paulb@45 | 99 | # node.attr plus node.attr.attr |
paulb@45 | 100 | # (obj.node).attr plus (obj.node).attr.attr |
paulb@45 | 101 | # Note that the deep cases are dealt with first using a recursive call. |
paulb@45 | 102 | |
paulb@45 | 103 | if getattr_has_prefix(node, prefix) or \ |
paulb@45 | 104 | isinstance(node.expr, compiler.ast.Getattr) and propagated_prefix(process_getattr(node.expr, prefix, node)): |
paulb@43 | 105 | |
paulb@43 | 106 | # Replace CallFunc plus Getattr occurrences: |
paulb@43 | 107 | # node.attr(args) -> Node_attr(node, args) |
paulb@45 | 108 | # fn(node.attr) -> fn(Node_attr(node)) |
paulb@43 | 109 | |
paulb@43 | 110 | if isinstance(parent, compiler.ast.CallFunc): |
paulb@45 | 111 | |
paulb@45 | 112 | # If this node is not an argument, transform the call. |
paulb@45 | 113 | |
paulb@45 | 114 | if index is None: |
paulb@45 | 115 | parent.node = compiler.ast.Name("Node_%s" % node.attrname) |
paulb@45 | 116 | parent.args.insert(0, node.expr) |
paulb@45 | 117 | |
paulb@45 | 118 | else: |
paulb@45 | 119 | replacement = compiler.ast.CallFunc( |
paulb@45 | 120 | compiler.ast.Name("Node_%s" % node.attrname), |
paulb@45 | 121 | [node.expr] |
paulb@45 | 122 | ) |
paulb@45 | 123 | parent.args[index] = replacement |
paulb@43 | 124 | |
paulb@43 | 125 | # Replace plain Getattr nodes: |
paulb@43 | 126 | # node.attr -> Node_attr(node) |
paulb@43 | 127 | # NOTE: Nasty but necessary rewiring of the parent node required. |
paulb@43 | 128 | |
paulb@43 | 129 | else: |
paulb@43 | 130 | replacement = compiler.ast.CallFunc( |
paulb@43 | 131 | compiler.ast.Name("Node_%s" % node.attrname), |
paulb@43 | 132 | [node.expr] |
paulb@43 | 133 | ) |
paulb@43 | 134 | for key, value in parent.__dict__.items(): |
paulb@43 | 135 | # Detect lists. |
paulb@43 | 136 | try: |
paulb@43 | 137 | if node in value: |
paulb@43 | 138 | index = value.index(node) |
paulb@43 | 139 | value[index] = replacement |
paulb@43 | 140 | except TypeError: |
paulb@43 | 141 | if value is node: |
paulb@43 | 142 | parent.__dict__[key] = replacement |
paulb@45 | 143 | |
paulb@45 | 144 | # Propagate whether the kind of result might need transforming itself. |
paulb@45 | 145 | |
paulb@45 | 146 | return node.attrname |
paulb@45 | 147 | |
paulb@43 | 148 | else: |
paulb@43 | 149 | process_nodes(node, prefix) |
paulb@45 | 150 | return None |
paulb@45 | 151 | |
paulb@45 | 152 | def propagated_prefix(attrname): |
paulb@45 | 153 | |
paulb@45 | 154 | """ |
paulb@45 | 155 | Return whether the given 'attrname' used in a transformation should be |
paulb@45 | 156 | considered significant at the parent level. |
paulb@45 | 157 | """ |
paulb@45 | 158 | |
paulb@45 | 159 | return attrname in ("ownerElement", "ownerDocument") |
paulb@45 | 160 | |
paulb@45 | 161 | def getattr_has_prefix(node, prefix): |
paulb@45 | 162 | |
paulb@45 | 163 | """ |
paulb@45 | 164 | Determine whether the given Getattr 'node' employs the special 'prefix' in a |
paulb@45 | 165 | number of ways. |
paulb@45 | 166 | """ |
paulb@45 | 167 | |
paulb@45 | 168 | # Check the expression as a simple name: |
paulb@45 | 169 | # node.attr |
paulb@45 | 170 | if isinstance(node.expr, compiler.ast.Name) and node.expr.name.startswith(prefix): |
paulb@45 | 171 | return 1 |
paulb@45 | 172 | # Check the attribute name of child expressions: |
paulb@45 | 173 | # (obj.node).attr |
paulb@45 | 174 | elif isinstance(node.expr, compiler.ast.Getattr) and node.expr.attrname.startswith(prefix): |
paulb@45 | 175 | return 1 |
paulb@45 | 176 | else: |
paulb@45 | 177 | return 0 |
paulb@43 | 178 | |
paulb@43 | 179 | def process_file(filename, prefix="x2_"): |
paulb@43 | 180 | |
paulb@43 | 181 | """ |
paulb@43 | 182 | Process the module given by the specified 'filename'. The optional special |
paulb@43 | 183 | 'prefix' marks those variables to be processed. |
paulb@43 | 184 | """ |
paulb@43 | 185 | |
paulb@43 | 186 | # Open the module as an AST. |
paulb@43 | 187 | |
paulb@43 | 188 | module = compiler.parseFile(filename) |
paulb@43 | 189 | |
paulb@43 | 190 | # Find references to special variables. |
paulb@43 | 191 | |
paulb@43 | 192 | process_nodes(module, prefix) |
paulb@45 | 193 | |
paulb@45 | 194 | # Write the module. |
paulb@45 | 195 | |
paulb@45 | 196 | write_module(module, filename, os.path.splitext(filename)[0] + ".pyc") |
paulb@43 | 197 | return module |
paulb@43 | 198 | |
paulb@45 | 199 | if __name__ == "__main__": |
paulb@45 | 200 | import sys |
paulb@45 | 201 | if len(sys.argv) < 2: |
paulb@45 | 202 | print "libxml2macro.py <module-filename>" |
paulb@45 | 203 | sys.exit(1) |
paulb@45 | 204 | process_file(sys.argv[1]) |
paulb@45 | 205 | |
paulb@43 | 206 | # vim: tabstop=4 expandtab shiftwidth=4 |