metripy 0.2.8__py3-none-any.whl → 0.3.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of metripy might be problematic. Click here for more details.
- metripy/Application/Analyzer.py +23 -3
- metripy/Application/Application.py +16 -2
- metripy/Application/Config/Config.py +34 -0
- metripy/Application/Config/File/ConfigFileReaderFactory.py +6 -5
- metripy/Application/Config/File/ConfigFileReaderInterface.py +70 -3
- metripy/Application/Config/File/JsonConfigFileReader.py +5 -70
- metripy/Application/Config/File/YamlConfigFileReader.py +17 -0
- metripy/Application/Config/Parser.py +24 -11
- metripy/Application/Config/ProjectConfig.py +64 -0
- metripy/Application/Info.py +36 -0
- metripy/Dependency/Dependency.py +2 -1
- metripy/Dependency/Pip/Pip.py +1 -2
- metripy/Dependency/Pip/PyPi.py +1 -0
- metripy/Git/GitAnalyzer.py +0 -3
- metripy/Import/Json/JsonImporter.py +17 -0
- metripy/LangAnalyzer/AbstractLangAnalyzer.py +4 -3
- metripy/LangAnalyzer/Php/PhpAnalyzer.py +2 -1
- metripy/LangAnalyzer/Python/PythonAnalyzer.py +31 -9
- metripy/LangAnalyzer/Python/PythonHalSteadAnalyzer.py +55 -0
- metripy/LangAnalyzer/Typescript/TypescriptAnalyzer.py +12 -9
- metripy/LangAnalyzer/Typescript/TypescriptAstParser.py +1 -1
- metripy/Metric/Code/AggregatedMetrics.py +12 -5
- metripy/Metric/Code/FileMetrics.py +32 -1
- metripy/Metric/Code/ModuleMetrics.py +5 -5
- metripy/Metric/Code/SegmentedMetrics.py +72 -36
- metripy/Metric/Code/Segmentor.py +44 -0
- metripy/Metric/FileTree/FileTreeParser.py +0 -4
- metripy/Metric/Git/GitMetrics.py +1 -1
- metripy/Metric/ProjectMetrics.py +17 -2
- metripy/Metric/Trend/AggregatedTrendMetric.py +101 -0
- metripy/Metric/Trend/ClassTrendMetric.py +20 -0
- metripy/Metric/Trend/FileTrendMetric.py +46 -0
- metripy/Metric/Trend/FunctionTrendMetric.py +28 -0
- metripy/Metric/Trend/SegmentedTrendMetric.py +29 -0
- metripy/Report/Html/DependencyPageRenderer.py +21 -0
- metripy/Report/Html/FilesPageRenderer.py +28 -0
- metripy/Report/Html/GitAnalysisPageRenderer.py +55 -0
- metripy/Report/Html/IndexPageRenderer.py +40 -0
- metripy/Report/Html/PageRenderer.py +43 -0
- metripy/Report/Html/PageRendererFactory.py +37 -0
- metripy/Report/Html/Reporter.py +49 -130
- metripy/Report/Html/TopOffendersPageRenderer.py +84 -0
- metripy/Report/Html/TrendsPageRenderer.py +114 -0
- metripy/Report/Json/GitJsonReporter.py +3 -1
- metripy/Report/Json/JsonReporter.py +4 -1
- metripy/Report/ReporterFactory.py +4 -2
- metripy/Tree/ClassNode.py +21 -0
- metripy/Tree/FunctionNode.py +66 -1
- metripy/Trend/TrendAnalyzer.py +150 -0
- {metripy-0.2.8.dist-info → metripy-0.3.1.dist-info}/METADATA +3 -3
- metripy-0.3.1.dist-info/RECORD +85 -0
- metripy-0.2.8.dist-info/RECORD +0 -66
- {metripy-0.2.8.dist-info → metripy-0.3.1.dist-info}/WHEEL +0 -0
- {metripy-0.2.8.dist-info → metripy-0.3.1.dist-info}/entry_points.txt +0 -0
- {metripy-0.2.8.dist-info → metripy-0.3.1.dist-info}/licenses/LICENSE +0 -0
- {metripy-0.2.8.dist-info → metripy-0.3.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
class FileTrendMetric:
|
|
2
|
+
def __init__(
|
|
3
|
+
self,
|
|
4
|
+
historical_loc: int,
|
|
5
|
+
loc: int,
|
|
6
|
+
historical_totalCc: int,
|
|
7
|
+
totalCc: int,
|
|
8
|
+
historical_avgCcPerFunction: float,
|
|
9
|
+
avgCcPerFunction: float,
|
|
10
|
+
historical_maintainabilityIndex: float,
|
|
11
|
+
maintainabilityIndex: float,
|
|
12
|
+
historical_avgLocPerFunction: float,
|
|
13
|
+
avgLocPerFunction: float,
|
|
14
|
+
):
|
|
15
|
+
self.historical_loc = historical_loc
|
|
16
|
+
self.loc_delta = loc - historical_loc
|
|
17
|
+
|
|
18
|
+
self.historical_totalCc = historical_totalCc
|
|
19
|
+
self.totalCc_delta = totalCc - historical_totalCc
|
|
20
|
+
|
|
21
|
+
self.historical_avgCcPerFunction = historical_avgCcPerFunction
|
|
22
|
+
self.avgCcPerFunction_delta = avgCcPerFunction - historical_avgCcPerFunction
|
|
23
|
+
|
|
24
|
+
self.maintainabilityIndex_delta = (
|
|
25
|
+
maintainabilityIndex - historical_maintainabilityIndex
|
|
26
|
+
)
|
|
27
|
+
self.historical_maintainabilityIndex = historical_maintainabilityIndex
|
|
28
|
+
|
|
29
|
+
self.avgLocPerFunction_delta = avgLocPerFunction - historical_avgLocPerFunction
|
|
30
|
+
self.historical_avgLocPerFunction = historical_avgLocPerFunction
|
|
31
|
+
|
|
32
|
+
def to_dict(self) -> dict:
|
|
33
|
+
return {
|
|
34
|
+
"historical_loc": round(self.historical_loc, 2),
|
|
35
|
+
"loc_delta": round(self.loc_delta, 2),
|
|
36
|
+
"historical_totalCc": round(self.historical_totalCc, 2),
|
|
37
|
+
"totalCc_delta": round(self.totalCc_delta, 2),
|
|
38
|
+
"historical_avgCcPerFunction": round(self.historical_avgCcPerFunction, 2),
|
|
39
|
+
"avgCcPerFunction_delta": round(self.avgCcPerFunction_delta, 2),
|
|
40
|
+
"historical_maintainabilityIndex": round(
|
|
41
|
+
self.historical_maintainabilityIndex, 2
|
|
42
|
+
),
|
|
43
|
+
"maintainabilityIndex_delta": round(self.maintainabilityIndex_delta, 2),
|
|
44
|
+
"historical_avgLocPerFunction": round(self.historical_avgLocPerFunction, 2),
|
|
45
|
+
"avgLocPerFunction_delta": round(self.avgLocPerFunction_delta, 2),
|
|
46
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
class FunctionTrendMetric:
|
|
2
|
+
def __init__(
|
|
3
|
+
self,
|
|
4
|
+
historical_loc: int,
|
|
5
|
+
loc: int,
|
|
6
|
+
historical_complexity: int,
|
|
7
|
+
complexity: int,
|
|
8
|
+
historical_maintainability_index: float,
|
|
9
|
+
maintainability_index: float,
|
|
10
|
+
):
|
|
11
|
+
self.historical_loc = historical_loc
|
|
12
|
+
self.loc_delta = loc - historical_loc
|
|
13
|
+
self.historical_complexity = historical_complexity
|
|
14
|
+
self.complexity_delta = complexity - historical_complexity
|
|
15
|
+
self.historical_maintainability_index = historical_maintainability_index
|
|
16
|
+
self.maintainability_index_delta = (
|
|
17
|
+
maintainability_index - historical_maintainability_index
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
def to_dict(self) -> dict:
|
|
21
|
+
return {
|
|
22
|
+
"historical_loc": self.historical_loc,
|
|
23
|
+
"loc_delta": self.loc_delta,
|
|
24
|
+
"historical_complexity": self.historical_complexity,
|
|
25
|
+
"complexity_delta": self.complexity_delta,
|
|
26
|
+
"historical_maintainability_index": self.historical_maintainability_index,
|
|
27
|
+
"maintainability_index_delta": self.maintainability_index_delta,
|
|
28
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from metripy.Metric.Code.SegmentedMetrics import SegmentedMetrics
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class SegmentedTrendMetric:
|
|
5
|
+
def __init__(
|
|
6
|
+
self,
|
|
7
|
+
historical_segmentation: SegmentedMetrics,
|
|
8
|
+
segmentation: SegmentedMetrics,
|
|
9
|
+
):
|
|
10
|
+
self.historical_good = historical_segmentation.good
|
|
11
|
+
self.good_delta = segmentation.good - historical_segmentation.good
|
|
12
|
+
self.historical_ok = historical_segmentation.ok
|
|
13
|
+
self.ok_delta = segmentation.ok - historical_segmentation.ok
|
|
14
|
+
self.historical_warning = historical_segmentation.warning
|
|
15
|
+
self.warning_delta = segmentation.warning - historical_segmentation.warning
|
|
16
|
+
self.historical_critical = historical_segmentation.critical
|
|
17
|
+
self.critical_delta = segmentation.critical - historical_segmentation.critical
|
|
18
|
+
|
|
19
|
+
def to_dict(self) -> dict:
|
|
20
|
+
return {
|
|
21
|
+
"historical_good": self.historical_good,
|
|
22
|
+
"good_delta": self.good_delta,
|
|
23
|
+
"historical_ok": self.historical_ok,
|
|
24
|
+
"ok_delta": self.ok_delta,
|
|
25
|
+
"historical_warning": self.historical_warning,
|
|
26
|
+
"warning_delta": self.warning_delta,
|
|
27
|
+
"historical_critical": self.historical_critical,
|
|
28
|
+
"critical_delta": self.critical_delta,
|
|
29
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from metripy.Dependency.Dependency import Dependency
|
|
2
|
+
from metripy.Metric.ProjectMetrics import ProjectMetrics
|
|
3
|
+
from metripy.Report.Html.PageRenderer import PageRenderer
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class DependencyPageRenderer(PageRenderer):
|
|
7
|
+
def __init__(self, template_dir: str, output_dir: str, project_name: str):
|
|
8
|
+
super().__init__(template_dir, output_dir, project_name)
|
|
9
|
+
|
|
10
|
+
def render(self, metrics: ProjectMetrics):
|
|
11
|
+
dependencies = metrics.dependencies if metrics.dependencies is not None else []
|
|
12
|
+
license_by_type = Dependency.get_lisence_distribution(dependencies)
|
|
13
|
+
|
|
14
|
+
self.render_template(
|
|
15
|
+
"dependencies.html",
|
|
16
|
+
{
|
|
17
|
+
"has_dependencies_data": bool(metrics.dependencies),
|
|
18
|
+
"dependencies": [d.to_dict() for d in dependencies],
|
|
19
|
+
"license_distribution": license_by_type,
|
|
20
|
+
},
|
|
21
|
+
)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
from metripy.Metric.FileTree.FileTreeParser import FileTreeParser
|
|
4
|
+
from metripy.Metric.ProjectMetrics import ProjectMetrics
|
|
5
|
+
from metripy.Report.Html.PageRenderer import PageRenderer
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class FilesPageRenderer(PageRenderer):
|
|
9
|
+
def __init__(self, template_dir: str, output_dir: str, project_name: str):
|
|
10
|
+
super().__init__(template_dir, output_dir, project_name)
|
|
11
|
+
|
|
12
|
+
def render(self, metrics: ProjectMetrics):
|
|
13
|
+
file_names = []
|
|
14
|
+
file_details = {}
|
|
15
|
+
for file_metrics in metrics.file_metrics:
|
|
16
|
+
file_name = file_metrics.full_name
|
|
17
|
+
file_details[file_name] = file_metrics.to_dict()
|
|
18
|
+
file_names.append(file_name)
|
|
19
|
+
|
|
20
|
+
filetree = FileTreeParser.parse(file_names, shorten=True)
|
|
21
|
+
|
|
22
|
+
self.render_template(
|
|
23
|
+
"files.html",
|
|
24
|
+
{
|
|
25
|
+
"filetree": json.dumps(filetree.to_dict()),
|
|
26
|
+
"file_details": json.dumps(file_details),
|
|
27
|
+
},
|
|
28
|
+
)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
from metripy.Metric.ProjectMetrics import ProjectMetrics
|
|
4
|
+
from metripy.Report.Html.PageRenderer import PageRenderer
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class GitAnalysisPageRenderer(PageRenderer):
|
|
8
|
+
def __init__(self, template_dir: str, output_dir: str, project_name: str):
|
|
9
|
+
super().__init__(template_dir, output_dir, project_name)
|
|
10
|
+
|
|
11
|
+
def _render_empty_template(self):
|
|
12
|
+
self.render_template(
|
|
13
|
+
"git_analysis.html",
|
|
14
|
+
{
|
|
15
|
+
"has_git_analysis_data": False,
|
|
16
|
+
"git_analysis": {},
|
|
17
|
+
"git_analysis_json": "{}",
|
|
18
|
+
"git_stats_data": "{}",
|
|
19
|
+
"git_churn_data": "{}",
|
|
20
|
+
"git_silos_data": [],
|
|
21
|
+
"git_contributors": [],
|
|
22
|
+
"git_hotspots_data": [],
|
|
23
|
+
},
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
def render(self, metrics: ProjectMetrics):
|
|
27
|
+
if not metrics.git_metrics:
|
|
28
|
+
self._render_empty_template()
|
|
29
|
+
return
|
|
30
|
+
|
|
31
|
+
self.render_template(
|
|
32
|
+
"git_analysis.html",
|
|
33
|
+
{
|
|
34
|
+
"has_git_analysis_data": bool(metrics.git_metrics),
|
|
35
|
+
"git_analysis": metrics.git_metrics.to_dict(),
|
|
36
|
+
"git_analysis_json": json.dumps(
|
|
37
|
+
metrics.git_metrics.get_contributors_dict(), indent=4
|
|
38
|
+
),
|
|
39
|
+
"git_stats_data": json.dumps(
|
|
40
|
+
metrics.git_metrics.get_commit_stats_per_month(), indent=4
|
|
41
|
+
), # git commit graph
|
|
42
|
+
"git_churn_data": json.dumps(
|
|
43
|
+
metrics.git_metrics.get_churn_per_month(), indent=4
|
|
44
|
+
), # git chrun graph
|
|
45
|
+
"git_silos_data": metrics.git_metrics.get_silos_list()[
|
|
46
|
+
:10
|
|
47
|
+
], # silos list
|
|
48
|
+
"git_contributors": metrics.git_metrics.get_contributors_list()[
|
|
49
|
+
:10
|
|
50
|
+
], # contributors list
|
|
51
|
+
"git_hotspots_data": metrics.git_metrics.get_hotspots_list()[
|
|
52
|
+
:10
|
|
53
|
+
], # hotspots list
|
|
54
|
+
},
|
|
55
|
+
)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
from metripy.Metric.ProjectMetrics import ProjectMetrics
|
|
4
|
+
from metripy.Report.Html.PageRenderer import PageRenderer
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class IndexPageRenderer(PageRenderer):
|
|
8
|
+
def __init__(self, template_dir: str, output_dir: str, project_name: str):
|
|
9
|
+
super().__init__(template_dir, output_dir, project_name)
|
|
10
|
+
|
|
11
|
+
def render(self, metrics: ProjectMetrics):
|
|
12
|
+
git_stats_data = {}
|
|
13
|
+
if metrics.git_metrics:
|
|
14
|
+
git_stats_data = metrics.git_metrics.get_commit_stats_per_month()
|
|
15
|
+
|
|
16
|
+
self.render_template(
|
|
17
|
+
"index.html",
|
|
18
|
+
{
|
|
19
|
+
"git_stats_data": json.dumps(git_stats_data, indent=4),
|
|
20
|
+
"total_code_metrics": metrics.total_code_metrics.to_dict(),
|
|
21
|
+
"has_total_code_metrics_trend": metrics.total_code_metrics.trend
|
|
22
|
+
is not None,
|
|
23
|
+
"total_code_metrics_trend": (
|
|
24
|
+
metrics.total_code_metrics.trend.to_dict()
|
|
25
|
+
if metrics.total_code_metrics.trend
|
|
26
|
+
else None
|
|
27
|
+
),
|
|
28
|
+
"segmentation_data": json.dumps(
|
|
29
|
+
metrics.total_code_metrics.to_dict_segmentation(), indent=4
|
|
30
|
+
),
|
|
31
|
+
"segmentation_data_trend": (
|
|
32
|
+
json.dumps(
|
|
33
|
+
metrics.total_code_metrics.trend.to_dict_segmentation(),
|
|
34
|
+
indent=4,
|
|
35
|
+
)
|
|
36
|
+
if metrics.total_code_metrics.trend
|
|
37
|
+
else None
|
|
38
|
+
),
|
|
39
|
+
},
|
|
40
|
+
)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
|
|
4
|
+
from py_template_engine import TemplateEngine
|
|
5
|
+
|
|
6
|
+
from metripy.Application.Info import Info
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class PageRenderer:
|
|
10
|
+
def __init__(self, template_dir: str, output_dir: str, project_name: str):
|
|
11
|
+
self.template_dir = template_dir
|
|
12
|
+
self.output_dir = output_dir
|
|
13
|
+
|
|
14
|
+
self.global_template_args = {
|
|
15
|
+
"project_name": project_name,
|
|
16
|
+
"last_updated": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
|
17
|
+
"date": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
|
18
|
+
"author": "Metripy",
|
|
19
|
+
"version": Info().get_version(),
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@staticmethod
|
|
23
|
+
def _stringify_values(obj):
|
|
24
|
+
if isinstance(obj, dict):
|
|
25
|
+
return {
|
|
26
|
+
key: PageRenderer._stringify_values(value) for key, value in obj.items()
|
|
27
|
+
}
|
|
28
|
+
elif isinstance(obj, list):
|
|
29
|
+
return [PageRenderer._stringify_values(item) for item in obj]
|
|
30
|
+
else:
|
|
31
|
+
return str(obj)
|
|
32
|
+
|
|
33
|
+
def render_template(self, template_name: str, data: dict):
|
|
34
|
+
data = self._stringify_values(
|
|
35
|
+
{
|
|
36
|
+
**self.global_template_args,
|
|
37
|
+
**data,
|
|
38
|
+
}
|
|
39
|
+
)
|
|
40
|
+
engine = TemplateEngine(os.path.join(self.template_dir, template_name))
|
|
41
|
+
content = engine.render(**data)
|
|
42
|
+
with open(os.path.join(self.output_dir, template_name), "w") as file:
|
|
43
|
+
file.write(content)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from metripy.Report.Html.DependencyPageRenderer import DependencyPageRenderer
|
|
2
|
+
from metripy.Report.Html.FilesPageRenderer import FilesPageRenderer
|
|
3
|
+
from metripy.Report.Html.GitAnalysisPageRenderer import GitAnalysisPageRenderer
|
|
4
|
+
from metripy.Report.Html.IndexPageRenderer import IndexPageRenderer
|
|
5
|
+
from metripy.Report.Html.TopOffendersPageRenderer import TopOffendersPageRenderer
|
|
6
|
+
from metripy.Report.Html.TrendsPageRenderer import TrendsPageRenderer
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class PageRendererFactory:
|
|
10
|
+
def __init__(self, template_dir: str, output_dir: str, project_name: str):
|
|
11
|
+
self.template_dir = template_dir
|
|
12
|
+
self.output_dir = output_dir
|
|
13
|
+
self.project_name = project_name
|
|
14
|
+
|
|
15
|
+
def create_index_page_renderer(self) -> IndexPageRenderer:
|
|
16
|
+
return IndexPageRenderer(self.template_dir, self.output_dir, self.project_name)
|
|
17
|
+
|
|
18
|
+
def create_files_page_renderer(self) -> FilesPageRenderer:
|
|
19
|
+
return FilesPageRenderer(self.template_dir, self.output_dir, self.project_name)
|
|
20
|
+
|
|
21
|
+
def create_top_offenders_page_renderer(self) -> TopOffendersPageRenderer:
|
|
22
|
+
return TopOffendersPageRenderer(
|
|
23
|
+
self.template_dir, self.output_dir, self.project_name
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
def create_git_analysis_page_renderer(self) -> GitAnalysisPageRenderer:
|
|
27
|
+
return GitAnalysisPageRenderer(
|
|
28
|
+
self.template_dir, self.output_dir, self.project_name
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
def create_dependency_page_renderer(self) -> DependencyPageRenderer:
|
|
32
|
+
return DependencyPageRenderer(
|
|
33
|
+
self.template_dir, self.output_dir, self.project_name
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
def create_trends_page_renderer(self) -> TrendsPageRenderer:
|
|
37
|
+
return TrendsPageRenderer(self.template_dir, self.output_dir, self.project_name)
|
metripy/Report/Html/Reporter.py
CHANGED
|
@@ -1,16 +1,13 @@
|
|
|
1
|
-
import json
|
|
2
1
|
import os
|
|
3
2
|
import shutil
|
|
4
3
|
from datetime import datetime
|
|
5
4
|
|
|
6
|
-
from py_template_engine import TemplateEngine
|
|
7
5
|
|
|
8
6
|
from metripy.Application.Config.ReportConfig import ReportConfig
|
|
9
7
|
from metripy.Component.Output.CliOutput import CliOutput
|
|
10
|
-
from metripy.Metric.FileTree.FileTreeParser import FileTreeParser
|
|
11
8
|
from metripy.Metric.ProjectMetrics import ProjectMetrics
|
|
9
|
+
from metripy.Report.Html.PageRendererFactory import PageRendererFactory
|
|
12
10
|
from metripy.Report.ReporterInterface import ReporterInterface
|
|
13
|
-
from metripy.Dependency.Dependency import Dependency
|
|
14
11
|
|
|
15
12
|
|
|
16
13
|
class Reporter(ReporterInterface):
|
|
@@ -20,6 +17,11 @@ class Reporter(ReporterInterface):
|
|
|
20
17
|
self.config: ReportConfig = config
|
|
21
18
|
self.output = output
|
|
22
19
|
self.template_dir = os.path.join(os.getcwd(), "templates/html_report")
|
|
20
|
+
self.project_name = project_name
|
|
21
|
+
|
|
22
|
+
self.page_renderer_factory = PageRendererFactory(
|
|
23
|
+
self.template_dir, self.config.path, self.project_name
|
|
24
|
+
)
|
|
23
25
|
|
|
24
26
|
self.global_template_args = {
|
|
25
27
|
"project_name": project_name,
|
|
@@ -54,152 +56,69 @@ class Reporter(ReporterInterface):
|
|
|
54
56
|
os.path.join(self.config.path, "css"),
|
|
55
57
|
dirs_exist_ok=True,
|
|
56
58
|
)
|
|
57
|
-
|
|
59
|
+
shutil.copytree(
|
|
60
|
+
os.path.join(self.template_dir, "images"),
|
|
61
|
+
os.path.join(self.config.path, "images"),
|
|
62
|
+
dirs_exist_ok=True,
|
|
63
|
+
)
|
|
58
64
|
# shutil.copytree(os.path.join(self.template_dir, "fonts"), os.path.join(self.config.path, "fonts"), dirs_exist_ok=True)
|
|
59
65
|
|
|
60
|
-
#
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
self.output.writeln("<info>Rendering index page</info>")
|
|
66
|
-
# Render main index page
|
|
67
|
-
self.render_template(
|
|
68
|
-
"index.html",
|
|
69
|
-
{
|
|
70
|
-
"git_stats_data": json.dumps(git_stats_data, indent=4),
|
|
71
|
-
"total_code_metrics": metrics.total_code_metrics.to_dict(),
|
|
72
|
-
"segmentation_data": json.dumps(
|
|
73
|
-
metrics.total_code_metrics.to_dict_segmentation(), indent=4
|
|
74
|
-
),
|
|
75
|
-
"project_name": "Metripy",
|
|
76
|
-
"last_updated": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
|
77
|
-
"date": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
|
78
|
-
"author": "Metripy",
|
|
79
|
-
"version": "1.0.0",
|
|
80
|
-
},
|
|
66
|
+
# copy logo, lies 2 down from the templates directory
|
|
67
|
+
shutil.copy(
|
|
68
|
+
os.path.join(self.template_dir, "../..", "logo.svg"),
|
|
69
|
+
os.path.join(self.config.path, "images", "logo.svg"),
|
|
81
70
|
)
|
|
82
|
-
self.output.writeln("<success>Done rendering index page</success>")
|
|
83
71
|
|
|
84
|
-
# Render
|
|
72
|
+
# Render main pages
|
|
73
|
+
self.render_index_page(metrics)
|
|
85
74
|
self.render_files_page(metrics)
|
|
86
|
-
|
|
75
|
+
self.render_top_offenders_page(metrics)
|
|
87
76
|
self.render_git_analysis_page(metrics)
|
|
88
|
-
|
|
89
77
|
self.render_dependencies_page(metrics)
|
|
78
|
+
self.render_trends_page(metrics)
|
|
90
79
|
|
|
91
80
|
self.output.writeln(
|
|
92
81
|
f"<success>HTML report generated in {self.config.path} directory</success>"
|
|
93
82
|
)
|
|
94
|
-
|
|
95
|
-
def render_template(self, template_name: str, data: dict) -> str:
|
|
96
|
-
engine = TemplateEngine(os.path.join(self.template_dir, template_name))
|
|
97
|
-
content = engine.render(**data)
|
|
98
|
-
with open(os.path.join(self.config.path, template_name), "w") as file:
|
|
99
|
-
file.write(content)
|
|
100
|
-
|
|
101
|
-
def render_dependencies_page(self, metrics: ProjectMetrics):
|
|
102
|
-
"""Render the dependencies page with dependency details and stats"""
|
|
103
|
-
if not metrics.dependencies:
|
|
104
|
-
self.output.writeln("<success>No dependencies to render</success>")
|
|
105
|
-
return
|
|
106
|
-
|
|
107
|
-
self.output.writeln("<info>Rendering dependencies page</info>")
|
|
108
|
-
|
|
109
|
-
dependencies = metrics.dependencies if metrics.dependencies is not None else []
|
|
110
|
-
|
|
111
|
-
# TODO render a pie chart
|
|
112
|
-
license_by_type = Dependency.get_lisence_distribution(dependencies)
|
|
113
|
-
|
|
114
|
-
print(json.dumps(license_by_type, indent=2))
|
|
115
|
-
|
|
116
|
-
self.render_template(
|
|
117
|
-
"dependencies.html",
|
|
118
|
-
{
|
|
119
|
-
"dependencies": [d.to_dict() for d in dependencies],
|
|
120
|
-
"project_name": "Metripy",
|
|
121
|
-
"last_updated": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
|
122
|
-
},
|
|
123
|
-
)
|
|
124
83
|
self.output.writeln(
|
|
125
|
-
"<success>
|
|
84
|
+
f"<success>Open HTML report: {self.config.path}/index.html</success>"
|
|
126
85
|
)
|
|
127
86
|
|
|
87
|
+
def render_index_page(self, metrics: ProjectMetrics):
|
|
88
|
+
self.output.writeln("<info>Rendering index page</info>")
|
|
89
|
+
self.page_renderer_factory.create_index_page_renderer().render(metrics)
|
|
90
|
+
self.output.writeln("<success>Done rendering index page</success>")
|
|
91
|
+
|
|
128
92
|
def render_files_page(self, metrics: ProjectMetrics):
|
|
129
93
|
"""Render the files page with file details and analysis"""
|
|
130
94
|
self.output.writeln("<info>Rendering files page</info>")
|
|
95
|
+
self.page_renderer_factory.create_files_page_renderer().render(metrics)
|
|
96
|
+
self.output.writeln("<success>Files page generated successfully</success>")
|
|
131
97
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
file_names.append(file_name)
|
|
138
|
-
|
|
139
|
-
filetree = FileTreeParser.parse(file_names, shorten=True)
|
|
140
|
-
|
|
141
|
-
self.render_template(
|
|
142
|
-
"files.html",
|
|
143
|
-
{
|
|
144
|
-
"filetree": json.dumps(filetree.to_dict()),
|
|
145
|
-
"file_details": json.dumps(file_details),
|
|
146
|
-
"project_name": "Metripy",
|
|
147
|
-
"last_updated": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
|
148
|
-
},
|
|
98
|
+
def render_top_offenders_page(self, metrics: ProjectMetrics):
|
|
99
|
+
self.output.writeln("<info>Rendering top offenders page</info>")
|
|
100
|
+
self.page_renderer_factory.create_top_offenders_page_renderer().render(metrics)
|
|
101
|
+
self.output.writeln(
|
|
102
|
+
"<success>Top offenders page generated successfully</success>"
|
|
149
103
|
)
|
|
150
|
-
self.output.writeln("<success>Files page generated successfully</success>")
|
|
151
104
|
|
|
152
105
|
def render_git_analysis_page(self, metrics: ProjectMetrics):
|
|
153
106
|
"""Render the git analysis page with comprehensive git data"""
|
|
154
|
-
if not metrics.git_metrics:
|
|
155
|
-
self.output.writeln("<success>No git metrics to render</success>")
|
|
156
|
-
return
|
|
157
|
-
|
|
158
|
-
def stringify_values(obj):
|
|
159
|
-
if isinstance(obj, dict):
|
|
160
|
-
return {key: stringify_values(value) for key, value in obj.items()}
|
|
161
|
-
elif isinstance(obj, list):
|
|
162
|
-
return [stringify_values(item) for item in obj]
|
|
163
|
-
else:
|
|
164
|
-
return str(obj)
|
|
165
|
-
|
|
166
107
|
self.output.writeln("<info>Rendering git analysis page</info>")
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
:10
|
|
185
|
-
], # silos list
|
|
186
|
-
"git_contributors": metrics.git_metrics.get_contributors_list()[
|
|
187
|
-
:10
|
|
188
|
-
], # contributors list
|
|
189
|
-
"git_hotspots_data": metrics.git_metrics.get_hotspots_list()[
|
|
190
|
-
:10
|
|
191
|
-
], # hotspots list
|
|
192
|
-
"project_name": "Metripy",
|
|
193
|
-
"last_updated": metrics.git_metrics.get_analysis_start_date(),
|
|
194
|
-
}
|
|
195
|
-
),
|
|
196
|
-
)
|
|
197
|
-
|
|
198
|
-
self.output.writeln(
|
|
199
|
-
"<success>Git analysis page generated successfully</success>"
|
|
200
|
-
)
|
|
201
|
-
except Exception as e:
|
|
202
|
-
raise e
|
|
203
|
-
self.output.writeln(
|
|
204
|
-
f"<error>Error generating git analysis page: {e}</error>"
|
|
205
|
-
)
|
|
108
|
+
self.page_renderer_factory.create_git_analysis_page_renderer().render(metrics)
|
|
109
|
+
self.output.writeln(
|
|
110
|
+
"<success>Git analysis page generated successfully</success>"
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
def render_dependencies_page(self, metrics: ProjectMetrics):
|
|
114
|
+
"""Render the dependencies page with dependency details and stats"""
|
|
115
|
+
self.output.writeln("<info>Rendering dependencies page</info>")
|
|
116
|
+
self.page_renderer_factory.create_dependency_page_renderer().render(metrics)
|
|
117
|
+
self.output.writeln(
|
|
118
|
+
"<success>Dependencies page generated successfully</success>"
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
def render_trends_page(self, metrics: ProjectMetrics):
|
|
122
|
+
self.output.writeln("<info>Rendering trends page</info>")
|
|
123
|
+
self.page_renderer_factory.create_trends_page_renderer().render(metrics)
|
|
124
|
+
self.output.writeln("<success>Trends page generated successfully</success>")
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
from metripy.Metric.Code.Segmentor import Segmentor
|
|
2
|
+
from metripy.Metric.ProjectMetrics import ProjectMetrics
|
|
3
|
+
from metripy.Report.Html.PageRenderer import PageRenderer
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TopOffendersPageRenderer(PageRenderer):
|
|
7
|
+
def __init__(self, template_dir: str, output_dir: str, project_name: str):
|
|
8
|
+
super().__init__(template_dir, output_dir, project_name)
|
|
9
|
+
|
|
10
|
+
def render(self, metrics: ProjectMetrics):
|
|
11
|
+
orderedByTotalCc = sorted(
|
|
12
|
+
metrics.file_metrics, key=lambda x: x.totalCc, reverse=True
|
|
13
|
+
)[:10]
|
|
14
|
+
orderedByMI = sorted(
|
|
15
|
+
metrics.file_metrics, key=lambda x: x.maintainabilityIndex, reverse=False
|
|
16
|
+
)[:10]
|
|
17
|
+
orderedByLoc = sorted(metrics.file_metrics, key=lambda x: x.loc, reverse=True)[
|
|
18
|
+
:10
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
all_functions: list = []
|
|
22
|
+
for fm in metrics.file_metrics:
|
|
23
|
+
all_functions.extend(fm.function_nodes)
|
|
24
|
+
|
|
25
|
+
functionsOrderedByCc = sorted(
|
|
26
|
+
all_functions, key=lambda x: x.complexity, reverse=True
|
|
27
|
+
)[:10]
|
|
28
|
+
functionsOrderedByMi = sorted(
|
|
29
|
+
all_functions, key=lambda x: x.maintainability_index, reverse=False
|
|
30
|
+
)[:10]
|
|
31
|
+
functionsOrderedByLoc = sorted(
|
|
32
|
+
all_functions, key=lambda x: x.get_loc(), reverse=True
|
|
33
|
+
)[:10]
|
|
34
|
+
|
|
35
|
+
# TODO maintainability index per function, we dont calc yet
|
|
36
|
+
|
|
37
|
+
self.render_template(
|
|
38
|
+
"top_offenders.html",
|
|
39
|
+
{
|
|
40
|
+
"file_loc_offenders": [
|
|
41
|
+
{**e.to_dict(), "status": Segmentor.get_loc_segment(e.loc)}
|
|
42
|
+
for e in orderedByLoc
|
|
43
|
+
],
|
|
44
|
+
"file_cc_offenders": [
|
|
45
|
+
{
|
|
46
|
+
**e.to_dict(),
|
|
47
|
+
"status": Segmentor.get_complexity_segment(e.totalCc),
|
|
48
|
+
}
|
|
49
|
+
for e in orderedByTotalCc
|
|
50
|
+
],
|
|
51
|
+
"file_mi_offenders": [
|
|
52
|
+
{
|
|
53
|
+
**e.to_dict(),
|
|
54
|
+
"status": Segmentor.get_maintainability_segment(
|
|
55
|
+
e.maintainabilityIndex
|
|
56
|
+
),
|
|
57
|
+
}
|
|
58
|
+
for e in orderedByMI
|
|
59
|
+
],
|
|
60
|
+
"function_size_offenders": [
|
|
61
|
+
{
|
|
62
|
+
**e.to_dict(),
|
|
63
|
+
"status": Segmentor.get_method_size_segment(e.get_loc()),
|
|
64
|
+
}
|
|
65
|
+
for e in functionsOrderedByLoc
|
|
66
|
+
],
|
|
67
|
+
"function_cc_offenders": [
|
|
68
|
+
{
|
|
69
|
+
**e.to_dict(),
|
|
70
|
+
"status": Segmentor.get_complexity_segment(e.complexity),
|
|
71
|
+
}
|
|
72
|
+
for e in functionsOrderedByCc
|
|
73
|
+
],
|
|
74
|
+
"function_mi_offenders": [
|
|
75
|
+
{
|
|
76
|
+
**e.to_dict(),
|
|
77
|
+
"status": Segmentor.get_maintainability_segment(
|
|
78
|
+
e.maintainability_index
|
|
79
|
+
),
|
|
80
|
+
}
|
|
81
|
+
for e in functionsOrderedByMi
|
|
82
|
+
],
|
|
83
|
+
},
|
|
84
|
+
)
|