Changeset 36
- Timestamp:
- 06/01/06 15:16:17 (6 years ago)
- Files:
-
- branches/mk/cheesecake/codeparser.py (modified) (6 diffs)
- branches/mk/tests/data/module1.py (modified) (3 diffs)
- branches/mk/tests/test_code_parser.py (modified) (5 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
branches/mk/cheesecake/codeparser.py
r35 r36 4 4 from model import System, Module, Class, Function, parseFile, processModuleAst 5 5 6 def use_reST(text):7 """Return True if text includes reST and False otherwise.8 6 9 See http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html 10 for reference. 7 def compile_regex(pattern, user_map=None): 8 """Compile a regex pattern using default or user mapping. 9 """ 11 10 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]|$)'} 40 14 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 28 def 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 35 def 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 40 supported_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 67 def 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. 51 72 """ 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]: 105 74 if re.search(pattern, text): 106 75 return True … … 116 85 """ 117 86 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 """ 118 94 if log: 119 95 self.log = log.codeparser … … 127 103 self.functions = [] 128 104 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] = [] 130 110 131 111 (path, filename) = os.path.split(pyfile) … … 149 129 if isinstance(obj.docstring, str) and obj.docstring.strip(): 150 130 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) 153 135 154 136 for method_or_func in self.method_func: … … 166 148 self.log("methods: " + ",".join(self.methods)) 167 149 self.log("functions: " + ",".join(self.functions)) 150 self.log("docstrings: %s" % self.docstrings_by_format) 168 151 169 152 def object_count(self): … … 187 170 return len(self.docstrings) 188 171 189 def rest_docstring_count(self):172 def docstring_count_by_type(self, type): 190 173 """Return number of reST docstrings found in this module 191 174 """ 192 return len(self. rest_docstrings)175 return len(self.docstrings_by_format[type]) 193 176 194 177 def functions_called(self): branches/mk/tests/data/module1.py
r33 r36 1 1 """ 2 2 Docstring for module1 3 4 @summary: Code used inside test_code_parser.py unit test. 3 5 """ 4 6 … … 50 52 pass 51 53 54 52 55 def func1(): 53 56 """Docstring for func1""" … … 82 85 "Time to get *a bit* of reST." 83 86 pass 87 88 def func8(argument): 89 """This is test function for the epytext parser. 90 91 @param argument: Description of an argument. 92 """ 93 pass 94 95 96 class 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 4 4 from _helper_cheesecake import set 5 5 6 from cheesecake.codeparser import CodeParser 6 from cheesecake.codeparser import CodeParser, use_format 7 7 8 8 datadir = os.path.abspath(os.path.join(os.path.dirname(__file__), "data")) … … 16 16 17 17 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 ] 19 23 20 24 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 ] 28 34 29 35 def test_functions(self): 30 36 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 ] 38 46 39 47 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 43 52 44 53 def test_docstrings(self): … … 47 56 "module1.Class1", 48 57 "module1.Class2", 58 "module1.Class3", 49 59 "module1.Class1.__init__", 50 60 "module1.Class1.__another_method__", … … 58 68 "module1.__func5__", 59 69 "module1.func7", 70 "module1.func8", 60 71 ] 61 72 … … 65 76 ] 66 77 78 objects_with_epytext_docstrings = [ 79 "module1", 80 "module1.Class3", 81 "module1.func8", 82 ] 83 67 84 print self.code1.docstrings 68 85 69 86 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 91 class 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)
