Skip to content

Commit 7a33cc2

Browse files
authored
Merge pull request #27 from matsoftware/analyze-submodules-part3
Analyze submodules (part 3)
2 parents 1b983ca + be8bc53 commit 7a33cc2

File tree

10 files changed

+560
-61
lines changed

10 files changed

+560
-61
lines changed

swift_code_metrics/_analyzer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ def __populate_submodule(framework: 'Framework', swift_file: 'SwiftFile'):
8383
if len(list(existing_submodule)) > 0:
8484
submodule = existing_submodule.first()
8585
else:
86-
new_submodule = SubModule(name=path, files=[], submodules=[])
86+
new_submodule = SubModule(name=path, files=[], submodules=[], parent=submodule)
8787
submodule.submodules.append(new_submodule)
8888
submodule = new_submodule
8989

swift_code_metrics/_graphics.py renamed to swift_code_metrics/_graph_helpers.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ def pie_plot(self, title, sizes, labels, legend):
2727
plt.title(title)
2828
patches, _, _ = plt.pie(sizes, labels=labels, autopct='%1.1f%%', shadow=True, startangle=90)
2929
plt.legend(patches, legend, loc='best')
30-
plt.tight_layout()
3130
plt.axis('equal')
31+
plt.tight_layout()
3232

3333
self.__render(plt, title)
3434

@@ -84,7 +84,7 @@ def __render(self, plt, name):
8484
plt.show()
8585
else:
8686
save_file = self.__file_path(name)
87-
plt.savefig(save_file)
87+
plt.savefig(save_file, bbox_inches='tight')
8888
plt.close()
8989

9090
def __file_path(self, name, extension='.pdf'):

swift_code_metrics/_presenter.py renamed to swift_code_metrics/_graphs_presenter.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from swift_code_metrics._metrics import Metrics
2-
from ._graphics import Graph
2+
from ._graph_helpers import Graph
33
from functional import seq
44
from math import ceil
55

@@ -20,7 +20,7 @@ def sorted_data_plot(self, title, list_of_frameworks, f_of_framework):
2020

2121
self.graph.bar_plot(title, plot_data)
2222

23-
def pie_plot(self, title, list_of_frameworks, f_of_framework):
23+
def frameworks_pie_plot(self, title, list_of_frameworks, f_of_framework):
2424
"""
2525
Renders the percentage distribution data related to a framework in a pie chart.
2626
:param title: The chart title
@@ -40,6 +40,16 @@ def pie_plot(self, title, list_of_frameworks, f_of_framework):
4040

4141
self.graph.pie_plot(title, plot_data[0], plot_data[1], plot_data[2])
4242

43+
def submodules_pie_plot(self, title, list_of_submodules, f_of_submodules):
44+
sorted_data = sorted(list(map(lambda s: (f_of_submodules(s),
45+
s.name),
46+
list_of_submodules)),
47+
key=lambda tup: tup[0])
48+
plot_data = (list(map(lambda f: f[0], sorted_data)),
49+
list(map(lambda f: f[1], sorted_data)))
50+
51+
self.graph.pie_plot(title, plot_data[0], plot_data[1], plot_data[1])
52+
4353
def distance_from_main_sequence_plot(self, list_of_frameworks, x_ax_f_framework, y_ax_f_framework):
4454
"""
4555
Renders framework related data to a scattered plot
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
from ._metrics import Metrics, SubModule
2+
from ._report import ReportingHelpers
3+
from dataclasses import dataclass
4+
from typing import List
5+
from ._graphs_presenter import GraphPresenter
6+
7+
8+
@dataclass
9+
class GraphsRender:
10+
"Component responsible to generate the needed graphs for the given report."
11+
artifacts_path: str
12+
test_frameworks: List['Framework']
13+
non_test_frameworks: List['Framework']
14+
report: 'Report'
15+
16+
def render_graphs(self):
17+
graph_presenter = GraphPresenter(self.artifacts_path)
18+
19+
# Project graphs
20+
self.__project_graphs(graph_presenter=graph_presenter)
21+
22+
# Submodules graphs
23+
self.__submodules_graphs(graph_presenter=graph_presenter)
24+
25+
def __project_graphs(self, graph_presenter: 'GraphPresenter'):
26+
# Sorted data plots
27+
non_test_reports_sorted_data = {
28+
'N. of classes and structs': lambda fr: fr.data.number_of_concrete_data_structures,
29+
'Lines Of Code - LOC': lambda fr: fr.data.loc,
30+
'Number Of Comments - NOC': lambda fr: fr.data.noc,
31+
'N. of imports - NOI': lambda fr: fr.number_of_imports
32+
}
33+
34+
tests_reports_sorted_data = {
35+
'Number of tests - NOT': lambda fr: fr.data.number_of_tests
36+
}
37+
38+
# Non-test graphs
39+
for title, framework_function in non_test_reports_sorted_data.items():
40+
graph_presenter.sorted_data_plot(title, self.non_test_frameworks, framework_function)
41+
42+
# Distance from the main sequence
43+
all_frameworks = self.test_frameworks + self.non_test_frameworks
44+
graph_presenter.distance_from_main_sequence_plot(self.non_test_frameworks,
45+
lambda fr: Metrics.instability(fr, all_frameworks),
46+
lambda fr: Metrics.abstractness(fr))
47+
48+
# Dependency graph
49+
graph_presenter.dependency_graph(self.non_test_frameworks,
50+
self.report.non_test_framework_aggregate.loc,
51+
self.report.non_test_framework_aggregate.n_o_i)
52+
53+
# Code distribution
54+
graph_presenter.frameworks_pie_plot('Code distribution', self.non_test_frameworks,
55+
lambda fr:
56+
ReportingHelpers.decimal_format(fr.data.loc
57+
/ self.report.non_test_framework_aggregate.loc))
58+
59+
# Test graphs
60+
for title, framework_function in tests_reports_sorted_data.items():
61+
graph_presenter.sorted_data_plot(title, self.test_frameworks, framework_function)
62+
63+
def __submodules_graphs(self, graph_presenter: 'GraphPresenter'):
64+
for framework in self.non_test_frameworks:
65+
GraphsRender.__render_submodules(parent='Code distribution',
66+
root_submodule=framework.submodule,
67+
graph_presenter=graph_presenter)
68+
69+
@staticmethod
70+
def __render_submodules(parent: str, root_submodule: 'SubModule', graph_presenter: 'GraphPresenter'):
71+
current_submodule = root_submodule.next
72+
while current_submodule != root_submodule:
73+
GraphsRender.__render_submodule_loc(parent=parent,
74+
submodule=current_submodule,
75+
graph_presenter=graph_presenter)
76+
current_submodule = current_submodule.next
77+
78+
@staticmethod
79+
def __render_submodule_loc(parent: str, submodule: 'SubModule', graph_presenter: 'GraphPresenter'):
80+
submodules = submodule.submodules
81+
if len(submodules) == 0:
82+
return
83+
total_loc = submodule.data.loc
84+
if total_loc == submodules[0].data.loc:
85+
# Single submodule folder - not useful
86+
return
87+
if len(submodule.files) > 0:
88+
# Add a submodule to represent the root slice
89+
submodules = submodules + [SubModule(name='(root)',
90+
files=submodule.files,
91+
submodules=[],
92+
parent=submodule)]
93+
94+
chart_name = f'{parent} {submodule.path}'
95+
graph_presenter.submodules_pie_plot(chart_name, submodules,
96+
lambda s: ReportingHelpers.decimal_format(s.data.loc / total_loc))

swift_code_metrics/_helpers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import re
22
import logging
33
import json
4-
from typing import List,Dict
4+
from typing import Dict
55
from functional import seq
66

77

swift_code_metrics/_metrics.py

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,8 @@ def __init__(self, name: str, is_test_framework: bool = False):
344344
self.submodule = SubModule(
345345
name=self.name,
346346
files=[],
347-
submodules=[]
347+
submodules=[],
348+
parent=None
348349
)
349350
self.is_test_framework = is_test_framework
350351

@@ -423,13 +424,39 @@ class SubModule:
423424
name: str
424425
files: List['SwiftFile']
425426
submodules: List['SubModule']
427+
parent: Optional['SubModule']
428+
429+
@property
430+
def next(self) -> 'SubModule':
431+
if len(self.submodules) == 0:
432+
if self.parent is None:
433+
return self
434+
else:
435+
return self.submodules[0]
436+
437+
next_level = self.parent
438+
current_level = self
439+
while next_level is not None:
440+
next_i = next_level.submodules.index(current_level) + 1
441+
if next_i < len(next_level.submodules):
442+
return next_level.submodules[next_i]
443+
else:
444+
current_level = next_level
445+
next_level = next_level.parent
446+
447+
return current_level
426448

427449
@property
428450
def n_of_files(self) -> int:
429451
sub_files = 0 if (len(self.submodules) == 0) else \
430452
seq([s.n_of_files for s in self.submodules]).reduce(lambda a, b: a + b)
431453
return len(self.files) + sub_files
432454

455+
@property
456+
def path(self) -> str:
457+
parent_path = "" if not self.parent else f'{self.parent.path} > '
458+
return f'{parent_path}{self.name}'
459+
433460
@property
434461
def data(self) -> 'SyntheticData':
435462
root_module_files = [SyntheticData()] if (len(self.files) == 0) else \
@@ -443,7 +470,8 @@ def as_dict(self) -> Dict:
443470
return {
444471
self.name: {
445472
"n_of_files": self.n_of_files,
446-
"metric": self.data.as_dict
473+
"metric": self.data.as_dict,
474+
"submodules": [s.as_dict for s in self.submodules]
447475
}
448476
}
449477

swift_code_metrics/_report.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ def __framework_analysis(framework: 'Framework', frameworks: List['Framework'])
7878
"noi": n_of_imports,
7979
"analysis": analysis,
8080
"dependencies": dependencies,
81+
"submodules": framework.submodule.as_dict
8182
}
8283

8384
return {

swift_code_metrics/scm.py

Lines changed: 10 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
#!/usr/bin/python
1+
#!/usr/bin/python3
22

33
from argparse import ArgumentParser
44

5-
from ._helpers import Log,ReportingHelpers
5+
from ._helpers import Log
66
from ._analyzer import Inspector
7-
from ._metrics import Metrics
87
from .version import VERSION
98
import sys
109

@@ -74,43 +73,13 @@ def main():
7473
sys.exit(0)
7574

7675
# Creates graphs
77-
from ._presenter import GraphPresenter
78-
graph_presenter = GraphPresenter(artifacts)
76+
from ._graphs_renderer import GraphsRender
7977
non_test_frameworks = analyzer.filtered_frameworks(is_test=False)
8078
test_frameworks = analyzer.filtered_frameworks(is_test=True)
81-
82-
# Sorted data plots
83-
non_test_reports_sorted_data = {
84-
'N. of classes and structs': lambda fr: fr.data.number_of_concrete_data_structures,
85-
'Lines Of Code - LOC': lambda fr: fr.data.loc,
86-
'Number Of Comments - NOC': lambda fr: fr.data.noc,
87-
'N. of imports - NOI': lambda fr: fr.number_of_imports
88-
}
89-
90-
tests_reports_sorted_data = {
91-
'Number of tests - NOT': lambda fr: fr.data.number_of_tests
92-
}
93-
94-
# Non-test graphs
95-
for title, framework_function in non_test_reports_sorted_data.items():
96-
graph_presenter.sorted_data_plot(title, non_test_frameworks, framework_function)
97-
98-
# Distance from the main sequence
99-
graph_presenter.distance_from_main_sequence_plot(non_test_frameworks,
100-
lambda fr: Metrics.instability(fr, analyzer.frameworks),
101-
lambda fr: Metrics.abstractness(fr))
102-
103-
# Dependency graph
104-
graph_presenter.dependency_graph(non_test_frameworks,
105-
analyzer.report.non_test_framework_aggregate.loc,
106-
analyzer.report.non_test_framework_aggregate.n_o_i)
107-
108-
# Code distribution
109-
graph_presenter.pie_plot('Code distribution', non_test_frameworks,
110-
lambda fr:
111-
ReportingHelpers.decimal_format(fr.data.loc
112-
/ analyzer.report.non_test_framework_aggregate.loc))
113-
114-
# Test graphs
115-
for title, framework_function in tests_reports_sorted_data.items():
116-
graph_presenter.sorted_data_plot(title, test_frameworks, framework_function)
79+
graphs_renderer = GraphsRender(
80+
artifacts_path=artifacts,
81+
test_frameworks=test_frameworks,
82+
non_test_frameworks=non_test_frameworks,
83+
report=analyzer.report
84+
)
85+
graphs_renderer.render_graphs()

swift_code_metrics/tests/test_metrics.py

Lines changed: 61 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -353,18 +353,53 @@ def setUp(self):
353353
self.submodule = SubModule(
354354
name="BusinessModule",
355355
files=[example_swiftfile],
356-
submodules=[
357-
SubModule(
358-
name="Helper",
359-
files=[example_file2],
360-
submodules=[]
361-
)
362-
]
356+
submodules=[],
357+
parent=None
363358
)
359+
self.helper = SubModule(
360+
name="Helper",
361+
files=[example_file2],
362+
submodules=[],
363+
parent=self.submodule
364+
)
365+
self.additional_module = SubModule(
366+
name="AdditionalModule",
367+
files=[example_file2],
368+
submodules=[],
369+
parent=self.submodule
370+
)
371+
self.additional_submodule = SubModule(
372+
name="AdditionalSubModule",
373+
files=[example_file2],
374+
submodules=[],
375+
parent=self.additional_module
376+
)
377+
self.additional_module.submodules.append(self.additional_submodule)
378+
self.submodule.submodules.append(self.helper)
364379

365380
def test_n_of_files(self):
366381
self.assertEqual(2, self.submodule.n_of_files)
367382

383+
def test_path(self):
384+
self.submodule.submodules.append(self.additional_module)
385+
self.assertEqual('BusinessModule > AdditionalModule > AdditionalSubModule', self.additional_submodule.path)
386+
387+
def test_next_only_module(self):
388+
self.additional_submodule.parent = None
389+
self.assertEqual(self.additional_submodule, self.additional_submodule.next)
390+
391+
def test_next_closed_circle(self):
392+
self.submodule.submodules.append(self.additional_module)
393+
# *
394+
# / \
395+
# H AM
396+
# \
397+
# AS
398+
self.assertEqual(self.helper, self.submodule.next)
399+
self.assertEqual(self.additional_module, self.helper.next)
400+
self.assertEqual(self.additional_submodule, self.additional_module.next)
401+
self.assertEqual(self.submodule, self.additional_submodule.next)
402+
368403
def test_data(self):
369404
data = SyntheticData(
370405
loc=2,
@@ -385,7 +420,7 @@ def test_empty_data(self):
385420
number_of_methods=0,
386421
number_of_tests=0
387422
)
388-
self.assertEqual(data, SubModule(name="", files=[], submodules=[]).data)
423+
self.assertEqual(data, SubModule(name="", files=[], submodules=[], parent=None).data)
389424

390425
def test_dict_repr(self):
391426
self.assertEqual({
@@ -399,7 +434,24 @@ def test_dict_repr(self):
399434
"nom": 8,
400435
"not": 2,
401436
"poc": 87.5
402-
}
437+
},
438+
"submodules": [
439+
{
440+
"Helper": {
441+
"n_of_files": 1,
442+
"metric": {
443+
"loc": 1,
444+
"n_a": 8,
445+
"n_c": 4,
446+
"noc": 7,
447+
"nom": 4,
448+
"not": 1,
449+
"poc": 87.5
450+
},
451+
"submodules": []
452+
}
453+
}
454+
]
403455
}
404456
}, self.submodule.as_dict)
405457

0 commit comments

Comments
 (0)