metripy 0.2.8__py3-none-any.whl → 0.3.1__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.
- metripy/Application/Analyzer.py +23 -3
- metripy/Application/Application.py +16 -2
- metripy/Application/Config/Config.py +34 -0
- metripy/Application/Config/File/ConfigFileReaderFactory.py +6 -5
- metripy/Application/Config/File/ConfigFileReaderInterface.py +70 -3
- metripy/Application/Config/File/JsonConfigFileReader.py +5 -70
- metripy/Application/Config/File/YamlConfigFileReader.py +17 -0
- metripy/Application/Config/Parser.py +24 -11
- metripy/Application/Config/ProjectConfig.py +64 -0
- metripy/Application/Info.py +36 -0
- metripy/Dependency/Dependency.py +2 -1
- metripy/Dependency/Pip/Pip.py +1 -2
- metripy/Dependency/Pip/PyPi.py +1 -0
- metripy/Git/GitAnalyzer.py +0 -3
- metripy/Import/Json/JsonImporter.py +17 -0
- metripy/LangAnalyzer/AbstractLangAnalyzer.py +4 -3
- metripy/LangAnalyzer/Php/PhpAnalyzer.py +2 -1
- metripy/LangAnalyzer/Python/PythonAnalyzer.py +31 -9
- metripy/LangAnalyzer/Python/PythonHalSteadAnalyzer.py +55 -0
- metripy/LangAnalyzer/Typescript/TypescriptAnalyzer.py +12 -9
- metripy/LangAnalyzer/Typescript/TypescriptAstParser.py +1 -1
- metripy/Metric/Code/AggregatedMetrics.py +12 -5
- metripy/Metric/Code/FileMetrics.py +32 -1
- metripy/Metric/Code/ModuleMetrics.py +5 -5
- metripy/Metric/Code/SegmentedMetrics.py +72 -36
- metripy/Metric/Code/Segmentor.py +44 -0
- metripy/Metric/FileTree/FileTreeParser.py +0 -4
- metripy/Metric/Git/GitMetrics.py +1 -1
- metripy/Metric/ProjectMetrics.py +17 -2
- metripy/Metric/Trend/AggregatedTrendMetric.py +101 -0
- metripy/Metric/Trend/ClassTrendMetric.py +20 -0
- metripy/Metric/Trend/FileTrendMetric.py +46 -0
- metripy/Metric/Trend/FunctionTrendMetric.py +28 -0
- metripy/Metric/Trend/SegmentedTrendMetric.py +29 -0
- metripy/Report/Html/DependencyPageRenderer.py +21 -0
- metripy/Report/Html/FilesPageRenderer.py +28 -0
- metripy/Report/Html/GitAnalysisPageRenderer.py +55 -0
- metripy/Report/Html/IndexPageRenderer.py +40 -0
- metripy/Report/Html/PageRenderer.py +43 -0
- metripy/Report/Html/PageRendererFactory.py +37 -0
- metripy/Report/Html/Reporter.py +49 -130
- metripy/Report/Html/TopOffendersPageRenderer.py +84 -0
- metripy/Report/Html/TrendsPageRenderer.py +114 -0
- metripy/Report/Json/GitJsonReporter.py +3 -1
- metripy/Report/Json/JsonReporter.py +4 -1
- metripy/Report/ReporterFactory.py +4 -2
- metripy/Tree/ClassNode.py +21 -0
- metripy/Tree/FunctionNode.py +66 -1
- metripy/Trend/TrendAnalyzer.py +150 -0
- {metripy-0.2.8.dist-info → metripy-0.3.1.dist-info}/METADATA +3 -3
- metripy-0.3.1.dist-info/RECORD +85 -0
- metripy-0.2.8.dist-info/RECORD +0 -66
- {metripy-0.2.8.dist-info → metripy-0.3.1.dist-info}/WHEEL +0 -0
- {metripy-0.2.8.dist-info → metripy-0.3.1.dist-info}/entry_points.txt +0 -0
- {metripy-0.2.8.dist-info → metripy-0.3.1.dist-info}/licenses/LICENSE +0 -0
- {metripy-0.2.8.dist-info → metripy-0.3.1.dist-info}/top_level.txt +0 -0
metripy/Application/Analyzer.py
CHANGED
|
@@ -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
|
|
@@ -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)
|
|
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
|
-
|
|
7
|
-
|
|
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
|
-
|
|
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
|
-
|
|
12
|
+
self.filename = filename
|
|
11
13
|
|
|
12
14
|
@abstractmethod
|
|
13
15
|
def read(self, config: Config) -> None:
|
|
14
16
|
pass
|
|
17
|
+
|
|
18
|
+
def resolve_path(self, path: str) -> str:
|
|
19
|
+
return os.path.join(os.path.dirname(self.filename), path)
|
|
20
|
+
|
|
21
|
+
def parse_data(self, data: dict, config: Config) -> None:
|
|
22
|
+
# configs
|
|
23
|
+
if configs := data.get("configs"):
|
|
24
|
+
for project_name, project_config in configs.items():
|
|
25
|
+
project_config = self.parse_config_json(project_name, project_config)
|
|
26
|
+
config.project_configs.append(project_config)
|
|
27
|
+
|
|
28
|
+
def parse_config_json(self, project_name: str, data: dict) -> ProjectConfig:
|
|
29
|
+
project_config = ProjectConfig(project_name)
|
|
30
|
+
|
|
31
|
+
# extensions
|
|
32
|
+
if base_path := data.get("base_path"):
|
|
33
|
+
project_config.base_path = base_path
|
|
34
|
+
|
|
35
|
+
# includes
|
|
36
|
+
if includes := data.get("includes"):
|
|
37
|
+
files = []
|
|
38
|
+
# with config file, includes are relative to the config file
|
|
39
|
+
for include in includes:
|
|
40
|
+
include = self.resolve_path(include)
|
|
41
|
+
files.append(include)
|
|
42
|
+
|
|
43
|
+
project_config.includes = files
|
|
44
|
+
|
|
45
|
+
# extensions
|
|
46
|
+
if extensions := data.get("extensions"):
|
|
47
|
+
project_config.extensions = extensions
|
|
48
|
+
|
|
49
|
+
# excludes
|
|
50
|
+
if excludes := data.get("excludes"):
|
|
51
|
+
project_config.excludes = excludes
|
|
52
|
+
|
|
53
|
+
# reports
|
|
54
|
+
if reports := data.get("reports"):
|
|
55
|
+
for report_type, path in reports.items():
|
|
56
|
+
path = self.resolve_path(path)
|
|
57
|
+
project_config.reports.append(ReportConfig(report_type, path))
|
|
58
|
+
|
|
59
|
+
# git
|
|
60
|
+
if git := data.get("git"):
|
|
61
|
+
project_config.git = GitConfig()
|
|
62
|
+
project_config.git.repo = project_config.base_path
|
|
63
|
+
project_config.git.branch = git.get("branch", project_config.git.branch)
|
|
64
|
+
|
|
65
|
+
# composer
|
|
66
|
+
if composer := data.get("composer"):
|
|
67
|
+
project_config.composer = composer
|
|
68
|
+
|
|
69
|
+
# pip
|
|
70
|
+
if pip := data.get("pip"):
|
|
71
|
+
project_config.pip = pip
|
|
72
|
+
|
|
73
|
+
# npm
|
|
74
|
+
if npm := data.get("npm"):
|
|
75
|
+
project_config.npm = npm
|
|
76
|
+
|
|
77
|
+
# trends
|
|
78
|
+
if history_path := data.get("trends"):
|
|
79
|
+
project_config.history_path = self.resolve_path(history_path)
|
|
80
|
+
|
|
81
|
+
return project_config
|
|
@@ -1,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
|
-
|
|
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
|
-
|
|
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.
|
|
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]
|
|
13
|
-
|
|
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
|
-
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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,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
|
+
)
|
metripy/Dependency/Dependency.py
CHANGED
metripy/Dependency/Pip/Pip.py
CHANGED
metripy/Dependency/Pip/PyPi.py
CHANGED
|
@@ -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"):
|
metripy/Git/GitAnalyzer.py
CHANGED
|
@@ -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
|