# HG changeset patch # User paulb@localhost.localdomain # Date 1165617680 -3600 # Node ID 3c8d0c13dd43cf4bfe2968223b0732c9b2808359 # Parent 3edadae4cec1d625458ad724a2e0f4b14dcac6fd Added support for importing modules, changing the annotation API and providing a convenience function, load, for easy loading of module files. Added support for the output of multiple modules in the same viewing directory. Added some docstrings for the Namespace and Importer classes. diff -r 3edadae4cec1 -r 3c8d0c13dd43 annotate.py --- a/annotate.py Wed Dec 06 23:54:48 2006 +0100 +++ b/annotate.py Fri Dec 08 23:41:20 2006 +0100 @@ -24,11 +24,23 @@ -------- -To use this module, the easiest approach is to use the annotate function: +To use this module, the easiest approach is to use the load function: + +load(filename, builtins) + +To control module importing, an importer should be constructed and employed. +Here, the standard path for module searching is used: + +importer = Importer(sys.path) +load(filename, builtins, importer) + +Underneath the load function, the annotate function provides support for +annotating modules already processed by simplify and fixnames: annotate(module, builtins) -The more complicated approach involves obtaining an Annotator: +And at the most basic level, the most intricate approach involves obtaining an +Annotator object: annotator = Annotator() @@ -43,7 +55,9 @@ """ from simplified import * +import simplify, fixnames # for the load function import compiler +import os class System: @@ -117,20 +131,20 @@ types possible when the means of constructing the namespace may depend on run-time behaviour. - Covered: Assign, CheckExc, Conditional, Global, InvokeBlock, InvokeFunction, - LoadAttr, LoadExc, LoadName, LoadRef, LoadTemp, Module, Not, Pass, - Raise, ReleaseTemp, ReturnFromBlock, ReturnFromFunction, StoreAttr, - StoreName, StoreTemp, Subprogram, Try. - - Missing: Import. + Covered: Assign, CheckExc, Conditional, Global, Import, InvokeBlock, + InvokeFunction, LoadAttr, LoadExc, LoadName, LoadRef, LoadTemp, + Module, Not, Pass, Raise, ReleaseTemp, ReturnFromBlock, + ReturnFromFunction, StoreAttr, StoreName, StoreTemp, Subprogram, + Try. """ - def __init__(self): + def __init__(self, importer=None): - "Initialise the visitor." + "Initialise the visitor with an optional 'importer'." Visitor.__init__(self) self.system = system + self.importer = importer or Importer() # Satisfy visitor issues. @@ -366,6 +380,20 @@ return global_ + def visitImport(self, import_): + + """ + Return the 'import_' node, importing the module with the stated name + and storing details on the node. + """ + + module = self.importer.load(import_.name, self.builtins) + if module is not None: + self.namespace.set_types([module]) + else: + self.namespace.set_types([]) + return import_ + def _visitInvoke(self, invoke, invocation_types, have_args): """ @@ -555,7 +583,7 @@ raised. """ - self.namespace.types = self.namespace.raises[:] + self.namespace.set_types(self.namespace.raises[:]) self.annotate(loadexc) return loadexc @@ -872,14 +900,21 @@ invoke.syscount = self.system.count # Provide the correct namespace for the invocation. + # This may be a "shared" namespace... if getattr(invoke, "share_locals", 0): namespace = Namespace() namespace.merge_namespace(self.namespace, everything=0) using_module_namespace = self.namespace is self.module.namespace + + # Or it may be a structure... + elif getattr(target, "structure", None): namespace = Namespace() using_module_namespace = 0 + + # Or it may be a new namespace populated with the supplied parameters. + else: items = self.make_items(invoke, target, context) namespace = Namespace() @@ -1157,31 +1192,57 @@ self.types = [] def set_types(self, types): + + "Set the current collection of 'types'." + self.types = types def add(self, name, types): + + "Add to the entry with the given 'name' the specified 'types'." + if self.names.has_key(name): combine(self.names[name], types) else: self.store(name, types) def store(self, name, types): + + "Store in (or associate with) the given 'name' the specified 'types'." + self.names[name] = types __setitem__ = store def load(self, name): + + "Load the types associated with the given 'name'." + return self.names[name] __getitem__ = load def revoke(self, name, type): + + "Revoke from the entry for the given 'name' the specified 'type'." + self.names[name].remove(type) def revoke_exception_type(self, type): + + "Revoke the given 'type' from the collection of exception types." + self.raises.remove(type) def merge_namespace(self, namespace, everything=1): + + """ + Merge items from the given 'namespace' with this namespace. When the + optional 'everything' parameter is set to a false value (unlike the + default), return values and locals snapshots will not be copied to this + namespace. + """ + self.merge_items(namespace.names.items()) if everything: combine(self.returns, namespace.returns) @@ -1194,10 +1255,16 @@ combine(self.temp[name][-1], values[-1]) def merge_items(self, items): + + "Merge the given 'items' with this namespace." + for name, types in items: self.merge(name, types) def merge(self, name, types): + + "Merge the entry for the given 'name' and 'types' with this namespace." + if not self.names.has_key(name): self.names[name] = types[:] else: @@ -1249,6 +1316,126 @@ def __init__(self, attribute): self.types = [attribute] +class Importer: + + "An import machine, searching for and loading modules." + + def __init__(self, path=None): + + """ + Initialise the importer with the given search 'path' - a list of + directories to search for Python modules. + """ + + self.path = path or [os.getcwd()] + self.modules = [] + + def find_in_path(self, name): + + """ + Find the given module 'name' in the search path, returning None where no + such module could be found, or a 2-tuple from the 'find' method + otherwise. + """ + + for d in self.path: + m = self.find(d, name) + if m: return m + return None + + def find(self, d, name): + + """ + In the directory 'd', find the given module 'name', where 'name' can + either refer to a single file module or to a package. Return None if the + 'name' cannot be associated with either a file or a package directory, + or a 2-tuple from '_find_package' or '_find_module' otherwise. + """ + + m = self._find_package(d, name) + if m: return m + m = self._find_module(d, name) + if m: return m + return None + + def _find_module(self, d, name): + + """ + In the directory 'd', find the given module 'name', returning None where + no suitable file exists in the directory, or a 2-tuple consisting of + None (indicating that no package directory is involved) and a filename + indicating the location of the module. + """ + + name_py = name + os.extsep + "py" + filename = self._find_file(d, name_py) + if filename: + return None, filename + return None + + def _find_package(self, d, name): + + """ + In the directory 'd', find the given package 'name', returning None + where no suitable package directory exists, or a 2-tuple consisting of + a directory (indicating the location of the package directory itself) + and a filename indicating the location of the __init__.py module which + declares the package's top-level contents. + """ + + filename = self._find_file(d, name) + if filename: + init_py = "__init__" + os.path.extsep + "py" + init_py_filename = self._find_file(filename, init_py) + if init_py_filename: + return filename, init_py_filename + return None + + def _find_file(self, d, filename): + + """ + Return the filename obtained when searching the directory 'd' for the + given 'filename', or None if no actual file exists for the filename. + """ + + filename = os.path.join(d, filename) + if os.path.exists(filename): + return filename + else: + return None + + def load(self, name, builtins): + + """ + Load the module or package with the given 'name' and using the specified + 'builtins'. Return an Attribute object referencing the loaded module or + package, or None if no such module or package exists. + """ + + path = name.split(".") + m = self.find_in_path(path[0]) + if not m: + return None # NOTE: Import error. + d, filename = m + module = load(filename, builtins) + self.modules.append(module) + + if len(path) > 1: + for p in path[1:]: + m = self.find(d, p) + if not m: + return None # NOTE: Import error. + d, filename = m + submodule = load(filename, builtins) + self.modules.append(submodule) + + # Store the submodule within its parent module. + + module.namespace[p] = [Attribute(None, submodule)] + module = submodule + + return Attribute(None, module) + def combine(target, additions): """ @@ -1341,22 +1528,31 @@ # Convenience functions. -def annotate(module, builtins=None): +def load(name, builtins=None, importer=None): + + """ + Load the module with the given 'name' (which may be a full module path), + using the optional 'builtins' to resolve built-in names, and using the + optional 'importer' to provide a means of finding and loading modules. + """ + + module = simplify.simplify(name, builtins is None) + fixnames.fix(module, builtins) + annotate(module, builtins, importer) + return module + +def annotate(module, builtins=None, importer=None): """ Annotate the given 'module', also employing the optional 'builtins' module, - if specified. + if specified. If the optional 'importer' is given, use that to find and load + modules. """ - annotator = Annotator() + annotator = Annotator(importer) if builtins is not None: annotator.process(module, builtins) else: annotator.process(module) -def annotate_all(modules, builtins): - annotate(builtins) - for module in modules: - annotate(module, builtins) - # vim: tabstop=4 expandtab shiftwidth=4 diff -r 3edadae4cec1 -r 3c8d0c13dd43 test.py --- a/test.py Wed Dec 06 23:54:48 2006 +0100 +++ b/test.py Fri Dec 08 23:41:20 2006 +0100 @@ -1,22 +1,16 @@ #!/usr/bin/env python import sys, os -import simplify -import fixnames import viewer -from annotate import AnnotationError, annotate_all as aa +from annotate import Importer, load if __name__ == "__main__": - builtins = simplify.simplify(os.path.join("lib", "builtins.py"), 1) - module = simplify.simplify(sys.argv[1]) - fixnames.fix(builtins) - fixnames.fix(module, builtins) - - if "-a" in sys.argv: - aa([module], builtins) + importer = Importer(sys.path) + builtins = load(os.path.join("lib", "builtins.py")) + module = load(sys.argv[1], builtins, importer) if "-d" in sys.argv: - viewer.makedocs(module, builtins) + viewer.makedocs(module, importer.modules, builtins) # vim: tabstop=4 expandtab shiftwidth=4 diff -r 3edadae4cec1 -r 3c8d0c13dd43 viewer.py --- a/viewer.py Wed Dec 06 23:54:48 2006 +0100 +++ b/viewer.py Fri Dec 08 23:41:20 2006 +0100 @@ -862,11 +862,11 @@ finally: stream.close() -def makedocs(module, builtins): +def makedocs(module, modules, builtins): dirname = "%s-docs" % module.name if not os.path.exists(dirname): os.mkdir(dirname) - makedoc(module, os.path.join(dirname, "%s%shtml" % (module.name, os.path.extsep))) - makedoc(builtins, os.path.join(dirname, "%s%shtml" % (builtins.name, os.path.extsep))) + for m in [module, builtins] + modules: + makedoc(m, os.path.join(dirname, "%s%shtml" % (m.name, os.path.extsep))) # vim: tabstop=4 expandtab shiftwidth=4