3
r1 : Première version très naïve, poc.
4
r2 : Traitement des modules n'étant pas dans le dossier courant
5
r3 : Traitement des dossiers (tous les fichiers python d'un dossier).
8
r6 : docstrings handling
9
r7 : correct handling of directories
10
r8 : correct handling of single files (bug probably introduced in r7)
14
+ Module : mod1 => 102 loc
15
`--+ 306 functions => 3940 loc
16
`-- function 1 : 20 loc
17
`--+ 49 classes => 2048 loc
18
`--+ class MyClass => 92 loc
22
r9.1 : printing by + module
30
r10 : accept a --verbose option to enable/disable info prints
39
class Inspector(object):
41
def __init__(self,modules_names,max_length=25,stop_at_warning=False):
42
self.modules_names = modules_names
43
self.max_length = max_length
46
self.stop_at_warning = stop_at_warning
47
self.stats = {} # {module_name : {classes : {nb_methods:NBM,#loc=LOC}, nb_functions:NBF, #loc=LOC}, #loc : N}
48
self.stats["#loc"] = 0
51
def print_stats(self):
52
self.print_modules_stats()
54
self.print_avg_stats()
55
self.print_max_stats()
57
def print_gl_stats(self):
58
nb_modules = len(self.stats) - 1
59
self.nb_modules = nb_modules
60
total_loc = self.stats["#loc"]
61
nb_functions = sum(map(lambda x: x!="#loc" and len(self.stats[x]["functions"] or 0),self.stats.keys()))
62
self.nb_functions = nb_functions
63
classes_stats = filter(lambda x:x != {},
64
map(lambda module_name:module_name!="#loc" and self.stats[module_name]["classes"] or {},
68
pprint.pprint(classes_stats)
71
# 'Inspector': {'#loc': 134,
72
# 'methods': {'__init__': 8,
73
# 'print_class_stats': 28,
74
# 'print_functions_stats': 14,
75
# 'print_module_stats': 17,
76
# 'print_modules_stats': 5,
78
# 'process_callables': 4,
79
# 'process_modules': 30},
82
extract_methods = lambda tup:map(lambda class_name: class_name != "#loc" and tup[0][class_name]["methods"] or {},tup[1])
83
extract_nbmethods = lambda tup:map(lambda class_name: class_name != "#loc" and tup[0][class_name]["nb_methods"] or 0,tup[1])
84
methods_stats = map(extract_methods,zip(classes_stats,map(lambda x:x.keys(),classes_stats)))
85
pprint.pprint(methods_stats)
86
nb_classes = sum(map(lambda x:x != "#loc" and self.stats[x]["nb_classes"] or 0,self.stats.keys()))
87
self.nb_classes = nb_classes
88
nb_methods = map(extract_nbmethods,zip(classes_stats,map(lambda x:x.keys(),classes_stats)))
89
nb_methods = len(nb_methods) > 1 and reduce(lambda x,y:sum(x)+sum(y) ,nb_methods) or sum(nb_methods[0])
90
self.nb_methods = nb_methods
92
print "Number of modules %s" % nb_modules
93
output = "Total : %s loc across %s modules containing %s functions, %s classes and %s methods."
94
print output % (total_loc,nb_modules,nb_functions,nb_classes,nb_methods)
96
def print_avg_stats(self):
97
self.print_avg_functions_stats()
98
self.print_avg_methods_stats()
99
self.print_avg_classes_stats()
100
self.print_avg_modules_stats()
102
def print_avg_functions_stats(self):
103
functions_loc = lambda x,y: x != "#loc" and y!="#loc" and self.stats[x]["functions"]["#loc"]+self.stats[y]['functions']["#loc"] or 0
104
functions_loc = len(self.stats) > 1 and reduce(functions_loc,self.stats.keys()) or self.stats.values()[0]["functions"]["#loc"]
105
func_avg_loc = functions_loc * 1.0 / self.nb_functions
106
print "avg loc per function : %s" % func_avg_loc
108
def print_avg_methods_stats(self):
109
methods_loc = sum([self.stats[module]["classes"]["#loc"] for module in self.stats.keys() if module != "#loc"])
110
methods_loc_avg = 1.0 * methods_loc / self.nb_methods
111
print "avg loc per method : %s" % methods_loc_avg
113
def print_avg_classes_stats(self):
114
class_loc = sum([self.stats[module]["classes"][classname]["#loc"]
115
for module in self.stats.keys()
117
for classname in self.stats[module]["classes"].keys()
118
if classname != "#loc"
121
class_avg_loc = 1.0 * class_loc / self.nb_classes
122
print "avg loc per class : %s" % class_avg_loc
124
def print_avg_modules_stats(self):
125
module_loc = 1.0 * self.stats["#loc"] / self.nb_modules
126
print "avg loc per module : %s" % module_loc
128
def print_max_stats(self):
129
print "longest function : %s with %s loc"
130
print "longest method : %s with %s loc"
131
print "longest class : %s with %s loc"
132
print "longest module : %s with %s loc"
134
def print_modules_stats(self):
135
for module_name in self.stats.keys():
136
if module_name == '#loc' :
138
self.print_module_stats(module_name)
140
def print_module_stats(self,module_name):
141
output = "+ Module %s => %s loc"
142
module_dict = self.stats[module_name]
145
for function_name in module_dict["functions"]:
146
if function_name == "#loc":
149
loc += module_dict["functions"][function_name]
151
for class_name in module_dict["classes"].keys():
152
if class_name == '#loc':
154
for method_name in module_dict["classes"][class_name]["methods"].keys():
155
loc += module_dict["classes"][class_name]["methods"][method_name]
157
output %= (module_name,loc)
159
self.print_functions_stats(module_name)
160
self.print_class_stats(module_name)
162
def print_functions_stats(self,module_name):
165
functions_names = self.stats[module_name]["functions"].keys()
166
longest_name_length = max(map(lambda x:len(x),functions_names))
167
lnl = longest_name_length
168
for function_name in functions_names:
169
if function_name == '#loc':
172
loc = self.stats[module_name]["functions"][function_name]
174
delayed_output.append(format_branch(3," "+function_name.ljust(lnl) + " => %s loc" %loc))
176
print_branch(0,"+ %s functions => %s loc" % (len(self.stats[module_name]["functions"]),total_loc))
177
print "\n".join(delayed_output)
179
def print_class_stats(self,module_name):
182
if not self.stats[module_name]["classes"]:
184
for class_name in self.stats[module_name]["classes"]:
185
if class_name == '#loc':
188
nb_methods = len(self.stats[module_name]["classes"][class_name]["methods"])
190
methods_names = self.stats[module_name]["classes"][class_name]["methods"].keys()
191
if not methods_names :
193
longest_name_length = max(map(lambda x:len(x),methods_names))
194
lnl = longest_name_length
195
for method_name in methods_names :
196
if method_name == '#loc':
198
methods_dict = self.stats[module_name]["classes"][class_name]["methods"]
199
loc = methods_dict[method_name]
200
total_class_loc += loc
202
methods_output.append(format_branch(6," "+method_name.ljust(lnl) + " => %s loc" %loc))
204
delayed_output.append(format_branch(3,"+ class %s => %s loc over %s methods" % (class_name,total_class_loc,nb_methods)))
205
delayed_output += methods_output
207
print_branch(0,"+ %s classes => %s loc" % (len(self.stats[module_name]["classes"]),total_loc))
208
print "\n".join(delayed_output)
210
def process_modules(self):
212
main function of this class
214
for module_name in self.modules_names:
215
# module_name could be a directory_name, therefor, we loop.
216
for module in get_module(module_name):
217
functions = get_functions(module)
218
classes = get_classes(module)
219
self.stats[module.__name__] = {}
220
self.stats[module.__name__]["#loc"] = 0
221
self.stats[module.__name__]["functions"] = {}
222
self.stats[module.__name__]["functions"]["#loc"] = 0
223
self.stats[module.__name__]["nb_functions"] = len(functions)
225
for function_name, loc in self.process_callables(functions):
226
self.stats[module.__name__]["functions"][function_name] = loc
227
self.stats[module.__name__]["functions"]["#loc"] += loc
228
self.stats[module.__name__]["#loc"] += loc
229
self.stats["#loc"] += loc
231
self.stats[module.__name__]["classes"] = {}
232
self.stats[module.__name__]["classes"]["#loc"] = 0
233
self.stats[module.__name__]["nb_classes"] = len(classes)
235
for klass in classes :
236
methods = get_methods(klass)
237
self.stats[module.__name__]["classes"][klass.__name__] = {}
238
self.stats[module.__name__]["classes"][klass.__name__]["#loc"] = 0
239
self.stats[module.__name__]["classes"][klass.__name__]["methods"] = {}
240
self.stats[module.__name__]["classes"][klass.__name__]["nb_methods"] = len(methods)
242
for method_name,loc in self.process_callables(methods):
243
self.stats[module.__name__]["classes"][klass.__name__]["methods"][method_name] = loc
244
self.stats[module.__name__]["classes"][klass.__name__]["#loc"] += loc
245
self.stats[module.__name__]["classes"]["#loc"] += loc
246
self.stats[module.__name__]["#loc"] += loc
247
self.stats["#loc"] += loc
249
def process_callables(self,callables):
250
for kallable in callables:
251
loc = count_loc(inspect.getsourcelines(kallable)[0])
252
yield kallable.__name__,loc
254
def print_branch(depth,s):
255
print (depth != 0 and '|' or '') + " "*depth+"`--"+s
257
def format_branch(depth,s):
258
return (depth != 0 and '|' or '') + " "*depth+"`--"+s
260
def repeat(kallable,times):
261
for i in xrange(times):
264
def pretty_box(string):
266
print "##"+"#"*l+"##"
268
print "##"+"#"*l+"##"
270
def add_to_path(dir_name):
271
if dir_name not in sys.path:
272
sys.path.append(dir_name)
273
print "added '%s' to the path" % (dir_name)
275
def get_module(file_name):
276
# if it's a directory return all modules and submodules recursively
277
if os.path.isdir(file_name):
279
add_to_path(dir_name)
280
files = dircache.listdir(dir_name)
281
for one_file in files:
282
complete_file_name = os.path.join(dir_name,one_file)
283
for module in get_module(complete_file_name):
286
elif file_name.endswith(".py"):
287
exploded_path = file_name.split(".py")[0].split(os.sep)
288
module_name = exploded_path[-1]
289
if "/" in file_name :
290
dir_name = os.path.join(*exploded_path[:-1])
291
add_to_path(dir_name)
293
yield __import__(module_name)
295
def count_loc(lines):
303
or line.startswith("#") \
304
or docstring and not (line.startswith('"""') or line.startswith("'''"))\
305
or (line.startswith("'''") and line.endswith("'''") and len(line) >3) \
306
or (line.startswith('"""') and line.endswith('"""') and len(line) >3) :
309
# this is either a starting or ending docstring
310
elif line.startswith('"""') or line.startswith("'''"):
311
docstring = not docstring
320
def print_module_separator():
323
def print_class_separator():
326
print_functions_separator = print_class_separator
328
def print_module(module_name):
329
pretty_box("Module :"+module_name)
331
def print_class(class_name):
332
print "Class :",class_name
334
def get_functions(module):
335
return get_callables(module,inspect.isfunction)
337
def get_methods(module):
338
return get_callables(module,inspect.ismethod)
340
def get_callables(module,type_filter):
341
return map(lambda x:x[1],inspect.getmembers(module,type_filter))
343
def get_classes(module):
344
return map(lambda x:x[1],inspect.getmembers(module,inspect.isclass))
347
modules_names = sys.argv[1:]
348
if not modules_names:
349
print "usage : %s FILE_OR_DIRECTORY ... " % sys.argv[0]
352
tahar = Inspector(modules_names)
353
tahar.process_modules()
356
def test_get_module():
357
for module in get_module("."):
360
if __name__ == "__main__":