Index: /branches/mk/cheesecake/codeparser.py =================================================================== --- /branches/mk/cheesecake/codeparser.py (revision 32) +++ /branches/mk/cheesecake/codeparser.py (revision 33) @@ -1,8 +1,102 @@ import os +import re + from model import System, Module, Class, Function, parseFile, processModuleAst +def use_reST(text): + """Return True if text includes reST and False otherwise. + + See http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html + for reference. + + >>> use_reST("String with *emphasis*.") + True + >>> use_reST("*Multi-word emphasis.*") + True + >>> use_reST("How about testing **strong string**?") + True + >>> use_reST("Some *noisy!* punctuation") + True + >>> use_reST("**characters?**, in the way.") + True + >>> use_reST("Don\'t forget ``inline literals``.") + True + >>> use_reST("This is reST (hyperlink_).") + True + >>> use_reST("This is (`quite long hyperlink`_).") + True + >>> use_reST("* Bullet\\n* List\\n") + True + >>> use_reST(":Field: list\\n:indeed: it is\\n") + True + + >>> use_reST("Plain string.") + False + >>> use_reST("Do some math: 2 * 2a* 2 = 8a") + False + >>> use_reST("Not*really*strong.") + False + >>> use_reST("Interpreted `text` is widely used as quotes, so exclude it.") + False + >>> use_reST("Not a :field:.") + False + """ + def compile(pattern, user_map=None): + """Compile a regex pattern using default or user mapping. + """ + + # Word in reST can also contain hyphens and punctuation characters. + mapping = {'ALPHA': r'[-.,?!\w]', 'WORD': r'[-.,?!\s\w]', + 'START': r'(^|\s)', 'END': r'([.,?!\s]|$)'} + + if user_map: + mapping = mapping.copy() + mapping.update(user_map) + + def sub(text, mapping): + for From, To in mapping.iteritems(): + text = text.replace(From, To) + return text + + return re.compile(sub(pattern, mapping), re.LOCALE | re.VERBOSE) + + def inline_markup(start, end=None, mapping=None): + if not end: + end = start + return compile(r'''(START %(start)s ALPHA %(end)s END) | + (START %(start)s ALPHA WORD* ALPHA %(end)s END)'''\ + % {'start': start, 'end': end}, mapping) + + def line_markup(start, end=None): + return inline_markup(start, end, mapping={'ALPHA': r'[-.,?!\s\w]', + 'START': r'(\n|^)[\ \t]*', + 'END': r''}) + + emphasis_pattern = inline_markup(r'\*') + strong_pattern = inline_markup(r'\*\*') + inline_pattern = inline_markup(r'``') + hyperlink_pattern = inline_markup(r'\(', r'_\)', + {'ALPHA': r'\w', 'WORD': r'[-.\w]'}) + long_hyperlink_pattern = inline_markup(r'\(`', r'`_\)') + field_pattern = line_markup(r':') + bullet_pattern = line_markup(r'\*', r'') + + rest_patterns = [strong_pattern, + emphasis_pattern, + inline_pattern, + hyperlink_pattern, + long_hyperlink_pattern, + field_pattern, + bullet_pattern] + + for pattern in rest_patterns: + if re.search(pattern, text): + return True + + return False + + class CodeParser(object): - """ - Information about the structure of a Python module + """Information about the structure of a Python module. * Collects modules, classes, methods, functions and associated docstrings @@ -20,5 +114,6 @@ self.method_func = [] self.functions = [] - self.docstrings = {} + self.docstrings = [] # objects that have docstrings + self.rest_docstrings = [] # objects that have docstrings with reST (path, filename) = os.path.split(pyfile) @@ -41,5 +136,7 @@ self.method_func.append(fullname) if isinstance(obj.docstring, str) and obj.docstring.strip(): - self.docstrings[fullname] = 1 + self.docstrings.append(fullname) + if use_reST(obj.docstring): + self.rest_docstrings.append(fullname) for method_or_func in self.method_func: @@ -59,7 +156,7 @@ def object_count(self): - """ - Return number of objects found in this module + """Return number of objects found in this module. + Objects include: * module * classes @@ -74,8 +171,12 @@ def docstring_count(self): + """Return number of docstrings found in this module """ - Return number of docstrings found in this module + return len(self.docstrings) + + def rest_docstring_count(self): + """Return number of reST docstrings found in this module """ - return len(self.docstrings.keys()) + return len(self.rest_docstrings) def functions_called(self): Index: /branches/mk/tests/test_count_files.py =================================================================== --- /branches/mk/tests/test_count_files.py (revision 29) +++ /branches/mk/tests/test_count_files.py (revision 33) @@ -1,7 +1,5 @@ from _mockup_cheesecake import MockupCheesecakeTest - -if 'set' not in dir(__builtins__): - from sets import Set as set +from _helper_cheesecake import set Index: /branches/mk/tests/_helper_cheesecake.py =================================================================== --- /branches/mk/tests/_helper_cheesecake.py (revision 33) +++ /branches/mk/tests/_helper_cheesecake.py (revision 33) @@ -0,0 +1,3 @@ + +if 'set' not in dir(__builtins__): + from sets import Set as set Index: /branches/mk/tests/data/module1.py =================================================================== --- /branches/mk/tests/data/module1.py (revision 32) +++ /branches/mk/tests/data/module1.py (revision 33) @@ -36,4 +36,11 @@ pass + def method5(self): + """Method with few definitions. + + :Word: And its definition. + """ + pass + class Class2: @@ -71,2 +78,6 @@ """ pass + +def func7(): + "Time to get *a bit* of reST." + pass Index: /branches/mk/tests/test_code_parser.py =================================================================== --- /branches/mk/tests/test_code_parser.py (revision 32) +++ /branches/mk/tests/test_code_parser.py (revision 33) @@ -2,4 +2,6 @@ import _path_cheesecake +from _helper_cheesecake import set + from cheesecake.codeparser import CodeParser @@ -22,5 +24,6 @@ "module1.Class1.method2", "module1.Class1.method3", - "module1.Class1.method4"] + "module1.Class1.method4", + "module1.Class1.method5",] def test_functions(self): @@ -31,9 +34,11 @@ "module1.func4", "module1.__func5__", - "module1.func6"] + "module1.func6", + "module1.func7"] def test_count(self): - assert self.code1.object_count() == 15 - assert self.code1.docstring_count() == 12 + assert self.code1.object_count() == 17 + assert self.code1.docstring_count() == 14 + assert self.code1.rest_docstring_count() == 2 def test_docstrings(self): @@ -47,21 +52,19 @@ "module1.Class1.method2", "module1.Class1.method3", + "module1.Class1.method5", "module1.func1", "module1.func2", "module1.func3", "module1.__func5__", + "module1.func7", ] - objects_without_docstrings = [ - "module1.Class1.method4", - "module1.func4", - "module1.func6", + objects_with_rest_docstrings = [ + "module1.Class1.method5", + "module1.func7", ] print self.code1.docstrings - for obj in objects_with_docstrings: - assert self.code1.docstrings.get(obj) == 1 - - for obj in objects_without_docstrings: - assert not self.code1.docstrings.get(obj) + assert set(objects_with_docstrings) == set(self.code1.docstrings) + assert set(objects_with_rest_docstrings) == set(self.code1.rest_docstrings) Index: /branches/mk/tests/test_index_docstrings.py =================================================================== --- /branches/mk/tests/test_index_docstrings.py (revision 2) +++ /branches/mk/tests/test_index_docstrings.py (revision 33) @@ -1,9 +1,11 @@ +import os +from math import ceil + import _path_cheesecake from cheesecake.cheesecake_index import Cheesecake, CodeParser -import os -from math import ceil + datadir = os.path.abspath(os.path.join(os.path.dirname(__file__), "data")) -class TestIndexDocstrings: +class TestIndexDocstrings(object): def setUp(self): self.cheesecake = Cheesecake(path=os.path.join(datadir, "package1.tar.gz"))