metripy 0.2.7__py3-none-any.whl → 0.3.6__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 (67) hide show
  1. metripy/Application/Analyzer.py +23 -3
  2. metripy/Application/Application.py +16 -2
  3. metripy/Application/Config/Config.py +34 -0
  4. metripy/Application/Config/File/ConfigFileReaderFactory.py +6 -5
  5. metripy/Application/Config/File/ConfigFileReaderInterface.py +70 -3
  6. metripy/Application/Config/File/JsonConfigFileReader.py +5 -70
  7. metripy/Application/Config/File/YamlConfigFileReader.py +17 -0
  8. metripy/Application/Config/Parser.py +24 -11
  9. metripy/Application/Config/ProjectConfig.py +64 -0
  10. metripy/Application/Info.py +61 -0
  11. metripy/Dependency/Dependency.py +17 -1
  12. metripy/Dependency/Pip/Pip.py +21 -31
  13. metripy/Dependency/Pip/PyPi.py +1 -0
  14. metripy/Git/GitAnalyzer.py +0 -3
  15. metripy/Import/Json/JsonImporter.py +17 -0
  16. metripy/LangAnalyzer/AbstractLangAnalyzer.py +4 -3
  17. metripy/LangAnalyzer/Php/PhpAnalyzer.py +2 -1
  18. metripy/LangAnalyzer/Python/PythonAnalyzer.py +31 -9
  19. metripy/LangAnalyzer/Python/PythonHalSteadAnalyzer.py +55 -0
  20. metripy/LangAnalyzer/Typescript/TypescriptAnalyzer.py +12 -9
  21. metripy/LangAnalyzer/Typescript/TypescriptAstParser.py +1 -1
  22. metripy/Metric/Code/AggregatedMetrics.py +12 -5
  23. metripy/Metric/Code/FileMetrics.py +32 -1
  24. metripy/Metric/Code/ModuleMetrics.py +5 -5
  25. metripy/Metric/Code/SegmentedMetrics.py +72 -36
  26. metripy/Metric/Code/Segmentor.py +44 -0
  27. metripy/Metric/FileTree/FileTreeParser.py +0 -4
  28. metripy/Metric/Git/GitMetrics.py +1 -1
  29. metripy/Metric/ProjectMetrics.py +29 -0
  30. metripy/Metric/Trend/AggregatedTrendMetric.py +101 -0
  31. metripy/Metric/Trend/ClassTrendMetric.py +20 -0
  32. metripy/Metric/Trend/FileTrendMetric.py +46 -0
  33. metripy/Metric/Trend/FunctionTrendMetric.py +28 -0
  34. metripy/Metric/Trend/SegmentedTrendMetric.py +29 -0
  35. metripy/Report/Html/DependencyPageRenderer.py +21 -0
  36. metripy/Report/Html/FilesPageRenderer.py +28 -0
  37. metripy/Report/Html/GitAnalysisPageRenderer.py +55 -0
  38. metripy/Report/Html/IndexPageRenderer.py +47 -0
  39. metripy/Report/Html/PageRenderer.py +43 -0
  40. metripy/Report/Html/PageRendererFactory.py +37 -0
  41. metripy/Report/Html/Reporter.py +78 -137
  42. metripy/Report/Html/TopOffendersPageRenderer.py +84 -0
  43. metripy/Report/Html/TrendsPageRenderer.py +137 -0
  44. metripy/Report/Json/GitJsonReporter.py +3 -0
  45. metripy/Report/Json/JsonReporter.py +6 -2
  46. metripy/Report/ReporterFactory.py +6 -3
  47. metripy/Tree/ClassNode.py +21 -0
  48. metripy/Tree/FunctionNode.py +66 -1
  49. metripy/Trend/TrendAnalyzer.py +150 -0
  50. metripy/templates/html_report/css/styles.css +1386 -0
  51. metripy/templates/html_report/dependencies.html +411 -0
  52. metripy/templates/html_report/files.html +1080 -0
  53. metripy/templates/html_report/git_analysis.html +325 -0
  54. metripy/templates/html_report/images/logo.svg +31 -0
  55. metripy/templates/html_report/index.html +374 -0
  56. metripy/templates/html_report/js/charts.js +313 -0
  57. metripy/templates/html_report/js/dashboard.js +546 -0
  58. metripy/templates/html_report/js/git_analysis.js +383 -0
  59. metripy/templates/html_report/top_offenders.html +267 -0
  60. metripy/templates/html_report/trends.html +468 -0
  61. {metripy-0.2.7.dist-info → metripy-0.3.6.dist-info}/METADATA +27 -9
  62. metripy-0.3.6.dist-info/RECORD +96 -0
  63. {metripy-0.2.7.dist-info → metripy-0.3.6.dist-info}/licenses/LICENSE +1 -1
  64. metripy-0.2.7.dist-info/RECORD +0 -66
  65. {metripy-0.2.7.dist-info → metripy-0.3.6.dist-info}/WHEEL +0 -0
  66. {metripy-0.2.7.dist-info → metripy-0.3.6.dist-info}/entry_points.txt +0 -0
  67. {metripy-0.2.7.dist-info → metripy-0.3.6.dist-info}/top_level.txt +0 -0
@@ -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(
@@ -4,6 +4,10 @@ from metripy.Application.Config.ProjectConfig import ProjectConfig
4
4
  class Config:
5
5
  def __init__(self):
6
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
7
11
 
8
12
  def to_dict(self) -> dict:
9
13
  return {
@@ -11,3 +15,33 @@ class Config:
11
15
  project_config.to_dict() for project_config in self.project_configs
12
16
  ],
13
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,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 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,82 +1,17 @@
1
1
  import json
2
- import os
3
2
 
4
3
  from metripy.Application.Config.Config import Config
5
- from metripy.Application.Config.File.ConfigFileReaderInterface import \
6
- ConfigFileReaderInterface
7
- from metripy.Application.Config.GitConfig import GitConfig
8
- from metripy.Application.Config.ProjectConfig import ProjectConfig
9
- from metripy.Application.Config.ReportConfig import ReportConfig
4
+ from metripy.Application.Config.File.ConfigFileReaderInterface import (
5
+ ConfigFileReaderInterface,
6
+ )
10
7
 
11
8
 
12
9
  class JsonConfigFileReader(ConfigFileReaderInterface):
13
10
  def __init__(self, filename: str):
14
- self.filename = filename
11
+ super().__init__(filename)
15
12
 
16
13
  def read(self, config: Config) -> None:
17
14
  with open(self.filename, "r") as file:
18
15
  json_data = json.load(file)
19
16
 
20
- self.parse_json(json_data, config)
21
-
22
- def resolve_path(self, path: str) -> str:
23
- return os.path.join(os.path.dirname(self.filename), path)
24
-
25
- def parse_json(self, json_data: dict, config: Config) -> None:
26
-
27
- # configs
28
- if configs := json_data.get("configs"):
29
- for project_name, project_config in configs.items():
30
- project_config = self.parse_config_json(project_name, project_config)
31
- config.project_configs.append(project_config)
32
-
33
- def parse_config_json(self, project_name: str, json_data: dict) -> ProjectConfig:
34
- project_config = ProjectConfig(project_name)
35
-
36
- # extensions
37
- if base_path := json_data.get("base_path"):
38
- project_config.base_path = base_path
39
-
40
- # includes
41
- if includes := json_data.get("includes"):
42
- files = []
43
- # with config file, includes are relative to the config file
44
- for include in includes:
45
- include = self.resolve_path(include)
46
- files.append(include)
47
-
48
- project_config.includes = files
49
-
50
- # extensions
51
- if extensions := json_data.get("extensions"):
52
- project_config.extensions = extensions
53
-
54
- # excludes
55
- if excludes := json_data.get("excludes"):
56
- project_config.excludes = excludes
57
-
58
- # reports
59
- if reports := json_data.get("reports"):
60
- for report_type, path in reports.items():
61
- path = self.resolve_path(path)
62
- project_config.reports.append(ReportConfig(report_type, path))
63
-
64
- # git
65
- if git := json_data.get("git"):
66
- project_config.git = GitConfig()
67
- project_config.git.repo = project_config.base_path
68
- project_config.git.branch = git.get("branch", project_config.git.branch)
69
-
70
- # composer
71
- if composer := json_data.get("composer"):
72
- project_config.composer = composer
73
-
74
- # pip
75
- if pip := json_data.get("pip"):
76
- project_config.pip = pip
77
-
78
- # npm
79
- if npm := json_data.get("npm"):
80
- project_config.npm = npm
81
-
82
- 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)
@@ -1,21 +1,22 @@
1
1
  import re
2
2
 
3
3
  from metripy.Application.Config.Config import Config
4
- from metripy.Application.Config.File.ConfigFileReaderFactory import \
5
- ConfigFileReaderFactory
4
+ from metripy.Application.Config.File.ConfigFileReaderFactory import (
5
+ ConfigFileReaderFactory,
6
+ )
6
7
 
7
8
 
8
9
  class Parser:
9
10
  def parse(self, argv: list[str]) -> Config:
10
11
  config = Config()
11
12
 
12
- if argv[0] == "codemetrics.py" or argv[0] == "codemetrics":
13
- # TODO, fix when path ends with codemetrics.py
14
- pass
15
- argv.pop(0)
13
+ if argv[0].endswith("metripy.py") or argv[0].endswith("metripy"):
14
+ argv.pop(0)
16
15
 
17
16
  # check for a config file
18
- for key, arg in enumerate(argv):
17
+ key = 0
18
+ while key < len(argv):
19
+ arg = argv[key]
19
20
  if matches := re.search(r"^--config=(.+)$", arg):
20
21
  fileReader = ConfigFileReaderFactory.createFromFileName(
21
22
  matches.group(1)
@@ -23,9 +24,21 @@ class Parser:
23
24
  fileReader.read(config)
24
25
  argv.pop(key)
25
26
 
26
- # TODO: add the following
27
- # arguments with options
28
- # arguments without options
29
- # last argument
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
30
43
 
31
44
  return config
@@ -14,6 +14,7 @@ class ProjectConfig:
14
14
  self.pip: bool = False
15
15
  self.npm: bool = False
16
16
  self.reports: list[ReportConfig] = []
17
+ self.history_path: str | None = None
17
18
 
18
19
  def to_dict(self) -> dict:
19
20
  return {
@@ -22,6 +23,69 @@ class ProjectConfig:
22
23
  "includes": self.includes,
23
24
  "excludes": self.excludes,
24
25
  "extensions": self.extensions,
26
+ "composer": self.composer,
27
+ "pip": self.pip,
28
+ "npm": self.npm,
25
29
  "git": self.git.to_dict() if self.git else None,
26
30
  "reports": [report.to_dict() for report in self.reports],
31
+ "history_path": self.history_path,
27
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,61 @@
1
+ from importlib.metadata import version, metadata
2
+ import toml
3
+
4
+
5
+ class Info:
6
+ def __init__(self):
7
+ self.version = self._get_version()
8
+ self.url = self._get_homepage_url()
9
+
10
+ def _get_pyproject_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
+ """Get version from installed package metadata"""
17
+ try:
18
+ return version("metripy")
19
+ except Exception:
20
+ # Fallback for development if not installed
21
+ return self._get_pyproject_data()["project"]["version"]
22
+
23
+ def _get_homepage_url(self) -> str:
24
+ """Get homepage URL from installed package metadata"""
25
+ try:
26
+ meta = metadata("metripy")
27
+ # Try to get Home-Page from metadata
28
+ homepage = meta.get("Home-Page")
29
+ if not homepage:
30
+ # Try Project-URL field
31
+ for line in meta.get_all("Project-URL") or []:
32
+ if line.startswith("Homepage"):
33
+ homepage = line.split(",", 1)[1].strip()
34
+ break
35
+ return homepage or "no homepage found"
36
+ except Exception:
37
+ # Fallback
38
+ return self._get_pyproject_data()["project"]["urls"]["Homepage"]
39
+
40
+ def get_version(self) -> str:
41
+ return self.version
42
+
43
+ def get_version_info(self) -> str:
44
+ return f"""
45
+ Metripy {self.get_version()}
46
+ {self.url}
47
+ """
48
+
49
+ def get_help(self) -> str:
50
+ return (
51
+ self.get_version_info()
52
+ + """
53
+ Usage: metripy [options]
54
+ Options:
55
+ --config=<file> Use a custom config file
56
+ --version Show the version and exit
57
+ --help Show this help message and exit
58
+ --debug Enable debug mode
59
+ --quiet Disable output
60
+ """
61
+ )
@@ -1,5 +1,8 @@
1
+ from typing import Self
2
+
3
+
1
4
  class Dependency:
2
- def __init__(self, name: str, version: str):
5
+ def __init__(self, name: str, version: str | None):
3
6
  self.name = name
4
7
  self.version = version
5
8
  self.latest: str = ""
@@ -28,3 +31,16 @@ class Dependency:
28
31
  "downloads_monthly": self.downloads_monthly,
29
32
  "licenses": ",".join(self.license),
30
33
  }
34
+
35
+ @staticmethod
36
+ def get_lisence_distribution(dependencies: list[Self]) -> dict[str, int]:
37
+ license_by_type = {}
38
+
39
+ dependency: Dependency
40
+ for dependency in dependencies:
41
+ for license_name in dependency.license:
42
+ if license_name not in license_by_type.keys():
43
+ license_by_type[license_name] = 0
44
+ license_by_type[license_name] += 1
45
+
46
+ return license_by_type
@@ -1,5 +1,4 @@
1
1
  import os
2
- import re
3
2
 
4
3
  import toml
5
4
 
@@ -23,47 +22,38 @@ class Pip:
23
22
  return [item for item in packages if item is not None]
24
23
 
25
24
  def get_from_requirements_txt(self, path: str) -> list[Dependency]:
26
- requirements = []
27
-
28
- pattern = re.compile(r"([a-zA-Z0-9_\-]+)([<>=!~]+[^\s]+)?")
29
25
  with open(os.path.join(path, "requirements.txt"), "r") as file:
30
26
  lines = file.readlines()
31
- for line in lines:
27
+ return self._parse_dependencies(lines)
32
28
 
33
- line = line.strip()
34
- if line and not line.startswith("#"):
35
- match = pattern.match(line)
36
- if match:
37
- name = match.group(1)
38
- version = match.group(2) if match.group(2) else None
39
- requirements.append(Dependency(name, version))
40
- return requirements
29
+ return []
41
30
 
42
31
  def get_from_pyproject_toml(self, path: str) -> list[Dependency]:
43
- dependencies = []
44
-
45
32
  with open(os.path.join(path, "pyproject.toml"), "r") as f:
46
33
  data = toml.load(f)
47
34
 
48
35
  # For PEP 621 / setuptools projects
49
36
  if "project" in data:
50
37
  deps = data["project"].get("dependencies", [])
51
- for dep in deps:
52
- # dep is a string like "requests>=2.32.5"
53
- # You can split it if needed
54
- if "==" in dep:
55
- name, version = dep.split("==")
56
- elif ">=" in dep:
57
- name, version = dep.split(">=")
58
- else:
59
- name, version = dep, None
60
- dependencies.append(
61
- Dependency(name.strip(), version.strip() if version else None)
62
- )
38
+ return self._parse_dependencies(deps)
63
39
 
64
- return dependencies
40
+ return []
65
41
 
42
+ def _parse_dependencies(self, lines: list[str]) -> list[Dependency]:
43
+ dependencies = []
44
+ for dep in lines:
45
+ dep = dep.strip()
46
+ if not dep or dep.startswith("#"):
47
+ continue
48
+ # dep is a string like "requests>=2.32.5"
49
+ if "==" in dep:
50
+ name, version = dep.split("==")
51
+ elif ">=" in dep:
52
+ name, version = dep.split(">=")
53
+ else:
54
+ name, version = dep, None
55
+ dependencies.append(
56
+ Dependency(name.strip(), version.strip() if version else None)
57
+ )
66
58
 
67
- if __name__ == "__main__":
68
- pip = Pip()
69
- pip.get_dependencies("./")
59
+ return dependencies
@@ -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