metripy 0.2.7__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 (66) hide show
  1. metripy/Application/Analyzer.py +106 -0
  2. metripy/Application/Application.py +54 -0
  3. metripy/Application/Config/Config.py +13 -0
  4. metripy/Application/Config/File/ConfigFileReaderFactory.py +24 -0
  5. metripy/Application/Config/File/ConfigFileReaderInterface.py +14 -0
  6. metripy/Application/Config/File/JsonConfigFileReader.py +82 -0
  7. metripy/Application/Config/GitConfig.py +10 -0
  8. metripy/Application/Config/Parser.py +31 -0
  9. metripy/Application/Config/ProjectConfig.py +27 -0
  10. metripy/Application/Config/ReportConfig.py +10 -0
  11. metripy/Application/__init__.py +0 -0
  12. metripy/Component/Debug/Debugger.py +20 -0
  13. metripy/Component/File/Finder.py +37 -0
  14. metripy/Component/Output/CliOutput.py +49 -0
  15. metripy/Component/Output/ProgressBar.py +27 -0
  16. metripy/Dependency/Composer/Composer.py +30 -0
  17. metripy/Dependency/Composer/Packegist.py +55 -0
  18. metripy/Dependency/Dependency.py +30 -0
  19. metripy/Dependency/Npm/Npm.py +30 -0
  20. metripy/Dependency/Npm/NpmOrg.py +47 -0
  21. metripy/Dependency/Pip/Pip.py +69 -0
  22. metripy/Dependency/Pip/PyPi.py +49 -0
  23. metripy/Git/GitAnalyzer.py +86 -0
  24. metripy/LangAnalyzer/AbstractLangAnalyzer.py +65 -0
  25. metripy/LangAnalyzer/Generic/HalSteadAnalyzer.py +58 -0
  26. metripy/LangAnalyzer/Generic/__init__.py +0 -0
  27. metripy/LangAnalyzer/Php/PhpAnalyzer.py +193 -0
  28. metripy/LangAnalyzer/Php/PhpBasicAstParser.py +56 -0
  29. metripy/LangAnalyzer/Php/PhpBasicLocAnalyzer.py +174 -0
  30. metripy/LangAnalyzer/Php/PhpHalSteadAnalyzer.py +44 -0
  31. metripy/LangAnalyzer/Python/PythonAnalyzer.py +129 -0
  32. metripy/LangAnalyzer/Typescript/TypescriptAnalyzer.py +208 -0
  33. metripy/LangAnalyzer/Typescript/TypescriptAstParser.py +68 -0
  34. metripy/LangAnalyzer/Typescript/TypescriptBasicComplexityAnalyzer.py +114 -0
  35. metripy/LangAnalyzer/Typescript/TypescriptBasicLocAnalyzer.py +69 -0
  36. metripy/LangAnalyzer/Typescript/TypescriptHalSteadAnalyzer.py +55 -0
  37. metripy/LangAnalyzer/__init__.py +0 -0
  38. metripy/Metric/Code/AggregatedMetrics.py +42 -0
  39. metripy/Metric/Code/FileMetrics.py +33 -0
  40. metripy/Metric/Code/ModuleMetrics.py +32 -0
  41. metripy/Metric/Code/SegmentedMetrics.py +65 -0
  42. metripy/Metric/FileTree/FileTree.py +15 -0
  43. metripy/Metric/FileTree/FileTreeParser.py +42 -0
  44. metripy/Metric/Git/GitCodeHotspot.py +37 -0
  45. metripy/Metric/Git/GitContributor.py +37 -0
  46. metripy/Metric/Git/GitKnowledgeSilo.py +27 -0
  47. metripy/Metric/Git/GitMetrics.py +148 -0
  48. metripy/Metric/ProjectMetrics.py +55 -0
  49. metripy/Report/Csv/Reporter.py +12 -0
  50. metripy/Report/Html/Reporter.py +210 -0
  51. metripy/Report/Json/AbstractJsonReporter.py +11 -0
  52. metripy/Report/Json/GitJsonReporter.py +21 -0
  53. metripy/Report/Json/JsonReporter.py +12 -0
  54. metripy/Report/ReporterFactory.py +22 -0
  55. metripy/Report/ReporterInterface.py +17 -0
  56. metripy/Tree/ClassNode.py +32 -0
  57. metripy/Tree/FunctionNode.py +49 -0
  58. metripy/Tree/ModuleNode.py +42 -0
  59. metripy/__init__.py +0 -0
  60. metripy/metripy.py +15 -0
  61. metripy-0.2.7.dist-info/METADATA +113 -0
  62. metripy-0.2.7.dist-info/RECORD +66 -0
  63. metripy-0.2.7.dist-info/WHEEL +5 -0
  64. metripy-0.2.7.dist-info/entry_points.txt +2 -0
  65. metripy-0.2.7.dist-info/licenses/LICENSE +21 -0
  66. metripy-0.2.7.dist-info/top_level.txt +1 -0
@@ -0,0 +1,148 @@
1
+ from metripy.Metric.Git.GitCodeHotspot import GitCodeHotspot
2
+ from metripy.Metric.Git.GitContributor import GitContributor
3
+ from metripy.Metric.Git.GitKnowledgeSilo import GitKnowledgeSilo
4
+
5
+
6
+ class GitMetrics:
7
+ def __init__(
8
+ self,
9
+ analysis_start_date: str,
10
+ commit_stats_per_month: dict[str, int],
11
+ churn_per_month: dict[str, dict[str, int]],
12
+ contributor_stats: dict[str, dict[str, int]],
13
+ file_contributors: dict[str, dict[str, int]],
14
+ ):
15
+ self.analysis_start_date = analysis_start_date
16
+ self._commit_stats_per_month: dict[str, int] = commit_stats_per_month
17
+ self._churn_per_month: dict[str, dict[str, int]] = churn_per_month
18
+ self._contributor_stats = contributor_stats
19
+ self.total_commits = sum(data["commits"] for data in contributor_stats.values())
20
+ self.contributors: list[GitContributor] = self._calc_contributors(
21
+ contributor_stats
22
+ )
23
+ self.core_contributors, self.core_percentage = self._calc_core_contributors(
24
+ self.contributors
25
+ )
26
+ self.silos: list[GitKnowledgeSilo] = self._calc_silos(file_contributors)
27
+ self.hotspots: list[GitCodeHotspot] = self._calc_hotspots(file_contributors)
28
+
29
+ def get_analysis_start_date(self) -> str:
30
+ return self.analysis_start_date
31
+
32
+ def get_commit_stats_per_month(self) -> dict[str, int]:
33
+ return self._commit_stats_per_month
34
+
35
+ def get_churn_per_month(self) -> dict[str, dict[str, int]]:
36
+ return self._churn_per_month
37
+
38
+ def get_avg_commit_size(self) -> float:
39
+ return (
40
+ sum(
41
+ data["lines_added"] + data["lines_removed"]
42
+ for data in self._contributor_stats.values()
43
+ )
44
+ / self.total_commits
45
+ )
46
+
47
+ def get_contributors_dict(self) -> dict[str, dict[str, int]]:
48
+ return {
49
+ contributor.name: contributor.to_dict() for contributor in self.contributors
50
+ }
51
+
52
+ def get_contributors_list(self) -> list[dict[str, int]]:
53
+ return [contributor.to_dict() for contributor in self.contributors]
54
+
55
+ def get_hotspots_list(self) -> list[dict[str, int]]:
56
+ return [hotspot.to_dict() for hotspot in self.hotspots]
57
+
58
+ def get_silos_dict(self) -> dict[str, dict[str, int]]:
59
+ return {silo.file_path: silo.to_dict() for silo in self.silos}
60
+
61
+ def get_silos_list(self) -> list[dict[str, int]]:
62
+ return [silo.to_dict() for silo in self.silos]
63
+
64
+ def _calc_hotspots(
65
+ self, file_contributors: dict[str, dict[str, int]]
66
+ ) -> list[GitCodeHotspot]:
67
+ hotspots = []
68
+ for file_path, data in file_contributors.items():
69
+ changes_count = data["commits"]
70
+ if changes_count < 10:
71
+ continue
72
+ hotspots.append(
73
+ GitCodeHotspot(
74
+ file_path=file_path,
75
+ changes_count=changes_count,
76
+ contributors_count=len(data["contributors"]),
77
+ )
78
+ )
79
+ return sorted(hotspots, key=lambda x: x.changes_count, reverse=True)
80
+
81
+ def _calc_contributors(
82
+ self, contributor_stats: dict[str, dict[str, int]]
83
+ ) -> list[GitContributor]:
84
+ # determine top contributors
85
+ total_commits = self.total_commits
86
+ contributors = []
87
+
88
+ for name, data in contributor_stats.items():
89
+ percentage = (
90
+ (data["commits"] / total_commits * 100) if total_commits > 0 else 0
91
+ )
92
+ contributors.append(
93
+ GitContributor(
94
+ name=name,
95
+ commits_count=data["commits"],
96
+ lines_added=data["lines_added"],
97
+ lines_removed=data["lines_removed"],
98
+ contribution_percentage=int(percentage),
99
+ )
100
+ )
101
+
102
+ return sorted(contributors, key=lambda x: x.commits_count, reverse=True)
103
+
104
+ def _calc_core_contributors(
105
+ self, contributors: list[GitContributor]
106
+ ) -> tuple[int, int]:
107
+ # Calculate core contributors (top contributors making up 67% of commits)
108
+ core_contributors = 0
109
+ core_percentage = 0
110
+ for contributor in contributors:
111
+ core_percentage += contributor.contribution_percentage
112
+ core_contributors += 1
113
+ if core_percentage >= 67:
114
+ break
115
+ core_percentage = min(67, core_percentage)
116
+
117
+ return core_contributors, core_percentage
118
+
119
+ def _calc_silos(
120
+ self, file_contributors: dict[str, dict[str, int]]
121
+ ) -> list[GitKnowledgeSilo]:
122
+ silos = []
123
+ for file_path, data in file_contributors.items():
124
+ if len(data["contributors"]) > 1 or data["commits"] < 3:
125
+ continue
126
+ owner = list(data["contributors"])[0]
127
+ silos.append(
128
+ GitKnowledgeSilo(
129
+ file_path=file_path,
130
+ owner=owner,
131
+ commits_count=data["commits"],
132
+ )
133
+ )
134
+ return sorted(silos, key=lambda x: x.commits_count, reverse=True)
135
+
136
+ def to_dict(self) -> dict[str, any]:
137
+ return {
138
+ "analysis_start_date": self.analysis_start_date,
139
+ "avg_commit_size": f"{self.get_avg_commit_size():.2f}",
140
+ "commit_stats_per_month": self.get_commit_stats_per_month(),
141
+ "churn_per_month": self.get_churn_per_month(),
142
+ "total_commits": self.total_commits,
143
+ "active_contributors": len(self.contributors),
144
+ "contributors": self.get_contributors_dict(),
145
+ "core_contributors": self.core_contributors,
146
+ "core_percentage": self.core_percentage,
147
+ "silos": self.get_silos_list(),
148
+ }
@@ -0,0 +1,55 @@
1
+ from metripy.Dependency.Dependency import Dependency
2
+ from metripy.Metric.Code.AggregatedMetrics import AggregatedMetrics
3
+ from metripy.Metric.Code.FileMetrics import FileMetrics
4
+ from metripy.Metric.Code.SegmentedMetrics import SegmentedMetrics
5
+ from metripy.Metric.Git.GitMetrics import GitMetrics
6
+
7
+
8
+ class ProjectMetrics:
9
+ def __init__(
10
+ self,
11
+ file_metrics: list[FileMetrics],
12
+ git_metrics: GitMetrics | None,
13
+ dependencies: list[Dependency] | None,
14
+ ):
15
+ self.file_metrics = file_metrics
16
+ self.git_metrics = git_metrics
17
+ self.dependencies = dependencies
18
+ self.total_code_metrics = self._compile_total_metrics(self.file_metrics)
19
+
20
+ def _compile_total_metrics(
21
+ self, file_metrics: list[FileMetrics]
22
+ ) -> AggregatedMetrics:
23
+ files = 0
24
+ locs = []
25
+ avgCcPerFunctions = []
26
+ maintainabilityIndices = []
27
+ avgLocPerFunctions = []
28
+ for file_metric in file_metrics:
29
+ files += 1
30
+ locs.append(file_metric.loc)
31
+ avgCcPerFunctions.append(file_metric.avgCcPerFunction)
32
+ maintainabilityIndices.append(file_metric.maintainabilityIndex)
33
+ avgLocPerFunctions.append(file_metric.avgLocPerFunction)
34
+
35
+ if files == 0:
36
+ return AggregatedMetrics()
37
+
38
+ return AggregatedMetrics(
39
+ loc=sum(locs),
40
+ avgCcPerFunction=self._avg(avgCcPerFunctions),
41
+ maintainabilityIndex=self._avg(maintainabilityIndices),
42
+ avgLocPerFunction=self._avg(avgLocPerFunctions),
43
+ num_files=files,
44
+ segmented_loc=SegmentedMetrics().set_loc(locs),
45
+ segmented_complexity=SegmentedMetrics().set_complexity(avgCcPerFunctions),
46
+ segmented_maintainability=SegmentedMetrics().set_maintainability(
47
+ maintainabilityIndices
48
+ ),
49
+ segmented_method_size=SegmentedMetrics().set_method_size(
50
+ avgLocPerFunctions
51
+ ),
52
+ )
53
+
54
+ def _avg(self, items: list[float | int]) -> float:
55
+ return sum(items) / len(items)
@@ -0,0 +1,12 @@
1
+ from metripy.Application.Config.Config import Config
2
+ from metripy.Component.Output.CliOutput import CliOutput
3
+ from metripy.Metric.ProjectMetrics import ProjectMetrics
4
+
5
+
6
+ class Reporter:
7
+ def __init__(self, config: Config, output: CliOutput):
8
+ self.config = config
9
+ self.output = output
10
+
11
+ def generate(self, metrics: ProjectMetrics):
12
+ raise NotImplementedError("CSV metrics report is not yet implemented")
@@ -0,0 +1,210 @@
1
+ import json
2
+ import os
3
+ import shutil
4
+ from datetime import datetime
5
+
6
+ from py_template_engine import TemplateEngine
7
+
8
+ from metripy.Application.Config.ReportConfig import ReportConfig
9
+ from metripy.Component.Output.CliOutput import CliOutput
10
+ from metripy.Metric.FileTree.FileTreeParser import FileTreeParser
11
+ from metripy.Metric.ProjectMetrics import ProjectMetrics
12
+ from metripy.Report.ReporterInterface import ReporterInterface
13
+
14
+
15
+ class Reporter(ReporterInterface):
16
+ def __init__(
17
+ self, config: ReportConfig, output: CliOutput, project_name: str = "foobar"
18
+ ):
19
+ self.config: ReportConfig = config
20
+ self.output = output
21
+ self.template_dir = os.path.join(os.getcwd(), "templates/html_report")
22
+
23
+ self.global_template_args = {
24
+ "project_name": project_name,
25
+ "last_updated": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
26
+ }
27
+
28
+ def generate(self, metrics: ProjectMetrics):
29
+
30
+ self.output.writeln("<info>Generating HTML report...</info>")
31
+
32
+ # copy sources
33
+ if not os.path.exists(os.path.join(self.config.path, "js")):
34
+ os.makedirs(os.path.join(self.config.path, "js"))
35
+ if not os.path.exists(os.path.join(self.config.path, "css")):
36
+ os.makedirs(os.path.join(self.config.path, "css"))
37
+ if not os.path.exists(os.path.join(self.config.path, "images")):
38
+ os.makedirs(os.path.join(self.config.path, "images"))
39
+ if not os.path.exists(os.path.join(self.config.path, "fonts")):
40
+ os.makedirs(os.path.join(self.config.path, "fonts"))
41
+ if not os.path.exists(os.path.join(self.config.path, "data")):
42
+ os.makedirs(os.path.join(self.config.path, "data"))
43
+
44
+ # shutil.copy(os.path.join(self.template_dir, "favicon.ico"), os.path.join(self.config.path, "favicon.ico"))
45
+
46
+ shutil.copytree(
47
+ os.path.join(self.template_dir, "js"),
48
+ os.path.join(self.config.path, "js"),
49
+ dirs_exist_ok=True,
50
+ )
51
+ shutil.copytree(
52
+ os.path.join(self.template_dir, "css"),
53
+ os.path.join(self.config.path, "css"),
54
+ dirs_exist_ok=True,
55
+ )
56
+ # shutil.copytree(os.path.join(self.template_dir, "images"), os.path.join(self.config.path, "images"), dirs_exist_ok=True)
57
+ # shutil.copytree(os.path.join(self.template_dir, "fonts"), os.path.join(self.config.path, "fonts"), dirs_exist_ok=True)
58
+
59
+ # render templates
60
+ git_stats_data = {}
61
+ if metrics.git_metrics:
62
+ git_stats_data = metrics.git_metrics.get_commit_stats_per_month()
63
+
64
+ self.output.writeln("<info>Rendering index page</info>")
65
+ # Render main index page
66
+ self.render_template(
67
+ "index.html",
68
+ {
69
+ "git_stats_data": json.dumps(git_stats_data, indent=4),
70
+ "total_code_metrics": metrics.total_code_metrics.to_dict(),
71
+ "segmentation_data": json.dumps(
72
+ metrics.total_code_metrics.to_dict_segmentation(), indent=4
73
+ ),
74
+ "project_name": "CodeMetrics",
75
+ "last_updated": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
76
+ "date": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
77
+ "author": "CodeMetrics",
78
+ "version": "1.0.0",
79
+ },
80
+ )
81
+ self.output.writeln("<success>Done rendering index page</success>")
82
+
83
+ # Render files page
84
+ self.render_files_page(metrics)
85
+ # Render git analysis page
86
+ self.render_git_analysis_page(metrics)
87
+
88
+ self.render_dependencies_page(metrics)
89
+
90
+ self.output.writeln(
91
+ f"<success>HTML report generated in {self.config.path} directory</success>"
92
+ )
93
+
94
+ def render_template(self, template_name: str, data: dict) -> str:
95
+ engine = TemplateEngine(os.path.join(self.template_dir, template_name))
96
+ content = engine.render(**data)
97
+ with open(os.path.join(self.config.path, template_name), "w") as file:
98
+ file.write(content)
99
+
100
+ def render_dependencies_page(self, metrics: ProjectMetrics):
101
+ """Render the dependencies page with dependency details and stats"""
102
+ if not metrics.dependencies:
103
+ self.output.writeln("<success>No dependencies to render</success>")
104
+ return
105
+
106
+ self.output.writeln("<info>Rendering dependencies page</info>")
107
+
108
+ dependencies = metrics.dependencies if metrics.dependencies is not None else []
109
+
110
+ license_by_type = {}
111
+
112
+ # TODO render a pie chart
113
+ for dependency in dependencies:
114
+ for license_name in dependency.license:
115
+ if license_name not in license_by_type.keys():
116
+ license_by_type[license_name] = 0
117
+ license_by_type[license_name] += 1
118
+
119
+ print(json.dumps(license_by_type, indent=2))
120
+
121
+ self.render_template(
122
+ "dependencies.html",
123
+ {
124
+ "dependencies": [d.to_dict() for d in dependencies],
125
+ "project_name": "CodeMetrics",
126
+ "last_updated": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
127
+ },
128
+ )
129
+ self.output.writeln(
130
+ "<success>Dependencies page generated successfully</success>"
131
+ )
132
+
133
+ def render_files_page(self, metrics: ProjectMetrics):
134
+ """Render the files page with file details and analysis"""
135
+ self.output.writeln("<info>Rendering files page</info>")
136
+
137
+ file_names = []
138
+ file_details = {}
139
+ for file_metrics in metrics.file_metrics:
140
+ file_name = file_metrics.full_name
141
+ file_details[file_name] = file_metrics.to_dict()
142
+ file_names.append(file_name)
143
+
144
+ filetree = FileTreeParser.parse(file_names, shorten=True)
145
+
146
+ self.render_template(
147
+ "files.html",
148
+ {
149
+ "filetree": json.dumps(filetree.to_dict()),
150
+ "file_details": json.dumps(file_details),
151
+ "project_name": "CodeMetrics",
152
+ "last_updated": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
153
+ },
154
+ )
155
+ self.output.writeln("<success>Files page generated successfully</success>")
156
+
157
+ def render_git_analysis_page(self, metrics: ProjectMetrics):
158
+ """Render the git analysis page with comprehensive git data"""
159
+ if not metrics.git_metrics:
160
+ self.output.writeln("<success>No git metrics to render</success>")
161
+ return
162
+
163
+ def stringify_values(obj):
164
+ if isinstance(obj, dict):
165
+ return {key: stringify_values(value) for key, value in obj.items()}
166
+ elif isinstance(obj, list):
167
+ return [stringify_values(item) for item in obj]
168
+ else:
169
+ return str(obj)
170
+
171
+ self.output.writeln("<info>Rendering git analysis page</info>")
172
+ try:
173
+ # Render git analysis template
174
+ self.render_template(
175
+ "git_analysis.html",
176
+ stringify_values(
177
+ {
178
+ "git_analysis": metrics.git_metrics.to_dict(),
179
+ "git_analysis_json": json.dumps(
180
+ metrics.git_metrics.get_contributors_dict(), indent=4
181
+ ),
182
+ "git_stats_data": json.dumps(
183
+ metrics.git_metrics.get_commit_stats_per_month(), indent=4
184
+ ), # git commit graph
185
+ "git_churn_data": json.dumps(
186
+ metrics.git_metrics.get_churn_per_month(), indent=4
187
+ ), # git chrun graph
188
+ "git_silos_data": metrics.git_metrics.get_silos_list()[
189
+ :10
190
+ ], # silos list
191
+ "git_contributors": metrics.git_metrics.get_contributors_list()[
192
+ :10
193
+ ], # contributors list
194
+ "git_hotspots_data": metrics.git_metrics.get_hotspots_list()[
195
+ :10
196
+ ], # hotspots list
197
+ "project_name": "CodeMetrics",
198
+ "last_updated": metrics.git_metrics.get_analysis_start_date(),
199
+ }
200
+ ),
201
+ )
202
+
203
+ self.output.writeln(
204
+ "<success>Git analysis page generated successfully</success>"
205
+ )
206
+ except Exception as e:
207
+ raise e
208
+ self.output.writeln(
209
+ f"<error>Error generating git analysis page: {e}</error>"
210
+ )
@@ -0,0 +1,11 @@
1
+ import json
2
+ import os
3
+
4
+ from metripy.Report.ReporterInterface import ReporterInterface
5
+
6
+
7
+ class AbstractJsonReporter(ReporterInterface):
8
+ def put_data(self, data: dict) -> None:
9
+ os.makedirs(os.path.dirname(self.config.path), exist_ok=True)
10
+ with open(self.config.path, "w") as file:
11
+ json.dump(data, file, indent=2)
@@ -0,0 +1,21 @@
1
+ from metripy.Metric.ProjectMetrics import ProjectMetrics
2
+ from metripy.Report.Json.AbstractJsonReporter import AbstractJsonReporter
3
+
4
+
5
+ class GitJsonReporter(AbstractJsonReporter):
6
+ def generate(self, metrics: ProjectMetrics) -> None:
7
+ if not metrics.git_metrics:
8
+ self.output.writeln(
9
+ "<error>Wants git json report, but no git metrics</error>"
10
+ )
11
+ return
12
+
13
+ data = {
14
+ "commits_per_month": metrics.git_metrics.get_commit_stats_per_month(),
15
+ "churn_data": metrics.git_metrics.get_churn_per_month(),
16
+ "possible_silos": metrics.git_metrics.get_silos_list()[:10],
17
+ "top_contributors": metrics.git_metrics.get_contributors_list()[:10],
18
+ "top_hotspots": metrics.git_metrics.get_hotspots_list()[:10],
19
+ }
20
+
21
+ self.put_data(data)
@@ -0,0 +1,12 @@
1
+ from metripy.Application.Config.Config import Config
2
+ from metripy.Component.Output.CliOutput import CliOutput
3
+ from metripy.Metric.ProjectMetrics import ProjectMetrics
4
+
5
+
6
+ class JsonReporter:
7
+ def __init__(self, config: Config, output: CliOutput):
8
+ self.config = config
9
+ self.output = output
10
+
11
+ def generate(self, metrics: ProjectMetrics):
12
+ raise NotImplementedError
@@ -0,0 +1,22 @@
1
+ from metripy.Application.Config.ReportConfig import ReportConfig
2
+ from metripy.Component.Output.CliOutput import CliOutput
3
+ from metripy.Report import ReporterInterface
4
+ from metripy.Report.Html.Reporter import Reporter as HtmlReporter
5
+ from metripy.Report.Json.GitJsonReporter import GitJsonReporter
6
+
7
+
8
+ class ReporterFactory:
9
+ @staticmethod
10
+ def create(config: ReportConfig, output: CliOutput) -> ReporterInterface:
11
+ if config.type == "html":
12
+ return HtmlReporter(config, output)
13
+ elif config.type == "json":
14
+ raise NotImplementedError
15
+ elif config.type == "csv":
16
+ raise NotImplementedError
17
+ elif config.type == "cli":
18
+ raise NotImplementedError
19
+ elif config.type == "json-git":
20
+ return GitJsonReporter(config, output)
21
+ else:
22
+ raise ValueError(f"Unsupported report type: {config.type}")
@@ -0,0 +1,17 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+ from metripy.Application.Config.ReportConfig import ReportConfig
4
+ from metripy.Component.Output.CliOutput import CliOutput
5
+ from metripy.Metric.ProjectMetrics import ProjectMetrics
6
+
7
+
8
+ class ReporterInterface(ABC):
9
+ def __init__(
10
+ self, config: ReportConfig, output: CliOutput, project_name: str = "foobar"
11
+ ):
12
+ self.config: ReportConfig = config
13
+ self.output = output
14
+
15
+ @abstractmethod
16
+ def generate(self, metrics: ProjectMetrics) -> None:
17
+ raise NotImplementedError()
@@ -0,0 +1,32 @@
1
+ from metripy.Tree.FunctionNode import FunctionNode
2
+
3
+
4
+ class ClassNode:
5
+ def __init__(
6
+ self,
7
+ full_name: str,
8
+ name: str,
9
+ lineno: int,
10
+ col_offset: int,
11
+ real_complexity: int,
12
+ ):
13
+ self.full_name = full_name
14
+ self.name = name
15
+ self.lineno = lineno
16
+ self.col_offset = col_offset
17
+ self.real_complexity = real_complexity
18
+ self.functions: list[FunctionNode] = []
19
+
20
+ def to_dict(self) -> dict:
21
+ """Convert ClassNode to a dictionary for JSON serialization."""
22
+ return {
23
+ "full_name": self.full_name,
24
+ "name": self.name,
25
+ "lineno": self.lineno,
26
+ "col_offset": self.col_offset,
27
+ "real_complexity": self.real_complexity,
28
+ "functions": [func.to_dict() for func in self.functions],
29
+ }
30
+
31
+ def __dict__(self) -> dict:
32
+ return self.to_dict()
@@ -0,0 +1,49 @@
1
+ class FunctionNode:
2
+ def __init__(
3
+ self, full_name: str, name: str, lineno: int, col_offset: int, complexity: int
4
+ ):
5
+ self.full_name = full_name
6
+ self.name = name
7
+ self.lineno = lineno
8
+ self.line_end = 0
9
+ self.col_offset = col_offset
10
+ self.complexity = complexity
11
+ self.h1 = 0
12
+ self.h2 = 0
13
+ self.N1 = 0
14
+ self.N2 = 0
15
+ self.vocabulary = 0
16
+ self.length = 0
17
+ self.calculated_length = 0
18
+ self.volume = 0
19
+ self.difficulty = 0
20
+ self.effort = 0
21
+ self.time = 0
22
+ self.bugs = 0
23
+ self.maintainability_index = 0
24
+
25
+ def to_dict(self) -> dict:
26
+ """Convert FunctionNode to a dictionary for JSON serialization."""
27
+ return {
28
+ "full_name": self.full_name,
29
+ "name": self.name,
30
+ "lineno": self.lineno,
31
+ "col_offset": self.col_offset,
32
+ "complexity": self.complexity,
33
+ "h1": self.h1,
34
+ "h2": self.h2,
35
+ "N1": self.N1,
36
+ "N2": self.N2,
37
+ "vocabulary": self.vocabulary,
38
+ "length": self.length,
39
+ "calculated_length": self.calculated_length,
40
+ "volume": self.volume,
41
+ "difficulty": self.difficulty,
42
+ "effort": self.effort,
43
+ "time": self.time,
44
+ "bugs": self.bugs,
45
+ "maintainability_index": self.maintainability_index,
46
+ }
47
+
48
+ def __dict__(self) -> dict:
49
+ return self.to_dict()
@@ -0,0 +1,42 @@
1
+ from metripy.Tree.ClassNode import ClassNode
2
+ from metripy.Tree.FunctionNode import FunctionNode
3
+
4
+
5
+ class ModuleNode:
6
+ def __init__(
7
+ self,
8
+ full_name: str,
9
+ loc: int,
10
+ lloc: int,
11
+ sloc: int,
12
+ comments: int,
13
+ multi: int,
14
+ blank: int,
15
+ single_comments: int,
16
+ ):
17
+ self.full_name = full_name
18
+ self.loc = loc
19
+ self.lloc = lloc
20
+ self.sloc = sloc
21
+ self.comments = comments
22
+ self.multi = multi
23
+ self.blank = blank
24
+ self.single_comments = single_comments
25
+ self.maintainability_index = 0
26
+ self.classes: list[ClassNode] = []
27
+ self.functions: list[FunctionNode] = []
28
+
29
+ def to_dict(self) -> dict:
30
+ return {
31
+ "full_name": self.full_name,
32
+ "loc": self.loc,
33
+ "lloc": self.lloc,
34
+ "sloc": self.sloc,
35
+ "comments": self.comments,
36
+ "multi": self.multi,
37
+ "blank": self.blank,
38
+ "single_comments": self.single_comments,
39
+ "maintainability_index": self.maintainability_index,
40
+ "classes": [c.to_dict() for c in self.classes],
41
+ "functions": [f.to_dict() for f in self.functions],
42
+ }
metripy/__init__.py ADDED
File without changes
metripy/metripy.py ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env python3
2
+ """main module"""
3
+ import sys
4
+
5
+ from metripy.Application.Application import Application
6
+
7
+
8
+ def main():
9
+ """cli entry point to application"""
10
+ application = Application()
11
+ application.run(sys.argv)
12
+
13
+
14
+ if __name__ == "__main__":
15
+ main()