| 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() |
|---|