metripy 0.2.8__tar.gz → 0.3.1__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 (98) hide show
  1. {metripy-0.2.8 → metripy-0.3.1}/PKG-INFO +3 -3
  2. {metripy-0.2.8 → metripy-0.3.1}/metripy/Application/Analyzer.py +23 -3
  3. {metripy-0.2.8 → metripy-0.3.1}/metripy/Application/Application.py +16 -2
  4. metripy-0.3.1/metripy/Application/Config/Config.py +47 -0
  5. {metripy-0.2.8 → metripy-0.3.1}/metripy/Application/Config/File/ConfigFileReaderFactory.py +6 -5
  6. metripy-0.2.8/metripy/Application/Config/File/JsonConfigFileReader.py → metripy-0.3.1/metripy/Application/Config/File/ConfigFileReaderInterface.py +20 -21
  7. metripy-0.3.1/metripy/Application/Config/File/JsonConfigFileReader.py +17 -0
  8. metripy-0.3.1/metripy/Application/Config/File/YamlConfigFileReader.py +17 -0
  9. metripy-0.3.1/metripy/Application/Config/Parser.py +44 -0
  10. metripy-0.3.1/metripy/Application/Config/ProjectConfig.py +91 -0
  11. metripy-0.3.1/metripy/Application/Info.py +36 -0
  12. {metripy-0.2.8 → metripy-0.3.1}/metripy/Dependency/Dependency.py +2 -1
  13. {metripy-0.2.8 → metripy-0.3.1}/metripy/Dependency/Pip/Pip.py +1 -2
  14. {metripy-0.2.8 → metripy-0.3.1}/metripy/Dependency/Pip/PyPi.py +1 -0
  15. {metripy-0.2.8 → metripy-0.3.1}/metripy/Git/GitAnalyzer.py +0 -3
  16. metripy-0.3.1/metripy/Import/Json/JsonImporter.py +17 -0
  17. {metripy-0.2.8 → metripy-0.3.1}/metripy/LangAnalyzer/AbstractLangAnalyzer.py +4 -3
  18. {metripy-0.2.8 → metripy-0.3.1}/metripy/LangAnalyzer/Php/PhpAnalyzer.py +2 -1
  19. {metripy-0.2.8 → metripy-0.3.1}/metripy/LangAnalyzer/Python/PythonAnalyzer.py +31 -9
  20. metripy-0.3.1/metripy/LangAnalyzer/Python/PythonHalSteadAnalyzer.py +55 -0
  21. {metripy-0.2.8 → metripy-0.3.1}/metripy/LangAnalyzer/Typescript/TypescriptAnalyzer.py +12 -9
  22. {metripy-0.2.8 → metripy-0.3.1}/metripy/LangAnalyzer/Typescript/TypescriptAstParser.py +1 -1
  23. {metripy-0.2.8 → metripy-0.3.1}/metripy/Metric/Code/AggregatedMetrics.py +12 -5
  24. metripy-0.3.1/metripy/Metric/Code/FileMetrics.py +64 -0
  25. {metripy-0.2.8 → metripy-0.3.1}/metripy/Metric/Code/ModuleMetrics.py +5 -5
  26. metripy-0.3.1/metripy/Metric/Code/SegmentedMetrics.py +101 -0
  27. metripy-0.3.1/metripy/Metric/Code/Segmentor.py +44 -0
  28. {metripy-0.2.8 → metripy-0.3.1}/metripy/Metric/FileTree/FileTreeParser.py +0 -4
  29. {metripy-0.2.8 → metripy-0.3.1}/metripy/Metric/Git/GitMetrics.py +1 -1
  30. {metripy-0.2.8 → metripy-0.3.1}/metripy/Metric/ProjectMetrics.py +17 -2
  31. metripy-0.3.1/metripy/Metric/Trend/AggregatedTrendMetric.py +101 -0
  32. metripy-0.3.1/metripy/Metric/Trend/ClassTrendMetric.py +20 -0
  33. metripy-0.3.1/metripy/Metric/Trend/FileTrendMetric.py +46 -0
  34. metripy-0.3.1/metripy/Metric/Trend/FunctionTrendMetric.py +28 -0
  35. metripy-0.3.1/metripy/Metric/Trend/SegmentedTrendMetric.py +29 -0
  36. metripy-0.3.1/metripy/Report/Html/DependencyPageRenderer.py +21 -0
  37. metripy-0.3.1/metripy/Report/Html/FilesPageRenderer.py +28 -0
  38. metripy-0.3.1/metripy/Report/Html/GitAnalysisPageRenderer.py +55 -0
  39. metripy-0.3.1/metripy/Report/Html/IndexPageRenderer.py +40 -0
  40. metripy-0.3.1/metripy/Report/Html/PageRenderer.py +43 -0
  41. metripy-0.3.1/metripy/Report/Html/PageRendererFactory.py +37 -0
  42. metripy-0.3.1/metripy/Report/Html/Reporter.py +124 -0
  43. metripy-0.3.1/metripy/Report/Html/TopOffendersPageRenderer.py +84 -0
  44. metripy-0.3.1/metripy/Report/Html/TrendsPageRenderer.py +114 -0
  45. {metripy-0.2.8 → metripy-0.3.1}/metripy/Report/Json/GitJsonReporter.py +3 -1
  46. {metripy-0.2.8 → metripy-0.3.1}/metripy/Report/Json/JsonReporter.py +4 -1
  47. {metripy-0.2.8 → metripy-0.3.1}/metripy/Report/ReporterFactory.py +4 -2
  48. {metripy-0.2.8 → metripy-0.3.1}/metripy/Tree/ClassNode.py +21 -0
  49. metripy-0.3.1/metripy/Tree/FunctionNode.py +114 -0
  50. metripy-0.3.1/metripy/Trend/TrendAnalyzer.py +150 -0
  51. {metripy-0.2.8 → metripy-0.3.1}/metripy.egg-info/PKG-INFO +3 -3
  52. {metripy-0.2.8 → metripy-0.3.1}/metripy.egg-info/SOURCES.txt +20 -1
  53. {metripy-0.2.8 → metripy-0.3.1}/pyproject.toml +3 -3
  54. metripy-0.2.8/metripy/Application/Config/Config.py +0 -13
  55. metripy-0.2.8/metripy/Application/Config/File/ConfigFileReaderInterface.py +0 -14
  56. metripy-0.2.8/metripy/Application/Config/Parser.py +0 -31
  57. metripy-0.2.8/metripy/Application/Config/ProjectConfig.py +0 -27
  58. metripy-0.2.8/metripy/Metric/Code/FileMetrics.py +0 -33
  59. metripy-0.2.8/metripy/Metric/Code/SegmentedMetrics.py +0 -65
  60. metripy-0.2.8/metripy/Report/Html/Reporter.py +0 -205
  61. metripy-0.2.8/metripy/Tree/FunctionNode.py +0 -49
  62. {metripy-0.2.8 → metripy-0.3.1}/LICENSE +0 -0
  63. {metripy-0.2.8 → metripy-0.3.1}/README.md +0 -0
  64. {metripy-0.2.8 → metripy-0.3.1}/metripy/Application/Config/GitConfig.py +0 -0
  65. {metripy-0.2.8 → metripy-0.3.1}/metripy/Application/Config/ReportConfig.py +0 -0
  66. {metripy-0.2.8 → metripy-0.3.1}/metripy/Application/__init__.py +0 -0
  67. {metripy-0.2.8 → metripy-0.3.1}/metripy/Component/Debug/Debugger.py +0 -0
  68. {metripy-0.2.8 → metripy-0.3.1}/metripy/Component/File/Finder.py +0 -0
  69. {metripy-0.2.8 → metripy-0.3.1}/metripy/Component/Output/CliOutput.py +0 -0
  70. {metripy-0.2.8 → metripy-0.3.1}/metripy/Component/Output/ProgressBar.py +0 -0
  71. {metripy-0.2.8 → metripy-0.3.1}/metripy/Dependency/Composer/Composer.py +0 -0
  72. {metripy-0.2.8 → metripy-0.3.1}/metripy/Dependency/Composer/Packegist.py +0 -0
  73. {metripy-0.2.8 → metripy-0.3.1}/metripy/Dependency/Npm/Npm.py +0 -0
  74. {metripy-0.2.8 → metripy-0.3.1}/metripy/Dependency/Npm/NpmOrg.py +0 -0
  75. {metripy-0.2.8 → metripy-0.3.1}/metripy/LangAnalyzer/Generic/HalSteadAnalyzer.py +0 -0
  76. {metripy-0.2.8 → metripy-0.3.1}/metripy/LangAnalyzer/Generic/__init__.py +0 -0
  77. {metripy-0.2.8 → metripy-0.3.1}/metripy/LangAnalyzer/Php/PhpBasicAstParser.py +0 -0
  78. {metripy-0.2.8 → metripy-0.3.1}/metripy/LangAnalyzer/Php/PhpBasicLocAnalyzer.py +0 -0
  79. {metripy-0.2.8 → metripy-0.3.1}/metripy/LangAnalyzer/Php/PhpHalSteadAnalyzer.py +0 -0
  80. {metripy-0.2.8 → metripy-0.3.1}/metripy/LangAnalyzer/Typescript/TypescriptBasicComplexityAnalyzer.py +0 -0
  81. {metripy-0.2.8 → metripy-0.3.1}/metripy/LangAnalyzer/Typescript/TypescriptBasicLocAnalyzer.py +0 -0
  82. {metripy-0.2.8 → metripy-0.3.1}/metripy/LangAnalyzer/Typescript/TypescriptHalSteadAnalyzer.py +0 -0
  83. {metripy-0.2.8 → metripy-0.3.1}/metripy/LangAnalyzer/__init__.py +0 -0
  84. {metripy-0.2.8 → metripy-0.3.1}/metripy/Metric/FileTree/FileTree.py +0 -0
  85. {metripy-0.2.8 → metripy-0.3.1}/metripy/Metric/Git/GitCodeHotspot.py +0 -0
  86. {metripy-0.2.8 → metripy-0.3.1}/metripy/Metric/Git/GitContributor.py +0 -0
  87. {metripy-0.2.8 → metripy-0.3.1}/metripy/Metric/Git/GitKnowledgeSilo.py +0 -0
  88. {metripy-0.2.8 → metripy-0.3.1}/metripy/Report/Csv/Reporter.py +0 -0
  89. {metripy-0.2.8 → metripy-0.3.1}/metripy/Report/Json/AbstractJsonReporter.py +0 -0
  90. {metripy-0.2.8 → metripy-0.3.1}/metripy/Report/ReporterInterface.py +0 -0
  91. {metripy-0.2.8 → metripy-0.3.1}/metripy/Tree/ModuleNode.py +0 -0
  92. {metripy-0.2.8 → metripy-0.3.1}/metripy/__init__.py +0 -0
  93. {metripy-0.2.8 → metripy-0.3.1}/metripy/metripy.py +0 -0
  94. {metripy-0.2.8 → metripy-0.3.1}/metripy.egg-info/dependency_links.txt +0 -0
  95. {metripy-0.2.8 → metripy-0.3.1}/metripy.egg-info/entry_points.txt +0 -0
  96. {metripy-0.2.8 → metripy-0.3.1}/metripy.egg-info/requires.txt +0 -0
  97. {metripy-0.2.8 → metripy-0.3.1}/metripy.egg-info/top_level.txt +0 -0
  98. {metripy-0.2.8 → metripy-0.3.1}/setup.cfg +0 -0
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: metripy
3
- Version: 0.2.8
3
+ Version: 0.3.1
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
@@ -7,14 +7,15 @@ from metripy.Dependency.Dependency import Dependency
7
7
  from metripy.Dependency.Npm.Npm import Npm
8
8
  from metripy.Dependency.Pip.Pip import Pip
9
9
  from metripy.Git.GitAnalyzer import GitAnalyzer
10
+ from metripy.Import.Json.JsonImporter import JsonImporter
10
11
  from metripy.LangAnalyzer.AbstractLangAnalyzer import AbstractLangAnalyzer
11
12
  from metripy.LangAnalyzer.Php.PhpAnalyzer import PhpAnalyzer
12
13
  from metripy.LangAnalyzer.Python.PythonAnalyzer import PythonAnalyzer
13
- from metripy.LangAnalyzer.Typescript.TypescriptAnalyzer import \
14
- TypescriptAnalyzer
14
+ from metripy.LangAnalyzer.Typescript.TypescriptAnalyzer import TypescriptAnalyzer
15
15
  from metripy.Metric.Code.FileMetrics import FileMetrics
16
16
  from metripy.Metric.Git.GitMetrics import GitMetrics
17
17
  from metripy.Metric.ProjectMetrics import ProjectMetrics
18
+ from metripy.Trend.TrendAnalyzer import TrendAnalyzer
18
19
 
19
20
 
20
21
  class Analyzer:
@@ -85,6 +86,18 @@ class Analyzer:
85
86
 
86
87
  return dependencies
87
88
 
89
+ def add_trends(self, project_metrics: ProjectMetrics):
90
+ self.output.writeln("<info>Analyzing trends...</info>")
91
+ importer = JsonImporter(self.output)
92
+ historical_project_metrics = importer.import_data(self.config.history_path)
93
+ TrendAnalyzer().add_historical_file_trends(
94
+ project_metrics.file_metrics, historical_project_metrics.file_metrics
95
+ )
96
+ TrendAnalyzer().add_historical_project_trends(
97
+ project_metrics, historical_project_metrics
98
+ )
99
+ self.output.writeln("<success>Trends analyzed</success>")
100
+
88
101
  def run(self, files: list[str]) -> ProjectMetrics:
89
102
  git_stats = None
90
103
  if self.config.git:
@@ -103,4 +116,11 @@ class Analyzer:
103
116
  elif self.config.npm:
104
117
  packages = self.analyze_npm()
105
118
 
106
- return ProjectMetrics(file_metrics, git_stats, packages)
119
+ if not self.config.history_path:
120
+ return ProjectMetrics(file_metrics, git_stats, packages)
121
+
122
+ # analyze trends
123
+ project_metrics = ProjectMetrics(file_metrics, git_stats, packages)
124
+ self.add_trends(project_metrics)
125
+
126
+ return project_metrics
@@ -2,6 +2,7 @@ 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
@@ -14,9 +15,22 @@ class Application:
14
15
  output = CliOutput()
15
16
 
16
17
  # issues and debug
17
- debugger = Debugger(output).enable()
18
+ debugger = Debugger(output)
18
19
 
19
20
  config = Parser().parse(argv)
21
+ if config.debug:
22
+ debugger.enable()
23
+
24
+ if config.version:
25
+ output.writeln(Info().get_version_info())
26
+ return
27
+
28
+ if config.help:
29
+ output.writeln(Info().get_help())
30
+ return
31
+
32
+ if config.quiet:
33
+ output.set_quiet(True)
20
34
 
21
35
  finder = Finder()
22
36
  files = finder.fetch(config.project_configs)
@@ -46,7 +60,7 @@ class Application:
46
60
  )
47
61
  for report_config in project_config.reports:
48
62
  reporter: ReporterInterface = ReporterFactory.create(
49
- report_config, output
63
+ report_config, output, project_config.name
50
64
  )
51
65
  reporter.generate(project_metrics)
52
66
  output.writeln(
@@ -0,0 +1,47 @@
1
+ from metripy.Application.Config.ProjectConfig import ProjectConfig
2
+
3
+
4
+ class Config:
5
+ def __init__(self):
6
+ self.project_configs: list[ProjectConfig] = []
7
+ self.quiet: bool = False
8
+ self.version: bool = False
9
+ self.help: bool = False
10
+ self.debug: bool = False
11
+
12
+ def to_dict(self) -> dict:
13
+ return {
14
+ "project_configs": [
15
+ project_config.to_dict() for project_config in self.project_configs
16
+ ],
17
+ }
18
+
19
+ def set(self, param: str, value: any) -> None:
20
+ if param == "quiet":
21
+ self.quiet = value
22
+ elif param == "version":
23
+ self.version = value
24
+ elif param == "help":
25
+ self.help = value
26
+ elif param == "debug":
27
+ self.debug = value
28
+ elif param.startswith("configs."):
29
+ self._set_project_value(param[len("configs.") :], value)
30
+ else:
31
+ # ignore unknown parameters
32
+ return
33
+
34
+ def _set_project_value(self, param: str, value: any) -> None:
35
+ keys = param.split(".")
36
+ project_name = keys[0]
37
+ project_config = next(
38
+ (pc for pc in self.project_configs if pc.name == project_name), None
39
+ )
40
+ if not project_config:
41
+ project_config = ProjectConfig(project_name)
42
+ self.project_configs.append(project_config)
43
+ if len(keys) > 1:
44
+ project_config.set(keys[1:], value)
45
+ else:
46
+ # weird but okay
47
+ return
@@ -1,10 +1,11 @@
1
1
  import os
2
2
  import pathlib
3
3
 
4
- from metripy.Application.Config.File.ConfigFileReaderInterface import \
5
- ConfigFileReaderInterface
6
- from metripy.Application.Config.File.JsonConfigFileReader import \
7
- JsonConfigFileReader
4
+ from metripy.Application.Config.File.ConfigFileReaderInterface import (
5
+ ConfigFileReaderInterface,
6
+ )
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,44 +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
5
  from metripy.Application.Config.GitConfig import GitConfig
8
6
  from metripy.Application.Config.ProjectConfig import ProjectConfig
9
7
  from metripy.Application.Config.ReportConfig import ReportConfig
10
8
 
11
9
 
12
- class JsonConfigFileReader(ConfigFileReaderInterface):
10
+ class ConfigFileReaderInterface(ABC):
13
11
  def __init__(self, filename: str):
14
12
  self.filename = filename
15
13
 
14
+ @abstractmethod
16
15
  def read(self, config: Config) -> None:
17
- with open(self.filename, "r") as file:
18
- json_data = json.load(file)
19
-
20
- self.parse_json(json_data, config)
16
+ pass
21
17
 
22
18
  def resolve_path(self, path: str) -> str:
23
19
  return os.path.join(os.path.dirname(self.filename), path)
24
20
 
25
- def parse_json(self, json_data: dict, config: Config) -> None:
26
-
21
+ def parse_data(self, data: dict, config: Config) -> None:
27
22
  # configs
28
- if configs := json_data.get("configs"):
23
+ if configs := data.get("configs"):
29
24
  for project_name, project_config in configs.items():
30
25
  project_config = self.parse_config_json(project_name, project_config)
31
26
  config.project_configs.append(project_config)
32
27
 
33
- def parse_config_json(self, project_name: str, json_data: dict) -> ProjectConfig:
28
+ def parse_config_json(self, project_name: str, data: dict) -> ProjectConfig:
34
29
  project_config = ProjectConfig(project_name)
35
30
 
36
31
  # extensions
37
- if base_path := json_data.get("base_path"):
32
+ if base_path := data.get("base_path"):
38
33
  project_config.base_path = base_path
39
34
 
40
35
  # includes
41
- if includes := json_data.get("includes"):
36
+ if includes := data.get("includes"):
42
37
  files = []
43
38
  # with config file, includes are relative to the config file
44
39
  for include in includes:
@@ -48,35 +43,39 @@ class JsonConfigFileReader(ConfigFileReaderInterface):
48
43
  project_config.includes = files
49
44
 
50
45
  # extensions
51
- if extensions := json_data.get("extensions"):
46
+ if extensions := data.get("extensions"):
52
47
  project_config.extensions = extensions
53
48
 
54
49
  # excludes
55
- if excludes := json_data.get("excludes"):
50
+ if excludes := data.get("excludes"):
56
51
  project_config.excludes = excludes
57
52
 
58
53
  # reports
59
- if reports := json_data.get("reports"):
54
+ if reports := data.get("reports"):
60
55
  for report_type, path in reports.items():
61
56
  path = self.resolve_path(path)
62
57
  project_config.reports.append(ReportConfig(report_type, path))
63
58
 
64
59
  # git
65
- if git := json_data.get("git"):
60
+ if git := data.get("git"):
66
61
  project_config.git = GitConfig()
67
62
  project_config.git.repo = project_config.base_path
68
63
  project_config.git.branch = git.get("branch", project_config.git.branch)
69
64
 
70
65
  # composer
71
- if composer := json_data.get("composer"):
66
+ if composer := data.get("composer"):
72
67
  project_config.composer = composer
73
68
 
74
69
  # pip
75
- if pip := json_data.get("pip"):
70
+ if pip := data.get("pip"):
76
71
  project_config.pip = pip
77
72
 
78
73
  # npm
79
- if npm := json_data.get("npm"):
74
+ if npm := data.get("npm"):
80
75
  project_config.npm = npm
81
76
 
77
+ # trends
78
+ if history_path := data.get("trends"):
79
+ project_config.history_path = self.resolve_path(history_path)
80
+
82
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)
@@ -0,0 +1,44 @@
1
+ import re
2
+
3
+ from metripy.Application.Config.Config import Config
4
+ from metripy.Application.Config.File.ConfigFileReaderFactory import (
5
+ ConfigFileReaderFactory,
6
+ )
7
+
8
+
9
+ class Parser:
10
+ def parse(self, argv: list[str]) -> Config:
11
+ config = Config()
12
+
13
+ if argv[0].endswith("metripy.py") or argv[0].endswith("metripy"):
14
+ argv.pop(0)
15
+
16
+ # check for a config file
17
+ key = 0
18
+ while key < len(argv):
19
+ arg = argv[key]
20
+ if matches := re.search(r"^--config=(.+)$", arg):
21
+ fileReader = ConfigFileReaderFactory.createFromFileName(
22
+ matches.group(1)
23
+ )
24
+ fileReader.read(config)
25
+ argv.pop(key)
26
+
27
+ # arguments with options
28
+ elif matches := re.search(r"^--([\w]+(?:\.[\w]+)*)=(.*)$", arg):
29
+ param = matches.group(1)
30
+ value = matches.group(2)
31
+ config.set(param, value)
32
+ argv.pop(key)
33
+
34
+ # arguments without options
35
+ elif matches := re.search(r"^--([\w]+(?:\.[\w]+)*)$", arg):
36
+ param = matches.group(1)
37
+ config.set(param, True)
38
+ argv.pop(key)
39
+ else:
40
+ key += 1
41
+
42
+ # TODO handle remaining arguments
43
+
44
+ return config
@@ -0,0 +1,91 @@
1
+ from metripy.Application.Config.GitConfig import GitConfig
2
+ from metripy.Application.Config.ReportConfig import ReportConfig
3
+
4
+
5
+ class ProjectConfig:
6
+ def __init__(self, name: str):
7
+ self.name: str = name
8
+ self.base_path: str = "./"
9
+ self.includes: list[str] = []
10
+ self.excludes: list[str] = []
11
+ self.extensions: list[str] = []
12
+ self.git: GitConfig | None = None
13
+ self.composer: bool = False
14
+ self.pip: bool = False
15
+ self.npm: bool = False
16
+ self.reports: list[ReportConfig] = []
17
+ self.history_path: str | None = None
18
+
19
+ def to_dict(self) -> dict:
20
+ return {
21
+ "name": self.name,
22
+ "base_path": self.base_path,
23
+ "includes": self.includes,
24
+ "excludes": self.excludes,
25
+ "extensions": self.extensions,
26
+ "composer": self.composer,
27
+ "pip": self.pip,
28
+ "npm": self.npm,
29
+ "git": self.git.to_dict() if self.git else None,
30
+ "reports": [report.to_dict() for report in self.reports],
31
+ "history_path": self.history_path,
32
+ }
33
+
34
+ @staticmethod
35
+ def str_to_bool(value):
36
+ if isinstance(value, bool):
37
+ return value
38
+ return str(value).lower() in ("true", "1", "yes")
39
+
40
+ def set(self, keys: list[str], value: any) -> None:
41
+ if len(keys) == 0:
42
+ return
43
+ primary_key = keys[0]
44
+ # single value
45
+ if primary_key == "base_path":
46
+ self.base_path = value
47
+ elif primary_key == "pip":
48
+ self.pip = self.str_to_bool(value)
49
+ elif primary_key == "npm":
50
+ self.npm = self.str_to_bool(value)
51
+ elif primary_key == "composer":
52
+ self.composer = self.str_to_bool(value)
53
+ elif primary_key == "trends":
54
+ self.history_path = value
55
+ elif primary_key == "git":
56
+ self.git = GitConfig()
57
+ self.git.repo = self.base_path
58
+ self.git.branch = value
59
+
60
+ # list values
61
+ elif primary_key == "includes":
62
+ if value == "":
63
+ self.includes = []
64
+ else:
65
+ self.includes.append(value)
66
+ elif primary_key == "excludes":
67
+ if value == "":
68
+ self.excludes = []
69
+ else:
70
+ self.excludes.append(value)
71
+ elif primary_key == "extensions":
72
+ if value == "":
73
+ self.extensions = []
74
+ else:
75
+ self.extensions.append(value)
76
+
77
+ # dict values
78
+ elif primary_key == "reports":
79
+ if len(keys) == 1:
80
+ return
81
+ report_type = keys[1]
82
+ report_path = value
83
+ if value != "":
84
+ self.reports.append(ReportConfig(report_type, report_path))
85
+ else:
86
+ report_config = next(
87
+ (rc for rc in self.reports if rc.type == report_type), None
88
+ )
89
+ if not report_config:
90
+ return
91
+ self.reports.remove(report_config)
@@ -0,0 +1,36 @@
1
+ import toml
2
+
3
+
4
+ class Info:
5
+ def __init__(self):
6
+ data = self._get_data()
7
+ self.version = data["project"]["version"]
8
+ self.url = data["project"]["urls"]["Homepage"]
9
+
10
+ def _get_data(self) -> dict:
11
+ with open("pyproject.toml", "r") as file:
12
+ data = toml.load(file)
13
+ return data
14
+
15
+ def get_version(self) -> str:
16
+ return self.version
17
+
18
+ def get_version_info(self) -> str:
19
+ return f"""
20
+ Metripy {self.get_version()}
21
+ {self.url}
22
+ """
23
+
24
+ def get_help(self) -> str:
25
+ return (
26
+ self.get_version_info()
27
+ + """
28
+ Usage: metripy [options]
29
+ Options:
30
+ --config=<file> Use a custom config file
31
+ --version Show the version and exit
32
+ --help Show this help message and exit
33
+ --debug Enable debug mode
34
+ --quiet Disable output
35
+ """
36
+ )
@@ -1,7 +1,8 @@
1
1
  from typing import Self
2
2
 
3
+
3
4
  class Dependency:
4
- def __init__(self, name: str, version: str|None):
5
+ def __init__(self, name: str, version: str | None):
5
6
  self.name = name
6
7
  self.version = version
7
8
  self.latest: str = ""
@@ -1,5 +1,4 @@
1
1
  import os
2
- import re
3
2
 
4
3
  import toml
5
4
 
@@ -39,7 +38,7 @@ class Pip:
39
38
  return self._parse_dependencies(deps)
40
39
 
41
40
  return []
42
-
41
+
43
42
  def _parse_dependencies(self, lines: list[str]) -> list[Dependency]:
44
43
  dependencies = []
45
44
  for dep in lines:
@@ -16,6 +16,7 @@ class PyPi:
16
16
  print(f"Package '{dependency.name}' has no info section")
17
17
  return dependency
18
18
 
19
+ dependency.type = "pip"
19
20
  dependency.description = info.get("summary")
20
21
  dependency.repository = info.get("project_url") or info.get("home_page")
21
22
  if info.get("license"):
@@ -9,8 +9,6 @@ from metripy.Metric.Git.GitMetrics import GitMetrics
9
9
 
10
10
  class GitAnalyzer:
11
11
  def __init__(self, git_config: GitConfig):
12
- print(git_config.repo)
13
- print(git_config.branch)
14
12
  self.repo = Repo(git_config.repo)
15
13
  self.branch_name = git_config.branch
16
14
 
@@ -22,7 +20,6 @@ class GitAnalyzer:
22
20
  first_of_month_last_year = datetime(now.year - 1, now.month, 1)
23
21
  # first_of_month_last_year = datetime(now.year, now.month, 1)
24
22
  after_date = first_of_month_last_year.strftime("%Y-%m-%d")
25
- print(f"analyzing from {after_date}")
26
23
 
27
24
  return self.get_metrics(after_date)
28
25
 
@@ -0,0 +1,17 @@
1
+ import json
2
+
3
+ from metripy.Component.Output.CliOutput import CliOutput
4
+ from metripy.Metric.ProjectMetrics import ProjectMetrics
5
+
6
+
7
+ class JsonImporter:
8
+ def __init__(self, output: CliOutput):
9
+ self.output = output
10
+
11
+ def import_data(self, path: str) -> ProjectMetrics:
12
+ self.output.writeln(f"<info>Importing data from {path}...</info>")
13
+ with open(path, "r") as file:
14
+ data = json.load(file)
15
+ project_metrics = ProjectMetrics.from_dict(data)
16
+ self.output.writeln("<success>Data imported successfuly</success>")
17
+ return project_metrics
@@ -40,13 +40,13 @@ class AbstractLangAnalyzer(ABC):
40
40
  full_name = module.full_name
41
41
 
42
42
  if len(module.functions) > 0:
43
- avgCcPerFunction = sum(
44
- function.complexity for function in module.functions
45
- ) / len(module.functions)
43
+ totalCc = sum(function.complexity for function in module.functions)
44
+ avgCcPerFunction = totalCc / len(module.functions)
46
45
  avgLocPerFunction = (
47
46
  module.lloc - module.comments - len(module.functions)
48
47
  ) / len(module.functions)
49
48
  else:
49
+ totalCc = 0
50
50
  avgCcPerFunction = 0
51
51
  avgLocPerFunction = 0
52
52
  maintainabilityIndex = module.maintainability_index
@@ -54,6 +54,7 @@ class AbstractLangAnalyzer(ABC):
54
54
  file_metric = FileMetrics(
55
55
  full_name=full_name,
56
56
  loc=module.loc,
57
+ totalCc=totalCc,
57
58
  avgCcPerFunction=avgCcPerFunction,
58
59
  maintainabilityIndex=maintainabilityIndex,
59
60
  avgLocPerFunction=avgLocPerFunction,
@@ -131,7 +131,7 @@ class PhpAnalyzer(AbstractLangAnalyzer):
131
131
 
132
132
  code_lines = code.split("\n")
133
133
  for func_name, function_node in functions.items():
134
- lines = code_lines[function_node.lineno:function_node.line_end]
134
+ lines = code_lines[function_node.lineno : function_node.line_end]
135
135
  function_metrics = self.halstead_analyzer.calculate_halstead_metrics(
136
136
  "\n".join(lines)
137
137
  )
@@ -147,6 +147,7 @@ class PhpAnalyzer(AbstractLangAnalyzer):
147
147
  function_node.calculated_length = function_metrics["calculated_length"]
148
148
  function_node.bugs = function_metrics["bugs"]
149
149
  function_node.time = function_metrics["time"]
150
+ function_node.calc_mi()
150
151
 
151
152
  maintainability_index = self._calculate_maintainability_index(
152
153
  functions.values(), module_node