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.
- {metripy-0.2.7 → metripy-0.3.0}/LICENSE +1 -1
- {metripy-0.2.7 → metripy-0.3.0}/PKG-INFO +25 -8
- {metripy-0.2.7 → metripy-0.3.0}/README.md +20 -3
- {metripy-0.2.7 → metripy-0.3.0}/metripy/Application/Analyzer.py +23 -3
- {metripy-0.2.7 → metripy-0.3.0}/metripy/Application/Application.py +16 -1
- metripy-0.3.0/metripy/Application/Config/Config.py +46 -0
- {metripy-0.2.7 → metripy-0.3.0}/metripy/Application/Config/File/ConfigFileReaderFactory.py +4 -4
- {metripy-0.2.7 → metripy-0.3.0}/metripy/Application/Config/File/JsonConfigFileReader.py +7 -2
- metripy-0.3.0/metripy/Application/Config/Parser.py +46 -0
- metripy-0.3.0/metripy/Application/Config/ProjectConfig.py +90 -0
- metripy-0.3.0/metripy/Application/Info.py +29 -0
- {metripy-0.2.7 → metripy-0.3.0}/metripy/Dependency/Dependency.py +17 -1
- {metripy-0.2.7 → metripy-0.3.0}/metripy/Dependency/Pip/Pip.py +21 -31
- {metripy-0.2.7 → metripy-0.3.0}/metripy/Dependency/Pip/PyPi.py +1 -0
- {metripy-0.2.7 → metripy-0.3.0}/metripy/Git/GitAnalyzer.py +0 -3
- metripy-0.3.0/metripy/Import/Json/JsonImporter.py +17 -0
- {metripy-0.2.7 → metripy-0.3.0}/metripy/LangAnalyzer/AbstractLangAnalyzer.py +4 -3
- {metripy-0.2.7 → metripy-0.3.0}/metripy/LangAnalyzer/Php/PhpAnalyzer.py +2 -1
- {metripy-0.2.7 → metripy-0.3.0}/metripy/LangAnalyzer/Python/PythonAnalyzer.py +31 -9
- metripy-0.3.0/metripy/LangAnalyzer/Python/PythonHalSteadAnalyzer.py +55 -0
- {metripy-0.2.7 → metripy-0.3.0}/metripy/LangAnalyzer/Typescript/TypescriptAnalyzer.py +12 -9
- {metripy-0.2.7 → metripy-0.3.0}/metripy/LangAnalyzer/Typescript/TypescriptAstParser.py +1 -1
- {metripy-0.2.7 → metripy-0.3.0}/metripy/Metric/Code/AggregatedMetrics.py +12 -5
- metripy-0.3.0/metripy/Metric/Code/FileMetrics.py +64 -0
- {metripy-0.2.7 → metripy-0.3.0}/metripy/Metric/Code/ModuleMetrics.py +5 -5
- metripy-0.3.0/metripy/Metric/Code/SegmentedMetrics.py +101 -0
- metripy-0.3.0/metripy/Metric/Code/Segmentor.py +42 -0
- {metripy-0.2.7 → metripy-0.3.0}/metripy/Metric/FileTree/FileTreeParser.py +0 -4
- {metripy-0.2.7 → metripy-0.3.0}/metripy/Metric/Git/GitMetrics.py +1 -1
- {metripy-0.2.7 → metripy-0.3.0}/metripy/Metric/ProjectMetrics.py +29 -0
- metripy-0.3.0/metripy/Metric/Trend/AggregatedTrendMetric.py +101 -0
- metripy-0.3.0/metripy/Metric/Trend/ClassTrendMetric.py +20 -0
- metripy-0.3.0/metripy/Metric/Trend/FileTrendMetric.py +46 -0
- metripy-0.3.0/metripy/Metric/Trend/FunctionTrendMetric.py +28 -0
- metripy-0.3.0/metripy/Metric/Trend/SegmentedTrendMetric.py +29 -0
- metripy-0.3.0/metripy/Report/Html/Reporter.py +424 -0
- {metripy-0.2.7 → metripy-0.3.0}/metripy/Report/Json/GitJsonReporter.py +3 -0
- {metripy-0.2.7 → metripy-0.3.0}/metripy/Report/Json/JsonReporter.py +6 -2
- {metripy-0.2.7 → metripy-0.3.0}/metripy/Report/ReporterFactory.py +2 -1
- {metripy-0.2.7 → metripy-0.3.0}/metripy/Tree/ClassNode.py +21 -0
- metripy-0.3.0/metripy/Tree/FunctionNode.py +114 -0
- metripy-0.3.0/metripy/Trend/TrendAnalyzer.py +150 -0
- {metripy-0.2.7 → metripy-0.3.0}/metripy.egg-info/PKG-INFO +25 -8
- {metripy-0.2.7 → metripy-0.3.0}/metripy.egg-info/SOURCES.txt +11 -1
- {metripy-0.2.7 → metripy-0.3.0}/pyproject.toml +5 -5
- metripy-0.2.7/metripy/Application/Config/Config.py +0 -13
- metripy-0.2.7/metripy/Application/Config/Parser.py +0 -31
- metripy-0.2.7/metripy/Application/Config/ProjectConfig.py +0 -27
- metripy-0.2.7/metripy/Metric/Code/FileMetrics.py +0 -33
- metripy-0.2.7/metripy/Metric/Code/SegmentedMetrics.py +0 -65
- metripy-0.2.7/metripy/Report/Html/Reporter.py +0 -210
- metripy-0.2.7/metripy/Tree/FunctionNode.py +0 -49
- {metripy-0.2.7 → metripy-0.3.0}/metripy/Application/Config/File/ConfigFileReaderInterface.py +0 -0
- {metripy-0.2.7 → metripy-0.3.0}/metripy/Application/Config/GitConfig.py +0 -0
- {metripy-0.2.7 → metripy-0.3.0}/metripy/Application/Config/ReportConfig.py +0 -0
- {metripy-0.2.7 → metripy-0.3.0}/metripy/Application/__init__.py +0 -0
- {metripy-0.2.7 → metripy-0.3.0}/metripy/Component/Debug/Debugger.py +0 -0
- {metripy-0.2.7 → metripy-0.3.0}/metripy/Component/File/Finder.py +0 -0
- {metripy-0.2.7 → metripy-0.3.0}/metripy/Component/Output/CliOutput.py +0 -0
- {metripy-0.2.7 → metripy-0.3.0}/metripy/Component/Output/ProgressBar.py +0 -0
- {metripy-0.2.7 → metripy-0.3.0}/metripy/Dependency/Composer/Composer.py +0 -0
- {metripy-0.2.7 → metripy-0.3.0}/metripy/Dependency/Composer/Packegist.py +0 -0
- {metripy-0.2.7 → metripy-0.3.0}/metripy/Dependency/Npm/Npm.py +0 -0
- {metripy-0.2.7 → metripy-0.3.0}/metripy/Dependency/Npm/NpmOrg.py +0 -0
- {metripy-0.2.7 → metripy-0.3.0}/metripy/LangAnalyzer/Generic/HalSteadAnalyzer.py +0 -0
- {metripy-0.2.7 → metripy-0.3.0}/metripy/LangAnalyzer/Generic/__init__.py +0 -0
- {metripy-0.2.7 → metripy-0.3.0}/metripy/LangAnalyzer/Php/PhpBasicAstParser.py +0 -0
- {metripy-0.2.7 → metripy-0.3.0}/metripy/LangAnalyzer/Php/PhpBasicLocAnalyzer.py +0 -0
- {metripy-0.2.7 → metripy-0.3.0}/metripy/LangAnalyzer/Php/PhpHalSteadAnalyzer.py +0 -0
- {metripy-0.2.7 → metripy-0.3.0}/metripy/LangAnalyzer/Typescript/TypescriptBasicComplexityAnalyzer.py +0 -0
- {metripy-0.2.7 → metripy-0.3.0}/metripy/LangAnalyzer/Typescript/TypescriptBasicLocAnalyzer.py +0 -0
- {metripy-0.2.7 → metripy-0.3.0}/metripy/LangAnalyzer/Typescript/TypescriptHalSteadAnalyzer.py +0 -0
- {metripy-0.2.7 → metripy-0.3.0}/metripy/LangAnalyzer/__init__.py +0 -0
- {metripy-0.2.7 → metripy-0.3.0}/metripy/Metric/FileTree/FileTree.py +0 -0
- {metripy-0.2.7 → metripy-0.3.0}/metripy/Metric/Git/GitCodeHotspot.py +0 -0
- {metripy-0.2.7 → metripy-0.3.0}/metripy/Metric/Git/GitContributor.py +0 -0
- {metripy-0.2.7 → metripy-0.3.0}/metripy/Metric/Git/GitKnowledgeSilo.py +0 -0
- {metripy-0.2.7 → metripy-0.3.0}/metripy/Report/Csv/Reporter.py +0 -0
- {metripy-0.2.7 → metripy-0.3.0}/metripy/Report/Json/AbstractJsonReporter.py +0 -0
- {metripy-0.2.7 → metripy-0.3.0}/metripy/Report/ReporterInterface.py +0 -0
- {metripy-0.2.7 → metripy-0.3.0}/metripy/Tree/ModuleNode.py +0 -0
- {metripy-0.2.7 → metripy-0.3.0}/metripy/__init__.py +0 -0
- {metripy-0.2.7 → metripy-0.3.0}/metripy/metripy.py +0 -0
- {metripy-0.2.7 → metripy-0.3.0}/metripy.egg-info/dependency_links.txt +0 -0
- {metripy-0.2.7 → metripy-0.3.0}/metripy.egg-info/entry_points.txt +0 -0
- {metripy-0.2.7 → metripy-0.3.0}/metripy.egg-info/requires.txt +0 -0
- {metripy-0.2.7 → metripy-0.3.0}/metripy.egg-info/top_level.txt +0 -0
- {metripy-0.2.7 → metripy-0.3.0}/setup.cfg +0 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: metripy
|
|
3
|
-
Version: 0.
|
|
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://
|
|
8
|
-
Project-URL: Repository, https://github.com/zimmer-yan/
|
|
9
|
-
Project-URL: Documentation, https://
|
|
10
|
-
Project-URL: Bug Tracker, https://github.com/zimmer-yan/
|
|
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
|
-
#
|
|
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/
|
|
106
|
-
"json-git": "./build/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
|
-
#
|
|
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/
|
|
67
|
-
"json-git": "./build/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
|
-
|
|
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)
|
|
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
|
-
|
|
7
|
-
|
|
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
|
-
|
|
27
|
+
return self._parse_dependencies(lines)
|
|
32
28
|
|
|
33
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
44
|
-
|
|
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
|