metripy 0.3.0__tar.gz → 0.3.2__tar.gz

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 (92) hide show
  1. {metripy-0.3.0 → metripy-0.3.2}/PKG-INFO +2 -1
  2. {metripy-0.3.0 → metripy-0.3.2}/metripy/Application/Application.py +2 -3
  3. {metripy-0.3.0 → metripy-0.3.2}/metripy/Application/Config/Config.py +4 -3
  4. {metripy-0.3.0 → metripy-0.3.2}/metripy/Application/Config/File/ConfigFileReaderFactory.py +2 -1
  5. metripy-0.3.0/metripy/Application/Config/File/JsonConfigFileReader.py → metripy-0.3.2/metripy/Application/Config/File/ConfigFileReaderInterface.py +17 -23
  6. metripy-0.3.2/metripy/Application/Config/File/JsonConfigFileReader.py +17 -0
  7. metripy-0.3.2/metripy/Application/Config/File/YamlConfigFileReader.py +17 -0
  8. {metripy-0.3.0 → metripy-0.3.2}/metripy/Application/Config/Parser.py +0 -2
  9. {metripy-0.3.0 → metripy-0.3.2}/metripy/Application/Config/ProjectConfig.py +4 -3
  10. {metripy-0.3.0 → metripy-0.3.2}/metripy/Application/Info.py +9 -2
  11. {metripy-0.3.0 → metripy-0.3.2}/metripy/LangAnalyzer/Python/PythonAnalyzer.py +1 -1
  12. {metripy-0.3.0 → metripy-0.3.2}/metripy/Metric/Code/Segmentor.py +2 -0
  13. metripy-0.3.2/metripy/Report/Html/DependencyPageRenderer.py +21 -0
  14. metripy-0.3.2/metripy/Report/Html/FilesPageRenderer.py +28 -0
  15. metripy-0.3.2/metripy/Report/Html/GitAnalysisPageRenderer.py +55 -0
  16. metripy-0.3.2/metripy/Report/Html/IndexPageRenderer.py +40 -0
  17. metripy-0.3.2/metripy/Report/Html/PageRenderer.py +43 -0
  18. metripy-0.3.2/metripy/Report/Html/PageRendererFactory.py +37 -0
  19. metripy-0.3.2/metripy/Report/Html/Reporter.py +124 -0
  20. metripy-0.3.2/metripy/Report/Html/TopOffendersPageRenderer.py +84 -0
  21. metripy-0.3.2/metripy/Report/Html/TrendsPageRenderer.py +114 -0
  22. {metripy-0.3.0 → metripy-0.3.2}/metripy/Report/ReporterFactory.py +4 -2
  23. {metripy-0.3.0 → metripy-0.3.2}/metripy.egg-info/PKG-INFO +2 -1
  24. {metripy-0.3.0 → metripy-0.3.2}/metripy.egg-info/SOURCES.txt +9 -0
  25. {metripy-0.3.0 → metripy-0.3.2}/metripy.egg-info/requires.txt +1 -0
  26. {metripy-0.3.0 → metripy-0.3.2}/pyproject.toml +3 -2
  27. metripy-0.3.0/metripy/Application/Config/File/ConfigFileReaderInterface.py +0 -14
  28. metripy-0.3.0/metripy/Report/Html/Reporter.py +0 -424
  29. {metripy-0.3.0 → metripy-0.3.2}/LICENSE +0 -0
  30. {metripy-0.3.0 → metripy-0.3.2}/README.md +0 -0
  31. {metripy-0.3.0 → metripy-0.3.2}/metripy/Application/Analyzer.py +0 -0
  32. {metripy-0.3.0 → metripy-0.3.2}/metripy/Application/Config/GitConfig.py +0 -0
  33. {metripy-0.3.0 → metripy-0.3.2}/metripy/Application/Config/ReportConfig.py +0 -0
  34. {metripy-0.3.0 → metripy-0.3.2}/metripy/Application/__init__.py +0 -0
  35. {metripy-0.3.0 → metripy-0.3.2}/metripy/Component/Debug/Debugger.py +0 -0
  36. {metripy-0.3.0 → metripy-0.3.2}/metripy/Component/File/Finder.py +0 -0
  37. {metripy-0.3.0 → metripy-0.3.2}/metripy/Component/Output/CliOutput.py +0 -0
  38. {metripy-0.3.0 → metripy-0.3.2}/metripy/Component/Output/ProgressBar.py +0 -0
  39. {metripy-0.3.0 → metripy-0.3.2}/metripy/Dependency/Composer/Composer.py +0 -0
  40. {metripy-0.3.0 → metripy-0.3.2}/metripy/Dependency/Composer/Packegist.py +0 -0
  41. {metripy-0.3.0 → metripy-0.3.2}/metripy/Dependency/Dependency.py +0 -0
  42. {metripy-0.3.0 → metripy-0.3.2}/metripy/Dependency/Npm/Npm.py +0 -0
  43. {metripy-0.3.0 → metripy-0.3.2}/metripy/Dependency/Npm/NpmOrg.py +0 -0
  44. {metripy-0.3.0 → metripy-0.3.2}/metripy/Dependency/Pip/Pip.py +0 -0
  45. {metripy-0.3.0 → metripy-0.3.2}/metripy/Dependency/Pip/PyPi.py +0 -0
  46. {metripy-0.3.0 → metripy-0.3.2}/metripy/Git/GitAnalyzer.py +0 -0
  47. {metripy-0.3.0 → metripy-0.3.2}/metripy/Import/Json/JsonImporter.py +0 -0
  48. {metripy-0.3.0 → metripy-0.3.2}/metripy/LangAnalyzer/AbstractLangAnalyzer.py +0 -0
  49. {metripy-0.3.0 → metripy-0.3.2}/metripy/LangAnalyzer/Generic/HalSteadAnalyzer.py +0 -0
  50. {metripy-0.3.0 → metripy-0.3.2}/metripy/LangAnalyzer/Generic/__init__.py +0 -0
  51. {metripy-0.3.0 → metripy-0.3.2}/metripy/LangAnalyzer/Php/PhpAnalyzer.py +0 -0
  52. {metripy-0.3.0 → metripy-0.3.2}/metripy/LangAnalyzer/Php/PhpBasicAstParser.py +0 -0
  53. {metripy-0.3.0 → metripy-0.3.2}/metripy/LangAnalyzer/Php/PhpBasicLocAnalyzer.py +0 -0
  54. {metripy-0.3.0 → metripy-0.3.2}/metripy/LangAnalyzer/Php/PhpHalSteadAnalyzer.py +0 -0
  55. {metripy-0.3.0 → metripy-0.3.2}/metripy/LangAnalyzer/Python/PythonHalSteadAnalyzer.py +0 -0
  56. {metripy-0.3.0 → metripy-0.3.2}/metripy/LangAnalyzer/Typescript/TypescriptAnalyzer.py +0 -0
  57. {metripy-0.3.0 → metripy-0.3.2}/metripy/LangAnalyzer/Typescript/TypescriptAstParser.py +0 -0
  58. {metripy-0.3.0 → metripy-0.3.2}/metripy/LangAnalyzer/Typescript/TypescriptBasicComplexityAnalyzer.py +0 -0
  59. {metripy-0.3.0 → metripy-0.3.2}/metripy/LangAnalyzer/Typescript/TypescriptBasicLocAnalyzer.py +0 -0
  60. {metripy-0.3.0 → metripy-0.3.2}/metripy/LangAnalyzer/Typescript/TypescriptHalSteadAnalyzer.py +0 -0
  61. {metripy-0.3.0 → metripy-0.3.2}/metripy/LangAnalyzer/__init__.py +0 -0
  62. {metripy-0.3.0 → metripy-0.3.2}/metripy/Metric/Code/AggregatedMetrics.py +0 -0
  63. {metripy-0.3.0 → metripy-0.3.2}/metripy/Metric/Code/FileMetrics.py +0 -0
  64. {metripy-0.3.0 → metripy-0.3.2}/metripy/Metric/Code/ModuleMetrics.py +0 -0
  65. {metripy-0.3.0 → metripy-0.3.2}/metripy/Metric/Code/SegmentedMetrics.py +0 -0
  66. {metripy-0.3.0 → metripy-0.3.2}/metripy/Metric/FileTree/FileTree.py +0 -0
  67. {metripy-0.3.0 → metripy-0.3.2}/metripy/Metric/FileTree/FileTreeParser.py +0 -0
  68. {metripy-0.3.0 → metripy-0.3.2}/metripy/Metric/Git/GitCodeHotspot.py +0 -0
  69. {metripy-0.3.0 → metripy-0.3.2}/metripy/Metric/Git/GitContributor.py +0 -0
  70. {metripy-0.3.0 → metripy-0.3.2}/metripy/Metric/Git/GitKnowledgeSilo.py +0 -0
  71. {metripy-0.3.0 → metripy-0.3.2}/metripy/Metric/Git/GitMetrics.py +0 -0
  72. {metripy-0.3.0 → metripy-0.3.2}/metripy/Metric/ProjectMetrics.py +0 -0
  73. {metripy-0.3.0 → metripy-0.3.2}/metripy/Metric/Trend/AggregatedTrendMetric.py +0 -0
  74. {metripy-0.3.0 → metripy-0.3.2}/metripy/Metric/Trend/ClassTrendMetric.py +0 -0
  75. {metripy-0.3.0 → metripy-0.3.2}/metripy/Metric/Trend/FileTrendMetric.py +0 -0
  76. {metripy-0.3.0 → metripy-0.3.2}/metripy/Metric/Trend/FunctionTrendMetric.py +0 -0
  77. {metripy-0.3.0 → metripy-0.3.2}/metripy/Metric/Trend/SegmentedTrendMetric.py +0 -0
  78. {metripy-0.3.0 → metripy-0.3.2}/metripy/Report/Csv/Reporter.py +0 -0
  79. {metripy-0.3.0 → metripy-0.3.2}/metripy/Report/Json/AbstractJsonReporter.py +0 -0
  80. {metripy-0.3.0 → metripy-0.3.2}/metripy/Report/Json/GitJsonReporter.py +0 -0
  81. {metripy-0.3.0 → metripy-0.3.2}/metripy/Report/Json/JsonReporter.py +0 -0
  82. {metripy-0.3.0 → metripy-0.3.2}/metripy/Report/ReporterInterface.py +0 -0
  83. {metripy-0.3.0 → metripy-0.3.2}/metripy/Tree/ClassNode.py +0 -0
  84. {metripy-0.3.0 → metripy-0.3.2}/metripy/Tree/FunctionNode.py +0 -0
  85. {metripy-0.3.0 → metripy-0.3.2}/metripy/Tree/ModuleNode.py +0 -0
  86. {metripy-0.3.0 → metripy-0.3.2}/metripy/Trend/TrendAnalyzer.py +0 -0
  87. {metripy-0.3.0 → metripy-0.3.2}/metripy/__init__.py +0 -0
  88. {metripy-0.3.0 → metripy-0.3.2}/metripy/metripy.py +0 -0
  89. {metripy-0.3.0 → metripy-0.3.2}/metripy.egg-info/dependency_links.txt +0 -0
  90. {metripy-0.3.0 → metripy-0.3.2}/metripy.egg-info/entry_points.txt +0 -0
  91. {metripy-0.3.0 → metripy-0.3.2}/metripy.egg-info/top_level.txt +0 -0
  92. {metripy-0.3.0 → metripy-0.3.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: metripy
3
- Version: 0.3.0
3
+ Version: 0.3.2
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
@@ -26,6 +26,7 @@ Requires-Dist: packaging==25.0
26
26
  Requires-Dist: toml==0.10.2
27
27
  Requires-Dist: tree-sitter==0.21.3
28
28
  Requires-Dist: tree-sitter-languages==1.10.2
29
+ Requires-Dist: PyYAML==6.0.3
29
30
  Provides-Extra: dev
30
31
  Requires-Dist: pytest>=7.0.0; extra == "dev"
31
32
  Requires-Dist: pytest-cov==7.0.0; extra == "dev"
@@ -2,14 +2,13 @@ import json
2
2
 
3
3
  from metripy.Application.Analyzer import Analyzer
4
4
  from metripy.Application.Config.Parser import Parser
5
+ from metripy.Application.Info import Info
5
6
  from metripy.Component.Debug.Debugger import Debugger
6
7
  from metripy.Component.File.Finder import Finder
7
8
  from metripy.Component.Output.CliOutput import CliOutput
8
9
  from metripy.Report.ReporterFactory import ReporterFactory
9
10
  from metripy.Report.ReporterInterface import ReporterInterface
10
11
 
11
- from metripy.Application.Info import Info
12
-
13
12
 
14
13
  class Application:
15
14
  def run(self, argv) -> None:
@@ -61,7 +60,7 @@ class Application:
61
60
  )
62
61
  for report_config in project_config.reports:
63
62
  reporter: ReporterInterface = ReporterFactory.create(
64
- report_config, output
63
+ report_config, output, project_config.name
65
64
  )
66
65
  reporter.generate(project_metrics)
67
66
  output.writeln(
@@ -17,7 +17,6 @@ class Config:
17
17
  }
18
18
 
19
19
  def set(self, param: str, value: any) -> None:
20
- print(f"Setting {param} to {value}")
21
20
  if param == "quiet":
22
21
  self.quiet = value
23
22
  elif param == "version":
@@ -27,7 +26,7 @@ class Config:
27
26
  elif param == "debug":
28
27
  self.debug = value
29
28
  elif param.startswith("configs."):
30
- self._set_project_value(param[len("configs."):], value)
29
+ self._set_project_value(param[len("configs.") :], value)
31
30
  else:
32
31
  # ignore unknown parameters
33
32
  return
@@ -35,7 +34,9 @@ class Config:
35
34
  def _set_project_value(self, param: str, value: any) -> None:
36
35
  keys = param.split(".")
37
36
  project_name = keys[0]
38
- project_config = next((pc for pc in self.project_configs if pc.name == project_name), None)
37
+ project_config = next(
38
+ (pc for pc in self.project_configs if pc.name == project_name), None
39
+ )
39
40
  if not project_config:
40
41
  project_config = ProjectConfig(project_name)
41
42
  self.project_configs.append(project_config)
@@ -5,6 +5,7 @@ from metripy.Application.Config.File.ConfigFileReaderInterface import (
5
5
  ConfigFileReaderInterface,
6
6
  )
7
7
  from metripy.Application.Config.File.JsonConfigFileReader import JsonConfigFileReader
8
+ from metripy.Application.Config.File.YamlConfigFileReader import YamlConfigFileReader
8
9
 
9
10
 
10
11
  class ConfigFileReaderFactory:
@@ -17,7 +18,7 @@ class ConfigFileReaderFactory:
17
18
  if extension == ".json":
18
19
  return JsonConfigFileReader(filename)
19
20
  elif extension == ".yaml" or extension == ".yml":
20
- raise NotImplementedError("YAML support is not implemented yet")
21
+ return YamlConfigFileReader(filename)
21
22
  elif extension == ".xml":
22
23
  raise NotImplementedError("XML support is not implemented yet")
23
24
  else:
@@ -1,45 +1,39 @@
1
- import json
2
1
  import os
2
+ from abc import ABC, abstractmethod
3
3
 
4
4
  from metripy.Application.Config.Config import Config
5
- from metripy.Application.Config.File.ConfigFileReaderInterface import (
6
- ConfigFileReaderInterface,
7
- )
8
5
  from metripy.Application.Config.GitConfig import GitConfig
9
6
  from metripy.Application.Config.ProjectConfig import ProjectConfig
10
7
  from metripy.Application.Config.ReportConfig import ReportConfig
11
8
 
12
9
 
13
- class JsonConfigFileReader(ConfigFileReaderInterface):
10
+ class ConfigFileReaderInterface(ABC):
14
11
  def __init__(self, filename: str):
15
12
  self.filename = filename
16
13
 
14
+ @abstractmethod
17
15
  def read(self, config: Config) -> None:
18
- with open(self.filename, "r") as file:
19
- json_data = json.load(file)
20
-
21
- self.parse_json(json_data, config)
16
+ pass
22
17
 
23
18
  def resolve_path(self, path: str) -> str:
24
19
  return os.path.join(os.path.dirname(self.filename), path)
25
20
 
26
- def parse_json(self, json_data: dict, config: Config) -> None:
27
-
21
+ def parse_data(self, data: dict, config: Config) -> None:
28
22
  # configs
29
- if configs := json_data.get("configs"):
23
+ if configs := data.get("configs"):
30
24
  for project_name, project_config in configs.items():
31
25
  project_config = self.parse_config_json(project_name, project_config)
32
26
  config.project_configs.append(project_config)
33
27
 
34
- def parse_config_json(self, project_name: str, json_data: dict) -> ProjectConfig:
28
+ def parse_config_json(self, project_name: str, data: dict) -> ProjectConfig:
35
29
  project_config = ProjectConfig(project_name)
36
30
 
37
31
  # extensions
38
- if base_path := json_data.get("base_path"):
32
+ if base_path := data.get("base_path"):
39
33
  project_config.base_path = base_path
40
34
 
41
35
  # includes
42
- if includes := json_data.get("includes"):
36
+ if includes := data.get("includes"):
43
37
  files = []
44
38
  # with config file, includes are relative to the config file
45
39
  for include in includes:
@@ -49,39 +43,39 @@ class JsonConfigFileReader(ConfigFileReaderInterface):
49
43
  project_config.includes = files
50
44
 
51
45
  # extensions
52
- if extensions := json_data.get("extensions"):
46
+ if extensions := data.get("extensions"):
53
47
  project_config.extensions = extensions
54
48
 
55
49
  # excludes
56
- if excludes := json_data.get("excludes"):
50
+ if excludes := data.get("excludes"):
57
51
  project_config.excludes = excludes
58
52
 
59
53
  # reports
60
- if reports := json_data.get("reports"):
54
+ if reports := data.get("reports"):
61
55
  for report_type, path in reports.items():
62
56
  path = self.resolve_path(path)
63
57
  project_config.reports.append(ReportConfig(report_type, path))
64
58
 
65
59
  # git
66
- if git := json_data.get("git"):
60
+ if git := data.get("git"):
67
61
  project_config.git = GitConfig()
68
62
  project_config.git.repo = project_config.base_path
69
63
  project_config.git.branch = git.get("branch", project_config.git.branch)
70
64
 
71
65
  # composer
72
- if composer := json_data.get("composer"):
66
+ if composer := data.get("composer"):
73
67
  project_config.composer = composer
74
68
 
75
69
  # pip
76
- if pip := json_data.get("pip"):
70
+ if pip := data.get("pip"):
77
71
  project_config.pip = pip
78
72
 
79
73
  # npm
80
- if npm := json_data.get("npm"):
74
+ if npm := data.get("npm"):
81
75
  project_config.npm = npm
82
76
 
83
77
  # trends
84
- if history_path := json_data.get("trends"):
78
+ if history_path := data.get("trends"):
85
79
  project_config.history_path = self.resolve_path(history_path)
86
80
 
87
81
  return project_config
@@ -0,0 +1,17 @@
1
+ import json
2
+
3
+ from metripy.Application.Config.Config import Config
4
+ from metripy.Application.Config.File.ConfigFileReaderInterface import (
5
+ ConfigFileReaderInterface,
6
+ )
7
+
8
+
9
+ class JsonConfigFileReader(ConfigFileReaderInterface):
10
+ def __init__(self, filename: str):
11
+ super().__init__(filename)
12
+
13
+ def read(self, config: Config) -> None:
14
+ with open(self.filename, "r") as file:
15
+ json_data = json.load(file)
16
+
17
+ self.parse_data(json_data, config)
@@ -0,0 +1,17 @@
1
+ import yaml
2
+
3
+ from metripy.Application.Config.Config import Config
4
+ from metripy.Application.Config.File.ConfigFileReaderInterface import (
5
+ ConfigFileReaderInterface,
6
+ )
7
+
8
+
9
+ class YamlConfigFileReader(ConfigFileReaderInterface):
10
+ def __init__(self, filename: str):
11
+ super().__init__(filename)
12
+
13
+ def read(self, config: Config) -> None:
14
+ with open(self.filename, "r") as file:
15
+ yaml_data = yaml.safe_load(file)
16
+
17
+ self.parse_data(yaml_data, config)
@@ -9,7 +9,6 @@ from metripy.Application.Config.File.ConfigFileReaderFactory import (
9
9
  class Parser:
10
10
  def parse(self, argv: list[str]) -> Config:
11
11
  config = Config()
12
- print(argv)
13
12
 
14
13
  if argv[0].endswith("metripy.py") or argv[0].endswith("metripy"):
15
14
  argv.pop(0)
@@ -18,7 +17,6 @@ class Parser:
18
17
  key = 0
19
18
  while key < len(argv):
20
19
  arg = argv[key]
21
- print(f"Key: {key} Arg: '{arg}'")
22
20
  if matches := re.search(r"^--config=(.+)$", arg):
23
21
  fileReader = ConfigFileReaderFactory.createFromFileName(
24
22
  matches.group(1)
@@ -41,7 +41,6 @@ class ProjectConfig:
41
41
  if len(keys) == 0:
42
42
  return
43
43
  primary_key = keys[0]
44
- print(f"Setting primary {primary_key} to {value}")
45
44
  # single value
46
45
  if primary_key == "base_path":
47
46
  self.base_path = value
@@ -84,7 +83,9 @@ class ProjectConfig:
84
83
  if value != "":
85
84
  self.reports.append(ReportConfig(report_type, report_path))
86
85
  else:
87
- report_config = next((rc for rc in self.reports if rc.type == report_type), None)
86
+ report_config = next(
87
+ (rc for rc in self.reports if rc.type == report_type), None
88
+ )
88
89
  if not report_config:
89
90
  return
90
- self.reports.remove(report_config)
91
+ self.reports.remove(report_config)
@@ -1,5 +1,6 @@
1
1
  import toml
2
2
 
3
+
3
4
  class Info:
4
5
  def __init__(self):
5
6
  data = self._get_data()
@@ -11,14 +12,19 @@ class Info:
11
12
  data = toml.load(file)
12
13
  return data
13
14
 
15
+ def get_version(self) -> str:
16
+ return self.version
17
+
14
18
  def get_version_info(self) -> str:
15
19
  return f"""
16
- Metripy {self.version}
20
+ Metripy {self.get_version()}
17
21
  {self.url}
18
22
  """
19
23
 
20
24
  def get_help(self) -> str:
21
- return self.get_version_info() + f"""
25
+ return (
26
+ self.get_version_info()
27
+ + """
22
28
  Usage: metripy [options]
23
29
  Options:
24
30
  --config=<file> Use a custom config file
@@ -27,3 +33,4 @@ Options:
27
33
  --debug Enable debug mode
28
34
  --quiet Disable output
29
35
  """
36
+ )
@@ -122,7 +122,7 @@ class PythonAnalyzer(AbstractLangAnalyzer):
122
122
  continue
123
123
  # if MI is 0, we want to take another look, radon does not like boring functions
124
124
 
125
- lines = code_lines[function_node.lineno:function_node.line_end]
125
+ lines = code_lines[function_node.lineno : function_node.line_end]
126
126
  function_metrics = (
127
127
  self.fallback_halstead_analyzer.calculate_halstead_metrics(
128
128
  "\n".join(lines)
@@ -40,3 +40,5 @@ class Segmentor:
40
40
  return "ok"
41
41
  elif method_size <= 50:
42
42
  return "warning"
43
+ else:
44
+ return "critical"
@@ -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)
@@ -0,0 +1,124 @@
1
+ import os
2
+ import shutil
3
+ from datetime import datetime
4
+
5
+
6
+ from metripy.Application.Config.ReportConfig import ReportConfig
7
+ from metripy.Component.Output.CliOutput import CliOutput
8
+ from metripy.Metric.ProjectMetrics import ProjectMetrics
9
+ from metripy.Report.Html.PageRendererFactory import PageRendererFactory
10
+ from metripy.Report.ReporterInterface import ReporterInterface
11
+
12
+
13
+ class Reporter(ReporterInterface):
14
+ def __init__(
15
+ self, config: ReportConfig, output: CliOutput, project_name: str = "foobar"
16
+ ):
17
+ self.config: ReportConfig = config
18
+ self.output = output
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
+ )
25
+
26
+ self.global_template_args = {
27
+ "project_name": project_name,
28
+ "last_updated": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
29
+ }
30
+
31
+ def generate(self, metrics: ProjectMetrics):
32
+
33
+ self.output.writeln("<info>Generating HTML report...</info>")
34
+
35
+ # copy sources
36
+ if not os.path.exists(os.path.join(self.config.path, "js")):
37
+ os.makedirs(os.path.join(self.config.path, "js"))
38
+ if not os.path.exists(os.path.join(self.config.path, "css")):
39
+ os.makedirs(os.path.join(self.config.path, "css"))
40
+ if not os.path.exists(os.path.join(self.config.path, "images")):
41
+ os.makedirs(os.path.join(self.config.path, "images"))
42
+ if not os.path.exists(os.path.join(self.config.path, "fonts")):
43
+ os.makedirs(os.path.join(self.config.path, "fonts"))
44
+ if not os.path.exists(os.path.join(self.config.path, "data")):
45
+ os.makedirs(os.path.join(self.config.path, "data"))
46
+
47
+ # shutil.copy(os.path.join(self.template_dir, "favicon.ico"), os.path.join(self.config.path, "favicon.ico"))
48
+
49
+ shutil.copytree(
50
+ os.path.join(self.template_dir, "js"),
51
+ os.path.join(self.config.path, "js"),
52
+ dirs_exist_ok=True,
53
+ )
54
+ shutil.copytree(
55
+ os.path.join(self.template_dir, "css"),
56
+ os.path.join(self.config.path, "css"),
57
+ dirs_exist_ok=True,
58
+ )
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
+ )
64
+ # shutil.copytree(os.path.join(self.template_dir, "fonts"), os.path.join(self.config.path, "fonts"), dirs_exist_ok=True)
65
+
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"),
70
+ )
71
+
72
+ # Render main pages
73
+ self.render_index_page(metrics)
74
+ self.render_files_page(metrics)
75
+ self.render_top_offenders_page(metrics)
76
+ self.render_git_analysis_page(metrics)
77
+ self.render_dependencies_page(metrics)
78
+ self.render_trends_page(metrics)
79
+
80
+ self.output.writeln(
81
+ f"<success>HTML report generated in {self.config.path} directory</success>"
82
+ )
83
+ self.output.writeln(
84
+ f"<success>Open HTML report: {self.config.path}/index.html</success>"
85
+ )
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
+
92
+ def render_files_page(self, metrics: ProjectMetrics):
93
+ """Render the files page with file details and analysis"""
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>")
97
+
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>"
103
+ )
104
+
105
+ def render_git_analysis_page(self, metrics: ProjectMetrics):
106
+ """Render the git analysis page with comprehensive git data"""
107
+ self.output.writeln("<info>Rendering git analysis page</info>")
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>")