metripy 0.2.8__py3-none-any.whl → 0.3.0__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.
Files changed (45) hide show
  1. metripy/Application/Analyzer.py +23 -3
  2. metripy/Application/Application.py +16 -1
  3. metripy/Application/Config/Config.py +33 -0
  4. metripy/Application/Config/File/ConfigFileReaderFactory.py +4 -4
  5. metripy/Application/Config/File/JsonConfigFileReader.py +7 -2
  6. metripy/Application/Config/Parser.py +26 -11
  7. metripy/Application/Config/ProjectConfig.py +63 -0
  8. metripy/Application/Info.py +29 -0
  9. metripy/Dependency/Dependency.py +2 -1
  10. metripy/Dependency/Pip/Pip.py +1 -2
  11. metripy/Dependency/Pip/PyPi.py +1 -0
  12. metripy/Git/GitAnalyzer.py +0 -3
  13. metripy/Import/Json/JsonImporter.py +17 -0
  14. metripy/LangAnalyzer/AbstractLangAnalyzer.py +4 -3
  15. metripy/LangAnalyzer/Php/PhpAnalyzer.py +2 -1
  16. metripy/LangAnalyzer/Python/PythonAnalyzer.py +31 -9
  17. metripy/LangAnalyzer/Python/PythonHalSteadAnalyzer.py +55 -0
  18. metripy/LangAnalyzer/Typescript/TypescriptAnalyzer.py +12 -9
  19. metripy/LangAnalyzer/Typescript/TypescriptAstParser.py +1 -1
  20. metripy/Metric/Code/AggregatedMetrics.py +12 -5
  21. metripy/Metric/Code/FileMetrics.py +32 -1
  22. metripy/Metric/Code/ModuleMetrics.py +5 -5
  23. metripy/Metric/Code/SegmentedMetrics.py +72 -36
  24. metripy/Metric/Code/Segmentor.py +42 -0
  25. metripy/Metric/FileTree/FileTreeParser.py +0 -4
  26. metripy/Metric/Git/GitMetrics.py +1 -1
  27. metripy/Metric/ProjectMetrics.py +17 -2
  28. metripy/Metric/Trend/AggregatedTrendMetric.py +101 -0
  29. metripy/Metric/Trend/ClassTrendMetric.py +20 -0
  30. metripy/Metric/Trend/FileTrendMetric.py +46 -0
  31. metripy/Metric/Trend/FunctionTrendMetric.py +28 -0
  32. metripy/Metric/Trend/SegmentedTrendMetric.py +29 -0
  33. metripy/Report/Html/Reporter.py +247 -28
  34. metripy/Report/Json/GitJsonReporter.py +3 -1
  35. metripy/Report/Json/JsonReporter.py +4 -1
  36. metripy/Tree/ClassNode.py +21 -0
  37. metripy/Tree/FunctionNode.py +66 -1
  38. metripy/Trend/TrendAnalyzer.py +150 -0
  39. {metripy-0.2.8.dist-info → metripy-0.3.0.dist-info}/METADATA +3 -3
  40. metripy-0.3.0.dist-info/RECORD +76 -0
  41. metripy-0.2.8.dist-info/RECORD +0 -66
  42. {metripy-0.2.8.dist-info → metripy-0.3.0.dist-info}/WHEEL +0 -0
  43. {metripy-0.2.8.dist-info → metripy-0.3.0.dist-info}/entry_points.txt +0 -0
  44. {metripy-0.2.8.dist-info → metripy-0.3.0.dist-info}/licenses/LICENSE +0 -0
  45. {metripy-0.2.8.dist-info → metripy-0.3.0.dist-info}/top_level.txt +0 -0
@@ -7,10 +7,12 @@ from py_template_engine import TemplateEngine
7
7
 
8
8
  from metripy.Application.Config.ReportConfig import ReportConfig
9
9
  from metripy.Component.Output.CliOutput import CliOutput
10
+ from metripy.Dependency.Dependency import Dependency
11
+ from metripy.Metric.Code.FileMetrics import FileMetrics
12
+ from metripy.Metric.Code.Segmentor import Segmentor
10
13
  from metripy.Metric.FileTree.FileTreeParser import FileTreeParser
11
14
  from metripy.Metric.ProjectMetrics import ProjectMetrics
12
15
  from metripy.Report.ReporterInterface import ReporterInterface
13
- from metripy.Dependency.Dependency import Dependency
14
16
 
15
17
 
16
18
  class Reporter(ReporterInterface):
@@ -54,24 +56,173 @@ 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
66
+ # copy logo, lies 2 down from the templates directory
67
+ shutil.copy(os.path.join(self.template_dir, "../..", "logo.svg"), os.path.join(self.config.path, "images", "logo.svg"))
68
+
69
+ # Render main pages
70
+ self.render_index_page(metrics)
71
+ self.render_files_page(metrics)
72
+ self.render_git_analysis_page(metrics)
73
+ self.render_dependencies_page(metrics)
74
+ self.render_top_offenders_page(metrics)
75
+ self.render_trends_page(metrics)
76
+
77
+ self.output.writeln(
78
+ f"<success>HTML report generated in {self.config.path} directory</success>"
79
+ )
80
+
81
+ def render_template(self, template_name: str, data: dict) -> str:
82
+ engine = TemplateEngine(os.path.join(self.template_dir, template_name))
83
+ content = engine.render(**data)
84
+ with open(os.path.join(self.config.path, template_name), "w") as file:
85
+ file.write(content)
86
+
87
+ def render_trends_page(self, metrics: ProjectMetrics):
88
+ def compile(file: FileMetrics) -> dict:
89
+ return {
90
+ "name": file.full_name,
91
+ "path": file.full_name,
92
+ "complexity_current": file.totalCc,
93
+ "complexity_prev": round(file.trend.historical_totalCc, 2),
94
+ "complexity_delta": round(file.trend.totalCc_delta, 2),
95
+ "maintainability_current": round(file.maintainabilityIndex, 2),
96
+ "maintainability_prev": round(
97
+ file.trend.historical_maintainabilityIndex, 2
98
+ ),
99
+ "maintainability_delta": round(
100
+ file.trend.maintainabilityIndex_delta, 2
101
+ ),
102
+ }
103
+
104
+ # Top improved complexity (complexity went down - negative delta)
105
+ top_improved_complexity = [
106
+ x
107
+ for x in metrics.file_metrics
108
+ if x.trend is not None and x.trend.totalCc_delta < 0
109
+ ]
110
+ top_improved_complexity = sorted(
111
+ top_improved_complexity, key=lambda x: x.trend.totalCc_delta
112
+ )[:10]
113
+
114
+ # Top worsened complexity (complexity went up - positive delta)
115
+ top_worsened_complexity = [
116
+ x
117
+ for x in metrics.file_metrics
118
+ if x.trend is not None and x.trend.totalCc_delta > 0
119
+ ]
120
+ top_worsened_complexity = sorted(
121
+ top_worsened_complexity, key=lambda x: x.trend.totalCc_delta, reverse=True
122
+ )[:10]
123
+
124
+ # Top improved maintainability (maintainability went up - positive delta)
125
+ top_improved_maintainability = [
126
+ x
127
+ for x in metrics.file_metrics
128
+ if x.trend is not None and round(x.trend.maintainabilityIndex_delta, 2) > 0
129
+ ]
130
+ top_improved_maintainability = sorted(
131
+ top_improved_maintainability,
132
+ key=lambda x: x.trend.maintainabilityIndex_delta,
133
+ reverse=True,
134
+ )[:10]
135
+
136
+ # Top worsened maintainability (maintainability went down - negative delta)
137
+ top_worsened_maintainability = [
138
+ x
139
+ for x in metrics.file_metrics
140
+ if x.trend is not None and round(x.trend.maintainabilityIndex_delta, 2) < 0
141
+ ]
142
+ top_worsened_maintainability = sorted(
143
+ top_worsened_maintainability,
144
+ key=lambda x: x.trend.maintainabilityIndex_delta,
145
+ )[:10]
146
+
147
+ trend_data = {
148
+ # Segment distributions for each metric
149
+ "loc_segments_current": metrics.total_code_metrics.segmentation_data[
150
+ "loc"
151
+ ].to_dict_with_percent(),
152
+ "loc_segments_prev": metrics.total_code_metrics.trend.historical_segmentation_data[
153
+ "loc"
154
+ ].to_dict_with_percent(),
155
+ "complexity_segments_current": metrics.total_code_metrics.segmentation_data[
156
+ "complexity"
157
+ ].to_dict_with_percent(),
158
+ "complexity_segments_prev": metrics.total_code_metrics.trend.historical_segmentation_data[
159
+ "complexity"
160
+ ].to_dict_with_percent(),
161
+ "maintainability_segments_current": metrics.total_code_metrics.segmentation_data[
162
+ "maintainability"
163
+ ].to_dict_with_percent(),
164
+ "maintainability_segments_prev": metrics.total_code_metrics.trend.historical_segmentation_data[
165
+ "maintainability"
166
+ ].to_dict_with_percent(),
167
+ "method_size_segments_current": metrics.total_code_metrics.segmentation_data[
168
+ "methodSize"
169
+ ].to_dict_with_percent(),
170
+ "method_size_segments_prev": metrics.total_code_metrics.trend.historical_segmentation_data[
171
+ "methodSize"
172
+ ].to_dict_with_percent(),
173
+ "top_improved_complexity": [compile(x) for x in top_improved_complexity],
174
+ "top_improved_maintainability": [
175
+ compile(x) for x in top_improved_maintainability
176
+ ],
177
+ "top_worsened_complexity": [compile(x) for x in top_worsened_complexity],
178
+ "top_worsened_maintainability": [
179
+ compile(x) for x in top_worsened_maintainability
180
+ ],
181
+ }
182
+
183
+ self.output.writeln("<info>Rendering trends page</info>")
184
+ self.render_template(
185
+ "trends.html",
186
+ {
187
+ "has_trend_data": metrics.total_code_metrics.trend is not None,
188
+ "trend_data": trend_data,
189
+ "project_name": "Metripy",
190
+ "last_updated": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
191
+ "date": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
192
+ "author": "Metripy",
193
+ "version": "1.0.0",
194
+ },
195
+ )
196
+
197
+ def render_index_page(self, metrics: ProjectMetrics):
61
198
  git_stats_data = {}
62
199
  if metrics.git_metrics:
63
200
  git_stats_data = metrics.git_metrics.get_commit_stats_per_month()
64
201
 
65
202
  self.output.writeln("<info>Rendering index page</info>")
66
- # Render main index page
67
203
  self.render_template(
68
204
  "index.html",
69
205
  {
70
206
  "git_stats_data": json.dumps(git_stats_data, indent=4),
71
207
  "total_code_metrics": metrics.total_code_metrics.to_dict(),
208
+ "has_total_code_metrics_trend": metrics.total_code_metrics.trend
209
+ is not None,
210
+ "total_code_metrics_trend": (
211
+ metrics.total_code_metrics.trend.to_dict()
212
+ if metrics.total_code_metrics.trend
213
+ else None
214
+ ),
72
215
  "segmentation_data": json.dumps(
73
216
  metrics.total_code_metrics.to_dict_segmentation(), indent=4
74
217
  ),
218
+ "segmentation_data_trend": (
219
+ json.dumps(
220
+ metrics.total_code_metrics.trend.to_dict_segmentation(),
221
+ indent=4,
222
+ )
223
+ if metrics.total_code_metrics.trend
224
+ else None
225
+ ),
75
226
  "project_name": "Metripy",
76
227
  "last_updated": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
77
228
  "date": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
@@ -81,23 +232,91 @@ class Reporter(ReporterInterface):
81
232
  )
82
233
  self.output.writeln("<success>Done rendering index page</success>")
83
234
 
84
- # Render files page
85
- self.render_files_page(metrics)
86
- # Render git analysis page
87
- self.render_git_analysis_page(metrics)
235
+ def render_top_offenders_page(self, metrics: ProjectMetrics):
236
+ self.output.writeln("<info>Rendering top offenders page</info>")
88
237
 
89
- self.render_dependencies_page(metrics)
238
+ orderedByTotalCc = sorted(
239
+ metrics.file_metrics, key=lambda x: x.totalCc, reverse=True
240
+ )[:10]
241
+ orderedByMI = sorted(
242
+ metrics.file_metrics, key=lambda x: x.maintainabilityIndex, reverse=False
243
+ )[:10]
244
+ orderedByLoc = sorted(metrics.file_metrics, key=lambda x: x.loc, reverse=True)[
245
+ :10
246
+ ]
247
+
248
+ all_functions: list = []
249
+ for fm in metrics.file_metrics:
250
+ all_functions.extend(fm.function_nodes)
90
251
 
252
+ functionsOrderedByCc = sorted(
253
+ all_functions, key=lambda x: x.complexity, reverse=True
254
+ )[:10]
255
+ functionsOrderedByMi = sorted(
256
+ all_functions, key=lambda x: x.maintainability_index, reverse=False
257
+ )[:10]
258
+ functionsOrderedByLoc = sorted(
259
+ all_functions, key=lambda x: x.get_loc(), reverse=True
260
+ )[:10]
261
+
262
+ # TODO maintainability index per function, we dont calc yet
263
+
264
+ self.render_template(
265
+ "top_offenders.html",
266
+ Reporter._stringify_values(
267
+ {
268
+ "file_loc_offenders": [
269
+ {**e.to_dict(), "status": Segmentor.get_loc_segment(e.loc)}
270
+ for e in orderedByLoc
271
+ ],
272
+ "file_cc_offenders": [
273
+ {
274
+ **e.to_dict(),
275
+ "status": Segmentor.get_complexity_segment(e.totalCc),
276
+ }
277
+ for e in orderedByTotalCc
278
+ ],
279
+ "file_mi_offenders": [
280
+ {
281
+ **e.to_dict(),
282
+ "status": Segmentor.get_maintainability_segment(
283
+ e.maintainabilityIndex
284
+ ),
285
+ }
286
+ for e in orderedByMI
287
+ ],
288
+ "function_size_offenders": [
289
+ {
290
+ **e.to_dict(),
291
+ "status": Segmentor.get_method_size_segment(e.get_loc()),
292
+ }
293
+ for e in functionsOrderedByLoc
294
+ ],
295
+ "function_cc_offenders": [
296
+ {
297
+ **e.to_dict(),
298
+ "status": Segmentor.get_complexity_segment(e.complexity),
299
+ }
300
+ for e in functionsOrderedByCc
301
+ ],
302
+ "function_mi_offenders": [
303
+ {
304
+ **e.to_dict(),
305
+ "status": Segmentor.get_maintainability_segment(
306
+ e.maintainability_index
307
+ ),
308
+ }
309
+ for e in functionsOrderedByMi
310
+ ],
311
+ "project_name": "Metripy",
312
+ "last_updated": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
313
+ }
314
+ ),
315
+ )
91
316
  self.output.writeln(
92
- f"<success>HTML report generated in {self.config.path} directory</success>"
317
+ "<success>Top offenders page generated successfully</success>"
93
318
  )
94
319
 
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
320
  def render_dependencies_page(self, metrics: ProjectMetrics):
102
321
  """Render the dependencies page with dependency details and stats"""
103
322
  if not metrics.dependencies:
@@ -108,11 +327,8 @@ class Reporter(ReporterInterface):
108
327
 
109
328
  dependencies = metrics.dependencies if metrics.dependencies is not None else []
110
329
 
111
- # TODO render a pie chart
112
330
  license_by_type = Dependency.get_lisence_distribution(dependencies)
113
331
 
114
- print(json.dumps(license_by_type, indent=2))
115
-
116
332
  self.render_template(
117
333
  "dependencies.html",
118
334
  {
@@ -149,26 +365,29 @@ class Reporter(ReporterInterface):
149
365
  )
150
366
  self.output.writeln("<success>Files page generated successfully</success>")
151
367
 
368
+ @staticmethod
369
+ def _stringify_values(obj):
370
+ if isinstance(obj, dict):
371
+ return {
372
+ key: Reporter._stringify_values(value) for key, value in obj.items()
373
+ }
374
+ elif isinstance(obj, list):
375
+ return [Reporter._stringify_values(item) for item in obj]
376
+ else:
377
+ return str(obj)
378
+
152
379
  def render_git_analysis_page(self, metrics: ProjectMetrics):
153
380
  """Render the git analysis page with comprehensive git data"""
154
381
  if not metrics.git_metrics:
155
382
  self.output.writeln("<success>No git metrics to render</success>")
156
383
  return
157
384
 
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
385
  self.output.writeln("<info>Rendering git analysis page</info>")
167
386
  try:
168
387
  # Render git analysis template
169
388
  self.render_template(
170
389
  "git_analysis.html",
171
- stringify_values(
390
+ Reporter._stringify_values(
172
391
  {
173
392
  "git_analysis": metrics.git_metrics.to_dict(),
174
393
  "git_analysis_json": json.dumps(
@@ -19,4 +19,6 @@ class GitJsonReporter(AbstractJsonReporter):
19
19
  }
20
20
 
21
21
  self.put_data(data)
22
- self.output.writeln(f"<success>Create git json report in {self.config.path}</success>")
22
+ self.output.writeln(
23
+ f"<success>Create git json report in {self.config.path}</success>"
24
+ )
@@ -3,6 +3,7 @@ from metripy.Component.Output.CliOutput import CliOutput
3
3
  from metripy.Metric.ProjectMetrics import ProjectMetrics
4
4
  from metripy.Report.Json.AbstractJsonReporter import AbstractJsonReporter
5
5
 
6
+
6
7
  class JsonReporter(AbstractJsonReporter):
7
8
  def __init__(self, config: Config, output: CliOutput):
8
9
  self.config = config
@@ -10,4 +11,6 @@ class JsonReporter(AbstractJsonReporter):
10
11
 
11
12
  def generate(self, metrics: ProjectMetrics):
12
13
  self.put_data(metrics.to_dict())
13
- self.output.writeln(f"<success>Create json report in {self.config.path}</success>")
14
+ self.output.writeln(
15
+ f"<success>Create json report in {self.config.path}</success>"
16
+ )
metripy/Tree/ClassNode.py CHANGED
@@ -1,3 +1,7 @@
1
+ from typing import Self
2
+
3
+ from metripy.Metric.Code.Segmentor import Segmentor
4
+ from metripy.Metric.Trend.ClassTrendMetric import ClassTrendMetric
1
5
  from metripy.Tree.FunctionNode import FunctionNode
2
6
 
3
7
 
@@ -17,6 +21,8 @@ class ClassNode:
17
21
  self.real_complexity = real_complexity
18
22
  self.functions: list[FunctionNode] = []
19
23
 
24
+ self.trend: ClassTrendMetric | None = None
25
+
20
26
  def to_dict(self) -> dict:
21
27
  """Convert ClassNode to a dictionary for JSON serialization."""
22
28
  return {
@@ -25,8 +31,23 @@ class ClassNode:
25
31
  "lineno": self.lineno,
26
32
  "col_offset": self.col_offset,
27
33
  "real_complexity": self.real_complexity,
34
+ "complexity_segment": Segmentor.get_complexity_segment(
35
+ self.real_complexity
36
+ ),
28
37
  "functions": [func.to_dict() for func in self.functions],
29
38
  }
30
39
 
31
40
  def __dict__(self) -> dict:
32
41
  return self.to_dict()
42
+
43
+ @staticmethod
44
+ def from_dict(data: dict) -> Self:
45
+ node = ClassNode(
46
+ full_name=data["full_name"],
47
+ name=data["name"],
48
+ lineno=data["lineno"],
49
+ col_offset=data["col_offset"],
50
+ real_complexity=data["real_complexity"],
51
+ )
52
+ node.functions = [FunctionNode.from_dict(d) for d in data["functions"]]
53
+ return node
@@ -1,3 +1,10 @@
1
+ import math
2
+ from typing import Self
3
+
4
+ from metripy.Metric.Code.Segmentor import Segmentor
5
+ from metripy.Metric.Trend.FunctionTrendMetric import FunctionTrendMetric
6
+
7
+
1
8
  class FunctionNode:
2
9
  def __init__(
3
10
  self, full_name: str, name: str, lineno: int, col_offset: int, complexity: int
@@ -21,6 +28,35 @@ class FunctionNode:
21
28
  self.time = 0
22
29
  self.bugs = 0
23
30
  self.maintainability_index = 0
31
+ self.trend: FunctionTrendMetric | None = None
32
+
33
+ def get_loc(self) -> int:
34
+ return self.line_end - self.lineno
35
+
36
+ def calc_mi(self):
37
+
38
+ total_volume = self.volume
39
+ total_complexity = self.complexity
40
+ total_length = self.length
41
+
42
+ if total_volume == 0 or total_length == 0:
43
+ return 100.0
44
+
45
+ # PHP maintainability index calculation
46
+ mi_base = max(
47
+ (
48
+ 171
49
+ - 5.2 * math.log(total_volume)
50
+ - 0.23 * total_complexity
51
+ - 16.2 * math.log(total_length)
52
+ )
53
+ * 100
54
+ / 171,
55
+ 0,
56
+ )
57
+
58
+ # no comment weight
59
+ self.maintainability_index = mi_base
24
60
 
25
61
  def to_dict(self) -> dict:
26
62
  """Convert FunctionNode to a dictionary for JSON serialization."""
@@ -28,8 +64,12 @@ class FunctionNode:
28
64
  "full_name": self.full_name,
29
65
  "name": self.name,
30
66
  "lineno": self.lineno,
67
+ "line_end": self.line_end,
68
+ "loc": self.get_loc(),
69
+ "loc_segment": Segmentor.get_loc_segment(self.get_loc()),
31
70
  "col_offset": self.col_offset,
32
71
  "complexity": self.complexity,
72
+ "complexity_segment": Segmentor.get_complexity_segment(self.complexity),
33
73
  "h1": self.h1,
34
74
  "h2": self.h2,
35
75
  "N1": self.N1,
@@ -42,8 +82,33 @@ class FunctionNode:
42
82
  "effort": self.effort,
43
83
  "time": self.time,
44
84
  "bugs": self.bugs,
45
- "maintainability_index": self.maintainability_index,
85
+ "maintainability_index": round(self.maintainability_index, 2),
86
+ "maintainability_segment": Segmentor.get_maintainability_segment(
87
+ self.maintainability_index
88
+ ),
46
89
  }
47
90
 
48
91
  def __dict__(self) -> dict:
49
92
  return self.to_dict()
93
+
94
+ @staticmethod
95
+ def from_dict(data: dict) -> Self:
96
+ node = FunctionNode(
97
+ full_name=data["full_name"],
98
+ name=data["name"],
99
+ lineno=data["lineno"],
100
+ col_offset=data["col_offset"],
101
+ complexity=data["complexity"],
102
+ )
103
+ node.line_end = data["line_end"]
104
+ node.vocabulary = data["vocabulary"]
105
+ node.length = data["length"]
106
+ node.calculated_length = data["calculated_length"]
107
+ node.volume = data["volume"]
108
+ node.difficulty = data["difficulty"]
109
+ node.effort = data["effort"]
110
+ node.time = data["time"]
111
+ node.bugs = data["bugs"]
112
+ node.maintainability_index = data["maintainability_index"]
113
+
114
+ return node
@@ -0,0 +1,150 @@
1
+ from metripy.Metric.Code.AggregatedMetrics import AggregatedMetrics
2
+ from metripy.Metric.Code.FileMetrics import FileMetrics
3
+ from metripy.Metric.ProjectMetrics import ProjectMetrics
4
+ from metripy.Metric.Trend.AggregatedTrendMetric import AggregatedTrendMetric
5
+ from metripy.Metric.Trend.ClassTrendMetric import ClassTrendMetric
6
+ from metripy.Metric.Trend.FileTrendMetric import FileTrendMetric
7
+ from metripy.Metric.Trend.FunctionTrendMetric import FunctionTrendMetric
8
+ from metripy.Tree.ClassNode import ClassNode
9
+ from metripy.Tree.FunctionNode import FunctionNode
10
+
11
+
12
+ class TrendAnalyzer:
13
+ def create_file_trend_metric(
14
+ self, file_metric: FileMetrics, historical_file_metric: FileMetrics
15
+ ) -> FileTrendMetric:
16
+ return FileTrendMetric(
17
+ historical_loc=historical_file_metric.loc,
18
+ loc=file_metric.loc,
19
+ historical_totalCc=historical_file_metric.totalCc,
20
+ totalCc=file_metric.totalCc,
21
+ historical_avgCcPerFunction=historical_file_metric.avgCcPerFunction,
22
+ avgCcPerFunction=file_metric.avgCcPerFunction,
23
+ historical_maintainabilityIndex=historical_file_metric.maintainabilityIndex,
24
+ maintainabilityIndex=file_metric.maintainabilityIndex,
25
+ historical_avgLocPerFunction=historical_file_metric.avgLocPerFunction,
26
+ avgLocPerFunction=file_metric.avgLocPerFunction,
27
+ )
28
+
29
+ def create_class_trend_metric(
30
+ self, class_metric: ClassNode, historical_class_metric: ClassNode
31
+ ) -> ClassTrendMetric:
32
+ return ClassTrendMetric(
33
+ historical_lineno=historical_class_metric.lineno,
34
+ lineno=class_metric.lineno,
35
+ historical_real_complexity=historical_class_metric.real_complexity,
36
+ real_complexity=class_metric.real_complexity,
37
+ )
38
+
39
+ def create_function_trend_metric(
40
+ self, function_metric: FunctionNode, historical_function_metric: FunctionNode
41
+ ) -> FunctionTrendMetric:
42
+ return FunctionTrendMetric(
43
+ historical_loc=historical_function_metric.get_loc(),
44
+ loc=function_metric.get_loc(),
45
+ historical_complexity=historical_function_metric.complexity,
46
+ complexity=function_metric.complexity,
47
+ historical_maintainability_index=historical_function_metric.maintainability_index,
48
+ maintainability_index=function_metric.maintainability_index,
49
+ )
50
+
51
+ def add_historical_file_trends(
52
+ self,
53
+ file_metrics: list[FileMetrics],
54
+ historical_file_metrics: list[FileMetrics],
55
+ ):
56
+ indexed_file_metrics = {m.full_name: m for m in file_metrics}
57
+ indexed_historical_file_metrics = {
58
+ m.full_name: m for m in historical_file_metrics
59
+ }
60
+
61
+ for full_name, file_metric in indexed_file_metrics.items():
62
+ historical_file_metric = indexed_historical_file_metrics.get(full_name)
63
+ if not historical_file_metric:
64
+ continue
65
+ file_metric.trend = self.create_file_trend_metric(
66
+ file_metric, historical_file_metric
67
+ )
68
+
69
+ indexed_class_nodes = {
70
+ n.full_name: n for n in historical_file_metric.class_nodes
71
+ }
72
+ for class_node in file_metric.class_nodes:
73
+ historical_class_node = indexed_class_nodes.get(class_node.full_name)
74
+ if not historical_class_node:
75
+ continue
76
+ class_node.trend = self.create_class_trend_metric(
77
+ class_node, historical_class_node
78
+ )
79
+
80
+ indexed_function_nodes = {
81
+ n.full_name: n for n in historical_class_node.functions
82
+ }
83
+ for function_node in class_node.functions:
84
+ historical_function_node = indexed_function_nodes.get(
85
+ function_node.full_name
86
+ )
87
+ if not historical_function_node:
88
+ continue
89
+ function_node.trend = self.create_function_trend_metric(
90
+ function_node, historical_function_node
91
+ )
92
+
93
+ indexed_function_nodes = {
94
+ n.full_name: n for n in historical_file_metric.function_nodes
95
+ }
96
+ for function_node in file_metric.function_nodes:
97
+ historical_function_node = indexed_function_nodes.get(
98
+ function_node.full_name
99
+ )
100
+ if not historical_function_node:
101
+ continue
102
+ function_node.trend = self.create_function_trend_metric(
103
+ function_node, historical_function_node
104
+ )
105
+
106
+ def create_aggregated_trend_metric(
107
+ self,
108
+ aggregated_metric: AggregatedMetrics,
109
+ historical_aggregated_metric: AggregatedMetrics,
110
+ ) -> AggregatedTrendMetric:
111
+ return AggregatedTrendMetric(
112
+ historical_loc=historical_aggregated_metric.loc,
113
+ loc=aggregated_metric.loc,
114
+ historical_avgCcPerFunction=historical_aggregated_metric.avgCcPerFunction,
115
+ avgCcPerFunction=aggregated_metric.avgCcPerFunction,
116
+ historical_maintainabilityIndex=historical_aggregated_metric.maintainabilityIndex,
117
+ maintainabilityIndex=aggregated_metric.maintainabilityIndex,
118
+ historical_avgLocPerFunction=historical_aggregated_metric.avgLocPerFunction,
119
+ avgLocPerFunction=aggregated_metric.avgLocPerFunction,
120
+ historical_num_files=historical_aggregated_metric.num_files,
121
+ num_files=aggregated_metric.num_files,
122
+ historical_segmented_loc=historical_aggregated_metric.segmentation_data[
123
+ "loc"
124
+ ],
125
+ segmented_loc=aggregated_metric.segmentation_data["loc"],
126
+ historical_segmented_complexity=historical_aggregated_metric.segmentation_data[
127
+ "complexity"
128
+ ],
129
+ segmented_complexity=aggregated_metric.segmentation_data["complexity"],
130
+ historical_segmented_maintainability=historical_aggregated_metric.segmentation_data[
131
+ "maintainability"
132
+ ],
133
+ segmented_maintainability=aggregated_metric.segmentation_data[
134
+ "maintainability"
135
+ ],
136
+ historical_segmented_method_size=historical_aggregated_metric.segmentation_data[
137
+ "methodSize"
138
+ ],
139
+ segmented_method_size=aggregated_metric.segmentation_data["methodSize"],
140
+ )
141
+
142
+ def add_historical_project_trends(
143
+ self,
144
+ project_metrics: ProjectMetrics,
145
+ historical_project_metrics: ProjectMetrics,
146
+ ):
147
+ project_metrics.total_code_metrics.trend = self.create_aggregated_trend_metric(
148
+ project_metrics.total_code_metrics,
149
+ historical_project_metrics.total_code_metrics,
150
+ )
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: metripy
3
- Version: 0.2.8
3
+ Version: 0.3.0
4
4
  Summary: A Python tool to generate multi project, multi language code metric reports
5
5
  Author-email: Yannick Zimmermann <yannick.zimmermann@proton.me>
6
6
  License: MIT
7
- Project-URL: Homepage, https://github.com/zimmer-yan/metripy
7
+ Project-URL: Homepage, https://zimmer-yan.github.io/metripy/
8
8
  Project-URL: Repository, https://github.com/zimmer-yan/metripy
9
- Project-URL: Documentation, https://github.com/zimmer-yan/metripy#readme
9
+ Project-URL: Documentation, https://zimmer-yan.github.io/metripy/
10
10
  Project-URL: Bug Tracker, https://github.com/zimmer-yan/metripy/issues
11
11
  Keywords: code metrics,multi-language,code analysis,git metrics,code visualization,software quality,static analysis,repository insights,developer productivity,codebase health,technical debt,language-agnostic
12
12
  Classifier: Development Status :: 3 - Alpha