diff options
author | wiemann <wiemann@929543f6-e4f2-0310-98a6-ba3bd3dd1d04> | 2006-01-09 20:44:25 +0000 |
---|---|---|
committer | wiemann <wiemann@929543f6-e4f2-0310-98a6-ba3bd3dd1d04> | 2006-01-09 20:44:25 +0000 |
commit | d77fdfef70e08114f57cbef5d91707df8717ea9f (patch) | |
tree | 49444e3486c0c333cb7b33dfa721296c08ee4ece /sandbox/tibs/pysource/visit.py | |
parent | 53cd16ca6ca5f638cbe5956988e88f9339e355cf (diff) | |
parent | 3993c4097756e9885bcfbd07cb1cc1e4e95e50e4 (diff) | |
download | docutils-0.4.tar.gz |
Release 0.4: tagging released revisiondocutils-0.4
git-svn-id: http://svn.code.sf.net/p/docutils/code/tags/docutils-0.4@4268 929543f6-e4f2-0310-98a6-ba3bd3dd1d04
Diffstat (limited to 'sandbox/tibs/pysource/visit.py')
-rwxr-xr-x | sandbox/tibs/pysource/visit.py | 2074 |
1 files changed, 0 insertions, 2074 deletions
diff --git a/sandbox/tibs/pysource/visit.py b/sandbox/tibs/pysource/visit.py deleted file mode 100755 index ec5a2992f..000000000 --- a/sandbox/tibs/pysource/visit.py +++ /dev/null @@ -1,2074 +0,0 @@ -#! /usr/bin/env python -"""Extract information from a Python file (module) for the DOCUTILS - -An example of using Jeremy Hylton's compiler module. - -Originally based on an example *by* Jeremy Hylton. In the introduction -to that example, he said: - - Here's a trivial example program that extracts some information - about methods and attributes from a class and its methods. - I wasn't exhaustive here. I'll get attributes assigned to by - `self.x` in a method body and I'll get method definitions in - the class body. I don't deal with obvious things like attributes - defined at the class level. - -I'm expanding it, initially as a bigger example, and also so I get to -understand how things work! - -Note: there should be an example Python file called test.py in the -same directory as this module - it's meant to be a suitable input -for testing/learning purposes. - -:Author: Tibs <tibs@tibsnjoan.co.uk> -:Version: Whatever. Oh, 0.4, then -:Date: 2002-January-18 - -Aims for DOCUTILS ------------------ -1. Locate all modules, classes, functions, methods and their docstrings. -2. "Filter" these to leave the ones that DOCUTILS is interested in. -3. Annotate functions and methods with their arguments. -4. Locate all assignments which have docstrings. -5. Similarly "filter" these. -6. Locate all uses of `global` assignments. -7. Filter out those which are not obvious from the module level, - so that the user can be warned about them. -8. Produce a tree structure suitable for DOCUTILS processing, containing - the suitable results of the above. - -(At least some of) 1, 3 and 4 have already been done. - -Still to do ------------ -* *all* sorts of assignment should be visited, so that - we can tie down the use of globals properly. -* the option of ignoring _name and __name things should be - available (should this be the default?) -* Some consideration should be given to freeing up this mess of things - when they are all finished with - there are so many interconnections - that automatic deletion is unlikely to work well -* other things as they occur to me -* Finish off InstanceValue (its potential link to the class that defines it) - -Names and fullnames -------------------- -A "name" is taken to be the name as presented in Python code. Note that -for class instance attribute references (e.g., "self.fred"), the name is -includes the class reference, dot and attribute name (e.g., "self.fred"). - -A "fullname" includes the full path to the name as well - that is, -including all of the elements required to "reach" this item. Thus, for -instance, a method Fred in class Jim in module Bill would be:: - - Bill.Jim.Fred - -whilst a name A defined within Fred would be:: - - Bill.Jim.Fred:A - -Note that if Fred contains a reference to self.B, then that would (for the -moment at least) be written as:: - - Bill.Jim.Fred:self.B - -Within an eventual HTML document, such fullnames become anchor and link -names for nodes. For that purpose, they will be transformed so that they -do not contain "." or ":", but are still not accidentally confusable with -legitimate Python names. Thus the last example would be transformed to:: - - Bill-Jim-Fred--self-B - -Note that we also have a "special" label, artificially "generated" - that -is, there is a single label for all arguments to a function or method, -which is formed of the label for the function or method, followed by -*three* hyphens, and then the text "args" - so, for example: - - Bill-Jim---args - -Special cases -------------- -Notes on special cases in name resolution: - -- obviously I should be taking account of explicit roles. - -- if it can't find the name directly via the "scope", it should be looking - up in the module - so the ScopeMixin needs to provide support for that... - -- the module name should be recognised as, in some sense, within the - module's "parent" - even if there is no such beast. - -- that generalises - in general, an object's own name should be visible - to its docstring, I think - so, for instance:: - - digit = 3 - '''We store the current digit of pi in `digit`''' - - (yes, I know that's poor documentation, but even so) - -- <self>.name in a method (where <self> is the first argument to said - method) should be regarded as being a name in the class containing - the method, in some sense (but what sense?) -""" #" - -import os -import sys -import string -import compiler - -from docutils.parsers.rst import Parser - -# Local modules -import utils -import visit - -__docformat__ = "reST" - - -# ---------------------------------------------------------------------- -def make_Docstring(text, settings): - """A factory function to handle non-existant docstrings. - """ - if text: - return Docstring(text, settings) - else: - return None - -class Docstring: - """We use this to represent a docstring, parsed and unparsed. - """ - - def __init__(self, text, settings, debug=0): - """Instantiate a new Docstring from its text. - """ - self.text = self._trim(text) - """The text of the docstring. - """ - self.document = None - """The result of parsing the docstring, if requested. - (thus, only non-None if we *are* parsing docstrings in - the containing module). - """ - self.settings = settings - """The docutils settings to apply to new document parsers. - """ - - self.debug = debug - - def _trim(self,docstring): - """Trim leading space appropriately from the docstring text. - - When the docstring is read from the source file, it is likely - to have all but the first line indented with respect to the - first - for example, consider this docstring. - - Before it is *used* for anything (e.g., typing it out to be - read by the user), it is necessary to adjust for this. The - way to do this is well-defined (???where???). - - Returns the adjusted text. - """ - - # Remove any confusing tabs - follow the normal Python rules - # - and split into convenient lines - lines = string.split(string.expandtabs(docstring),"\n") - - # Find significant indentation - firstline = lines[0] - if firstline and firstline[0] == " ": - oldlen = len(firstline) - firstline = string.lstrip(firstline) - indent = oldlen - len(firstline) - else: - indent = 0 - for line in lines[1:]: - oldlen = len(line) - lin = string.lstrip(line) - if lin: - indent = oldlen - len(lin) - break - - # And remove it... - newlines = [firstline] - for line in lines[1:]: - oldlen = len(line) - lin = string.lstrip(line) - if lin: - extra = oldlen - len(lin) - indent - if extra > 0: - lin = " "*extra + lin - newlines.append(lin) - - # Don't introduce a trailing blank line if they did some - # variant of the following layout:: - # """Some text - # """ - if newlines and newlines[-1] == "": - del newlines[-1] - - # And lastly, reassemble it back into text again - return string.join(newlines,"\n") - - def show(self,stream,spaces=""): - # Not optimised for efficiency... - lines = string.split(self.text,"\n") - - # For the moment, don't try to format it very nicely at all... - stream.write('%s"""%s\n'%(spaces,lines[0])) - for line in lines[1:]: - stream.write(spaces+line+"\n") - stream.write('%s"""\n'%spaces) - - def parse(self,parser): - """Parse our text with the given parser. - """ - import docutils - self.document = docutils.utils.new_document(self.text, self.settings) - parser.parse(self.text,self.document) - - def add_to_DPS(self,dps): - """Add this docstring content to a DOCUTILS subtree. - - Calls the appropriate things from `dps`, which is a - builtree.BuildTree instance. - """ - if self.document: - for child in self.document.children: - dps.addsubtree(child) - else: - dps.start("literal_block") - dps.add("text",self.text) - dps.end("literal_block") - - # :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - # Experimental <interpreted> node code... - # Copied from transform.py, and not adjusted yet (except superficially - # and stupidly) for its position in this class. - # - despite the comments in the docstring, it doesn't *do* anything yet... - - def transform_interpreted(self,thing): - """Deal with <interpreted> nodes in this docstring. - - The intent is to find any <interpreted> nodes, try to locate the - relevant Python value, and insert an appropriate cross-reference. - - `thing` is the `Module`, `Class`, `Function`, `Method` or - `Name` instance to which this docstring is "attached". It - is used to resolve the <interpreted> names. - """ - if self.debug: - print "\n*** Locating <interpreted> items for %s"%thing.fullname - self.show(sys.stdout," ") - self.locate_interpreted(self.document,thing) - - def locate_interpreted(self,element,thing): - """Deal with finding "interpreted" nodes in a document subtree. - """ - for node in element: - if node.tagname == "#text": - continue - elif node.tagname == "interpreted": - self.treat_interpreted(node,thing) - else: - self.locate_interpreted(node,thing) - - def treat_interpreted(self,element,thing): - """Do something interesting with an "interpreted" node. - """ - if element.has_key("role"): - role = element["role"] - else: - role = None - - name = element.astext() - - #if role: - # print "...<interpreted> :%s: %s"%(role,name) - #else: - # print "...<interpreted> %s"%name - - # Attempt to work out what this <interpreted> item refers to - target = thing.scope_find_name(name,role=role,indent="...") - if target: - element["refname"] = target - - -# ---------------------------------------------------------------------- -class ScopeMixin: - """Learning about how to represent scope. - - The idea of this mixin class is to work out the best way of representing - scope, so that I can use the results when working out exactly what an - <interpreted> item in a docstring refers to. - - Note that I do not intend (at least, for the moment) to worry about Python - before 2.2 - i.e., before embedded scope became non-optional. This is - purely laziness on my part... - """ - - scope_debug = 0 - - def scope_init(self): - """Call at the end of `__init__` to initilise our experiments - """ - - self.scope_module = None - """Which Module we are in. - """ - - self.scope_parent = None - """The *immediate* parent entity. - Note that for a method, this will be the class defining it. - - (Ultimately, it might be better if this were a weak reference.) - """ - - self.scope_encloser = None - """The innermost enclosing block. Note that for a method, - this should *not* be the class, but the innermost enclosing - function/method/module/whatever (whatever?). - """ - - self.scope_defines = [] - """A list of the names *defined* by this entity - (by assignment, definition or whatever). - - The list is ordered by last occurrence of the name. - """ - - self.scope_fullname = {} - """The key is the name from `self.scope_defines`, the value is the - "full" name for the defined value. - """ - - self.scope_children = {} - """A dictionary of the entities defined by this entity. - - The keys are the entity name (so correspond to those in - `self.scope_defines`), and the values are NULL if the entity - is defined by simple assignment, otherwise the relevant - "scope" instance (e.g., a Method or Class). - """ - - self.scope_globals = [] - """A list of names which are declared global in this scope. - """ - - def scope_define_parent(self,parent=None,module=None): - """Define our parent and encloser. - """ - self.scope_module = module - self.scope_parent = parent - self.scope_encloser = parent - if isinstance(self,Method): - while isinstance(self.scope_encloser,Class): - self.scope_encloser = self.scope_encloser.scope_parent - - def scope_define_name(self,name,fullname,child=None): - """Add a name that is defined in this scope. - - - `name` is the obvious name of the name being defined. - - `fullname` is the "path" to it, starting at the module. - - `child` is the class representing that name - e.g., a Class - or Name instance. - - Note - remember that functions/methods "define" their arguments... - - HOWEVER we are not trying for a general solution to the problem of - "find the item that is being referred to" - we are just trying to - provide useful links between <interpreted> items in docstrings and - those things that they might reasonably be expected to be referring to. - - Furthermore, we know that we will not show attributes, docstring or - not, if they are not - - a. At module level (so a ModuleValue) - b. Within a class (so a ClassValue) - c. Within a method called __init__ or __new__. - d. An argument (we always want to know about arguments). - - So we discard any attributes that do not match these criteria... - """ - if self.scope_debug: - if name in self.scope_defines: - redef = "(re)" - else: - redef = " " - print "%-15s %-30s %sdefined in %s"%(child.__class__.__name__, - name,redef,self.name) - - # Should we ignore this name? - if isinstance(child,Name): - if self.__class__ not in [Package,Module,Class] and \ - not (self.__class__ == Method and - self.name in ["__init__","__new__"]) and \ - child.__class__ != Argument: - return - # Remember to ensure the name is always at the END of the list - # (even if it was already there) - if name in self.scope_defines: - self.scope_defines.remove(name) - self.scope_defines.append(name) - self.scope_fullname[name] = fullname - self.scope_children[name] = child - - def scope_define_global(self,name): - """Indicate that a name is actually global. - - Note that it is entirely possible that the name is also present - in `self.scope_defines` - being global should be remembered as taking - precedence. - """ - if self.scope_debug: - if name in self.scope_defines: - redef = "(re)" - else: - redef = " " - print "%-10s %-30s %sdefined in %s"%("[Global]", - name,redef,self.name) - if name not in self.scope_globals: - self.scope_globals.append(name) - - def scope_get_object(self,name): - """Return the defined object corresponding to `name`, or None. - """ - try: - return self.scope_children[name] - except: - return None - - def scope_get_type(self,name): - """Return the "type" of the defined object corresponding to `name`. - """ - try: - return self.scope_children[name].__class__.__name__ - except: - return None - - def scope_name_in_self(self,name): - """Return true if the `name` is defined by ourselves. - """ - return name in self.scope_defines - - def scope_name_in_encloser(self,name): - """Return true if the `name` is defined by our encloser. - - (If we don't *have* an encloser - e.g., we're a module - then - it obviously returns false.) - """ - if self.scope_encloser: - return name in self.scope_encloser.scope_defines - else: - return 0 - - def scope_name_in_module(self,name): - """Return true if the `name` is defined by our module. - """ - if self.scope_module: - return name in self.scope_module.scope_defines - else: - return 0 - - def scope_up_is_module(self): - """Is our encloser the module? - """ - return self.scope_module == self.scope_encloser - - # We need a dictionary to relate role names to the classes we use - # A value of None against a key means that we are not attempting - # to handle this yet. - roles = {"package" : "Package", - "module" : "Module", - "class" : "Class", - "method" : "Method", - "function" : "Function", - "module_attribute" : "ModuleValue", - "class_attribute" : "ClassValue", - "instance_attribute": "InstanceValue", - "variable" : "Name", - "parameter" : "Argument", - "type" : None, - "exception_class" : None, - "exception" : None, - "warning_class" : None, - "warning" : None} - - def scope_obj_role_match(self,what,role): - """Decide if the role (if any) makes sense with respect to `what`. - - `what` is the object we thing might be our item of interest, - and `role` is the role we're looking for, or None if we're not - particular. - """ - if role: - if what.__class__.__name__ == self.roles[role]: - return 1 - else: - return 0 - else: - return 1 - - def scope_class_role_match(self,class_name,role): - """Decide if the role (if any) makes sense with respect to `what`. - - `what` is the class name of the object we thing might be our item - of interest, and `role` is the role we're looking for, or None if - we're not particular. - """ - if role: - if class_name == self.roles[role]: - return 1 - else: - return 0 - else: - return 1 - - def scope_callable_match(self,what,need_callable): - """Does the callable state of the `what` match our needs? Do we care? - """ - if need_callable: - return what.is_callable() - else: - return 1 - - def scope_find_name(self,name,role=None,indent=" "): - """A first pass at looking up names in scope. - - - `name` is the text that the user has enclosed in backquotes, - to make it interpreted - - `role`, if given, specifies exactly what sort of name the - user determines it to be. - - This makes no attempt to do anything beyond looking up a - simple name - it doesn't try to deal with (for instance) - a "()" at the end of the name, nor does it try to cope - with dotted names in any clever way. - - Returns either the label for the referenced entity (which is - a translation of its `fullname` into something legal as an - XML name), or None. - """ - if name.endswith("()"): - name = name[:-2] - need_callable = 1 - else: - need_callable = 0 - - if self.scope_debug: - print indent, - if role: - print ":%s:"%role, - - # Is it a reference to ourself? - if name == self.name and \ - self.scope_obj_role_match(self,role) and \ - self.scope_callable_match(self,need_callable): - if self.scope_debug: - print "%s '%s' IS %s %s"%\ - (self.__class__.__name__,name, - self.__class__.__name__,self.name) - return self.label() - - # Is it a reference to one of our children? - if self.scope_name_in_self(name): - what = self.scope_get_type(name) - whom = self.scope_children[name] - if self.scope_class_role_match(what,role) and \ - self.scope_callable_match(whom,need_callable): - if self.scope_debug: - print "%s '%s' found HERE in %s %s"%\ - (what,name,self.__class__.__name__,self.name) - if whom.__class__ == Argument: - return self.args_label() - else: - return whom.label() - - # Is it a reference to one of our encloser's children? - if self.scope_name_in_encloser(name): - what = self.scope_encloser.scope_get_type(name) - whom = self.scope_encloser.scope_children[name] - if self.scope_class_role_match(what,role) and \ - self.scope_callable_match(whom,need_callable): - if self.scope_debug: - print "%s '%s' found UP in %s %s"%\ - (what,name,self.scope_encloser.__class__.__name__, - self.scope_encloser.name) - if whom.__class__ == Argument: - return self.scope_encloser.args_label() - else: - return whom.label() - - # Is it a reference to our encloser? - if self.scope_encloser and name == self.scope_encloser.name: - # I'm not sure about this one - *should* we be able to "see" - # the name of our encloser? Or is what I'm *really* after - # the name of the *parent*? - # - # Specific instance - consider a method M in class C - # - should `C` in `M`s docstring refer to class C, - # even if class C is *not* defined at module level? - if self.scope_obj_role_match(self.scope_encloser,role) and \ - self.scope_callable_match(self.scope_encloser,need_callable): - if self.scope_debug: - print "%s '%s' IS %s %s"%\ - (self.scope_encloser.__class__.__name__,name, - self.scope_encloser.__class__.__name__, - self.scope_encloser.name) - return self.scope_encloser.label() - - # Is it a reference to a child of our module? - if not self.scope_up_is_module() and self.scope_name_in_module(name): - what = self.scope_module.scope_get_type(name) - whom = self.scope_module.scope_children[name] - if self.scope_class_role_match(what,role) and \ - self.scope_callable_match(whom,need_callable): - if self.scope_debug: - print "%s '%s' found GLOBAL in %s %s"%\ - (what,name,self.scope_module.__class__.__name__, - self.scope_module.name) - # Modules don't have arguments... - return whom.label() - - # Is it a reference to our module itself? - if name == self.scope_module.name: - if self.scope_obj_role_match(self.scope_module,role) and \ - not need_callable: - if self.scope_debug: - print "%s '%s' IS %s %s"%\ - (self.scope_module.__class__.__name__,name, - self.scope_module.__class__.__name__, - self.scope_module.name) - return self.scope_module.label() - - if self.scope_debug: - print "'%s' not in %s %s"%(name,self.__class__.__name__,self.name), - if self.scope_encloser: - print "or %s %s"%(self.scope_encloser.__class__.__name__, - self.scope_encloser.name), - if self.scope_up_is_module(): - print - else: - print "or %s %s"%(self.scope_module.__class__.__name__, - self.scope_module.name) - return None - - def scope_show(self,indent=""): - print "%s%s %s"%(indent,self.__class__.__name__,self.name), - if self.scope_encloser and self.scope_encloser != self.scope_parent: - print "<<upscope %s %s>>"%(self.scope_encloser.__class__.__name__, - self.scope_encloser.name) - else: - print - - extra_indent = indent+" " - - if self.scope_globals: - print "%sglobals %s"%(extra_indent,self.scope_globals) - - for name in self.scope_defines: - child = self.scope_children[name] - if child: - child.scope_show(extra_indent) - - -# ---------------------------------------------------------------------- -class Base(ScopeMixin): - """A simple base class for common functionality. - """ - - verbose = 0 - - def __init__(self, settings, name, fullname, docstring=None): - self.settings = settings - """ The parser settings - """ - - self.name = name - """The name of this scope entity. - """ - - self.fullname = fullname - """The fully qualified name of this entity - - for instance, for a method `fred` in a class `Jim` - in a module `bob`, this would be "bob.Jim.fred" - """ - - self.docstring = make_Docstring(docstring, settings) - """The docstring for this scope, or None. - Note that, at least for the moment, we're only supporting - one docstring for an entity. This may need to change for - proper DOCUTILS support (but I [1]_, personally, hope not). - - .. [1] "I" being Tibs, at this point, and at this time [2]_. - .. [2] "This time" being April 2002. - """ - - self.imports = {} - """Import statements within this scope. The key is the - name of each module being imported, the value is a list - of the names "as" which the module is imported, with - `None` meaning "as itself". - """ - - self.from_imports = {} - """From <module> import <names> statements within this - scope. The key is the <module>, the value a dictionary - like that used for `self.imports`. - """ - - self.classes = {} - """Classes defined within this scope. The key is the - class name, the value is the Class instance for this ``class``. - """ - - self.functions = {} - """Functions or methods defined within this scope. The key is the - function name, the value is the Function or Method instance - for this ``def``. - """ - - self.attributes = {} - """Attributes assigned to within a scope. The key is the - attribute name, the value is the Name instance for this ``name``. - """ - - self.children = [] - """A list of all of the items contained herein which might - have, or have children which have, docstrings - i.e., packages, - modules, classes, functions, methods. - """ - - self.class_list = [] - """A list of the class names defined in this scope, in order. - """ - - self.function_list = [] - """A list of the function/method names defined in this scope, in order. - """ - - self.attribute_list = [] - """A list of the attribute names defined in this scope, in order. - """ - - self.globals = {} - """The names mentioned in global statements within a scope. - The key is the name, the value doesn't matter. - """ - - self.parser = None - """We may need a reStructuredText parser to handle our docstrings - - if so, we'll generate one "on demand".""" - - def getParser(self): - """Retrieve a reStructuredText parser, for docstring usage. - """ - if not self.parser: - self.parser = Parser() - return self.parser - - def parse_docstrings(self,parser): - """If we are meant to, parse our docstrings. - """ - if self.docstring: - self.docstring.parse(parser) - for child in self.children: - child.parse_docstrings(parser) - - def is_callable(self): - """Does it make sense to call an object of this type? - """ - return 0 - - def label(self): - """Return an XML-legal name for this object. - - Note that we implicitly assume that all objects have their - output XML/HTML representation written to the same file - - i.e., that we do not have to worry about references that - are anything beyond simple names. - """ - str = string.replace(self.fullname,".","-") - str = string.replace(str,":","--") - return str - - def addGlobal(self,name): - self.globals[name] = name - - def addClass(self,klass): - self.classes[klass.name] = klass - self.class_list.append(klass.name) - self.children.append(klass) - - def addFunction(self,func): - self.functions[func.name] = func - self.function_list.append(func.name) - self.children.append(func) - - def addAttribute(self,name,instance): - self.attributes[name] = instance - self.attribute_list.append(name) - self.children.append(instance) - - def addImport(self,names): - """Remember an import statement. - - `names` is a list of ``(name,as)`` tuples, where ``name`` - is the name of the item being imported, and ``as`` is either - None of else the name "as" which this item is being imported - - Note we aren't even *trying* to remember its exact locality. - """ - for name,as in names: - if not self.imports.has_key(name): - self.imports[name] = [as] - else: - if as not in self.imports[name]: - self.imports[name].append(as) - - def addFromImport(self,modname,names): - """Remember a from import statement. - - `modname` is the name of the module "from" which things are - being imported. - - `names` is a list of ``(name,as)`` tuples, where ``name`` - is the name of the item being imported, and ``as`` is either - None of else the name "as" which this item is being imported - - Note we aren't even *trying* to remember its exact locality. - """ - if not self.from_imports.has_key(modname): - self.from_imports[modname] = {} - - dict = self.from_imports[modname] - for name,as in names: - if not dict.has_key(name): - dict[name] = [as] - else: - if as not in dict[name]: - dict[name].append(as) - - def isGlobal(self,name): - return self.globals.has_key(name) - - def getClassNames(self): - return self.class_list - #return self.classes.keys() - - def getClass(self,name): - return self.classes[name] - - def getFunctionNames(self): - return self.function_list - #return self.functions.keys() - - def getFunction(self,name): - return self.functions[name] - - def getAttributeNames(self): - return self.attribute_list - #return self.attributes.keys() - - def getAttribute(self,name): - return self.attributes[name] - - def getAttributeDocstring(self,name): - """Return an attribute docstring, or None - """ - return self.attributes[name] - - def getImports(self): - """Return our "import"s, but not in any particular order. - """ - return self.imports - - def getFromImports(self): - """Return our "from ... import"s, but not in any particular order. - """ - return self.from_imports - - def getFromImportNames(self,name): - return self.from_imports[name] - - def getSelf(self): - """Return the name used for "self". Doesn't do much elsewhere. - - (Actually, for a method *or* a function, it returns the name - of the first argument.) - """ - return None - - def _show_body(self,stream,indent=0): - spaces = indent*" " - - if self.docstring: - self.docstring.show(stream,spaces+" ") - - if self.imports: - names = self.imports.keys() - stream.write("%s Import %s\n"%(spaces,string.join(names,", "))) - - if self.from_imports: - modnames = self.from_imports.keys() - for mod in modnames: - names = self.from_imports[mod] - stream.write("%s From %s import %s\n"%\ - (spaces,mod,string.join(names,", "))) - - # Should we sort things alphabetically within each list? - - if self.verbose: - globals = self.globals.keys() - if globals: - stream.write("%s Global statements\n"%spaces) - for name in globals: - stream.write("%s %s\n"%(spaces,name)) - - attributes = self.attributes.keys() - if attributes: - stream.write("%s Attributes\n"%spaces) - for name in attributes: - self.attributes[name].show(stream,indent+4) - - class_names = self.classes.keys() - if class_names: - for name in class_names: - self.classes[name].show(stream,indent+2) - - func_names = self.functions.keys() - if func_names: - for name in func_names: - self.functions[name].show(stream,indent+2) - - def show(self,stream,indent=0): - """Override as necessary.""" - stream.write("%s%s %s\n"%(" "*indent,self.__class__.__name__, - self.name)) - self._show_body(stream,indent) - - # :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - # Experimental <interpreted> node code... - def find_docstrings(self): - """Find each docstring in our subtree. - """ - if self.docstring: - self.docstring.transform_interpreted(self) - for child in self.children: - child.find_docstrings() - # :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - -# ---------------------------------------------------------------------- -class Package(Base): - """The representation of a Python package. - - Unlike the other children of `Base`, this class actually visits - its members itself - that is, it is self-constructing. - """ - - def __init__(self,settings,directory,parent=None,debug=0): - """Instantiate a new package. - - If this is a "sub" package, then `parent` should be a Package - instance, within which we may be found... - - `settings` will give the parsing settings -- `verbose_parse` will - cause the generation of a message for each stage of the production - of our data, and `debug` will give a message for each "entity" - in the AST that we visit. - """ - - self.settings = settings - self.verbose = settings.verbose_parse - self.debug = debug - - path = os.path.expanduser(directory) # expand "~" - path = os.path.expandvars(directory) # expand "$fred" or "%fred%" - path = os.path.abspath(path) # expand to a 'full' path - path = os.path.normpath(path) # sort out "/fred" versus "/fred/" - self.directory = path - """The `directory` is our best guess as to the actual - path to the package.""" - - # Take the package name to be the final component - base,name = os.path.split(path) - if parent: - fullname = parent.fullname + "." + name - Base.__init__(self,settings,name,fullname) - else: - # With no parent, our name and fullname are the same. - Base.__init__(self,settings,name,name) - - self.modules = {} - """Remember the modules (or Python files, really) - in this package. The key is the module name, the - value the Module instance for this module. - """ - - # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - # Experimental scope setup - self.scope_init() - # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - self.parse_modules() - - def parse_modules(self): - """Locate our constituent modules and parse them. - """ - files = os.listdir(self.directory) - - if "__init__.py" not in files: - raise ValueError,\ - "Directory %s does not contain an __init__.py file"%\ - self.directory - - # Parsing the __init__.py file may give us further information, - # such as an __all__ value, which we may later want to use to - # determine which files to read and parse, and which to ignore - path = os.path.join(self.directory,"__init__.py") - self.addModule(Module(self.settings,path,package=self, debug=self.debug)) - - # But, for the moment, just parse all the Python files... - for file in files: - name,ext = os.path.splitext(file) - if name == "__init__": - continue - elif ext in [".py",".pyw"]: - path = os.path.join(self.directory,file) - self.addModule(Module(self.settings,path,package=self, - debug=self.debug)) - - def addModule(self,mod): - self.modules[mod.name] = mod - self.children.append(mod) - - def getModuleNames(self): - return self.modules.keys() - - def getModule(self,name): - return self.modules[name] - - def getModules(self): - """Return a list of Module instances. - """ - return self.modules.values() - - def show(self,stream,indent=0): - stream.write("%sPackage %s"%(" "*indent,self.name)) - if self.directory: - stream.write("(in %s)\n"%self.directory) - else: - stream.write("\n") - - names = self.modules.keys() - names.sort() - for name in names: - self.modules[name].show(stream) - - def show_ast(self,stream): - """Print out a representation of our ASTs. - """ - names = self.modules.keys() - names.sort() - for name in names: - self.modules[name].show_ast(stream) - - -# ---------------------------------------------------------------------- -class Module(Base): - """The representation of a Python module. - - This class also knows how to *parse* a Python module. - - Still known to be missing: - - - visitLambda (I shudder) - - should we also do visitExec? - - - visitAssign - - visitAugAssign - - These last two are needed to allow us to link a Name with its value(s). - - If attribute docstrings are to be found, then the AST on which we - act must first have been processed with `find_attr_docs()`. - - Note we also need to visit the *other* sorts of assignment, so - we can update our Module with any globals used therein... - """ - - def __init__(self,settings,filename,docstring=None,package=None,debug=0): - """Instantiate a new module. - - If this is a module within a package, then `package` should be - a Package instance. - - `settings` will give the parsing settings -- `verbose_parse` will - cause the generation of a message for each stage of the production - of our data, and `debug` will give a message for each "entity" - in the AST that we visit. - """ - self.settings = settings - self.package = package - self.verbose = settings.verbose_parse - self.debug = debug - - self.docformat,self.language = utils.docformat("plaintext") - - filename = os.path.expanduser(filename) # expand "~" - filename = os.path.expandvars(filename) # expand "$fred" or "%fred%" - filename = os.path.abspath(filename) # expand to a 'full' path - filename = os.path.normpath(filename) # and tidy up - self.filename = filename - """The `filename` is our best guess as to the actual - path to the module.""" - - dir,file = os.path.split(filename) - name,ext = os.path.splitext(file) - - if package: - fullname = package.fullname + "." + name - Base.__init__(self,settings, name,fullname,docstring) - else: - # Without a package, our name and fullname are the same - Base.__init__(self,settings, name,name,docstring) - - self.ast = None - """The parse tree, as produced by compiler. - """ - - self.globals_used = {} - """Remember the globals promulgated on this module - (that is, occurrences of a global statement, associated - with assignment *to* the named global, within the same - scope). - - The key is the global name, the value a list of - (scope,superscope) pairs. - """ - - # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - # Experimental scope setup - self.scope_init() - self.scope_define_parent(parent=package,module=self) - # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - # Now, parse ourselves! - self.parse_module() - - # And then perform our "finishing off" operations - self.determine_docformat() - - if self.verbose: - print >>sys.stderr, \ - " >> docformat: %s, language %s"%(self.docformat, - self.language) - - if self.docformat == utils.RESTRUCTUREDTEXT: - if self.verbose: - print >>sys.stderr," .. parsing docstrings in module",\ - self.name - parser = self.getParser() - self.parse_docstrings(parser) - if self.verbose: - print >>sys.stderr," .. finding <interpreted> nodes" - self.find_docstrings() - - def parse_module(self): - """Parse ourselves (!) - """ - if self.verbose: print >>sys.stderr, "*** Parsing file",self.filename - self.ast = compiler.parseFile(self.filename) - - # "Comb" the tree to sort out attribute docstrings - if self.verbose: print >>sys.stderr, " Locating attribute docstrings" - utils.find_attr_docs(self.ast) - - # And to link names and their values in assignments - if self.verbose: print >>sys.stderr, " Locating attribute values" - utils.find_attr_vals(self.ast) - - if self.verbose: print >>sys.stderr, " Walking the tree" - compiler.walk(self.ast,self) - - def determine_docformat(self): - """Call this after reading in the module, before "using" it. - - This detects whether there is a module value called __docformat__, and - if so, inspect its value. - """ - if self.attributes.has_key("__docformat__"): - name = self.attributes["__docformat__"] - try: - value = name.actualLastValue() - except ValueError, detail: - print >>sys.stderr, "Ignoring __docformat__" - print >>sys.stderr, detail - return - self.docformat,self.language = utils.docformat(value) - - def addGlobalUsed(self,name,where): - if self.globals_used.has_key(name): - self.globals_used[name].append(where) - else: - self.globals_used[name] = [where] - - def getObscureGlobals(self): - """Return a list of (name,[fullname]) tuples - """ - globals = self.globals_used.keys() - globals.sort() - attributes = self.attributes.keys() - obscure = [] - for name in globals: - if name not in attributes: - wherelist = self.globals_used[name] - obscure.append( (name,wherelist) ) - return obscure - - # ---------------------------------------------------------------------- - # Extraction code - - def _scopes(self,scope,superscope): - str = "" - if scope: - str = str + " in %-8s %-12s"%(scope.__class__.__name__, - `scope.name`) - if superscope: - str = str + " in %-8s %-12s"%(superscope.__class__.__name__, - `superscope.name`) - return str - - def _report(self,what,name,scope,superscope): - print >>sys.stderr,\ - "%-8s %-12s"%(what,name) + self._scopes(scope,superscope) - - def visitModule(self,node,scope=None,superscope=None): - """Visit a Module node - heh, this must be us! - - We expect `scope` and `superscope` both to be None - """ - if self.debug: - self._report("Module",self.filename,scope,superscope) - - self.docstring = make_Docstring(node.doc, self.settings) - - # Visit our children with ourselves as their scope - self.visit(node.node,self) - - def visitClass(self,node,scope=None,superscope=None): - """Visit a Class node - - `scope` is the scope of this Class, and `superscope` is - that scope's scope (if any). - - We don't yet cope well with things like:: - - class Fred(docutils.nodes) - - - this will need fixing. - """ - if self.debug: - self._report("Class",node.name,scope,superscope) - - if scope: - fullname = "%s.%s"%(scope.fullname,node.name) - else: - fullname = node.name - - # If we were nice, we'd resolve the - # Getattr(Getattr(Name('docutils'), 'nodes'), '_TextElement') - # that we get for something like docutils.nodes into something - # easier to deal with... - cls = Class(self.settings,node.name,fullname,node.bases,node.doc) - if scope: - scope.addClass(cls) - - # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - # Experimental scope work - if scope: - cls.scope_define_parent(scope,self) - scope.scope_define_name(node.name,fullname,cls) - # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - # Visit our children with ourselves as their scope, - # and our scope as their superscope - self.visit(node.code,cls,scope) - - def visitFunction(self,node,scope=None,superscope=None): - """Visit a Function node (nb: this means "function or method") - - `scope` is the scope of this Function, and `superscope` is - that scope's scope (if any). - """ - if self.debug: - if isinstance(scope,Class): - self._report("Method",node.name,scope,superscope) - else: - self._report("Function",node.name,scope,superscope) - - fullname = "%s.%s"%(scope.fullname,node.name) - - if isinstance(scope,Class): - fun = Method(self.settings,node.name,fullname, - node.argnames,node.defaults,node.flags,node.doc) - else: - fun = Function(self.settings,node.name,fullname, - node.argnames,node.defaults,node.flags,node.doc) - scope.addFunction(fun) - - # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - # Experimental scope work - fun.scope_define_parent(scope,self) - scope.scope_define_name(node.name,fullname,fun) - for argname in node.argnames: - fullname = "%s.%s:%s"%(scope.fullname,node.name,argname) - name = Argument(self.settings,argname,fullname) - name.scope_define_parent(fun,self) - fun.scope_define_name(argname,fullname,name) - # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - # Visit our children with this function as the scope, and - # our scope as the superscope - self.visit(node.code,fun,scope) - - def visitAssAttr(self,node,scope,superscope=None): - """Visit an attribute assignment node. - - For example:: - - self.a = 1 - - Note that this is assignment to a *single* attribute only. - - `scope` is the scope of this assignment, and `superscope` is - that scope's scope (if any). - """ - if self.debug: - self._report("attr",node.attrname,scope,superscope) - - # An AssAttr contains: - # .expr - the expression on the LH of the dot - # .attrname - the attribute name on the RH of the dot - # .flags - # We're (to a first approximation) only interested in - # assignments of the form <self>.<attr>, where "<self>" - # is whatever name was passed down as the first argument - # to a containing method. - if isinstance(node.expr,compiler.ast.Name): - fullname = "%s:%s.%s"%(scope.fullname,node.expr.name,node.attrname) - selfname = "%s.%s"%(node.expr.name,node.attrname) - if node.expr.name == scope.getSelf(): - what = InstanceValue - else: - what = Name # hmm, this feels wrong, but I do want to distinguish - - if hasattr(node,"docstring"): - #name = what(node.attrname,fullname,node.expr.name,node.docstring) - name = what(selfname,fullname,node.expr.name,node.docstring) - else: - #name = what(node.attrname,fullname,node.expr.name) - name = what(selfname,fullname,node.expr.name) - if hasattr(node,"assign_expr"): - name.setValue(node.assign_expr) - scope.addAttribute(selfname,name) - - if node.expr.name == scope.getSelf() and scope.__class__ == Class: - name.set_class(scope) - - # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - # We should do *something* for our experimental scope - # work at this point - but I guess it would be to add - # this name to the class that contains the method in - # which we are - and if we're actually in a *function* - # which is to be assigned to a class at run-time (for - # instance), that is a bit difficult (!). So ignore - # this case for now... - # OR maybe we should be adding in "fullname" and not - # "name" each time we add something to the scope. - name.scope_define_parent(scope,self) - scope.scope_define_name(selfname,fullname,name) - # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - else: - # What to do here??? - # - it's presumably something like fred[3].attr, so I guess - # we don't want to do anything very clever with it, at least - # for the moment. - - print - print ">>> Unhandled AST node <<<" - print " visitAssAttr: %s"%(node) - print " i.e.: %s"%(utils.stringify_expr(node)) - print " location: Module %s, %s %s, %s %s"%\ - (self.scope_module.name, - scope.__class__.__name__,scope.name, - superscope.__class__.__name__,superscope.name) - print - - # Hmm - what scope and superscope does the - # subexpression *really* want... - # - is there any point (from a DPV point of view) - # in following down the innards of the expression? - self.visit(node.expr,scope,superscope) - - def visitAssName(self,node,scope=None,superscope=None): - """Visit a name assignment node. - - For example:: - - a = 1 - - Note that this is assignment to a *single* name only. - - `scope` is the scope of this assignment, and `superscope` is - that scope's scope (if any). - """ - if self.debug: - self._report("name",node.name,scope,superscope) - - # For the moment, allow any assignments - later we may restrict - # this (e.g., to Module, Class, __init__ method) - if scope: - fullname = "%s:%s"%(scope.fullname,node.name) - else: - fullname = node.name - - if scope.__class__ == Module: - what = ModuleValue - elif scope.__class__ == Class: - what = ClassValue - else: - what = Name - - if hasattr(node,"docstring"): - name = what(self.settings,node.name,fullname, - docstring=node.docstring) - else: - name = what(self.settings,node.name,fullname) - if hasattr(node,"assign_expr"): - name.setValue(node.assign_expr) - if scope.isGlobal(node.name): - name.setGlobal() - scope.addAttribute(node.name,name) - - # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - # Experimental scope work - if scope: - name.scope_define_parent(scope,self) - scope.scope_define_name(node.name,fullname,name) - # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - # Hmm - check whether the name is a global, and if so, - # tell our Module about it... - if scope.isGlobal(node.name): - self.addGlobalUsed(node.name,scope.fullname) - - def visitAssTuple(self,node,scope=None,superscope=None): - """Visit a tuple assignment statement. - - For example: - - a, b, c = 1, 2, 3 - - Although we're not directly interested in these for docstring - purposes, we *may* want to know that a variable has participated - in one (why?). - """ - for item in node.nodes: - self.visit(item,scope,superscope) - - def visitAssList(self,node,scope=None,superscope=None): - """Visit a list assignment statement. - - For example: - - a, b, c = [1, 2, 3] - - Although we're not directly interested in these for docstring - purposes, we *may* want to know that a variable has participated - in one (why?). - """ - for item in node.nodes: - self.visit(item,scope,superscope) - - def visitGlobal(self,node,scope=None,superscope=None): - """Visit a global statement node - - `scope` is the scope of this global, and `superscope` is - that scope's scope (if any). - """ - if self.debug: - for name in node.names: - self._report("global",name,scope,superscope) - - for name in node.names: - scope.addGlobal(name) - # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - # Experimental scope work - scope.scope_define_global(name) - # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - def visitYield(self,node,scope=None,superscope=None): - """Visit a yield statement. - - The presence of a yield statement within a function indicates - that it is a generator (well, unless that statement is - unreachable - but we shall not worry about that, at least - for the moment!). - - `scope` is the scope of this statement, and `superscope` is - that scope's scope (if any). - """ - if self.debug: - self._report("yield",node.value,scope,superscope) - - # So tell our scope that it is (probably) a generator - scope.generator = 1 - - # There seems little point in visiting our value, but on - # the other hand, why not - self.visit(node.value,scope,superscope) - - - def visitImport(self,node,scope=None,superscope=None): - """Visit an import statement node - - `scope` is the scope of this import statement, and - `superscope` is that scope's scope (if any). - """ - if self.debug: - self._report("import","",scope,superscope) - print >>sys.stderr, "%8s %12s %s"%(" "," ",node.names) - - if scope: - scope.addImport(node.names) - # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - # Experimental scope work - for item in node.names: - fullname = scope.fullname + "." + item[0] - name = ImportName(self.settings,item[0],fullname) - name.scope_define_parent(scope,self) - scope.scope_define_name(item[0],fullname,name) - # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - def visitFrom(self,node,scope=None,superscope=None): - """Visit a from .. import statement node - - The rules for `scope` and `superscope` should be identical - to those for `visitImport()`. - """ - if self.debug: - self._report("from",node.modname,scope,superscope) - print >>sys.stderr, "%8s %12s %s"%(" "," ",node.names) - - if scope is not None: - scope.addFromImport(node.modname,node.names) - # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - # Experimental scope work - for item in node.names: - fullname = scope.fullname + "." + item[0] - name = ImportName(self.settings,item[0],fullname) - name.scope_define_parent(scope,self) - scope.scope_define_name(item[0],fullname,name) - # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - # ---------------------------------------------------------------------- - - def _prt(self,stream,indent,name,place): - stream.write("%sGlobal %-10s in %s\n"%(" "*indent,name,place)) - - def _and(self,stream,indent,name,place): - stream.write("%s %-10s and in %s\n"%(" "*indent," "*len(name), - place)) - - def _show_odd_globals(self,stream,indent=0): - """Show which global names are defined and used below module level - - Obviously (!) we are only interested in things which are not - already known to be module level attributes - we want to warn - the user about things that are defined in non-obvious places. - """ - obscure = self.getObscureGlobals() - done_header = 0 - for name,wherelist in obscure: - if not done_header: - stream.write("Globals defined *and used* below" - " the top level:\n") - done_header = 1 - self._prt(stream,indent+2,name,wherelist[0]) - for place in wherelist[1:]: - self._and(stream,indent+2,name,place) - - def show(self,stream,indent=0): - stream.write("%sModule %s"%(" "*indent,self.name)) - if self.filename: - stream.write("(in file %s)\n"%self.filename) - else: - stream.write("\n") - self._show_body(stream,indent) - self._show_odd_globals(stream,indent) - - def show_ast(self,stream): - """Print out a representation of our AST. - """ - stream.write("AST for module %s\n"%self.name) - utils.treeprint(stream,self.ast) - -# ---------------------------------------------------------------------- -class Class(Base): - """The representation of a Python class.""" - - def __init__(self,settings,name,fullname,bases,docstring): - Base.__init__(self,settings,name,fullname,docstring) - - self.bases = bases or [] - """A list of the base classes for this class.""" - - # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - # Experimental scope setup - self.scope_init() - # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - def is_callable(self): - """Does it make sense to call an object of this type? - - NB: it only makes sense to call this *after* the class has - been parsed! - """ - return self.functions.has_key("__call__") - - def show(self,stream,indent=0): - basestr = "" - if self.bases: - names = self.getBaseNames() - basestr = "(" + string.join(names,",") + ")" - stream.write("%sClass %s%s\n"%(" "*indent,self.name,basestr)) - self._show_body(stream,indent) - - def getBaseNames(self): - names = [] - for base in self.bases: - # Unfortunately, we don't yet cope with base classes - # like "docutils.nodes" in our visitor... - # Make the best of it we can, for now - try: - names.append(base.name) - except: - names.append("???") - return names - - # Is it worth doing this? - addMethod = Base.addFunction - getMethodNames = Base.getFunctionNames - - -# ---------------------------------------------------------------------- -class Function(Base): - """The representation of a Python function (and thus also of a method). - - But see also `Method`. - """ - - def __init__(self,settings,name,fullname,args,defaults,flags,docstring): - Base.__init__(self,settings,name,fullname,docstring) - - self.args = args - """The arguments for this function or method.""" - - self.defaults = defaults - """The defaults (if any) for the latter arguments. - - Note that if there are 4 arguments, and only the last - 2 have defaults, then this list will be 2 items long. - """ - - self.flags = flags - """Used to indicate \*args and \**kwargs. - """ - - self.generator = 0 - """Set to true if this function appears to be a generator - (i.e., it contains a "yield" statement). Obscure code that - doesn't actually *reach* the yield statement - for instance:: - - def fred(): - print "3" - if 1: return - yield "2" - - will also, incorrectly, be recognised as a generator - tough. - """ - - # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - # Experimental scope setup - self.scope_init() - # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - def is_callable(self): - """Does it make sense to call an object of this type? - """ - return 1 - - def args_label(self): - """Return the specialised label we use for all of our arguments. - """ - return self.label() + "---args" - - def getArgs(self): - """Return a representation of our arguments as a list (of strings). - - This is then suitable for joining with commas and displaying. - """ - args = utils.merge_args(self.args,self.defaults) - if not args: - return args - - # The file compiler/consts.py defines CO_VARARGS=1 and - # CO_VARKEYWORDS=2. - - # The method Transformer.com_arglist() in file compiler/transformer.py - # handles a function's argument list. - - # According to that, if there's a "*args" item, then "flags" gets the - # CO_VARARGS bit set, and if there's a "**args" item, then "flags" gets - # the CO_VARKEYWORDS bit set. In either case, we *know* they've got to - # be the last arguments, so we can count backwards (as we do for - # default arguments). - - if self.flags & compiler.consts.CO_VARKEYWORDS: - # If it's there, this must always be the last one... - args[-1] = "**" + args[-1] - - if self.flags & compiler.consts.CO_VARARGS: - # But this one might be last OR last but one - if self.flags & compiler.consts.CO_VARKEYWORDS: - args[-2] = "*" + args[-2] - else: - args[-1] = "*" + args[-1] - - return args - - def show(self,stream,indent=0): - stream.write("%s%s %s(%s)\n"%(" "*indent,self.__class__.__name__, - self.name, - string.join(self.getArgs(),", "))) - self._show_body(stream,indent) - - def getSelf(self): - """For a method, return the name used for "self". - - (Actually, for a method *or* a function, return the name of - the first argument - we need it on functions as well in case - someone is defining a function later to be assigned to a class - and used as a method.) - """ - if len(self.args) > 0: - return self.args[0] - else: - return None - - -# ---------------------------------------------------------------------- -class Method(Function): - """Just to get the class name right, I'm afraid... - """ - pass - - -# ---------------------------------------------------------------------- -class Name(ScopeMixin): - """Information about use of a name in assignment. - - STILL UNDER CONSTRUCTION - - Test using pysource.py's "--show" option... - - - We are not interested in *all* names, nor in all information - about them... - - (We *could* inherit from Base, but that really brings too much other - stuff that we don't need with it - and it doesn't *quite* seem worth - having a mixin class (although the more I work with this, the more - that seems a wrong decision - maybe next refactor time will change - things.) - """ - - def __init__(self,settings,name,fullname,selfname=None,docstring=None): - """Instantiate a new Name. - - * `settings` -- the settings for the parser - * `name` -- the name of this, erm, name - * `fullname` -- the "fully qualified" name - this is the "path" from - our top-level entity down to this name (e.g., module.class.name) - * `selfname` -- if this is an instance attribute, the string used - as the "self" equivalent - * `docstring` -- the docstring for this name, if any. - """ - - self.name = name - """The name of this, erm, name. - """ - - self.fullname = fullname - """The fully qualified name of this name - - for instance, for a name `fred` in a class `Jim` - in a module `bob`, this would be "bob.Jim.fred" - """ - - self.docstring = make_Docstring(docstring, settings) - """The docstring for this name, or None. - Note that, at least for the moment, we're only supporting - one docstring for an entity. See the equivalent comment - in `Base`. - """ - - self.firstuse = None - """Remember the first use of this name - i.e., what is - assigned to it. The value stored is the AST expression - assigned to our name. - """ - - self.lastuse = None - """Also remember the last use of this name. The value - stored is the AST expression, as for firstuse. - """ - - self.reused = 0 - """Assume it has not been assigned to more than once. - If it has, then we can't *really* report on its 'content', - and `self.firstuse` should be ignored. - """ - - self.selfname = selfname - """If this name is referred to as something like 'self.name' - then we want to remember the string used as 'self' (since the - user *might* be using something else). This will be ``None`` - if there is no 'prefix'. - """ - - self.isglobal = 0 - """True if this name is global to its module. - """ - - # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - # Experimental scope setup - self.scope_init() - # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - def parse_docstrings(self,parser): - """If we are meant to, parse our docstrings. - - (The oddly plural name is for compatibility with those things - that inherit from `Base` - another reason for thinking that a - mixin class may be about due...) - """ - if self.docstring: - self.docstring.parse(parser) - - # :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - # Experimental <interpreted> node code... - def find_docstrings(self): - """Find each docstring in our subtree. - - (The oddly plural name is for compatibility with those things - that inherit from `Base` - another reason for thinking that a - mixin class may be about due...) - """ - if self.docstring: - self.docstring.transform_interpreted(self) - # :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - - def is_callable(self): - """Does it make sense to call an object of this type? - """ - return 0 - - def label(self): - """Return an XML-legal name for this object. - - Note that we implicitly assume that all objects have their - output XML/HTML representation written to the same file - - i.e., that we do not have to worry about references that - are anything beyond simple names. - - (another duplicate of a method in Base) - """ - str = string.replace(self.fullname,".","-") - str = string.replace(str,":","--") - return str - - def setValue(self,expr): - self.lastuse = expr - if not self.firstuse: - self.firstuse = expr - else: - self.reused = 1 - - def setGlobal(self): - self.isglobal = 1 - - def isGlobal(self): - return self.isglobal - - def getDocstring(self): - return self.docstring - - def getSelfName(self): - return self.selfname - - def getValueString(self): - if self.reused or not self.firstuse: - return None - else: - return utils.stringify_expr(self.firstuse) - - def getLastValueString(self): - """Return the stringified value of this name, or None. - """ - return utils.stringify_expr(self.lastuse) - - def actualLastValue(self): - """Return the actual value assigned to this name. - - If the value assigned is a constant, then return it, otherwise - raise a ValueError (we can't just return NULL because that *is* - a sensible constant!). - """ - thing = self.lastuse - # See utils.stringify_expr() for information on what we think - # we're doing here... - if isinstance(thing,compiler.ast.Const): - return thing.value - else: - raise ValueError,"Value assigned to %s is not a constant:\n" \ - "(%s)"%(self.getSelfName(),self.getLastValueString()) - - def getAsString(self): - str = self.name - val = self.getValueString() - if val: - str += " = " + val - return str - - def show(self,stream,indent=0): - """Override as necessary.""" - stream.write("%s%s %s"%(" "*indent,self.__class__.__name__, - self.getAsString())) - if self.isglobal: - stream.write(" (global)\n") - else: - stream.write("\n") - if self.docstring: - self.docstring.show(stream," "*(indent+2)) - - -# ---------------------------------------------------------------------- -class Argument(Name): - """An argument to a method or function. - - This is a separate class just to get the class name right, I'm afraid... - """ - pass - - -# ---------------------------------------------------------------------- -class ModuleValue(Name): - """A name defined by assignment at the top level of a module. - - This is a separate class just to get the class name right, I'm afraid... - """ - pass - - -# ---------------------------------------------------------------------- -class ClassValue(Name): - """A name defined by assignment within a class definition. - - This is a separate class just to get the class name right, I'm afraid... - """ - pass - - -# ---------------------------------------------------------------------- -class InstanceValue(Name): - """An instance defined by assignment within a method - e.g., self.thing - - TO DO: Hmm - I need to actually *implement* the use of `self.our_class` - and `self.see_also`. - """ - - def __init__(self,*args,**kws): - Name.__init__(self,*args,**kws) - - self.our_class = None - """If we are self.thing in a method, then "self" refers to a class - (the one enclosing the method!), and this will be a reference to it. - """ - - self.see_also = None - """If the class already defined a `ClassInstance` of this name, - then we'll ultimately want a reference to that class here, so - that our output can say "see also class instance XXX". - """ - - def set_class(self,klass): - """Indicate which Class we really belong to. - """ - self.our_class = klass - - -# ---------------------------------------------------------------------- -class ImportName(Name): - """A name assigned by "import" or "from ... import" - - Should we distinguish these? ((yes)) - - This is a separate class just to get the class name right, I'm afraid... - - NB: we don't yet deal with "import ... as ..." - """ - - def is_callable(self): - """Does it make sense to call an object of this type? - - Unfortunately, if it's something imported with "from ... import" - there is no simple way of knowing - so maybe err on the side of - assuming so. - - ((Note that this is actually a good argument for separating out - the two sorts of import, since we know *modules* are not callable, - and "import" just makes modules visible.)) - """ - return 1 - - -# ---------------------------------------------------------------------- -def test_parse_module(filename): - print "Reading file %s"%filename - return Module(filename,debug=0) - -def test_show_ast(thing): - print - print "AST" - print "===" - thing.show(sys.stdout) - -def test_show_scopes(thing): - print - print "Scopes" - print "======" - thing.scope_show() - -def test(): - print "Testing pysource/visit.py" - if len(sys.argv) <= 1: - print "Usage: pysource/visit.py <python-file>" - return - filename = sys.argv[1] - thing = test_parse_module(filename) - test_show_ast(thing) - test_show_scopes(thing) - -if __name__ == "__main__": - test() |