Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion swift_code_metrics/_analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ def __populate_submodule(framework: 'Framework', swift_file: 'SwiftFile'):
if len(list(existing_submodule)) > 0:
submodule = existing_submodule.first()
else:
new_submodule = SubModule(name=path, files=[], submodules=[])
new_submodule = SubModule(name=path, files=[], submodules=[], parent=submodule)
submodule.submodules.append(new_submodule)
submodule = new_submodule

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ def pie_plot(self, title, sizes, labels, legend):
plt.title(title)
patches, _, _ = plt.pie(sizes, labels=labels, autopct='%1.1f%%', shadow=True, startangle=90)
plt.legend(patches, legend, loc='best')
plt.tight_layout()
plt.axis('equal')
plt.tight_layout()

self.__render(plt, title)

Expand Down Expand Up @@ -84,7 +84,7 @@ def __render(self, plt, name):
plt.show()
else:
save_file = self.__file_path(name)
plt.savefig(save_file)
plt.savefig(save_file, bbox_inches='tight')
plt.close()

def __file_path(self, name, extension='.pdf'):
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from swift_code_metrics._metrics import Metrics
from ._graphics import Graph
from ._graph_helpers import Graph
from functional import seq
from math import ceil

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

self.graph.bar_plot(title, plot_data)

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

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

def submodules_pie_plot(self, title, list_of_submodules, f_of_submodules):
sorted_data = sorted(list(map(lambda s: (f_of_submodules(s),
s.name),
list_of_submodules)),
key=lambda tup: tup[0])
plot_data = (list(map(lambda f: f[0], sorted_data)),
list(map(lambda f: f[1], sorted_data)))

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

def distance_from_main_sequence_plot(self, list_of_frameworks, x_ax_f_framework, y_ax_f_framework):
"""
Renders framework related data to a scattered plot
Expand Down
96 changes: 96 additions & 0 deletions swift_code_metrics/_graphs_renderer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
from ._metrics import Metrics, SubModule
from ._report import ReportingHelpers
from dataclasses import dataclass
from typing import List
from ._graphs_presenter import GraphPresenter


@dataclass
class GraphsRender:
"Component responsible to generate the needed graphs for the given report."
artifacts_path: str
test_frameworks: List['Framework']
non_test_frameworks: List['Framework']
report: 'Report'

def render_graphs(self):
graph_presenter = GraphPresenter(self.artifacts_path)

# Project graphs
self.__project_graphs(graph_presenter=graph_presenter)

# Submodules graphs
self.__submodules_graphs(graph_presenter=graph_presenter)

def __project_graphs(self, graph_presenter: 'GraphPresenter'):
# Sorted data plots
non_test_reports_sorted_data = {
'N. of classes and structs': lambda fr: fr.data.number_of_concrete_data_structures,
'Lines Of Code - LOC': lambda fr: fr.data.loc,
'Number Of Comments - NOC': lambda fr: fr.data.noc,
'N. of imports - NOI': lambda fr: fr.number_of_imports
}

tests_reports_sorted_data = {
'Number of tests - NOT': lambda fr: fr.data.number_of_tests
}

# Non-test graphs
for title, framework_function in non_test_reports_sorted_data.items():
graph_presenter.sorted_data_plot(title, self.non_test_frameworks, framework_function)

# Distance from the main sequence
all_frameworks = self.test_frameworks + self.non_test_frameworks
graph_presenter.distance_from_main_sequence_plot(self.non_test_frameworks,
lambda fr: Metrics.instability(fr, all_frameworks),
lambda fr: Metrics.abstractness(fr))

# Dependency graph
graph_presenter.dependency_graph(self.non_test_frameworks,
self.report.non_test_framework_aggregate.loc,
self.report.non_test_framework_aggregate.n_o_i)

# Code distribution
graph_presenter.frameworks_pie_plot('Code distribution', self.non_test_frameworks,
lambda fr:
ReportingHelpers.decimal_format(fr.data.loc
/ self.report.non_test_framework_aggregate.loc))

# Test graphs
for title, framework_function in tests_reports_sorted_data.items():
graph_presenter.sorted_data_plot(title, self.test_frameworks, framework_function)

def __submodules_graphs(self, graph_presenter: 'GraphPresenter'):
for framework in self.non_test_frameworks:
GraphsRender.__render_submodules(parent='Code distribution',
root_submodule=framework.submodule,
graph_presenter=graph_presenter)

@staticmethod
def __render_submodules(parent: str, root_submodule: 'SubModule', graph_presenter: 'GraphPresenter'):
current_submodule = root_submodule.next
while current_submodule != root_submodule:
GraphsRender.__render_submodule_loc(parent=parent,
submodule=current_submodule,
graph_presenter=graph_presenter)
current_submodule = current_submodule.next

@staticmethod
def __render_submodule_loc(parent: str, submodule: 'SubModule', graph_presenter: 'GraphPresenter'):
submodules = submodule.submodules
if len(submodules) == 0:
return
total_loc = submodule.data.loc
if total_loc == submodules[0].data.loc:
# Single submodule folder - not useful
return
if len(submodule.files) > 0:
# Add a submodule to represent the root slice
submodules = submodules + [SubModule(name='(root)',
files=submodule.files,
submodules=[],
parent=submodule)]

chart_name = f'{parent} {submodule.path}'
graph_presenter.submodules_pie_plot(chart_name, submodules,
lambda s: ReportingHelpers.decimal_format(s.data.loc / total_loc))
2 changes: 1 addition & 1 deletion swift_code_metrics/_helpers.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import re
import logging
import json
from typing import List,Dict
from typing import Dict
from functional import seq


Expand Down
32 changes: 30 additions & 2 deletions swift_code_metrics/_metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,8 @@ def __init__(self, name: str, is_test_framework: bool = False):
self.submodule = SubModule(
name=self.name,
files=[],
submodules=[]
submodules=[],
parent=None
)
self.is_test_framework = is_test_framework

Expand Down Expand Up @@ -423,13 +424,39 @@ class SubModule:
name: str
files: List['SwiftFile']
submodules: List['SubModule']
parent: Optional['SubModule']

@property
def next(self) -> 'SubModule':
if len(self.submodules) == 0:
if self.parent is None:
return self
else:
return self.submodules[0]

next_level = self.parent
current_level = self
while next_level is not None:
next_i = next_level.submodules.index(current_level) + 1
if next_i < len(next_level.submodules):
return next_level.submodules[next_i]
else:
current_level = next_level
next_level = next_level.parent

return current_level

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

@property
def path(self) -> str:
parent_path = "" if not self.parent else f'{self.parent.path} > '
return f'{parent_path}{self.name}'

@property
def data(self) -> 'SyntheticData':
root_module_files = [SyntheticData()] if (len(self.files) == 0) else \
Expand All @@ -443,7 +470,8 @@ def as_dict(self) -> Dict:
return {
self.name: {
"n_of_files": self.n_of_files,
"metric": self.data.as_dict
"metric": self.data.as_dict,
"submodules": [s.as_dict for s in self.submodules]
}
}

Expand Down
1 change: 1 addition & 0 deletions swift_code_metrics/_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ def __framework_analysis(framework: 'Framework', frameworks: List['Framework'])
"noi": n_of_imports,
"analysis": analysis,
"dependencies": dependencies,
"submodules": framework.submodule.as_dict
}

return {
Expand Down
51 changes: 10 additions & 41 deletions swift_code_metrics/scm.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
#!/usr/bin/python
#!/usr/bin/python3

from argparse import ArgumentParser

from ._helpers import Log,ReportingHelpers
from ._helpers import Log
from ._analyzer import Inspector
from ._metrics import Metrics
from .version import VERSION
import sys

Expand Down Expand Up @@ -74,43 +73,13 @@ def main():
sys.exit(0)

# Creates graphs
from ._presenter import GraphPresenter
graph_presenter = GraphPresenter(artifacts)
from ._graphs_renderer import GraphsRender
non_test_frameworks = analyzer.filtered_frameworks(is_test=False)
test_frameworks = analyzer.filtered_frameworks(is_test=True)

# Sorted data plots
non_test_reports_sorted_data = {
'N. of classes and structs': lambda fr: fr.data.number_of_concrete_data_structures,
'Lines Of Code - LOC': lambda fr: fr.data.loc,
'Number Of Comments - NOC': lambda fr: fr.data.noc,
'N. of imports - NOI': lambda fr: fr.number_of_imports
}

tests_reports_sorted_data = {
'Number of tests - NOT': lambda fr: fr.data.number_of_tests
}

# Non-test graphs
for title, framework_function in non_test_reports_sorted_data.items():
graph_presenter.sorted_data_plot(title, non_test_frameworks, framework_function)

# Distance from the main sequence
graph_presenter.distance_from_main_sequence_plot(non_test_frameworks,
lambda fr: Metrics.instability(fr, analyzer.frameworks),
lambda fr: Metrics.abstractness(fr))

# Dependency graph
graph_presenter.dependency_graph(non_test_frameworks,
analyzer.report.non_test_framework_aggregate.loc,
analyzer.report.non_test_framework_aggregate.n_o_i)

# Code distribution
graph_presenter.pie_plot('Code distribution', non_test_frameworks,
lambda fr:
ReportingHelpers.decimal_format(fr.data.loc
/ analyzer.report.non_test_framework_aggregate.loc))

# Test graphs
for title, framework_function in tests_reports_sorted_data.items():
graph_presenter.sorted_data_plot(title, test_frameworks, framework_function)
graphs_renderer = GraphsRender(
artifacts_path=artifacts,
test_frameworks=test_frameworks,
non_test_frameworks=non_test_frameworks,
report=analyzer.report
)
graphs_renderer.render_graphs()
70 changes: 61 additions & 9 deletions swift_code_metrics/tests/test_metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -353,18 +353,53 @@ def setUp(self):
self.submodule = SubModule(
name="BusinessModule",
files=[example_swiftfile],
submodules=[
SubModule(
name="Helper",
files=[example_file2],
submodules=[]
)
]
submodules=[],
parent=None
)
self.helper = SubModule(
name="Helper",
files=[example_file2],
submodules=[],
parent=self.submodule
)
self.additional_module = SubModule(
name="AdditionalModule",
files=[example_file2],
submodules=[],
parent=self.submodule
)
self.additional_submodule = SubModule(
name="AdditionalSubModule",
files=[example_file2],
submodules=[],
parent=self.additional_module
)
self.additional_module.submodules.append(self.additional_submodule)
self.submodule.submodules.append(self.helper)

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

def test_path(self):
self.submodule.submodules.append(self.additional_module)
self.assertEqual('BusinessModule > AdditionalModule > AdditionalSubModule', self.additional_submodule.path)

def test_next_only_module(self):
self.additional_submodule.parent = None
self.assertEqual(self.additional_submodule, self.additional_submodule.next)

def test_next_closed_circle(self):
self.submodule.submodules.append(self.additional_module)
# *
# / \
# H AM
# \
# AS
self.assertEqual(self.helper, self.submodule.next)
self.assertEqual(self.additional_module, self.helper.next)
self.assertEqual(self.additional_submodule, self.additional_module.next)
self.assertEqual(self.submodule, self.additional_submodule.next)

def test_data(self):
data = SyntheticData(
loc=2,
Expand All @@ -385,7 +420,7 @@ def test_empty_data(self):
number_of_methods=0,
number_of_tests=0
)
self.assertEqual(data, SubModule(name="", files=[], submodules=[]).data)
self.assertEqual(data, SubModule(name="", files=[], submodules=[], parent=None).data)

def test_dict_repr(self):
self.assertEqual({
Expand All @@ -399,7 +434,24 @@ def test_dict_repr(self):
"nom": 8,
"not": 2,
"poc": 87.5
}
},
"submodules": [
{
"Helper": {
"n_of_files": 1,
"metric": {
"loc": 1,
"n_a": 8,
"n_c": 4,
"noc": 7,
"nom": 4,
"not": 1,
"poc": 87.5
},
"submodules": []
}
}
]
}
}, self.submodule.as_dict)

Expand Down
Loading