1.1 --- a/README.txt Fri May 27 20:31:30 2005 +0000
1.2 +++ b/README.txt Sat May 28 13:55:46 2005 +0000
1.3 @@ -51,7 +51,7 @@
1.4 Then, run libxml2macro.py on the module like this (using tests/macrotest.py as
1.5 an example):
1.6
1.7 - libxml2macro.py tests/macrotest.py
1.8 + tools/libxml2macro.py tests/macrotest.py
1.9
1.10 This produces a compiled module that can be imported into Python; for example:
1.11
2.1 --- a/libxml2macro.py Fri May 27 20:31:30 2005 +0000
2.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
2.3 @@ -1,256 +0,0 @@
2.4 -#!/usr/bin/env python
2.5 -
2.6 -import compiler
2.7 -import marshal, imp, os, stat, struct
2.8 -
2.9 -# Originally from Analysis/Writers/Python.
2.10 -
2.11 -def write_module(module, source_filename, target_filename, syntax_check=1):
2.12 -
2.13 - """
2.14 - Write the given 'module', associated with the given 'source_filename', to a
2.15 - file with the given 'target_filename'. The optional 'syntax_check' flag (set
2.16 - by default) ensures that the module is syntactically correct.
2.17 - """
2.18 -
2.19 - if syntax_check:
2.20 - compiler.syntax.check(module)
2.21 - compiler.misc.set_filename(source_filename, module)
2.22 - generator = compiler.pycodegen.ModuleCodeGenerator(module)
2.23 - f = open(target_filename, "wb")
2.24 - f.write(get_header(source_filename))
2.25 - marshal.dump(generator.getCode(), f)
2.26 - f.close()
2.27 -
2.28 -def get_header(filename):
2.29 -
2.30 - "Taken from compiler.pycodegen. Prepare the compiled module header."
2.31 -
2.32 - MAGIC = imp.get_magic()
2.33 - mtime = os.stat(filename)[stat.ST_MTIME]
2.34 - mtime = struct.pack('<i', mtime)
2.35 - return MAGIC + mtime
2.36 -
2.37 -# Processing functions.
2.38 -
2.39 -def process_nodes(root_node, prefix=None):
2.40 -
2.41 - """
2.42 - Under the 'root_node', process all suitable expression nodes which employ
2.43 - special names using the given 'prefix'.
2.44 - """
2.45 -
2.46 - for node in root_node.getChildNodes():
2.47 -
2.48 - # Find imports and discover if they are really used to define the
2.49 - # special prefix.
2.50 -
2.51 - if isinstance(node, compiler.ast.Import):
2.52 - prefix = process_import(node, root_node) or prefix
2.53 -
2.54 - # Identify suitable names and add replacement nodes.
2.55 -
2.56 - elif isinstance(node, compiler.ast.CallFunc):
2.57 - process_callfunc(node, prefix)
2.58 - elif isinstance(node, compiler.ast.Getattr):
2.59 - process_getattr(node, prefix, root_node)
2.60 - else:
2.61 - process_nodes(node, prefix)
2.62 -
2.63 -def process_import(node, parent):
2.64 -
2.65 - """
2.66 - Process the Import 'node' searching for the special incantation required to
2.67 - set the prefix. Remove compliant nodes from their 'parent' node. If no new
2.68 - prefix is found, return None.
2.69 - """
2.70 -
2.71 - for name, alias in node.names:
2.72 - if name == "libxml2macro":
2.73 -
2.74 - # Remove this node from its parent.
2.75 -
2.76 - parent.nodes.remove(node)
2.77 - return alias
2.78 -
2.79 - return None
2.80 -
2.81 -def process_callfunc(node, prefix):
2.82 -
2.83 - """
2.84 - Process the CallFunc 'node' searching for special names with the given
2.85 - 'prefix'.
2.86 - """
2.87 -
2.88 - # Check the prefix.
2.89 -
2.90 - if prefix is None:
2.91 - return
2.92 -
2.93 - # Check the target.
2.94 -
2.95 - target = node.node
2.96 - if isinstance(target, compiler.ast.Getattr):
2.97 - if process_getattr(target, prefix, node):
2.98 -
2.99 - # Process all sibling arguments of the new first argument, too.
2.100 -
2.101 - process_callfunc_args(node, prefix, start_index=1)
2.102 -
2.103 - else:
2.104 - process_callfunc_args(node, prefix)
2.105 -
2.106 - else:
2.107 - process_callfunc_args(node, prefix)
2.108 -
2.109 -def process_callfunc_args(node, prefix, start_index=0):
2.110 -
2.111 - """
2.112 - Process the arguments of the given CallFunc 'node' searching for special
2.113 - names with the given 'prefix'. The optional 'start_index' is used to
2.114 - indicate the first argument to be processed.
2.115 - """
2.116 -
2.117 - for index in range(start_index, len(node.args)):
2.118 - arg = node.args[index]
2.119 - if isinstance(arg, compiler.ast.Getattr):
2.120 - process_getattr(arg, prefix, node, index=index)
2.121 -
2.122 -def process_getattr(node, prefix, parent, index=None):
2.123 -
2.124 - """
2.125 - Process the Getattr 'node' searching for special names with the given
2.126 - 'prefix', using the given 'parent' to add transformed expressions in place
2.127 - of the original ones.
2.128 -
2.129 - The optional 'index' is used when arguments of CallFunc nodes are being
2.130 - processed and where a replacement of such arguments is occurring.
2.131 - """
2.132 -
2.133 - # Check the prefix.
2.134 -
2.135 - if prefix is None:
2.136 - return
2.137 -
2.138 - # Detected cases:
2.139 - # node.attr plus node.attr.attr
2.140 - # (obj.node).attr plus (obj.node).attr.attr
2.141 - # Note that the deep cases are dealt with first using a recursive call.
2.142 -
2.143 - if getattr_has_prefix(node, prefix) or \
2.144 - isinstance(node.expr, compiler.ast.Getattr) and propagated_prefix(process_getattr(node.expr, prefix, node)):
2.145 -
2.146 - # Replace CallFunc plus Getattr occurrences:
2.147 - # node.attr(args) -> Node_attr(node, args)
2.148 - # fn(node.attr) -> fn(Node_attr(node))
2.149 -
2.150 - if isinstance(parent, compiler.ast.CallFunc):
2.151 -
2.152 - # If this node is not an argument, transform the call.
2.153 -
2.154 - if index is None:
2.155 - parent.node = compiler.ast.Name("Node_%s" % node.attrname)
2.156 - parent.args.insert(0, node.expr)
2.157 -
2.158 - else:
2.159 - replacement = compiler.ast.CallFunc(
2.160 - compiler.ast.Name("Node_%s" % node.attrname),
2.161 - [node.expr]
2.162 - )
2.163 - parent.args[index] = replacement
2.164 -
2.165 - # Replace plain Getattr nodes:
2.166 - # node.attr -> Node_attr(node)
2.167 - # NOTE: Nasty but necessary rewiring of the parent node required.
2.168 -
2.169 - else:
2.170 - replacement = compiler.ast.CallFunc(
2.171 - compiler.ast.Name("Node_%s" % node.attrname),
2.172 - [node.expr]
2.173 - )
2.174 - for key, value in parent.__dict__.items():
2.175 - # Detect lists.
2.176 - if hasattr(value, "__len__") and node in value:
2.177 - index = value.index(node)
2.178 - value[index] = replacement
2.179 - elif value is node:
2.180 - parent.__dict__[key] = replacement
2.181 -
2.182 - # Propagate whether the kind of result might need transforming itself.
2.183 -
2.184 - return node.attrname
2.185 -
2.186 - else:
2.187 - process_nodes(node, prefix)
2.188 - return None
2.189 -
2.190 -def propagated_prefix(attrname):
2.191 -
2.192 - """
2.193 - Return whether the given 'attrname' used in a transformation should be
2.194 - considered significant at the parent level.
2.195 - """
2.196 -
2.197 - return attrname in ("ownerElement", "ownerDocument")
2.198 -
2.199 -def getattr_has_prefix(node, prefix):
2.200 -
2.201 - """
2.202 - Determine whether the given Getattr 'node' employs the special 'prefix' in a
2.203 - number of ways.
2.204 - """
2.205 -
2.206 - # Check the expression as a simple name:
2.207 - # node.attr
2.208 -
2.209 - if isinstance(node.expr, compiler.ast.Name) and node.expr.name.startswith(prefix):
2.210 - return 1
2.211 -
2.212 - # Check the attribute name of child expressions:
2.213 - # (obj.node).attr
2.214 -
2.215 - elif isinstance(node.expr, compiler.ast.Getattr) and node.expr.attrname.startswith(prefix):
2.216 - return 1
2.217 - else:
2.218 - return 0
2.219 -
2.220 -def include_import(module):
2.221 -
2.222 - """
2.223 - Include an import statement in 'module' to make the macro library available.
2.224 - """
2.225 -
2.226 - module.node.nodes.insert(0, compiler.ast.From("libxml2dom.macrolib", [("*", None)]))
2.227 -
2.228 -def process_file(filename):
2.229 -
2.230 - """
2.231 - Process the module given by the specified 'filename'. The optional special
2.232 - 'prefix' marks those variables to be processed.
2.233 - """
2.234 -
2.235 - # Open the module as an AST.
2.236 -
2.237 - module = compiler.parseFile(filename)
2.238 -
2.239 - # Find references to special variables.
2.240 -
2.241 - process_nodes(module)
2.242 -
2.243 - # Add necessary imports.
2.244 -
2.245 - include_import(module)
2.246 -
2.247 - # Write the module.
2.248 -
2.249 - write_module(module, filename, os.path.splitext(filename)[0] + ".pyc")
2.250 - return module
2.251 -
2.252 -if __name__ == "__main__":
2.253 - import sys
2.254 - if len(sys.argv) < 2:
2.255 - print "libxml2macro.py <module-filename>"
2.256 - sys.exit(1)
2.257 - process_file(sys.argv[1])
2.258 -
2.259 -# vim: tabstop=4 expandtab shiftwidth=4
3.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
3.2 +++ b/tools/libxml2macro.py Sat May 28 13:55:46 2005 +0000
3.3 @@ -0,0 +1,256 @@
3.4 +#!/usr/bin/env python
3.5 +
3.6 +import compiler
3.7 +import marshal, imp, os, stat, struct
3.8 +
3.9 +# Originally from Analysis/Writers/Python.
3.10 +
3.11 +def write_module(module, source_filename, target_filename, syntax_check=1):
3.12 +
3.13 + """
3.14 + Write the given 'module', associated with the given 'source_filename', to a
3.15 + file with the given 'target_filename'. The optional 'syntax_check' flag (set
3.16 + by default) ensures that the module is syntactically correct.
3.17 + """
3.18 +
3.19 + if syntax_check:
3.20 + compiler.syntax.check(module)
3.21 + compiler.misc.set_filename(source_filename, module)
3.22 + generator = compiler.pycodegen.ModuleCodeGenerator(module)
3.23 + f = open(target_filename, "wb")
3.24 + f.write(get_header(source_filename))
3.25 + marshal.dump(generator.getCode(), f)
3.26 + f.close()
3.27 +
3.28 +def get_header(filename):
3.29 +
3.30 + "Taken from compiler.pycodegen. Prepare the compiled module header."
3.31 +
3.32 + MAGIC = imp.get_magic()
3.33 + mtime = os.stat(filename)[stat.ST_MTIME]
3.34 + mtime = struct.pack('<i', mtime)
3.35 + return MAGIC + mtime
3.36 +
3.37 +# Processing functions.
3.38 +
3.39 +def process_nodes(root_node, prefix=None):
3.40 +
3.41 + """
3.42 + Under the 'root_node', process all suitable expression nodes which employ
3.43 + special names using the given 'prefix'.
3.44 + """
3.45 +
3.46 + for node in root_node.getChildNodes():
3.47 +
3.48 + # Find imports and discover if they are really used to define the
3.49 + # special prefix.
3.50 +
3.51 + if isinstance(node, compiler.ast.Import):
3.52 + prefix = process_import(node, root_node) or prefix
3.53 +
3.54 + # Identify suitable names and add replacement nodes.
3.55 +
3.56 + elif isinstance(node, compiler.ast.CallFunc):
3.57 + process_callfunc(node, prefix)
3.58 + elif isinstance(node, compiler.ast.Getattr):
3.59 + process_getattr(node, prefix, root_node)
3.60 + else:
3.61 + process_nodes(node, prefix)
3.62 +
3.63 +def process_import(node, parent):
3.64 +
3.65 + """
3.66 + Process the Import 'node' searching for the special incantation required to
3.67 + set the prefix. Remove compliant nodes from their 'parent' node. If no new
3.68 + prefix is found, return None.
3.69 + """
3.70 +
3.71 + for name, alias in node.names:
3.72 + if name == "libxml2macro":
3.73 +
3.74 + # Remove this node from its parent.
3.75 +
3.76 + parent.nodes.remove(node)
3.77 + return alias
3.78 +
3.79 + return None
3.80 +
3.81 +def process_callfunc(node, prefix):
3.82 +
3.83 + """
3.84 + Process the CallFunc 'node' searching for special names with the given
3.85 + 'prefix'.
3.86 + """
3.87 +
3.88 + # Check the prefix.
3.89 +
3.90 + if prefix is None:
3.91 + return
3.92 +
3.93 + # Check the target.
3.94 +
3.95 + target = node.node
3.96 + if isinstance(target, compiler.ast.Getattr):
3.97 + if process_getattr(target, prefix, node):
3.98 +
3.99 + # Process all sibling arguments of the new first argument, too.
3.100 +
3.101 + process_callfunc_args(node, prefix, start_index=1)
3.102 +
3.103 + else:
3.104 + process_callfunc_args(node, prefix)
3.105 +
3.106 + else:
3.107 + process_callfunc_args(node, prefix)
3.108 +
3.109 +def process_callfunc_args(node, prefix, start_index=0):
3.110 +
3.111 + """
3.112 + Process the arguments of the given CallFunc 'node' searching for special
3.113 + names with the given 'prefix'. The optional 'start_index' is used to
3.114 + indicate the first argument to be processed.
3.115 + """
3.116 +
3.117 + for index in range(start_index, len(node.args)):
3.118 + arg = node.args[index]
3.119 + if isinstance(arg, compiler.ast.Getattr):
3.120 + process_getattr(arg, prefix, node, index=index)
3.121 +
3.122 +def process_getattr(node, prefix, parent, index=None):
3.123 +
3.124 + """
3.125 + Process the Getattr 'node' searching for special names with the given
3.126 + 'prefix', using the given 'parent' to add transformed expressions in place
3.127 + of the original ones.
3.128 +
3.129 + The optional 'index' is used when arguments of CallFunc nodes are being
3.130 + processed and where a replacement of such arguments is occurring.
3.131 + """
3.132 +
3.133 + # Check the prefix.
3.134 +
3.135 + if prefix is None:
3.136 + return
3.137 +
3.138 + # Detected cases:
3.139 + # node.attr plus node.attr.attr
3.140 + # (obj.node).attr plus (obj.node).attr.attr
3.141 + # Note that the deep cases are dealt with first using a recursive call.
3.142 +
3.143 + if getattr_has_prefix(node, prefix) or \
3.144 + isinstance(node.expr, compiler.ast.Getattr) and propagated_prefix(process_getattr(node.expr, prefix, node)):
3.145 +
3.146 + # Replace CallFunc plus Getattr occurrences:
3.147 + # node.attr(args) -> Node_attr(node, args)
3.148 + # fn(node.attr) -> fn(Node_attr(node))
3.149 +
3.150 + if isinstance(parent, compiler.ast.CallFunc):
3.151 +
3.152 + # If this node is not an argument, transform the call.
3.153 +
3.154 + if index is None:
3.155 + parent.node = compiler.ast.Name("Node_%s" % node.attrname)
3.156 + parent.args.insert(0, node.expr)
3.157 +
3.158 + else:
3.159 + replacement = compiler.ast.CallFunc(
3.160 + compiler.ast.Name("Node_%s" % node.attrname),
3.161 + [node.expr]
3.162 + )
3.163 + parent.args[index] = replacement
3.164 +
3.165 + # Replace plain Getattr nodes:
3.166 + # node.attr -> Node_attr(node)
3.167 + # NOTE: Nasty but necessary rewiring of the parent node required.
3.168 +
3.169 + else:
3.170 + replacement = compiler.ast.CallFunc(
3.171 + compiler.ast.Name("Node_%s" % node.attrname),
3.172 + [node.expr]
3.173 + )
3.174 + for key, value in parent.__dict__.items():
3.175 + # Detect lists.
3.176 + if hasattr(value, "__len__") and node in value:
3.177 + index = value.index(node)
3.178 + value[index] = replacement
3.179 + elif value is node:
3.180 + parent.__dict__[key] = replacement
3.181 +
3.182 + # Propagate whether the kind of result might need transforming itself.
3.183 +
3.184 + return node.attrname
3.185 +
3.186 + else:
3.187 + process_nodes(node, prefix)
3.188 + return None
3.189 +
3.190 +def propagated_prefix(attrname):
3.191 +
3.192 + """
3.193 + Return whether the given 'attrname' used in a transformation should be
3.194 + considered significant at the parent level.
3.195 + """
3.196 +
3.197 + return attrname in ("ownerElement", "ownerDocument")
3.198 +
3.199 +def getattr_has_prefix(node, prefix):
3.200 +
3.201 + """
3.202 + Determine whether the given Getattr 'node' employs the special 'prefix' in a
3.203 + number of ways.
3.204 + """
3.205 +
3.206 + # Check the expression as a simple name:
3.207 + # node.attr
3.208 +
3.209 + if isinstance(node.expr, compiler.ast.Name) and node.expr.name.startswith(prefix):
3.210 + return 1
3.211 +
3.212 + # Check the attribute name of child expressions:
3.213 + # (obj.node).attr
3.214 +
3.215 + elif isinstance(node.expr, compiler.ast.Getattr) and node.expr.attrname.startswith(prefix):
3.216 + return 1
3.217 + else:
3.218 + return 0
3.219 +
3.220 +def include_import(module):
3.221 +
3.222 + """
3.223 + Include an import statement in 'module' to make the macro library available.
3.224 + """
3.225 +
3.226 + module.node.nodes.insert(0, compiler.ast.From("libxml2dom.macrolib", [("*", None)]))
3.227 +
3.228 +def process_file(filename):
3.229 +
3.230 + """
3.231 + Process the module given by the specified 'filename'. The optional special
3.232 + 'prefix' marks those variables to be processed.
3.233 + """
3.234 +
3.235 + # Open the module as an AST.
3.236 +
3.237 + module = compiler.parseFile(filename)
3.238 +
3.239 + # Find references to special variables.
3.240 +
3.241 + process_nodes(module)
3.242 +
3.243 + # Add necessary imports.
3.244 +
3.245 + include_import(module)
3.246 +
3.247 + # Write the module.
3.248 +
3.249 + write_module(module, filename, os.path.splitext(filename)[0] + ".pyc")
3.250 + return module
3.251 +
3.252 +if __name__ == "__main__":
3.253 + import sys
3.254 + if len(sys.argv) < 2:
3.255 + print "libxml2macro.py <module-filename>"
3.256 + sys.exit(1)
3.257 + process_file(sys.argv[1])
3.258 +
3.259 +# vim: tabstop=4 expandtab shiftwidth=4