metripy 0.2.7__tar.gz → 0.3.0__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.
Files changed (88) hide show
  1. {metripy-0.2.7 → metripy-0.3.0}/LICENSE +1 -1
  2. {metripy-0.2.7 → metripy-0.3.0}/PKG-INFO +25 -8
  3. {metripy-0.2.7 → metripy-0.3.0}/README.md +20 -3
  4. {metripy-0.2.7 → metripy-0.3.0}/metripy/Application/Analyzer.py +23 -3
  5. {metripy-0.2.7 → metripy-0.3.0}/metripy/Application/Application.py +16 -1
  6. metripy-0.3.0/metripy/Application/Config/Config.py +46 -0
  7. {metripy-0.2.7 → metripy-0.3.0}/metripy/Application/Config/File/ConfigFileReaderFactory.py +4 -4
  8. {metripy-0.2.7 → metripy-0.3.0}/metripy/Application/Config/File/JsonConfigFileReader.py +7 -2
  9. metripy-0.3.0/metripy/Application/Config/Parser.py +46 -0
  10. metripy-0.3.0/metripy/Application/Config/ProjectConfig.py +90 -0
  11. metripy-0.3.0/metripy/Application/Info.py +29 -0
  12. {metripy-0.2.7 → metripy-0.3.0}/metripy/Dependency/Dependency.py +17 -1
  13. {metripy-0.2.7 → metripy-0.3.0}/metripy/Dependency/Pip/Pip.py +21 -31
  14. {metripy-0.2.7 → metripy-0.3.0}/metripy/Dependency/Pip/PyPi.py +1 -0
  15. {metripy-0.2.7 → metripy-0.3.0}/metripy/Git/GitAnalyzer.py +0 -3
  16. metripy-0.3.0/metripy/Import/Json/JsonImporter.py +17 -0
  17. {metripy-0.2.7 → metripy-0.3.0}/metripy/LangAnalyzer/AbstractLangAnalyzer.py +4 -3
  18. {metripy-0.2.7 → metripy-0.3.0}/metripy/LangAnalyzer/Php/PhpAnalyzer.py +2 -1
  19. {metripy-0.2.7 → metripy-0.3.0}/metripy/LangAnalyzer/Python/PythonAnalyzer.py +31 -9
  20. metripy-0.3.0/metripy/LangAnalyzer/Python/PythonHalSteadAnalyzer.py +55 -0
  21. {metripy-0.2.7 → metripy-0.3.0}/metripy/LangAnalyzer/Typescript/TypescriptAnalyzer.py +12 -9
  22. {metripy-0.2.7 → metripy-0.3.0}/metripy/LangAnalyzer/Typescript/TypescriptAstParser.py +1 -1
  23. {metripy-0.2.7 → metripy-0.3.0}/metripy/Metric/Code/AggregatedMetrics.py +12 -5
  24. metripy-0.3.0/metripy/Metric/Code/FileMetrics.py +64 -0
  25. {metripy-0.2.7 → metripy-0.3.0}/metripy/Metric/Code/ModuleMetrics.py +5 -5
  26. metripy-0.3.0/metripy/Metric/Code/SegmentedMetrics.py +101 -0
  27. metripy-0.3.0/metripy/Metric/Code/Segmentor.py +42 -0
  28. {metripy-0.2.7 → metripy-0.3.0}/metripy/Metric/FileTree/FileTreeParser.py +0 -4
  29. {metripy-0.2.7 → metripy-0.3.0}/metripy/Metric/Git/GitMetrics.py +1 -1
  30. {metripy-0.2.7 → metripy-0.3.0}/metripy/Metric/ProjectMetrics.py +29 -0
  31. metripy-0.3.0/metripy/Metric/Trend/AggregatedTrendMetric.py +101 -0
  32. metripy-0.3.0/metripy/Metric/Trend/ClassTrendMetric.py +20 -0
  33. metripy-0.3.0/metripy/Metric/Trend/FileTrendMetric.py +46 -0
  34. metripy-0.3.0/metripy/Metric/Trend/FunctionTrendMetric.py +28 -0
  35. metripy-0.3.0/metripy/Metric/Trend/SegmentedTrendMetric.py +29 -0
  36. metripy-0.3.0/metripy/Report/Html/Reporter.py +424 -0
  37. {metripy-0.2.7 → metripy-0.3.0}/metripy/Report/Json/GitJsonReporter.py +3 -0
  38. {metripy-0.2.7 → metripy-0.3.0}/metripy/Report/Json/JsonReporter.py +6 -2
  39. {metripy-0.2.7 → metripy-0.3.0}/metripy/Report/ReporterFactory.py +2 -1
  40. {metripy-0.2.7 → metripy-0.3.0}/metripy/Tree/ClassNode.py +21 -0
  41. metripy-0.3.0/metripy/Tree/FunctionNode.py +114 -0
  42. metripy-0.3.0/metripy/Trend/TrendAnalyzer.py +150 -0
  43. {metripy-0.2.7 → metripy-0.3.0}/metripy.egg-info/PKG-INFO +25 -8
  44. {metripy-0.2.7 → metripy-0.3.0}/metripy.egg-info/SOURCES.txt +11 -1
  45. {metripy-0.2.7 → metripy-0.3.0}/pyproject.toml +5 -5
  46. metripy-0.2.7/metripy/Application/Config/Config.py +0 -13
  47. metripy-0.2.7/metripy/Application/Config/Parser.py +0 -31
  48. metripy-0.2.7/metripy/Application/Config/ProjectConfig.py +0 -27
  49. metripy-0.2.7/metripy/Metric/Code/FileMetrics.py +0 -33
  50. metripy-0.2.7/metripy/Metric/Code/SegmentedMetrics.py +0 -65
  51. metripy-0.2.7/metripy/Report/Html/Reporter.py +0 -210
  52. metripy-0.2.7/metripy/Tree/FunctionNode.py +0 -49
  53. {metripy-0.2.7 → metripy-0.3.0}/metripy/Application/Config/File/ConfigFileReaderInterface.py +0 -0
  54. {metripy-0.2.7 → metripy-0.3.0}/metripy/Application/Config/GitConfig.py +0 -0
  55. {metripy-0.2.7 → metripy-0.3.0}/metripy/Application/Config/ReportConfig.py +0 -0
  56. {metripy-0.2.7 → metripy-0.3.0}/metripy/Application/__init__.py +0 -0
  57. {metripy-0.2.7 → metripy-0.3.0}/metripy/Component/Debug/Debugger.py +0 -0
  58. {metripy-0.2.7 → metripy-0.3.0}/metripy/Component/File/Finder.py +0 -0
  59. {metripy-0.2.7 → metripy-0.3.0}/metripy/Component/Output/CliOutput.py +0 -0
  60. {metripy-0.2.7 → metripy-0.3.0}/metripy/Component/Output/ProgressBar.py +0 -0
  61. {metripy-0.2.7 → metripy-0.3.0}/metripy/Dependency/Composer/Composer.py +0 -0
  62. {metripy-0.2.7 → metripy-0.3.0}/metripy/Dependency/Composer/Packegist.py +0 -0
  63. {metripy-0.2.7 → metripy-0.3.0}/metripy/Dependency/Npm/Npm.py +0 -0
  64. {metripy-0.2.7 → metripy-0.3.0}/metripy/Dependency/Npm/NpmOrg.py +0 -0
  65. {metripy-0.2.7 → metripy-0.3.0}/metripy/LangAnalyzer/Generic/HalSteadAnalyzer.py +0 -0
  66. {metripy-0.2.7 → metripy-0.3.0}/metripy/LangAnalyzer/Generic/__init__.py +0 -0
  67. {metripy-0.2.7 → metripy-0.3.0}/metripy/LangAnalyzer/Php/PhpBasicAstParser.py +0 -0
  68. {metripy-0.2.7 → metripy-0.3.0}/metripy/LangAnalyzer/Php/PhpBasicLocAnalyzer.py +0 -0
  69. {metripy-0.2.7 → metripy-0.3.0}/metripy/LangAnalyzer/Php/PhpHalSteadAnalyzer.py +0 -0
  70. {metripy-0.2.7 → metripy-0.3.0}/metripy/LangAnalyzer/Typescript/TypescriptBasicComplexityAnalyzer.py +0 -0
  71. {metripy-0.2.7 → metripy-0.3.0}/metripy/LangAnalyzer/Typescript/TypescriptBasicLocAnalyzer.py +0 -0
  72. {metripy-0.2.7 → metripy-0.3.0}/metripy/LangAnalyzer/Typescript/TypescriptHalSteadAnalyzer.py +0 -0
  73. {metripy-0.2.7 → metripy-0.3.0}/metripy/LangAnalyzer/__init__.py +0 -0
  74. {metripy-0.2.7 → metripy-0.3.0}/metripy/Metric/FileTree/FileTree.py +0 -0
  75. {metripy-0.2.7 → metripy-0.3.0}/metripy/Metric/Git/GitCodeHotspot.py +0 -0
  76. {metripy-0.2.7 → metripy-0.3.0}/metripy/Metric/Git/GitContributor.py +0 -0
  77. {metripy-0.2.7 → metripy-0.3.0}/metripy/Metric/Git/GitKnowledgeSilo.py +0 -0
  78. {metripy-0.2.7 → metripy-0.3.0}/metripy/Report/Csv/Reporter.py +0 -0
  79. {metripy-0.2.7 → metripy-0.3.0}/metripy/Report/Json/AbstractJsonReporter.py +0 -0
  80. {metripy-0.2.7 → metripy-0.3.0}/metripy/Report/ReporterInterface.py +0 -0
  81. {metripy-0.2.7 → metripy-0.3.0}/metripy/Tree/ModuleNode.py +0 -0
  82. {metripy-0.2.7 → metripy-0.3.0}/metripy/__init__.py +0 -0
  83. {metripy-0.2.7 → metripy-0.3.0}/metripy/metripy.py +0 -0
  84. {metripy-0.2.7 → metripy-0.3.0}/metripy.egg-info/dependency_links.txt +0 -0
  85. {metripy-0.2.7 → metripy-0.3.0}/metripy.egg-info/entry_points.txt +0 -0
  86. {metripy-0.2.7 → metripy-0.3.0}/metripy.egg-info/requires.txt +0 -0
  87. {metripy-0.2.7 → metripy-0.3.0}/metripy.egg-info/top_level.txt +0 -0
  88. {metripy-0.2.7 → metripy-0.3.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2024 CodeMetrics
3
+ Copyright (c) 2024 Metripy
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: metripy
3
- Version: 0.2.7
3
+ Version: 0.3.0
4
4
  Summary: A Python tool to generate multi project, multi language code metric reports
5
5
  Author-email: Yannick Zimmermann <yannick.zimmermann@proton.me>
6
6
  License: MIT
7
- Project-URL: Homepage, https://github.com/zimmer-yan/codemetrics
8
- Project-URL: Repository, https://github.com/zimmer-yan/codemetrics
9
- Project-URL: Documentation, https://github.com/zimmer-yan/codemetrics#readme
10
- Project-URL: Bug Tracker, https://github.com/zimmer-yan/codemetrics/issues
7
+ Project-URL: Homepage, https://zimmer-yan.github.io/metripy/
8
+ Project-URL: Repository, https://github.com/zimmer-yan/metripy
9
+ Project-URL: Documentation, https://zimmer-yan.github.io/metripy/
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
13
13
  Classifier: Intended Audience :: Developers
@@ -37,7 +37,7 @@ Requires-Dist: poethepoet==0.37.0; extra == "dev"
37
37
  Requires-Dist: isort==7.0.0; extra == "dev"
38
38
  Dynamic: license-file
39
39
 
40
- # Codemetrics
40
+ # Metripy
41
41
  A multilanguage, multi project code metrics analysis tool.
42
42
 
43
43
  # Languages
@@ -102,8 +102,8 @@ Sample configuraiton:
102
102
  "pip": true,
103
103
  // looks for base_path/requirements.txt or base_path/pyproject.toml and analyzes dependencies - for python projects
104
104
  "reports": {
105
- "html": "./build/report/codemetrics", // report should be put into this directory
106
- "json-git": "./build/json-report/codemetrics-git.json" // file where to put git json report
105
+ "html": "./build/report/metripy", // report should be put into this directory
106
+ "json-git": "./build/json-report/metripy-git.json" // file where to put git json report
107
107
  // more types of reports TBA
108
108
  }
109
109
  },
@@ -111,3 +111,20 @@ Sample configuraiton:
111
111
  }
112
112
  }
113
113
  ```
114
+
115
+ ## Configuration for only git stats
116
+ ```json
117
+ {
118
+ "configs": {
119
+ "metripy-git": {
120
+ "base_path": "./",
121
+ "git": {
122
+ "branch": "main"
123
+ },
124
+ "reports": {
125
+ "json-git": "./build/json-report/metripy-git.json"
126
+ }
127
+ }
128
+ }
129
+ }
130
+ ```
@@ -1,4 +1,4 @@
1
- # Codemetrics
1
+ # Metripy
2
2
  A multilanguage, multi project code metrics analysis tool.
3
3
 
4
4
  # Languages
@@ -63,8 +63,8 @@ Sample configuraiton:
63
63
  "pip": true,
64
64
  // looks for base_path/requirements.txt or base_path/pyproject.toml and analyzes dependencies - for python projects
65
65
  "reports": {
66
- "html": "./build/report/codemetrics", // report should be put into this directory
67
- "json-git": "./build/json-report/codemetrics-git.json" // file where to put git json report
66
+ "html": "./build/report/metripy", // report should be put into this directory
67
+ "json-git": "./build/json-report/metripy-git.json" // file where to put git json report
68
68
  // more types of reports TBA
69
69
  }
70
70
  },
@@ -72,3 +72,20 @@ Sample configuraiton:
72
72
  }
73
73
  }
74
74
  ```
75
+
76
+ ## Configuration for only git stats
77
+ ```json
78
+ {
79
+ "configs": {
80
+ "metripy-git": {
81
+ "base_path": "./",
82
+ "git": {
83
+ "branch": "main"
84
+ },
85
+ "reports": {
86
+ "json-git": "./build/json-report/metripy-git.json"
87
+ }
88
+ }
89
+ }
90
+ }
91
+ ```
@@ -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
@@ -8,15 +8,30 @@ from metripy.Component.Output.CliOutput import CliOutput
8
8
  from metripy.Report.ReporterFactory import ReporterFactory
9
9
  from metripy.Report.ReporterInterface import ReporterInterface
10
10
 
11
+ from metripy.Application.Info import Info
12
+
11
13
 
12
14
  class Application:
13
15
  def run(self, argv) -> None:
14
16
  output = CliOutput()
15
17
 
16
18
  # issues and debug
17
- debugger = Debugger(output).enable()
19
+ debugger = Debugger(output)
18
20
 
19
21
  config = Parser().parse(argv)
22
+ if config.debug:
23
+ debugger.enable()
24
+
25
+ if config.version:
26
+ output.writeln(Info().get_version_info())
27
+ return
28
+
29
+ if config.help:
30
+ output.writeln(Info().get_help())
31
+ return
32
+
33
+ if config.quiet:
34
+ output.set_quiet(True)
20
35
 
21
36
  finder = Finder()
22
37
  files = finder.fetch(config.project_configs)
@@ -0,0 +1,46 @@
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
+ print(f"Setting {param} to {value}")
21
+ if param == "quiet":
22
+ self.quiet = value
23
+ elif param == "version":
24
+ self.version = value
25
+ elif param == "help":
26
+ self.help = value
27
+ elif param == "debug":
28
+ self.debug = value
29
+ elif param.startswith("configs."):
30
+ self._set_project_value(param[len("configs."):], value)
31
+ else:
32
+ # ignore unknown parameters
33
+ return
34
+
35
+ def _set_project_value(self, param: str, value: any) -> None:
36
+ keys = param.split(".")
37
+ project_name = keys[0]
38
+ project_config = next((pc for pc in self.project_configs if pc.name == project_name), None)
39
+ if not project_config:
40
+ project_config = ProjectConfig(project_name)
41
+ self.project_configs.append(project_config)
42
+ if len(keys) > 1:
43
+ project_config.set(keys[1:], value)
44
+ else:
45
+ # weird but okay
46
+ return
@@ -1,10 +1,10 @@
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
8
 
9
9
 
10
10
  class ConfigFileReaderFactory:
@@ -2,8 +2,9 @@ import json
2
2
  import os
3
3
 
4
4
  from metripy.Application.Config.Config import Config
5
- from metripy.Application.Config.File.ConfigFileReaderInterface import \
6
- ConfigFileReaderInterface
5
+ from metripy.Application.Config.File.ConfigFileReaderInterface import (
6
+ ConfigFileReaderInterface,
7
+ )
7
8
  from metripy.Application.Config.GitConfig import GitConfig
8
9
  from metripy.Application.Config.ProjectConfig import ProjectConfig
9
10
  from metripy.Application.Config.ReportConfig import ReportConfig
@@ -79,4 +80,8 @@ class JsonConfigFileReader(ConfigFileReaderInterface):
79
80
  if npm := json_data.get("npm"):
80
81
  project_config.npm = npm
81
82
 
83
+ # trends
84
+ if history_path := json_data.get("trends"):
85
+ project_config.history_path = self.resolve_path(history_path)
86
+
82
87
  return project_config
@@ -0,0 +1,46 @@
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
+ print(argv)
13
+
14
+ if argv[0].endswith("metripy.py") or argv[0].endswith("metripy"):
15
+ argv.pop(0)
16
+
17
+ # check for a config file
18
+ key = 0
19
+ while key < len(argv):
20
+ arg = argv[key]
21
+ print(f"Key: {key} Arg: '{arg}'")
22
+ if matches := re.search(r"^--config=(.+)$", arg):
23
+ fileReader = ConfigFileReaderFactory.createFromFileName(
24
+ matches.group(1)
25
+ )
26
+ fileReader.read(config)
27
+ argv.pop(key)
28
+
29
+ # arguments with options
30
+ elif matches := re.search(r"^--([\w]+(?:\.[\w]+)*)=(.*)$", arg):
31
+ param = matches.group(1)
32
+ value = matches.group(2)
33
+ config.set(param, value)
34
+ argv.pop(key)
35
+
36
+ # arguments without options
37
+ elif matches := re.search(r"^--([\w]+(?:\.[\w]+)*)$", arg):
38
+ param = matches.group(1)
39
+ config.set(param, True)
40
+ argv.pop(key)
41
+ else:
42
+ key += 1
43
+
44
+ # TODO handle remaining arguments
45
+
46
+ return config
@@ -0,0 +1,90 @@
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
+ print(f"Setting primary {primary_key} to {value}")
45
+ # single value
46
+ if primary_key == "base_path":
47
+ self.base_path = value
48
+ elif primary_key == "pip":
49
+ self.pip = self.str_to_bool(value)
50
+ elif primary_key == "npm":
51
+ self.npm = self.str_to_bool(value)
52
+ elif primary_key == "composer":
53
+ self.composer = self.str_to_bool(value)
54
+ elif primary_key == "trends":
55
+ self.history_path = value
56
+ elif primary_key == "git":
57
+ self.git = GitConfig()
58
+ self.git.repo = self.base_path
59
+ self.git.branch = value
60
+
61
+ # list values
62
+ elif primary_key == "includes":
63
+ if value == "":
64
+ self.includes = []
65
+ else:
66
+ self.includes.append(value)
67
+ elif primary_key == "excludes":
68
+ if value == "":
69
+ self.excludes = []
70
+ else:
71
+ self.excludes.append(value)
72
+ elif primary_key == "extensions":
73
+ if value == "":
74
+ self.extensions = []
75
+ else:
76
+ self.extensions.append(value)
77
+
78
+ # dict values
79
+ elif primary_key == "reports":
80
+ if len(keys) == 1:
81
+ return
82
+ report_type = keys[1]
83
+ report_path = value
84
+ if value != "":
85
+ self.reports.append(ReportConfig(report_type, report_path))
86
+ else:
87
+ report_config = next((rc for rc in self.reports if rc.type == report_type), None)
88
+ if not report_config:
89
+ return
90
+ self.reports.remove(report_config)
@@ -0,0 +1,29 @@
1
+ import toml
2
+
3
+ class Info:
4
+ def __init__(self):
5
+ data = self._get_data()
6
+ self.version = data["project"]["version"]
7
+ self.url = data["project"]["urls"]["Homepage"]
8
+
9
+ def _get_data(self) -> dict:
10
+ with open("pyproject.toml", "r") as file:
11
+ data = toml.load(file)
12
+ return data
13
+
14
+ def get_version_info(self) -> str:
15
+ return f"""
16
+ Metripy {self.version}
17
+ {self.url}
18
+ """
19
+
20
+ def get_help(self) -> str:
21
+ return self.get_version_info() + f"""
22
+ Usage: metripy [options]
23
+ Options:
24
+ --config=<file> Use a custom config file
25
+ --version Show the version and exit
26
+ --help Show this help message and exit
27
+ --debug Enable debug mode
28
+ --quiet Disable output
29
+ """
@@ -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
 
@@ -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