root/branches/mk/cheesecake/codeparser.py

Revision 33, 6.1 kB (checked in by mk, 4 years ago)

Check docstrings for use of reST (closes ticket #11).

  • Property svn:executable set to *
Line 
1 import os
2 import re
3
4 from model import System, Module, Class, Function, parseFile, processModuleAst
5
6 def use_reST(text):
7     """Return True if text includes reST and False otherwise.
8
9     See http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html
10     for reference.
11
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(":Field: list\\n:indeed: it is\\n")
31     True
32
33     >>> use_reST("Plain string.")
34     False
35     >>> use_reST("Do some math: 2 * 2a* 2 = 8a")
36     False
37     >>> use_reST("Not*really*strong.")
38     False
39     >>> use_reST("Interpreted `text` is widely used as quotes, so exclude it.")
40     False
41     >>> use_reST("Not a :field:.")
42     False
43     """
44     def compile(pattern, user_map=None):
45         """Compile a regex pattern using default or user mapping.
46         """
47
48         # Word in reST can also contain hyphens and punctuation characters.
49         mapping = {'ALPHA': r'[-.,?!\w]', 'WORD': r'[-.,?!\s\w]',
50                            'START': r'(^|\s)', 'END': r'([.,?!\s]|$)'}
51
52         if user_map:
53             mapping = mapping.copy()
54             mapping.update(user_map)
55
56         def sub(text, mapping):
57             for From, To in mapping.iteritems():
58                 text = text.replace(From, To)
59             return text
60
61         return re.compile(sub(pattern, mapping), re.LOCALE | re.VERBOSE)
62
63     def inline_markup(start, end=None, mapping=None):
64         if not end:
65             end = start
66         return compile(r'''(START  %(start)s  ALPHA  %(end)s  END) |
67                            (START  %(start)s  ALPHA  WORD*  ALPHA  %(end)s  END)'''\
68                        % {'start': start, 'end': end}, mapping)
69
70     def line_markup(start, end=None):
71         return inline_markup(start, end, mapping={'ALPHA': r'[-.,?!\s\w]',
72                                                   'START': r'(\n|^)[\ \t]*',
73                                                   'END': r''})
74
75     emphasis_pattern = inline_markup(r'\*')
76     strong_pattern = inline_markup(r'\*\*')
77     inline_pattern = inline_markup(r'``')
78     hyperlink_pattern = inline_markup(r'\(', r'_\)',
79                                       {'ALPHA': r'\w', 'WORD': r'[-.\w]'})
80     long_hyperlink_pattern = inline_markup(r'\(`', r'`_\)')
81     field_pattern = line_markup(r':')
82     bullet_pattern = line_markup(r'\*', r'')
83
84     rest_patterns = [strong_pattern,
85                      emphasis_pattern,
86                      inline_pattern,
87                      hyperlink_pattern,
88                      long_hyperlink_pattern,
89                      field_pattern,
90                      bullet_pattern]
91
92     for pattern in rest_patterns:
93         if re.search(pattern, text):
94             return True
95
96     return False
97
98
99 class CodeParser(object):
100     """Information about the structure of a Python module.
101
102     * Collects modules, classes, methods, functions and associated docstrings
103     * Based on mwh's docextractor.model module
104     """
105     def __init__(self, pyfile, log=None):
106         if log:
107             self.log = log.codeparser
108         else:
109             import logger
110             self.log = logger.default.codeparser
111         self.modules = []
112         self.classes = []
113         self.methods = []
114         self.method_func = []
115         self.functions = []
116         self.docstrings = [] # objects that have docstrings
117         self.rest_docstrings = [] # objects that have docstrings with reST
118
119         (path, filename) = os.path.split(pyfile)
120         (module, ext) = os.path.splitext(filename)
121         self.log("Inspecting file: " + pyfile)
122
123         self.system = System()
124         try:
125             processModuleAst(parseFile(pyfile), module, self.system)
126         except:
127             return
128
129         for obj in self.system.orderedallobjects:
130             fullname = obj.fullName()
131             if isinstance(obj, Module):
132                 self.modules.append(fullname)
133             if isinstance(obj, Class):
134                 self.classes.append(fullname)
135             if isinstance(obj, Function):
136                 self.method_func.append(fullname)
137             if isinstance(obj.docstring, str) and obj.docstring.strip():
138                 self.docstrings.append(fullname)
139                 if use_reST(obj.docstring):
140                     self.rest_docstrings.append(fullname)
141
142         for method_or_func in self.method_func:
143             method_found = 0
144             for cls in self.classes:
145                 if method_or_func.startswith(cls):
146                     self.methods.append(method_or_func)
147                     method_found = 1
148                     break
149             if not method_found:
150                 self.functions.append(method_or_func)
151                
152         self.log("modules: " + ",".join(self.modules))
153         self.log("classes: " + ",".join(self.classes))
154         self.log("methods: " + ",".join(self.methods))
155         self.log("functions: " + ",".join(self.functions))
156
157     def object_count(self):
158         """Return number of objects found in this module.
159
160         Objects include:
161         * module
162         * classes
163         * methods
164         * functions
165         """
166         module_count = len(self.modules)
167         cls_count = len(self.classes)
168         method_count = len(self.methods)
169         func_count = len(self.functions)
170         return module_count + cls_count + method_count + func_count
171
172     def docstring_count(self):
173         """Return number of docstrings found in this module
174         """
175         return len(self.docstrings)
176
177     def rest_docstring_count(self):
178         """Return number of reST docstrings found in this module
179         """
180         return len(self.rest_docstrings)
181
182     def functions_called(self):
183         """
184         Return list of functions called by functions/methods
185         defined in this module
186         """
187         return self.system.func_called.keys()
Note: See TracBrowser for help on using the browser.