Show
Ignore:
Timestamp:
08/25/06 15:30:34 (5 years ago)
Author:
mk
Message:

Merging mk branch into the trunk.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/cheesecake/codeparser.py

    r11 r150  
     1import doctest 
    12import os 
     3import re 
     4 
     5import logger 
    26from model import System, Module, Class, Function, parseFile, processModuleAst 
    37 
     8 
     9# Python 2.3/2.4 compatibilty hacks. 
     10if getattr(doctest, 'DocTestParser', False): 
     11    # Python 2.4 have DocTestParser class. 
     12    get_doctests = doctest.DocTestParser().get_examples 
     13else: 
     14    # Python 2.3 have _extract_examples function. 
     15    get_doctests = doctest._extract_examples 
     16 
     17 
     18def compile_regex(pattern, user_map=None): 
     19    """Compile a regex pattern using default or user mapping. 
     20    """ 
     21 
     22    # Handy regular expressions. 
     23    mapping = {'ALPHA': r'[-.,?!\w]', 'WORD': r'[-.,?!\s\w]', 
     24                       'START': r'(^|\s)', 'END': r'([.,?!\s]|$)'} 
     25 
     26    if user_map: 
     27        mapping = mapping.copy() 
     28        mapping.update(user_map) 
     29 
     30    def sub(text, mapping): 
     31        for From, To in mapping.iteritems(): 
     32            text = text.replace(From, To) 
     33        return text 
     34 
     35    pattern = sub(pattern, mapping) 
     36 
     37    return re.compile(pattern, re.LOCALE | re.VERBOSE) 
     38 
     39def inline_markup(start, end=None, mapping=None): 
     40    if end is None: 
     41        end = start 
     42    return compile_regex(r'''(START  %(start)s  ALPHA  %(end)s  END) | 
     43           (START  %(start)s  ALPHA  WORD*  ALPHA  %(end)s  END)'''\ 
     44                         % {'start': start, 'end': end}, mapping) 
     45 
     46def line_markup(start, end=None): 
     47    return inline_markup(start, end, mapping={'ALPHA': r'[-.,?!\s\w]', 
     48                                              'START': r'(\n|^)[\ \t]*', 
     49                                              'END': r''}) 
     50 
     51supported_formats = { 
     52    # reST refrence: http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html 
     53    'reST': [ 
     54        inline_markup(r'\*'), # emphasis 
     55        inline_markup(r'\*\*'), # strong 
     56        inline_markup(r'``'), # inline 
     57        inline_markup(r'\(', r'_\)', # hyperlink 
     58                      {'ALPHA': r'\w', 'WORD': r'[-.\w]'}), 
     59        inline_markup(r'\(`', r'`_\)'), # long hyperlink 
     60        line_markup(r':'), # field 
     61        line_markup(r'[*+-]', r''), # unordered list 
     62        line_markup(r'((\d+) | ([a-zA-Z]+) [.\)])', r''), # ordered list 
     63        line_markup(r'\(  ((\d+)  |  ([a-zA-Z]+))  \)', r''), # ordered list 
     64    ], 
     65 
     66    # epytext reference: http://epydoc.sourceforge.net/epytext.html 
     67    'epytext': [ 
     68        re.compile(r'[BCEGILMSUX]\{.*\}'), # inline elements 
     69        line_markup(r'@[a-z]+([\ \t][a-zA-Z]+)?:', r''), # fields 
     70        line_markup(r'-', r''), # unordered list 
     71        line_markup(r'\d+(\.\d+)*', r''), # ordered list 
     72    ], 
     73 
     74    # javadoc reference: http://java.sun.com/j2se/1.4.2/docs/tooldocs/solaris/javadoc.html 
     75    'javadoc': [ 
     76        re.compile(r'<[a-zA-z]+[^>]*>'), # HTML elements 
     77        line_markup(r'@[a-z][a-zA-Z]*\s', r''), # normal tags 
     78        re.compile(r'{@  ((docRoot) | (inheritDoc) | (link) | (linkplain) |'\ 
     79                    ' (value))  [^}]*  }', re.VERBOSE), # special tags 
     80    ], 
     81} 
     82 
     83 
     84def use_format(text, format): 
     85    """Return True if text includes given documentation format 
     86    and False otherwise. 
     87 
     88    See supported_formats for list of known formats. 
     89    """ 
     90    for pattern in supported_formats[format]: 
     91        if re.search(pattern, text): 
     92            return True 
     93 
     94    return False 
     95 
     96 
    497class CodeParser(object): 
    5     """ 
    6     Information about the structure of a Python module 
     98    """Information about the structure of a Python module. 
    799 
    8100    * Collects modules, classes, methods, functions and associated docstrings 
     
    10102    """ 
    11103    def __init__(self, pyfile, log=None): 
     104        """Initialize Code Parser object. 
     105 
     106        :Parameters: 
     107          `pyfile` : str 
     108              Path to a Python module to parse. 
     109          `log` : logger.Producer instance 
     110              Logger to use during code parsing. 
     111        """ 
    12112        if log: 
    13113            self.log = log.codeparser 
    14114        else: 
    15             import logger 
    16115            self.log = logger.default.codeparser 
    17116        self.modules = [] 
     
    20119        self.method_func = [] 
    21120        self.functions = [] 
    22         self.docstrings = {} 
    23          
     121        self.docstrings = [] # objects that have docstrings 
     122        self.docstrings_by_format = {} 
     123        self.formatted_docstrings_count = 0 
     124        self.doctests_count = 0 
     125        self.unittests_count = 0 
     126 
     127        # Initialize lists of format docstrings. 
     128        for format in supported_formats: 
     129            self.docstrings_by_format[format] = [] 
     130 
    24131        (path, filename) = os.path.split(pyfile) 
    25132        (module, ext) = os.path.splitext(filename) 
     
    29136        try: 
    30137            processModuleAst(parseFile(pyfile), module, self.system) 
    31         except: 
     138        except Exception, e: 
     139            self.log("Code parsing error occured:\n***\n%s\n***" % str(e)) 
    32140            return 
    33141 
     
    35143            fullname = obj.fullName() 
    36144            if isinstance(obj, Module): 
    37                 self.modules.append(obj.fullName()
     145                self.modules.append(fullname
    38146            if isinstance(obj, Class): 
    39                 self.classes.append(obj.fullName()) 
     147                if 'unittest.TestCase' in obj.bases or 'TestCase' in obj.bases: 
     148                    self.unittests_count += 1 
     149                self.classes.append(fullname) 
    40150            if isinstance(obj, Function): 
    41151                self.method_func.append(fullname) 
    42             if obj.docstring: 
    43                 self.docstrings[fullname] = 1 
     152            if isinstance(obj.docstring, str) and obj.docstring.strip(): 
     153                self.docstrings.append(fullname) 
     154                # Check docstring for known documenation formats. 
     155                formatted = False 
     156                for format in supported_formats: 
     157                    if use_format(obj.docstring, format): 
     158                        self.docstrings_by_format[format].append(fullname) 
     159                        formatted = True 
     160                if formatted: 
     161                    self.formatted_docstrings_count += 1 
     162 
     163                # Check if docstring include any doctests. 
     164                if get_doctests(obj.docstring): 
     165                    self.doctests_count += 1 
    44166 
    45167        for method_or_func in self.method_func: 
     
    57179        self.log("methods: " + ",".join(self.methods)) 
    58180        self.log("functions: " + ",".join(self.functions)) 
     181        self.log("docstrings: %s" % self.docstrings_by_format) 
     182        self.log("number of doctests: %d" % self.doctests_count) 
    59183 
    60184    def object_count(self): 
    61         """ 
    62         Return number of objects found in this module 
    63  
     185        """Return number of objects found in this module. 
     186 
     187        Objects include: 
    64188        * module 
    65189        * classes 
     
    74198 
    75199    def docstring_count(self): 
    76         """ 
    77         Return number of docstrings found in this module 
    78         """ 
    79         return len(self.docstrings.keys()) 
    80  
    81     def functions_called(self): 
    82         """ 
    83         Return list of functions called by functions/methods 
    84         defined in this module 
     200        """Return number of docstrings found in this module. 
     201        """ 
     202        return len(self.docstrings) 
     203 
     204    def docstring_count_by_type(self, type): 
     205        """Return number of docstrings of given type found in this module. 
     206        """ 
     207        return len(self.docstrings_by_format[type]) 
     208 
     209    def _functions_called(self): 
     210        """Return list of functions called by functions/methods 
     211        defined in this module. 
    85212        """ 
    86213        return self.system.func_called.keys() 
     214 
     215    functions_called = property(_functions_called)