Update of /cvsroot/happydoc/HappyDoc3/happydoclib/parseinfo
In directory usw-pr-cvs1:/tmp/cvs-serv4203/happydoclib/parseinfo
Added Files:
utils.py suite.py parsecomments.py moduleinfo.py imports.py
functioninfo.py classinfo.py __init__.py
Log Message:
Import parseinfo modules from HappyDoc 2.x, with some mods. Move
parsecomments.py into this package.
--- NEW FILE: utils.py ---
#!/usr/bin/env python
#
# $Id: utils.py,v 1.1 2002/11/17 15:05:43 doughellmann Exp $
#
# Copyright 2001 Doug Hellmann.
#
#
# All Rights Reserved
#
# Permission to use, copy, modify, and distribute this software and
# its documentation for any purpose and without fee is hereby
# granted, provided that the above copyright notice appear in all
# copies and that both that copyright notice and this permission
# notice appear in supporting documentation, and that the name of Doug
# Hellmann not be used in advertising or publicity pertaining to
# distribution of the software without specific, written prior
# permission.
#
# DOUG HELLMANN DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
# NO EVENT SHALL DOUG HELLMANN BE LIABLE FOR ANY SPECIAL, INDIRECT OR
# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#
"""Utility functions for parseinfo package.
"""
__rcs_info__ = {
#
# Creation Information
#
'module_name' : '$RCSfile: utils.py,v $',
'rcs_id' : '$Id: utils.py,v 1.1 2002/11/17 15:05:43 doughellmann Exp $',
'creator' : 'Doug Hellmann <do...@he...>',
'project' : 'UNSPECIFIED',
'created' : 'Sun, 11-Nov-2001 10:47:39 EST',
#
# Current Information
#
'author' : '$Author: doughellmann $',
'version' : '$Revision: 1.1 $',
'date' : '$Date: 2002/11/17 15:05:43 $',
}
try:
__version__ = __rcs_info__['version'].split(' ')[1]
except:
__version__ = '0.0'
#
# Import system modules
#
import pprint
import symbol
import token
import types
#
# Import Local modules
#
#
# Module
#
# This pattern identifies compound statements, allowing them to be readily
# differentiated from simple statements.
#
COMPOUND_STMT_PATTERN = (
symbol.stmt,
(symbol.compound_stmt, ['compound'])
)
# This pattern matches the name of an item which appears in a
# testlist sequence. This can be used for finding the
# base classes of a class, or the parameters to a function or
# method.
#
# BASE_CLASS_NAME_PATTERN = (
# symbol.test,
# (symbol.and_test,
# (symbol.not_test,
# (symbol.comparison,
# (symbol.expr,
# (symbol.xor_expr,
# (symbol.and_expr,
# (symbol.shift_expr,
# (symbol.arith_expr,
# (symbol.term,
# (symbol.factor,
# (symbol.power,
# (symbol.atom,
# (token.NAME, ['name'])
# )))))))))))))
BASE_CLASS_NAME_PATTERN = (
symbol.test,
(symbol.and_test,
(symbol.not_test,
(symbol.comparison,
(symbol.expr,
(symbol.xor_expr,
(symbol.and_expr,
(symbol.shift_expr,
(symbol.arith_expr,
(symbol.term,
(symbol.factor, ['power'])
))))))))))
# This pattern will match a 'stmt' node which *might* represent a docstring;
# docstrings require that the statement which provides the docstring be the
# first statement in the class or function, which this pattern does not check.
#
DOCSTRING_STMT_PATTERN = (
symbol.stmt,
(symbol.simple_stmt,
(symbol.small_stmt,
(symbol.expr_stmt,
(symbol.testlist,
(symbol.test,
(symbol.and_test,
(symbol.not_test,
(symbol.comparison,
(symbol.expr,
(symbol.xor_expr,
(symbol.and_expr,
(symbol.shift_expr,
(symbol.arith_expr,
(symbol.term,
(symbol.factor,
(symbol.power,
(symbol.atom,
(token.STRING, ['docstring'])
)))))))))))))))),
(token.NEWLINE, '')
))
def joinCodeSnippets(first, second, separator):
"""Join two code snippets into one string.
Use some general code content rules to try to make the
resulting snippet look nice.
"""
if second.strip() in ('.',):
sep_to_be_used = ''
elif second and ( second[0] in ('.', ',', '(',) ):
if second[0] == '(' and first and first[-1] == ',':
sep_to_be_used = separator
else:
sep_to_be_used = ''
elif (not first) or (first and first[-1] in ('.',)) or (first in ('-',)):
sep_to_be_used = ''
elif ( (first and ( first[-1] in ('(', '[', '{') )) and
(second and ( second[-1] in (')', ']', '}') ))
):
sep_to_be_used = ''
elif first and ( first[-1] in ('(', '[', '{') ):
sep_to_be_used = separator
else:
sep_to_be_used = separator
text = '%s%s%s' % (first, sep_to_be_used, second)
return text
def parseTreeToString(tree, separator=' '):
"""Convert a parse tree to a string which would have parsed in that way.
Given a parse tree, walk it to determine the original string
which would have been parsed to produce that tree.
"""
#pprint 'STRINGING: ',
#pprint.pprint(tree)
text = ''
if tree and type(tree) in (types.TupleType, types.ListType):
if type(tree[0]) in (types.TupleType, types.ListType):
tree_parts = tree
else:
tree_parts = tree[1:]
sub_parts = map( lambda x, s=separator: parseTreeToString(x, s),
tree_parts)
for one_part in sub_parts:
text = joinCodeSnippets(text, one_part, separator)
else:
text = str(tree)
return text
def findNode(tree, node, response=None):
"Return a sequence of subtrees starting with node value of 'node'."
if response == None:
response = []
if type(tree) not in (types.ListType, types.TupleType):
return response
if tree[0] == node:
response.append(tree)
else:
for subtree in tree[1:]:
findNode(subtree, node, response)
return response
def drill(tree, depth):
"Return the section of the parse 'tree' that is 'depth' nodes deep."
for i in range(depth):
try:
tree = tree[1]
except IndexError:
return ()
return tree
def match(pattern, data, vars=None, dbg=0):
"""Match `data' to `pattern', with variable extraction.
pattern --
Pattern to match against, possibly containing variables.
data --
Data to be checked and against which variables are extracted.
vars --
Dictionary of variables which have already been found. If not
provided, an empty dictionary is created.
The `pattern' value may contain variables of the form ['varname'] which
are allowed to match anything. The value that is matched is returned as
part of a dictionary which maps 'varname' to the matched value. 'varname'
is not required to be a string object, but using strings makes patterns
and the code which uses them more readable.
This function returns two values: a boolean indicating whether a match
was found and a dictionary mapping variable names to their associated
values.
"""
pattern_type = type(pattern)
#if dbg:
# print 'PATTERN: ',
# pprint.pprint(pattern)
# print 'DATA:',
# pprint.pprint(data)
if vars is None:
vars = {}
if pattern_type is types.ListType: # 'variables' are ['varname']
#if dbg:
# print 'storing "%s" for variable "%s"' % (data, pattern[0])
vars[pattern[0]] = data
return 1, vars
if pattern_type is not types.TupleType:
#if dbg:
# print 'end recursion'
# print 'pattern=', pattern
# print 'data', data
#
# Ignore comments, since the pattern will include an empty
# string.
#
if (pattern_type == types.StringType) and (data and data[0] == '#'):
return 1, vars
return (pattern == data), vars
if len(data) != len(pattern):
#if dbg:
# print 'shortcut, length does not match'
return 0, vars
for pattern, data in map(None, pattern, data):
#if dbg:
# print 'recursing'
same, vars = match(pattern, data, vars, dbg=dbg)
if not same:
break
return same, vars
def lenientMatch(pattern, data, vars=None, dbg=0):
"""Match `data' to `pattern', with variable extraction.
pattern --
Pattern to match against, possibly containing variables.
data --
Data to be checked and against which variables are extracted.
vars --
Dictionary of variables which have already been found. If not
provided, an empty dictionary is created.
The `pattern' value may contain variables of the form ['varname'] which
are allowed to match anything. The value that is matched is returned as
part of a dictionary which maps 'varname' to the matched value. 'varname'
is not required to be a string object, but using strings makes patterns
and the code which uses them more readable.
This function is based on the match() function, but is more lenient.
The pattern does not have to completely describe the tree. Instead,
it can be the 'top' portion of the tree. Everything must match down
to the leaves of the pattern. At that point, the matching stops. If
a match was found at all, the return values indicate a match.
This function returns two values: a boolean indicating whether a match
was found and a dictionary mapping variable names to their associated
values.
"""
#if dbg:
# print 'PATTERN : ',
# pprint.pprint(pattern)
# print 'DATA :',
# #pprint.pprint(data)
# print data
if vars is None:
vars = {}
if type(pattern) is types.ListType: # 'variables' are ['varname']
#if dbg:
# print 'storing "%s" for variable "%s"' % (data, pattern[0])
vars[pattern[0]] = data
return 1, vars
if type(pattern) is not types.TupleType:
#if dbg:
# print 'end recursion'
return (pattern == data), vars
found_match = 0
if pattern and data:
for pattern, data in map(None, pattern, data):
#if dbg:
# print 'recursing'
same, vars = lenientMatch(pattern, data, vars, dbg=dbg)
if not same:
break
else:
found_match = same
return found_match, vars
--- NEW FILE: suite.py ---
#!/usr/bin/env python
#
# $Id: suite.py,v 1.1 2002/11/17 15:05:43 doughellmann Exp $
#
# Copyright 2001 Doug Hellmann.
#
#
# All Rights Reserved
#
# Permission to use, copy, modify, and distribute this software and
# its documentation for any purpose and without fee is hereby
# granted, provided that the above copyright notice appear in all
# copies and that both that copyright notice and this permission
# notice appear in supporting documentation, and that the name of Doug
# Hellmann not be used in advertising or publicity pertaining to
# distribution of the software without specific, written prior
# permission.
#
# DOUG HELLMANN DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
# NO EVENT SHALL DOUG HELLMANN BE LIABLE FOR ANY SPECIAL, INDIRECT OR
# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#
"""Base class for information gathering classes.
"""
__rcs_info__ = {
#
# Creation Information
#
'module_name' : '$RCSfile: suite.py,v $',
'rcs_id' : '$Id: suite.py,v 1.1 2002/11/17 15:05:43 doughellmann Exp $',
'creator' : 'Doug Hellmann <do...@he...>',
'project' : 'UNSPECIFIED',
'created' : 'Sun, 11-Nov-2001 10:45:58 EST',
#
# Current Information
#
'author' : '$Author: doughellmann $',
'version' : '$Revision: 1.1 $',
'date' : '$Date: 2002/11/17 15:05:43 $',
}
try:
__version__ = __rcs_info__['version'].split(' ')[1]
except:
__version__ = '0.0'
#
# Import system modules
#
import re
#
# Import Local modules
#
import happydoclib
from happydoclib.parseinfo.utils import *
#
# Module
#
class SuiteInfoBase:
"""Base class for information gathering classes.
Default implementation assumes that the user is interested
in learning about functions and classes defined within the
parse tree used for initialization. This makes implementation
of MethodInfo easy. Other derived classes add behavior to
find other information.
"""
#_docstring = ''
_docstring_summary = None
def __init__(self, name, parent, filename, tree,
commentInfo={},
defaultConfigValues={},
):
"""Initialize the info extractor.
Parameters:
name -- name of this object
parent -- parent object (e.g. Module for Class)
filename -- file which contains the tree
tree -- parse tree from which to extract information
commentInfo -- comments extracted from source file where
this object was found
"""
self._name = name
self._parent = parent
self._filename = filename
self._class_info = {}
self._function_info = {}
self._namespaces = ( self._class_info, self._function_info )
self._comment_info = commentInfo
self._configuration_values = {}
self._configuration_values.update(defaultConfigValues)
self._extractConfigurationValues()
comment_key = self.getCommentKey()
#print 'PARSEINFO: Looking for comments for %s in %s' % (name, comment_key)
self._comments = commentInfo.get(comment_key, '')
if tree:
self._extractInfo(tree)
return
def getName(self):
"Return this info object's name."
return self._name
def getSymbolInfo(self, name, tryParent=1):
"""Look up the info record for the name.
Looks in the namespaces registered for this DOM node. If no
value is found, 'None' is returned.
"""
for ns in self._namespaces:
info = ns.get(name, None)
if info:
return info
if tryParent and self.getParent():
return self.getParent().getSymbolInfo(name, tryParent)
return None
def __getitem__(self, itemName):
info = self.getSymbolInfo(itemName)
if not info:
raise KeyError('Unrecognized name: "%s"' % itemName, itemName)
return info
def getFilename(self):
return self._filename
def getCommentKey(self):
if self._parent:
return self._parent.getCommentKey() + (self._name,)
else:
return (self._name,)
##
## Internal Data Extraction
##
def getConfigurationValues(self):
"Return any HappyDoc configuration values related to this object."
values = None
if self._parent:
try:
values = self._parent.getConfigurationValues()
except:
values = None
if values is None:
values = self._configuration_values
return values
def _extractInfo(self, tree):
"Pull information out of the parse tree."
from happydoclib.parseinfo.classinfo import ClassInfo
from happydoclib.parseinfo.functioninfo import FunctionInfo
# extract docstring
if len(tree) == 2:
found, vars = match(DOCSTRING_STMT_PATTERN[1], tree[1])
else:
found, vars = match(DOCSTRING_STMT_PATTERN, tree[3])
if found:
self._docstring = eval(vars['docstring'])
else:
self._docstring = ''
# discover inner definitions
for node in tree[1:]:
found, vars = match(COMPOUND_STMT_PATTERN, node)
if found:
cstmt = vars['compound']
if cstmt[0] == symbol.funcdef:
name = cstmt[2][1]
self._function_info[name] = FunctionInfo(
tree=cstmt,
parent=self,
commentInfo=self._comment_info,
)
#pprint.pprint(cstmt)
elif cstmt[0] == symbol.classdef:
name = cstmt[2][1]
self._class_info[name] = ClassInfo(
tree=cstmt,
parent=self,
commentInfo=self._comment_info,
)
return
def _extractConfigurationValues(self):
"Default implementation does nothing."
return
_summary_pattern = re.compile(r'^\s*([^\n]+)\n')
def _extractSummary(self, text):
"Extract a summary text from a larger body."
text = text.strip()
#
# Remove surrounding quotes, if present.
#
while text and (text[0] in ('"', "'")):
text = text[1:]
while text and (text[-1] in ('"', "'")):
text = text[:-1]
#
# Pull out the first line, and return it if
# we can find it. Otherwise, return the whole
# string since that means that the whole thing
# is just one line.
#
matchObj = self._summary_pattern.search(text)
if matchObj:
return matchObj.group(0).strip()
else:
return text
##
## DocStrings
##
def getDocString(self):
"Return any __doc__ string value found for the object."
dstring = '%s\n\n%s' % (self._docstring, self._comments)
#print 'DOC STRING for %s is ' % self._name, dstring
return dstring
def getDocStringFormat(self):
"Returns the docstring converter format name for the docstring for this object."
config_values = self.getConfigurationValues()
return config_values['docStringFormat']
def getSummaryAndFormat(self):
"Return a summary of the __doc__ string for this object and the docstring converter name for the format of the text."
if self._docstring_summary is None:
self._docstring_summary = \
self._extractSummary(self.getDocString())
return self._docstring_summary, self.getDocStringFormat()
--- NEW FILE: parsecomments.py ---
#!/usr/bin/env python
#
# $Id: parsecomments.py,v 1.1 2002/11/17 15:05:43 doughellmann Exp $
#
# Copyright 2001 Doug Hellmann.
#
#
# All Rights Reserved
#
# Permission to use, copy, modify, and distribute this software and
# its documentation for any purpose and without fee is hereby
# granted, provided that the above copyright notice appear in all
# copies and that both that copyright notice and this permission
# notice appear in supporting documentation, and that the name of Doug
# Hellmann not be used in advertising or publicity pertaining to
# distribution of the software without specific, written prior
# permission.
#
# DOUG HELLMANN DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
# NO EVENT SHALL DOUG HELLMANN BE LIABLE FOR ANY SPECIAL, INDIRECT OR
# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#
"""Parse comment information from a module.
"""
__rcs_info__ = {
#
# Creation Information
#
'module_name' : '$RCSfile: parsecomments.py,v $',
'rcs_id' : '$Id: parsecomments.py,v 1.1 2002/11/17 15:05:43 doughellmann Exp $',
'creator' : 'Doug Hellmann <do...@he...>',
'project' : 'UNSPECIFIED',
'created' : 'Sat, 27-Oct-2001 17:49:02 EDT',
#
# Current Information
#
'author' : '$Author: doughellmann $',
'version' : '$Revision: 1.1 $',
'date' : '$Date: 2002/11/17 15:05:43 $',
}
try:
__version__ = __rcs_info__['version'].split(' ')[1]
except:
__version__ = '0.0'
#
# Import system modules
#
import re
try:
from cStringIO import StringIO
except:
from StringIO import StringIO
import unittest
#
# Import Local modules
#
#
# Module
#
class ParseStack:
"Helper class for 'extractComments' function in this module."
def __init__(self):
"Create a ParseStack."
self.data = []
return
def push(self, name, indent):
"""Push a new 'name' onto the stack.
Smartly determine, based on 'indent', whether to 'pop' other
'names' before pushing this one.
"""
while self.data and (indent <= self.data[-1][1]):
self.pop()
self.data.append( (name, indent) )
return
def pop(self):
"Remove the top of the stack and return it."
item, indent = self.data[-1]
self.data = self.data[:-1]
return item
def __names(self):
"Return a list of the names as they appear on the stack, bottom first."
return map(lambda x: x[0], self.data)
def __str__(self):
"Create a string representation."
return string.join(self.__names(), ':')
def key(self):
"Return a value to be used as a dictionary key."
return tuple(self.__names())
def extractComments(
text,
extractRe=re.compile('^((?P<blankline>\s*$)|(?P<namedobj>(?P<indent>\s*)(?P<nametype>(class|def))\s+(?P<name>[0-9A-Za-z_]+))|(?P<commentline>\s*#+(?P<comment>.*)))').search,
ignoreRe=re.compile('\s*[+-=#][ +-=#]*\s*$').match,
):
"""Given a block of Python source, extract the comments.
The comment text is associated with nearby named objects
(functions, methods, classes, etc.). This function returns
a dictionary of names and the associated comment text.
Arguments
text -- The Python source to be scanned.
"""
dbg=None
comment_info = {}
comment_text = ''
parse_stack = ParseStack()
current_name = None
f = StringIO(text)
line = f.readline()
while line:
#if dbg and (dbg >= 2): print '=>%s' % string.rstrip(line)
match_obj = extractRe(line)
if match_obj:
#
# Documentation before named object
#
match_dict = match_obj.groupdict()
comment = match_dict['comment']
name = match_dict['name']
nametype = match_dict['nametype']
blankline = match_dict['blankline']
indent = ((match_dict['indent'] and len(match_dict['indent'])) or 0)
if match_dict['commentline'] and not comment:
comment = ' '
if comment:
# Append new text to running buffer.
if ignoreRe and not ignoreRe(comment):
#if dbg: print 'PARSEINFO: Adding comment text.'
comment_text = '%s%s\n' % (comment_text, comment,)
elif name and comment_text:
if current_name:
# Hit a new name, store the comment_text buffer
# for the current_name
#if dbg:
# print 'PARSEINFO: 1 Storing comment for %s' % parse_stack
# print 'PARSEINFO: ', comment_text
comment_info[parse_stack.key()] = comment_text
# Update the parse stack
parse_stack.push(name, indent)
#if dbg:
# print 'PARSEINFO: switching to %s' % parse_stack
comment_text = ''
else:
# Hit a new name with existing comment_text,
# store the comment along with that name.
parse_stack.push(name, indent)
#if dbg:
# print 'PARSEINFO: 2 Storing comment for %s' % parse_stack
# print 'PARSEINFO: ', comment_text
comment_info[parse_stack.key()] = comment_text
comment_text = ''
current_name = None
elif name:
# Recognized new name definition.
#if dbg:
# print 'PARSEINFO: New name %d:%s:%s' % (indent,
# nametype,
# name,
# )
current_name = name
parse_stack.push(name, indent)
elif blankline:
# Reset when a blank line separates comment from
# named stuff.
#if dbg:
# print 'PARSEINFO: blank line'
if comment_text and current_name:
if not comment_info.get(parse_stack, None):
#if dbg:
# print 'PARSEINFO: Storing comment after name %s:%s' \
# % parse_stack
comment_info[parse_stack.key()] = comment_text
#else:
# if dbg:
# if comment_text:
# print 'PARSEINFO: Discarding comment "%s"' % comment_text
current_name = None
comment_text = ''
elif current_name and comment_text:
# Store all comment text for the current_name.
#if dbg:
# print 'PARSEINFO: 3 Storing comment for %s' % current_name
# print 'PARSEINFO: ', comment_text
comment_info[parse_stack.key()] = comment_text
comment_text = ''
current_name = None
else:
#if dbg:
# print 'PARSEINFO: Not matched (%s)' % string.strip(line)
current_name = None
comment_text = ''
line = f.readline()
f.close()
if current_name and comment_text:
# Final storage to make sure we have everything.
#if dbg:
# print 'PARSEINFO: Final storage of comment for %s' % current_name
comment_info[parse_stack.key()] = comment_text
#if dbg:
# pprint.pprint(comment_info)
return comment_info
class ParseCommentsTest(unittest.TestCase):
def testComments(self):
body = open('TestCases/parseinfo/test_ignore_comments.py', 'rt').read()
actual = extractComments(body)
expected = {
('WithComments',): ' \n This class is documented only with comments.\n \n Any documentation which appears for this class with the\n comment flag set to ignore comments indicates a bug.\n \n',
('WithComments', '__init__'): ' \n WithComments init method.\n \n You should not see this!\n \n',
}
assert actual == expected, \
'Did not get expected comment values. Got %s' % str(actual)
def testDecoratedComments(self):
body = open('TestCases/parseinfo/test_decorated_comments.py', 'rt').read()
actual = extractComments(body)
expected = {
('Dashes',): ' \n Func with dash lines\n \n',
('Equals',): ' \n Func with equal lines\n \n',
('Hashes',): ' \n \n Func with hash lines\n \n \n',
('StructuredTextTable',): " \n This function has, in the comments about it, a table. That table\n should be rendered via STNG to an HTML table in the test output.\n \n |-------------------------------------------------|\n | Function | Documentation |\n |=================================================|\n | '__str__' | This method converts the |\n | | the object to a string. |\n | | |\n | | - Blah |\n | | |\n | | - Blaf |\n | | |\n | | |--------------------------| |\n | | | Name | Favorite | |\n | | | | Color | |\n | | |==========================| |\n | | | Jim | Red | |\n | | |--------------------------| |\n | | | John | Blue | |\n | | |--------------------------| |\n |-------------------------------------------------|\n \n", ('Mixed',): ' \n Func with mixed dashes and equals\n \n',
}
assert actual == expected, \
'Did not get expected comment values. Got %s' % str(actual)
--- NEW FILE: moduleinfo.py ---
#!/usr/bin/env python
#
# $Id: moduleinfo.py,v 1.1 2002/11/17 15:05:43 doughellmann Exp $
#
# Copyright 2001 Doug Hellmann.
#
#
# All Rights Reserved
#
# Permission to use, copy, modify, and distribute this software and
# its documentation for any purpose and without fee is hereby
# granted, provided that the above copyright notice appear in all
# copies and that both that copyright notice and this permission
# notice appear in supporting documentation, and that the name of Doug
# Hellmann not be used in advertising or publicity pertaining to
# distribution of the software without specific, written prior
# permission.
#
# DOUG HELLMANN DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
# NO EVENT SHALL DOUG HELLMANN BE LIABLE FOR ANY SPECIAL, INDIRECT OR
# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#
"""Information gatherer for source code modules.
"""
__rcs_info__ = {
#
# Creation Information
#
'module_name' : '$RCSfile: moduleinfo.py,v $',
'rcs_id' : '$Id: moduleinfo.py,v 1.1 2002/11/17 15:05:43 doughellmann Exp $',
'creator' : 'Doug Hellmann <do...@he...>',
'project' : 'UNSPECIFIED',
'created' : 'Sun, 11-Nov-2001 10:52:52 EST',
#
# Current Information
#
'author' : '$Author: doughellmann $',
'version' : '$Revision: 1.1 $',
'date' : '$Date: 2002/11/17 15:05:43 $',
}
try:
__version__ = __rcs_info__['version'].split(' ')[1]
except:
__version__ = '0.0'
#
# Import system modules
#
import pprint
import re
import string
import sys
import traceback
import urllib
#
# Import Local modules
#
import happydoclib
from happydoclib.parseinfo.classinfo import ClassInfo
from happydoclib.parseinfo.functioninfo import SuiteFuncInfo, FunctionInfo
from happydoclib.parseinfo.suite import SuiteInfoBase
from happydoclib.parseinfo.imports import ImportInfo
from happydoclib.parseinfo.utils import *
#
# Module
#
class ModuleInfo(SuiteInfoBase, SuiteFuncInfo):
"""Information gatherer for source code modules.
Extract information about a source module from
its parse tree.
"""
def __init__(self,
parent,
tree,
name = "<string>",
fileName = None,
commentInfo = {},
defaultConfigValues={},
):
"""Initialize the info extractor.
Parameters:
tree -- parse tree from which to extract information
name -- name of the module
fileName -- name of the file containing the module
commentInfo -- comments extracted from the file
"""
happydoclib.TRACE.into('ModuleInfo', '__init__',
parent=parent,
tree=tree,
name=name,
fileName=fileName,
commentInfo=commentInfo,
defaultConfigValues=defaultConfigValues,
)
self._filename = fileName
SuiteInfoBase.__init__(self, name, parent, fileName, tree,
commentInfo=commentInfo,
defaultConfigValues=defaultConfigValues)
if tree:
#
# Look for doc string
#
found, vars = match(DOCSTRING_STMT_PATTERN, tree[1])
if found:
self._docstring = vars["docstring"]
#
# Look for imported modules
#
self._import_info = self._extractImportedModules(tree)
happydoclib.TRACE.outof()
return
##
## Internal data extraction
##
def _extractConfigurationValues(
self,
matchConfigValue=re.compile('^#\s*HappyDoc:(.+)$', re.IGNORECASE).match,
):
"""Look into the module source file and extract HappyDoc configuration values.
Variables can be embedded in the first comment block of the module.
"""
body = open(self._filename, 'rt').read()
lines = body.split('\n')
config_statement = ''
for l in lines:
l = l.strip()
if not l:
break
if l[0] != '#':
break
match = matchConfigValue(l)
if match:
config_statement = '%s\n%s' % (config_statement, match.group(1))
#
# Exec puts in values from the builtin modules to set up the
# namespace. That means we don't want to use our
# configuration value table for global and local names, so we
# create (and use) a dummy table here. We can pre-populate
# it with a few modules we think the user should be allowed
# to call.
#
global_namespace = {
'string':string,
'urlquote':urllib.quote,
'urlquote_plus':urllib.quote_plus,
'urlencode':urllib.urlencode,
}
local_namespace = {
}
try:
exec config_statement in global_namespace, local_namespace
except:
sys.stderr.write('\n--- Parser Config Value Extraction Error ---\n')
traceback.print_exc()
sys.stderr.write('--------------------------------------------\n\n')
else:
self._configuration_values.update(local_namespace)
return
def _extractImportedModules(self, tree):
"""Returns modules imported by code in tree.
Scan the parse tree for import statements
and return the names of all modules imported.
"""
dbg=0
IMPORT_STMT_WITH_LIST_PATTERN =(
symbol.stmt,
(symbol.simple_stmt,
(symbol.small_stmt,
['import_stmt']
),
(token.NEWLINE, '')
)
)
imported_modules = ImportInfo()
for subtree in tree[1:]:
#if dbg: print '\nNEW IMPORT SUBTREE'
found, vars = match(IMPORT_STMT_WITH_LIST_PATTERN, subtree, dbg=dbg)
#if dbg:
# print 'found:', found
# if found:
# print 'vars: ',
# pprint.pprint(vars)
if found:
# vars['import_stmt'] should be an import statement
# in one of several different forms
import_stmt = vars['import_stmt']
if import_stmt[0] == symbol.import_stmt:
first = import_stmt[1]
if (first[0] == token.NAME) and (first[1] == 'import'):
for import_module in import_stmt[2:]:
try:
if import_module[0] == symbol.dotted_as_name:
#if dbg: print 'Collapsing dotted_as_name'
import_module = import_module[1]
except AttributeError:
#
# Must be using python < 2.0
#
pass
if import_module[0] == symbol.dotted_name:
# Get the tuples with the name
module_name_parts = import_module[1:]
# Get the strings in the 2nd part of each tuple
module_name_parts = map(lambda x: x[1],
module_name_parts)
# Combine the strings into the name
module_name = ''.join(module_name_parts)
#if dbg: print 'ADDING module_name=%s' % module_name
imported_modules.addImport(module_name)
elif (first[0] == token.NAME) and (first[1] == 'from'):
#if dbg: print 'FROM ', import_stmt
x=import_stmt[2]
try:
if x[0] == symbol.dotted_name:
x = x[1:]
except AttributeError:
#
# Must be using python < 2.0
#
pass
#if dbg: print 'from x import y'
module_name = parseTreeToString(x)
try:
symbol_list = imported_modules.importedSymbols(module_name)
except ValueError:
symbol_list = []
names = import_stmt[4:]
#if dbg: print 'NAMES: ', names
for n in names:
if n[0] == token.NAME:
#symbol_list.append(n[1])
imported_modules.addImport(module_name, n[1])
elif n[0] == token.STAR:
#symbol_list.append('*')
imported_modules.addImport(module_name, '*')
elif n[0] == getattr(symbol, 'import_as_name', 9999):
# python 2.x "from X import Y as Z" feature
import_name = parseTreeToString(n[1])
imported_modules.addImport(module_name, import_name)
#if dbg:
# for part in import_stmt[1:]:
# pprint.pprint(part)
#if dbg: print 'ITERATION IMPORTS: ', imported_modules
#if dbg: print 'FINAL IMPORTS: ', imported_modules, '\n'
return imported_modules
##
##
##
def getClassNames(self):
"Return the names of classes defined within the module."
return self._class_info.keys()
def getClassInfo(self, name):
"Return a ClassInfo object for the class by name."
return self._class_info[name]
def getCommentKey(self):
return ()
def getImportData(self):
"Returns a list of which symbols are imported."
return self._import_info.items()
# def getReference(self, formatter, sourceNode):
# "Return a reference to this module from sourceNode."
# ref = formatter.getReference(self, sourceNode.name)
# return ref
def getReferenceTargetName(self):
"Return the name to use as a target for a reference such as a hyperlink."
#print 'PARSINFO: ModuleInfo::getReferenceTargetName(%s)' % self.getName()
target_name = self.getName()
if target_name == '__init__':
#print 'PARSINFO: \tasking for parent'
parent = self.getParent()
if parent:
#print 'PARSINFO: \tusing parent'
target_name = parent.getReferenceTargetName()
else:
#print 'PARSINFO: \tusing name'
target_name = self.getName()
#print 'PARSINFO: \ttarget:%s' % target_name
return target_name
--- NEW FILE: imports.py ---
#!/usr/bin/env python
#
# $Id: imports.py,v 1.1 2002/11/17 15:05:43 doughellmann Exp $
#
# Copyright 2001 Doug Hellmann.
#
#
# All Rights Reserved
#
# Permission to use, copy, modify, and distribute this software and
# its documentation for any purpose and without fee is hereby
# granted, provided that the above copyright notice appear in all
# copies and that both that copyright notice and this permission
# notice appear in supporting documentation, and that the name of Doug
# Hellmann not be used in advertising or publicity pertaining to
# distribution of the software without specific, written prior
# permission.
#
# DOUG HELLMANN DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
# NO EVENT SHALL DOUG HELLMANN BE LIABLE FOR ANY SPECIAL, INDIRECT OR
# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#
"""Collects info about imports for a module.
"""
__rcs_info__ = {
#
# Creation Information
#
'module_name' : '$RCSfile: imports.py,v $',
'rcs_id' : '$Id: imports.py,v 1.1 2002/11/17 15:05:43 doughellmann Exp $',
'creator' : 'Doug Hellmann <do...@he...>',
'project' : 'UNSPECIFIED',
'created' : 'Sun, 11-Nov-2001 10:51:52 EST',
#
# Current Information
#
'author' : '$Author: doughellmann $',
'version' : '$Revision: 1.1 $',
'date' : '$Date: 2002/11/17 15:05:43 $',
}
try:
__version__ = __rcs_info__['version'].split(' ')[1]
except:
__version__ = '0.0'
#
# Import system modules
#
#
# Import Local modules
#
#
# Module
#
class ImportInfo:
"""Collects info about imports for a module.
"""
def __init__(self):
self._straight_imports = []
self._named_imports = {}
return
def addImport(self, moduleName, symbolName=None, asName=None):
"""Add information about an import statement to the saved info.
Parameters
moduleName -- The name of the module involved in the import.
For example, in 'from X import Y', X is the moduleName and
in 'import A.B', A.B is the moduleName.
symbolName -- The name of the symbol being imported. For
example, in 'from X import Y', Y is the symbolName.
asName -- The name within the module by which symbolName can
be referenced. Usually, this is the same as symbolName, but
by using the 'import X as Y' syntax, the name can be changed.
"""
dbg=0
if symbolName:
#if dbg: print '\nIMPORT SYMBOL %s from MODULE %s' % (symbolName, moduleName)
name_list = self._named_imports.get(moduleName, [])
if symbolName not in name_list:
#if dbg: print '\t*** added'
name_list.append(symbolName)
self._named_imports[moduleName] = name_list
else:
#if dbg: print '\nIMPORT MODULE: %s' % moduleName
if moduleName not in self._straight_imports:
#if dbg: print '\t*** added'
self._straight_imports.append(moduleName)
#if dbg:
# print 'STRAIGHT: ',
# pprint.pprint(self._straight_imports)
# print 'NAMED: ',
# pprint.pprint(self._named_imports)
# print 'CURRENT IMPORTS: ', self.items()
return
def importedSymbols(self, moduleName):
if self._named_imports.has_key(moduleName):
return self._named_imports[moduleName]
else:
raise ValueError('No symbols imported for module', moduleName)
return
def __str__(self):
return '(%s)' % string.join( map(str, self.items()),
'\n'
)
def items(self):
"""Returns a sequence of tuples containing module names and the
symbols imported from them.
"""
all_names = self._straight_imports[:]
for name in self._named_imports.keys():
if name not in all_names:
all_names.append(name)
all_names.sort()
all_items = []
for name in all_names:
if name in self._straight_imports:
all_items.append( (name, None) )
if self._named_imports.has_key(name):
all_items.append( (name, self._named_imports[name]) )
return all_items
--- NEW FILE: functioninfo.py ---
#!/usr/bin/env python
#
# $Id: functioninfo.py,v 1.1 2002/11/17 15:05:43 doughellmann Exp $
#
# Copyright 2001 Doug Hellmann.
#
#
# All Rights Reserved
#
# Permission to use, copy, modify, and distribute this software and
# its documentation for any purpose and without fee is hereby
# granted, provided that the above copyright notice appear in all
# copies and that both that copyright notice and this permission
# notice appear in supporting documentation, and that the name of Doug
# Hellmann not be used in advertising or publicity pertaining to
# distribution of the software without specific, written prior
# permission.
#
# DOUG HELLMANN DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
# NO EVENT SHALL DOUG HELLMANN BE LIABLE FOR ANY SPECIAL, INDIRECT OR
# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#
"""Gather information about a function or method definition.
"""
__rcs_info__ = {
#
# Creation Information
#
'module_name' : '$RCSfile: functioninfo.py,v $',
'rcs_id' : '$Id: functioninfo.py,v 1.1 2002/11/17 15:05:43 doughellmann Exp $',
'creator' : 'Doug Hellmann <do...@he...>',
'project' : 'UNSPECIFIED',
'created' : 'Sun, 11-Nov-2001 10:54:53 EST',
#
# Current Information
#
'author' : '$Author: doughellmann $',
'version' : '$Revision: 1.1 $',
'date' : '$Date: 2002/11/17 15:05:43 $',
}
try:
__version__ = __rcs_info__['version'].split(' ')[1]
except:
__version__ = '0.0'
#
# Import system modules
#
import symbol
import token
#
# Import Local modules
#
import happydoclib
from happydoclib.parseinfo.suite import SuiteInfoBase
from happydoclib.parseinfo.utils import *
#
# Module
#
class SuiteFuncInfo:
# Mixin class providing access to function names and info.
def getFunctionNames(self):
return self._function_info.keys()
def getFunctionInfo(self, name):
return self._function_info[name]
class FunctionInfo(SuiteInfoBase, SuiteFuncInfo):
"Gather information about a function or method definition."
def __init__(self, parent=None, tree = None, commentInfo={}):
"""Initialize the info extractor.
Parameters:
parent -- parent object for this object (e.g. Module or Function)
tree -- parse tree from which to extract information
commentInfo -- comments extracted from the source file holding
this module
"""
happydoclib.TRACE.into('FunctionInfo', '__init__',
parent=parent,
tree=tree,
commentInfo=commentInfo,
)
SuiteInfoBase.__init__(self, tree[2][1], parent, parent.getFilename(),
(tree and tree[-1] or None),
commentInfo=commentInfo)
parameter_data = self._extractFunctionParameters(tree)
self._constructParameterInfo(parameter_data)
self._exception_info = self._extractThrownExceptions(tree)
#if self._exception_info:
# print 'EXCEPTIONS: ',
# pprint.pprint(self._exception_info)
happydoclib.TRACE.outof()
return
def getReference(self, formatter, sourceNode):
"Return a reference to this function from sourceNode."
ref = formatter.getNamedReference( self, self.getName(), sourceNode.name )
return ref
def getFullyQualifiedName(self):
"Return a complete, unique, name representing this object."
return self.getParent().getFullyQualifiedName()
##
## EXCEPTIONS
##
EXCEPTION_BY_NAME_PATTERN = (
(symbol.factor, ['exception'])
)
EXCEPTION_STRING_PATTERN = (
(symbol.factor,
(symbol.power,
(symbol.atom,
(token.STRING, ['exception'])
)))
)
def getExceptionNames(self):
"Return a list of the names of any exceptions raised by the function."
return self._exception_info.keys()
def getExceptionInfo(self, exceptionName):
"""Returns a type value for an exception.
The return value will be one of (token.NAME, token.STRING)
indicating whether the exception was thrown as a string
or a named object.
"""
return self._exception_info[exceptionName]
def _extractThrownExceptions(self, tree):
"Return a dictionary of exception->exception_type values."
#dbg = 0
thrown_exceptions = {}
if not tree:
return thrown_exceptions
if type(tree) in (types.ListType, types.TupleType):
raise_tree_list = findNode(tree, symbol.raise_stmt)
for tree in raise_tree_list:
try:
subtree = drill(tree[2], 10)
except IndexError:
subtree = tree
#if dbg: print 'subtree: ', parseTreeToString(subtree)
#if dbg: print 'found raise: ', parseTreeToString(tree)
#if dbg: print 'parsing...'
#
# Initialize
#
exception_name = None
exception_type = None
if not exception_name:
found, vars = lenientMatch(
self.EXCEPTION_STRING_PATTERN,
tree,
#dbg=1
)
if found and vars.has_key('exception'):
#if dbg: print 'FOUND STRING EXCEPTION: ', vars
exception_name = vars['exception']
exception_type = token.STRING
#if dbg: print 'GOT EXCEPTION: ', exception_name
if not exception_name:
found, vars = lenientMatch(
self.EXCEPTION_BY_NAME_PATTERN,
tree,
#dbg=1
)
if found and vars.has_key('exception'):
#if dbg: print 'FOUND NAMED EXCEPTION: ', vars
# Threw a named thing, record the name.
exception_name = parseTreeToString(vars['exception'])
exception_type = token.NAME
#if dbg: print 'GOT EXCEPTION: ', exception_name
if not exception_name:
#if dbg: print 'NO NAME,',
if len(tree) >= 3:
slice=tree[2:]
if slice:
#if dbg: print 'using slice of 2:1=', slice
exception_name = parseTreeToString(slice)
else:
#if dbg: print 'using whole tree=', tree
execption_name = parseTreeToString(tree)
if exception_name:
#if dbg: print 'STORING REFERENCE'
thrown_exceptions[exception_name] = exception_type
#if dbg and thrown_exceptions: print 'EXCEPTIONS: ', thrown_exceptions.keys()
return thrown_exceptions
##
## PARAMETERS
##
# This pattern matches the name of an item which appears in a
# testlist sequence. This can be used for finding the
# base classes of a class, or the parameters to a function or
# method.
#
PARAMETER_DEFAULT_PATTERN = (
symbol.test,
(symbol.and_test,
(symbol.not_test,
(symbol.comparison,
(symbol.expr,
(symbol.xor_expr,
(symbol.and_expr,
(symbol.shift_expr,
(symbol.arith_expr, ['term'], ['trailer'], ['trailer_bits'])
))))))))
oldPARAMETER_DEFAULT_PATTERN = (
symbol.test,
(symbol.and_test,
(symbol.not_test,
(symbol.comparison,
(symbol.expr,
(symbol.xor_expr,
(symbol.and_expr,
(symbol.shift_expr,
(symbol.arith_expr,
(symbol.term,
(symbol.factor,
(symbol.power, ['atom'])
)))))))))))
PARAMETER_DEFAULT_WITH_TRAILER_PATTERN = (
symbol.test,
(symbol.and_test,
(symbol.not_test,
(symbol.comparison,
(symbol.expr,
(symbol.xor_expr,
(symbol.and_expr,
(symbol.shift_expr,
(symbol.arith_expr,
(symbol.term,
(symbol.factor,
(symbol.power, ['atom'], ['trailer'], ['trailer_bits'])
))))))))))
)
PARAMETER_ARITH_DEFAULT_WITH_TRAILER_PATTERN = (
symbol.test,
(symbol.and_test,
(symbol.not_test,
(symbol.comparison,
(symbol.expr,
(symbol.xor_expr,
(symbol.and_expr,
(symbol.shift_expr,
(symbol.arith_expr,
#(symbol.term,
# (symbol.factor,
# (symbol.power, ['atom'], ['trailer'], ['trailer_bits'])
# ))
['expression'], ['trailer'], ['trailer_bits']
))))))))
)
def _constructParameterInfo(self, parameterData):
"""Construct storable parameter data from a parameter list.
Given the sequence of tuples extracted as a parameter list,
store the names (in order) in self._parameter_names and the
information about the parameter in self._parameter_info where
the keys are the parameter name and the info is a tuple containing:
(default_specified, default_value, default_value_type)
Where:
default_specified -- boolean indicating whether a default value
was specified
default_value -- the default value given, if any
default_value_type -- the type of the default value (token.STRING,
token.NAME, None). A type of None means
unknown.
"""
parameter_info = {}
parameter_names = []
for (param, default_specified,
default_value, default_value_type) in parameterData:
parameter_names.append(param)
parameter_info[param] = ( default_specified,
default_value,
default_value_type,
)
self._parameter_names = tuple(parameter_names)
self._parameter_info = parameter_info
return
def getParameterNames(self):
"""Returns a list of the names of all
parameters to the function, in order."""
return self._parameter_names
def getParameterInfo(self, paramName):
"""Returns the info record for a parameter.
The returned tuple consists of:
(default_specified, default_value, default_value_type)
Where:
default_specified -- boolean indicating whether a default value
was specified
default_value -- the default value given, if any
default_value_type -- the type of the default value (token.STRING,
token.NAME, None). A type of None means
unknown.
"""
return self._parameter_info[paramName]
def _extractFunctionParameters(self, tree):
"Extract information about a function's parameters."
dbg=0
#if dbg: print
#if dbg: print self._name
function_parameters = []
parameters = tree[3]
#if dbg: pprint.pprint(parameters)
if parameters[1][0] != token.LPAR:
raise 'Unrecognized parse result %s in %s' % (parameters[1],
parameters)
if parameters[2][0] == token.RPAR:
# No parameters: def func()
return function_parameters
if parameters[2][0] != symbol.varargslist:
raise 'Unrecognized parse result %s in %s' % (parameters[2],
parameters)
#
# Move down the parse tree and process the argument list
#
parameters = parameters[2]
#if dbg: pprint.pprint(parameters)
found_varargs = 0 # are we looking at a variable argument parameter?
found_kwargs = 0 # are we looking at keyword argument parameter?
name = None # what is the name of the parameter?
found_default_value = None # did we see a default value for the param?
default_value = None # what is the default value?
default_value_type = None # what is the type of the default value?
for parameter in parameters[1:]:
# Shortcut cases
if parameter[0] == token.COMMA:
continue
if parameter[0] == token.STAR:
# Start variable argument definition
found_varargs = 1
if parameter[0] == token.DOUBLESTAR:
# Start keyword argument definition
found_kwargs = 1
if (parameter[0] in (token.NAME, symbol.fpdef)) and name:
# We've already found a name,
# handle adding the previous
# def to a list.
function_parameters.append( (name,
...
[truncated message content] |