Changeset 36

Show
Ignore:
Timestamp:
06/01/06 15:16:17 (6 years ago)
Author:
mk
Message:

Check docstrings for use of epytext (closes ticket #10).

Files:

Legend:

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

    r35 r36  
    44from model import System, Module, Class, Function, parseFile, processModuleAst 
    55 
    6 def use_reST(text): 
    7     """Return True if text includes reST and False otherwise. 
    86 
    9     See http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html 
    10     for reference. 
     7def compile_regex(pattern, user_map=None): 
     8    """Compile a regex pattern using default or user mapping. 
     9    """ 
    1110 
    12     >>> use_reST("String with *emphasis*.") 
    13     True 
    14     >>> use_reST("*Multi-word emphasis.*") 
    15     True 
    16     >>> use_reST("How about testing **strong string**?") 
    17     True 
    18     >>> use_reST("Some *noisy!* punctuation") 
    19     True 
    20     >>> use_reST("**characters?**, in the way.") 
    21     True 
    22     >>> use_reST("Don\'t forget ``inline literals``.") 
    23     True 
    24     >>> use_reST("This is reST (hyperlink_).") 
    25     True 
    26     >>> use_reST("This is (`quite long hyperlink`_).") 
    27     True 
    28     >>> use_reST("* Bullet\\n* List\\n") 
    29     True 
    30     >>> use_reST("+ Another\\n+ Bullet\\n+ List\\n") 
    31     True 
    32     >>> use_reST("1. Ordered\\n2. List\\n") 
    33     True 
    34     >>> use_reST("  a) Another\\n  b) ordered\\n  c) list\\n") 
    35     True 
    36     >>> use_reST(" (a) one\\n (b) more") 
    37     True 
    38     >>> use_reST(":Field: list\\n:indeed: it is\\n") 
    39     True 
     11    # Word in reST can also contain hyphens and punctuation characters. 
     12    mapping = {'ALPHA': r'[-.,?!\w]', 'WORD': r'[-.,?!\s\w]', 
     13                       'START': r'(^|\s)', 'END': r'([.,?!\s]|$)'} 
    4014 
    41     >>> use_reST("Plain string.") 
    42     False 
    43     >>> use_reST("Do some math: 2 * 2a* 2 = 8a") 
    44     False 
    45     >>> use_reST("Not*really*strong.") 
    46     False 
    47     >>> use_reST("Interpreted `text` is widely used as quotes, so exclude it.") 
    48     False 
    49     >>> use_reST("Not a :field:.") 
    50     False 
     15    if user_map: 
     16        mapping = mapping.copy() 
     17        mapping.update(user_map) 
     18 
     19    def sub(text, mapping): 
     20        for From, To in mapping.iteritems(): 
     21            text = text.replace(From, To) 
     22        return text 
     23 
     24    pattern = sub(pattern, mapping) 
     25 
     26    return re.compile(pattern, re.LOCALE | re.VERBOSE) 
     27 
     28def inline_markup(start, end=None, mapping=None): 
     29    if end is None: 
     30        end = start 
     31    return compile_regex(r'''(START  %(start)s  ALPHA  %(end)s  END) | 
     32           (START  %(start)s  ALPHA  WORD*  ALPHA  %(end)s  END)'''\ 
     33                         % {'start': start, 'end': end}, mapping) 
     34 
     35def line_markup(start, end=None): 
     36    return inline_markup(start, end, mapping={'ALPHA': r'[-.,?!\s\w]', 
     37                                              'START': r'(\n|^)[\ \t]*', 
     38                                              'END': r''}) 
     39 
     40supported_formats = { 
     41    # reST refrence: http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html 
     42    'reST': [ 
     43        inline_markup(r'\*'), # emphasis 
     44        inline_markup(r'\*\*'), # strong 
     45        inline_markup(r'``'), # inline 
     46        inline_markup(r'\(', r'_\)', # hyperlink 
     47                      {'ALPHA': r'\w', 'WORD': r'[-.\w]'}), 
     48        inline_markup(r'\(`', r'`_\)'), # long hyperlink 
     49        line_markup(r':'), # field 
     50        line_markup(r'[*+-]', r''), # unordered list 
     51        line_markup(r'((\d+) | ([a-zA-Z]+) [.\)])', r''), # ordered list 
     52        line_markup(r'\(  ((\d+)  |  ([a-zA-Z]+))  \)', r''), # ordered list 
     53    ], 
     54 
     55    # epytext reference: http://epydoc.sourceforge.net/epytext.html 
     56    'epytext': [ 
     57        re.compile(r'[BCEGILMSUX]\{.*\}'), # inline elements 
     58        line_markup(r'@[a-z]+([\ \t][a-zA-Z]+)?:', r''), # fields 
     59        line_markup(r'-', r''), # unordered list 
     60        line_markup(r'\d+(\.\d+)*', r''), # ordered list 
     61    ], 
     62 
     63    'javadoc': [], 
     64
     65 
     66 
     67def use_format(text, format): 
     68    """Return True if text includes given documentation format 
     69    and False otherwise. 
     70 
     71    See supported_formats for list of known formats. 
    5172    """ 
    52     def compile(pattern, user_map=None): 
    53         """Compile a regex pattern using default or user mapping. 
    54         """ 
    55  
    56         # Word in reST can also contain hyphens and punctuation characters. 
    57         mapping = {'ALPHA': r'[-.,?!\w]', 'WORD': r'[-.,?!\s\w]', 
    58                            'START': r'(^|\s)', 'END': r'([.,?!\s]|$)'} 
    59  
    60         if user_map: 
    61             mapping = mapping.copy() 
    62             mapping.update(user_map) 
    63  
    64         def sub(text, mapping): 
    65             for From, To in mapping.iteritems(): 
    66                 text = text.replace(From, To) 
    67             return text 
    68  
    69         return re.compile(sub(pattern, mapping), re.LOCALE | re.VERBOSE) 
    70  
    71     def inline_markup(start, end=None, mapping=None): 
    72         if not end: 
    73             end = start 
    74         return compile(r'''(START  %(start)s  ALPHA  %(end)s  END) | 
    75                            (START  %(start)s  ALPHA  WORD*  ALPHA  %(end)s  END)'''\ 
    76                        % {'start': start, 'end': end}, mapping) 
    77  
    78     def line_markup(start, end=None): 
    79         return inline_markup(start, end, mapping={'ALPHA': r'[-.,?!\s\w]', 
    80                                                   'START': r'(\n|^)[\ \t]*', 
    81                                                   'END': r''}) 
    82  
    83     emphasis_pattern = inline_markup(r'\*') 
    84     strong_pattern = inline_markup(r'\*\*') 
    85     inline_pattern = inline_markup(r'``') 
    86     hyperlink_pattern = inline_markup(r'\(', r'_\)', 
    87                                       {'ALPHA': r'\w', 'WORD': r'[-.\w]'}) 
    88     long_hyperlink_pattern = inline_markup(r'\(`', r'`_\)') 
    89     field_pattern = line_markup(r':') 
    90     bullet_pattern_1 = line_markup(r'[*+-]', r'') 
    91     bullet_pattern_2 = line_markup(r'((\d+) | ([a-zA-Z]+) [.\)])', r'') 
    92     bullet_pattern_3 = line_markup(r'\(  ((\d+)  |  ([a-zA-Z]+))  \)', r'') 
    93  
    94     rest_patterns = [strong_pattern, 
    95                      emphasis_pattern, 
    96                      inline_pattern, 
    97                      hyperlink_pattern, 
    98                      long_hyperlink_pattern, 
    99                      field_pattern, 
    100                      bullet_pattern_1, 
    101                      bullet_pattern_2, 
    102                      bullet_pattern_3] 
    103  
    104     for pattern in rest_patterns: 
     73    for pattern in supported_formats[format]: 
    10574        if re.search(pattern, text): 
    10675            return True 
     
    11685    """ 
    11786    def __init__(self, pyfile, log=None): 
     87        """ 
     88        :Parameters: 
     89          `pyfile` : str 
     90              Path to a Python module to parse. 
     91          `log` : logger.Producer instance 
     92              Logger to use during code parsing. 
     93        """ 
    11894        if log: 
    11995            self.log = log.codeparser 
     
    127103        self.functions = [] 
    128104        self.docstrings = [] # objects that have docstrings 
    129         self.rest_docstrings = [] # objects that have docstrings with reST 
     105        self.docstrings_by_format = {} 
     106 
     107        # Initialize lists of format docstrings. 
     108        for format in supported_formats: 
     109            self.docstrings_by_format[format] = [] 
    130110 
    131111        (path, filename) = os.path.split(pyfile) 
     
    149129            if isinstance(obj.docstring, str) and obj.docstring.strip(): 
    150130                self.docstrings.append(fullname) 
    151                 if use_reST(obj.docstring): 
    152                     self.rest_docstrings.append(fullname) 
     131                # Check docstring for known documenation formats. 
     132                for format in supported_formats: 
     133                    if use_format(obj.docstring, format): 
     134                        self.docstrings_by_format[format].append(fullname) 
    153135 
    154136        for method_or_func in self.method_func: 
     
    166148        self.log("methods: " + ",".join(self.methods)) 
    167149        self.log("functions: " + ",".join(self.functions)) 
     150        self.log("docstrings: %s" % self.docstrings_by_format) 
    168151 
    169152    def object_count(self): 
     
    187170        return len(self.docstrings) 
    188171 
    189     def rest_docstring_count(self): 
     172    def docstring_count_by_type(self, type): 
    190173        """Return number of reST docstrings found in this module 
    191174        """ 
    192         return len(self.rest_docstrings
     175        return len(self.docstrings_by_format[type]
    193176 
    194177    def functions_called(self): 
  • branches/mk/tests/data/module1.py

    r33 r36  
    11""" 
    22Docstring for module1 
     3 
     4@summary: Code used inside test_code_parser.py unit test. 
    35""" 
    46 
     
    5052    pass 
    5153 
     54 
    5255def func1(): 
    5356    """Docstring for func1""" 
     
    8285    "Time to get *a bit* of reST." 
    8386    pass 
     87 
     88def func8(argument): 
     89    """This is test function for the epytext parser. 
     90 
     91    @param argument: Description of an argument. 
     92    """ 
     93    pass 
     94 
     95 
     96class Class3(object): 
     97    """ 
     98    Class with epytext link: U{http://pycheesecake.org}. 
     99    """ 
     100    pass 
     101 
  • branches/mk/tests/test_code_parser.py

    r33 r36  
    44from _helper_cheesecake import set 
    55 
    6 from cheesecake.codeparser import CodeParser 
     6from cheesecake.codeparser import CodeParser, use_format 
    77 
    88datadir = os.path.abspath(os.path.join(os.path.dirname(__file__), "data")) 
     
    1616 
    1717    def test_classes(self): 
    18         assert self.code1.classes == ["module1.Class1", "module1.Class2"] 
     18        assert self.code1.classes == [ 
     19            "module1.Class1", 
     20            "module1.Class2", 
     21            "module1.Class3", 
     22        ] 
    1923 
    2024    def test_methods(self): 
    21         assert self.code1.methods == ["module1.Class1.__init__", 
    22         "module1.Class1.__another_method__", 
    23         "module1.Class1.method1", 
    24         "module1.Class1.method2",  
    25         "module1.Class1.method3", 
    26         "module1.Class1.method4", 
    27         "module1.Class1.method5",] 
     25        assert self.code1.methods == [ 
     26            "module1.Class1.__init__", 
     27            "module1.Class1.__another_method__", 
     28            "module1.Class1.method1", 
     29            "module1.Class1.method2", 
     30            "module1.Class1.method3", 
     31            "module1.Class1.method4", 
     32            "module1.Class1.method5", 
     33        ] 
    2834 
    2935    def test_functions(self): 
    3036        assert self.code1.functions == [ 
    31         "module1.func1",  
    32         "module1.func2",  
    33         "module1.func3", 
    34         "module1.func4",  
    35         "module1.__func5__", 
    36         "module1.func6", 
    37         "module1.func7"] 
     37            "module1.func1", 
     38            "module1.func2", 
     39            "module1.func3", 
     40            "module1.func4", 
     41            "module1.__func5__", 
     42            "module1.func6", 
     43            "module1.func7", 
     44            "module1.func8", 
     45        ] 
    3846 
    3947    def test_count(self): 
    40         assert self.code1.object_count() == 17 
    41         assert self.code1.docstring_count() == 14 
    42         assert self.code1.rest_docstring_count() == 2 
     48        assert self.code1.object_count() == 19 
     49        assert self.code1.docstring_count() == 16 
     50        assert self.code1.docstring_count_by_type('reST') == 2 
     51        assert self.code1.docstring_count_by_type('epytext') == 3 
    4352 
    4453    def test_docstrings(self): 
     
    4756            "module1.Class1", 
    4857            "module1.Class2", 
     58            "module1.Class3", 
    4959            "module1.Class1.__init__", 
    5060            "module1.Class1.__another_method__", 
     
    5868            "module1.__func5__", 
    5969            "module1.func7", 
     70            "module1.func8", 
    6071        ] 
    6172 
     
    6576        ] 
    6677 
     78        objects_with_epytext_docstrings = [ 
     79            "module1", 
     80            "module1.Class3", 
     81            "module1.func8", 
     82        ] 
     83 
    6784        print self.code1.docstrings 
    6885 
    6986        assert set(objects_with_docstrings) == set(self.code1.docstrings) 
    70         assert set(objects_with_rest_docstrings) == set(self.code1.rest_docstrings) 
     87        assert set(objects_with_rest_docstrings) == set(self.code1.docstrings_by_format['reST']) 
     88        assert set(objects_with_epytext_docstrings) == set(self.code1.docstrings_by_format['epytext']) 
     89 
     90 
     91class TestDocumentationFormats(object): 
     92    def _do_it(self, format, valid, invalid): 
     93        for test in valid: 
     94            print "Trying '%s'" % test 
     95            assert use_format(test, format) is True 
     96 
     97        for test in invalid: 
     98            print "Trying '%s'" % test 
     99            assert use_format(test, format) is False 
     100 
     101    def test_reST(self): 
     102        valid_test_strings = [ 
     103            "String with *emphasis*.", 
     104            "*Multi-word emphasis.*", 
     105            "How about testing **strong string**?", 
     106            "Some *noisy!* punctuation", 
     107            "**characters?**, in the way.", 
     108            "Don't forget ``inline literals``.", 
     109            "This is reST (hyperlink_).", 
     110            "This is (`quite long hyperlink`_).", 
     111            "* Bullet\n* List\n", 
     112            "+ Another\n+ Bullet\n+ List\n", 
     113            "1. Ordered\n2. List\n", 
     114            "  a) Another\n  b) ordered\n  c) list\n", 
     115            " (a) one\n (b) more", 
     116            ":Field: list\n:indeed: it is\n", 
     117        ] 
     118        invalid_test_strings = [ 
     119            "Plain string.", 
     120            "Do some math: 2 * 2a* 2 = 8a", 
     121            "Not*really*strong.", 
     122            "Interpreted `text` is widely used as quotes, so exclude it.", 
     123            "Not a :field:.", 
     124        ] 
     125 
     126        self._do_it('reST', valid_test_strings, invalid_test_strings) 
     127 
     128    def test_epytext(self): 
     129        valid_test_strings = [ 
     130            "- Bullet\n- List\n", 
     131            "1. Ordered\n2. List\n", 
     132            "1.1 Few points\n1.2 To remember\n", 
     133            "Some I{italics} here.", 
     134            "And a small bit of C{code}.", 
     135            "@param self: You know what it means.", 
     136            "@return: Return a long\ndescription.", 
     137        ] 
     138        invalid_test_strings = [ 
     139            "Aha - This is not an unordered list.", 
     140            "email@example.com", 
     141            "Short Python dictionary: {0: 'zero', 1: 'one'}.", 
     142            "@ not a field: at all", 
     143        ] 
     144 
     145        self._do_it('epytext', valid_test_strings, invalid_test_strings)