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.

Files changed (56) hide show
  1. metripy/Application/Analyzer.py +23 -3
  2. metripy/Application/Application.py +16 -2
  3. metripy/Application/Config/Config.py +34 -0
  4. metripy/Application/Config/File/ConfigFileReaderFactory.py +6 -5
  5. metripy/Application/Config/File/ConfigFileReaderInterface.py +70 -3
  6. metripy/Application/Config/File/JsonConfigFileReader.py +5 -70
  7. metripy/Application/Config/File/YamlConfigFileReader.py +17 -0
  8. metripy/Application/Config/Parser.py +24 -11
  9. metripy/Application/Config/ProjectConfig.py +64 -0
  10. metripy/Application/Info.py +36 -0
  11. metripy/Dependency/Dependency.py +2 -1
  12. metripy/Dependency/Pip/Pip.py +1 -2
  13. metripy/Dependency/Pip/PyPi.py +1 -0
  14. metripy/Git/GitAnalyzer.py +0 -3
  15. metripy/Import/Json/JsonImporter.py +17 -0
  16. metripy/LangAnalyzer/AbstractLangAnalyzer.py +4 -3
  17. metripy/LangAnalyzer/Php/PhpAnalyzer.py +2 -1
  18. metripy/LangAnalyzer/Python/PythonAnalyzer.py +31 -9
  19. metripy/LangAnalyzer/Python/PythonHalSteadAnalyzer.py +55 -0
  20. metripy/LangAnalyzer/Typescript/TypescriptAnalyzer.py +12 -9
  21. metripy/LangAnalyzer/Typescript/TypescriptAstParser.py +1 -1
  22. metripy/Metric/Code/AggregatedMetrics.py +12 -5
  23. metripy/Metric/Code/FileMetrics.py +32 -1
  24. metripy/Metric/Code/ModuleMetrics.py +5 -5
  25. metripy/Metric/Code/SegmentedMetrics.py +72 -36
  26. metripy/Metric/Code/Segmentor.py +44 -0
  27. metripy/Metric/FileTree/FileTreeParser.py +0 -4
  28. metripy/Metric/Git/GitMetrics.py +1 -1
  29. metripy/Metric/ProjectMetrics.py +17 -2
  30. metripy/Metric/Trend/AggregatedTrendMetric.py +101 -0
  31. metripy/Metric/Trend/ClassTrendMetric.py +20 -0
  32. metripy/Metric/Trend/FileTrendMetric.py +46 -0
  33. metripy/Metric/Trend/FunctionTrendMetric.py +28 -0
  34. metripy/Metric/Trend/SegmentedTrendMetric.py +29 -0
  35. metripy/Report/Html/DependencyPageRenderer.py +21 -0
  36. metripy/Report/Html/FilesPageRenderer.py +28 -0
  37. metripy/Report/Html/GitAnalysisPageRenderer.py +55 -0
  38. metripy/Report/Html/IndexPageRenderer.py +40 -0
  39. metripy/Report/Html/PageRenderer.py +43 -0
  40. metripy/Report/Html/PageRendererFactory.py +37 -0
  41. metripy/Report/Html/Reporter.py +49 -130
  42. metripy/Report/Html/TopOffendersPageRenderer.py +84 -0
  43. metripy/Report/Html/TrendsPageRenderer.py +114 -0
  44. metripy/Report/Json/GitJsonReporter.py +3 -1
  45. metripy/Report/Json/JsonReporter.py +4 -1
  46. metripy/Report/ReporterFactory.py +4 -2
  47. metripy/Tree/ClassNode.py +21 -0
  48. metripy/Tree/FunctionNode.py +66 -1
  49. metripy/Trend/TrendAnalyzer.py +150 -0
  50. {metripy-0.2.8.dist-info → metripy-0.3.1.dist-info}/METADATA +3 -3
  51. metripy-0.3.1.dist-info/RECORD +85 -0
  52. metripy-0.2.8.dist-info/RECORD +0 -66
  53. {metripy-0.2.8.dist-info → metripy-0.3.1.dist-info}/WHEEL +0 -0
  54. {metripy-0.2.8.dist-info → metripy-0.3.1.dist-info}/entry_points.txt +0 -0
  55. {metripy-0.2.8.dist-info → metripy-0.3.1.dist-info}/licenses/LICENSE +0 -0
  56. {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)
@@ -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
- # shutil.copytree(os.path.join(self.template_dir, "images"), os.path.join(self.config.path, "images"), dirs_exist_ok=True)
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
- # render templates
61
- git_stats_data = {}
62
- if metrics.git_metrics:
63
- git_stats_data = metrics.git_metrics.get_commit_stats_per_month()
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 files page
72
+ # Render main pages
73
+ self.render_index_page(metrics)
85
74
  self.render_files_page(metrics)
86
- # Render git analysis page
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>Dependencies page generated successfully</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
- file_names = []
133
- file_details = {}
134
- for file_metrics in metrics.file_metrics:
135
- file_name = file_metrics.full_name
136
- file_details[file_name] = file_metrics.to_dict()
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
- try:
168
- # Render git analysis template
169
- self.render_template(
170
- "git_analysis.html",
171
- stringify_values(
172
- {
173
- "git_analysis": metrics.git_metrics.to_dict(),
174
- "git_analysis_json": json.dumps(
175
- metrics.git_metrics.get_contributors_dict(), indent=4
176
- ),
177
- "git_stats_data": json.dumps(
178
- metrics.git_metrics.get_commit_stats_per_month(), indent=4
179
- ), # git commit graph
180
- "git_churn_data": json.dumps(
181
- metrics.git_metrics.get_churn_per_month(), indent=4
182
- ), # git chrun graph
183
- "git_silos_data": metrics.git_metrics.get_silos_list()[
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
+ )