metripy 0.3.0__py3-none-any.whl → 0.3.2__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 (27) hide show
  1. metripy/Application/Application.py +2 -3
  2. metripy/Application/Config/Config.py +4 -3
  3. metripy/Application/Config/File/ConfigFileReaderFactory.py +2 -1
  4. metripy/Application/Config/File/ConfigFileReaderInterface.py +70 -3
  5. metripy/Application/Config/File/JsonConfigFileReader.py +2 -72
  6. metripy/Application/Config/File/YamlConfigFileReader.py +17 -0
  7. metripy/Application/Config/Parser.py +0 -2
  8. metripy/Application/Config/ProjectConfig.py +4 -3
  9. metripy/Application/Info.py +9 -2
  10. metripy/LangAnalyzer/Python/PythonAnalyzer.py +1 -1
  11. metripy/Metric/Code/Segmentor.py +2 -0
  12. metripy/Report/Html/DependencyPageRenderer.py +21 -0
  13. metripy/Report/Html/FilesPageRenderer.py +28 -0
  14. metripy/Report/Html/GitAnalysisPageRenderer.py +55 -0
  15. metripy/Report/Html/IndexPageRenderer.py +40 -0
  16. metripy/Report/Html/PageRenderer.py +43 -0
  17. metripy/Report/Html/PageRendererFactory.py +37 -0
  18. metripy/Report/Html/Reporter.py +34 -334
  19. metripy/Report/Html/TopOffendersPageRenderer.py +84 -0
  20. metripy/Report/Html/TrendsPageRenderer.py +114 -0
  21. metripy/Report/ReporterFactory.py +4 -2
  22. {metripy-0.3.0.dist-info → metripy-0.3.2.dist-info}/METADATA +2 -1
  23. {metripy-0.3.0.dist-info → metripy-0.3.2.dist-info}/RECORD +27 -18
  24. {metripy-0.3.0.dist-info → metripy-0.3.2.dist-info}/WHEEL +0 -0
  25. {metripy-0.3.0.dist-info → metripy-0.3.2.dist-info}/entry_points.txt +0 -0
  26. {metripy-0.3.0.dist-info → metripy-0.3.2.dist-info}/licenses/LICENSE +0 -0
  27. {metripy-0.3.0.dist-info → metripy-0.3.2.dist-info}/top_level.txt +0 -0
@@ -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,14 +1,81 @@
1
+ import os
1
2
  from abc import ABC, abstractmethod
2
3
 
3
4
  from metripy.Application.Config.Config import Config
5
+ from metripy.Application.Config.GitConfig import GitConfig
6
+ from metripy.Application.Config.ProjectConfig import ProjectConfig
7
+ from metripy.Application.Config.ReportConfig import ReportConfig
4
8
 
5
9
 
6
10
  class ConfigFileReaderInterface(ABC):
7
-
8
- @abstractmethod
9
11
  def __init__(self, filename: str):
10
- pass
12
+ self.filename = filename
11
13
 
12
14
  @abstractmethod
13
15
  def read(self, config: Config) -> None:
14
16
  pass
17
+
18
+ def resolve_path(self, path: str) -> str:
19
+ return os.path.join(os.path.dirname(self.filename), path)
20
+
21
+ def parse_data(self, data: dict, config: Config) -> None:
22
+ # configs
23
+ if configs := data.get("configs"):
24
+ for project_name, project_config in configs.items():
25
+ project_config = self.parse_config_json(project_name, project_config)
26
+ config.project_configs.append(project_config)
27
+
28
+ def parse_config_json(self, project_name: str, data: dict) -> ProjectConfig:
29
+ project_config = ProjectConfig(project_name)
30
+
31
+ # extensions
32
+ if base_path := data.get("base_path"):
33
+ project_config.base_path = base_path
34
+
35
+ # includes
36
+ if includes := data.get("includes"):
37
+ files = []
38
+ # with config file, includes are relative to the config file
39
+ for include in includes:
40
+ include = self.resolve_path(include)
41
+ files.append(include)
42
+
43
+ project_config.includes = files
44
+
45
+ # extensions
46
+ if extensions := data.get("extensions"):
47
+ project_config.extensions = extensions
48
+
49
+ # excludes
50
+ if excludes := data.get("excludes"):
51
+ project_config.excludes = excludes
52
+
53
+ # reports
54
+ if reports := data.get("reports"):
55
+ for report_type, path in reports.items():
56
+ path = self.resolve_path(path)
57
+ project_config.reports.append(ReportConfig(report_type, path))
58
+
59
+ # git
60
+ if git := data.get("git"):
61
+ project_config.git = GitConfig()
62
+ project_config.git.repo = project_config.base_path
63
+ project_config.git.branch = git.get("branch", project_config.git.branch)
64
+
65
+ # composer
66
+ if composer := data.get("composer"):
67
+ project_config.composer = composer
68
+
69
+ # pip
70
+ if pip := data.get("pip"):
71
+ project_config.pip = pip
72
+
73
+ # npm
74
+ if npm := data.get("npm"):
75
+ project_config.npm = npm
76
+
77
+ # trends
78
+ if history_path := data.get("trends"):
79
+ project_config.history_path = self.resolve_path(history_path)
80
+
81
+ return project_config
@@ -1,87 +1,17 @@
1
1
  import json
2
- import os
3
2
 
4
3
  from metripy.Application.Config.Config import Config
5
4
  from metripy.Application.Config.File.ConfigFileReaderInterface import (
6
5
  ConfigFileReaderInterface,
7
6
  )
8
- from metripy.Application.Config.GitConfig import GitConfig
9
- from metripy.Application.Config.ProjectConfig import ProjectConfig
10
- from metripy.Application.Config.ReportConfig import ReportConfig
11
7
 
12
8
 
13
9
  class JsonConfigFileReader(ConfigFileReaderInterface):
14
10
  def __init__(self, filename: str):
15
- self.filename = filename
11
+ super().__init__(filename)
16
12
 
17
13
  def read(self, config: Config) -> None:
18
14
  with open(self.filename, "r") as file:
19
15
  json_data = json.load(file)
20
16
 
21
- self.parse_json(json_data, config)
22
-
23
- def resolve_path(self, path: str) -> str:
24
- return os.path.join(os.path.dirname(self.filename), path)
25
-
26
- def parse_json(self, json_data: dict, config: Config) -> None:
27
-
28
- # configs
29
- if configs := json_data.get("configs"):
30
- for project_name, project_config in configs.items():
31
- project_config = self.parse_config_json(project_name, project_config)
32
- config.project_configs.append(project_config)
33
-
34
- def parse_config_json(self, project_name: str, json_data: dict) -> ProjectConfig:
35
- project_config = ProjectConfig(project_name)
36
-
37
- # extensions
38
- if base_path := json_data.get("base_path"):
39
- project_config.base_path = base_path
40
-
41
- # includes
42
- if includes := json_data.get("includes"):
43
- files = []
44
- # with config file, includes are relative to the config file
45
- for include in includes:
46
- include = self.resolve_path(include)
47
- files.append(include)
48
-
49
- project_config.includes = files
50
-
51
- # extensions
52
- if extensions := json_data.get("extensions"):
53
- project_config.extensions = extensions
54
-
55
- # excludes
56
- if excludes := json_data.get("excludes"):
57
- project_config.excludes = excludes
58
-
59
- # reports
60
- if reports := json_data.get("reports"):
61
- for report_type, path in reports.items():
62
- path = self.resolve_path(path)
63
- project_config.reports.append(ReportConfig(report_type, path))
64
-
65
- # git
66
- if git := json_data.get("git"):
67
- project_config.git = GitConfig()
68
- project_config.git.repo = project_config.base_path
69
- project_config.git.branch = git.get("branch", project_config.git.branch)
70
-
71
- # composer
72
- if composer := json_data.get("composer"):
73
- project_config.composer = composer
74
-
75
- # pip
76
- if pip := json_data.get("pip"):
77
- project_config.pip = pip
78
-
79
- # npm
80
- if npm := json_data.get("npm"):
81
- project_config.npm = npm
82
-
83
- # trends
84
- if history_path := json_data.get("trends"):
85
- project_config.history_path = self.resolve_path(history_path)
86
-
87
- return project_config
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)