metripy 0.3.0__tar.gz → 0.3.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of metripy might be problematic. Click here for more details.
- {metripy-0.3.0 → metripy-0.3.2}/PKG-INFO +2 -1
- {metripy-0.3.0 → metripy-0.3.2}/metripy/Application/Application.py +2 -3
- {metripy-0.3.0 → metripy-0.3.2}/metripy/Application/Config/Config.py +4 -3
- {metripy-0.3.0 → metripy-0.3.2}/metripy/Application/Config/File/ConfigFileReaderFactory.py +2 -1
- metripy-0.3.0/metripy/Application/Config/File/JsonConfigFileReader.py → metripy-0.3.2/metripy/Application/Config/File/ConfigFileReaderInterface.py +17 -23
- metripy-0.3.2/metripy/Application/Config/File/JsonConfigFileReader.py +17 -0
- metripy-0.3.2/metripy/Application/Config/File/YamlConfigFileReader.py +17 -0
- {metripy-0.3.0 → metripy-0.3.2}/metripy/Application/Config/Parser.py +0 -2
- {metripy-0.3.0 → metripy-0.3.2}/metripy/Application/Config/ProjectConfig.py +4 -3
- {metripy-0.3.0 → metripy-0.3.2}/metripy/Application/Info.py +9 -2
- {metripy-0.3.0 → metripy-0.3.2}/metripy/LangAnalyzer/Python/PythonAnalyzer.py +1 -1
- {metripy-0.3.0 → metripy-0.3.2}/metripy/Metric/Code/Segmentor.py +2 -0
- metripy-0.3.2/metripy/Report/Html/DependencyPageRenderer.py +21 -0
- metripy-0.3.2/metripy/Report/Html/FilesPageRenderer.py +28 -0
- metripy-0.3.2/metripy/Report/Html/GitAnalysisPageRenderer.py +55 -0
- metripy-0.3.2/metripy/Report/Html/IndexPageRenderer.py +40 -0
- metripy-0.3.2/metripy/Report/Html/PageRenderer.py +43 -0
- metripy-0.3.2/metripy/Report/Html/PageRendererFactory.py +37 -0
- metripy-0.3.2/metripy/Report/Html/Reporter.py +124 -0
- metripy-0.3.2/metripy/Report/Html/TopOffendersPageRenderer.py +84 -0
- metripy-0.3.2/metripy/Report/Html/TrendsPageRenderer.py +114 -0
- {metripy-0.3.0 → metripy-0.3.2}/metripy/Report/ReporterFactory.py +4 -2
- {metripy-0.3.0 → metripy-0.3.2}/metripy.egg-info/PKG-INFO +2 -1
- {metripy-0.3.0 → metripy-0.3.2}/metripy.egg-info/SOURCES.txt +9 -0
- {metripy-0.3.0 → metripy-0.3.2}/metripy.egg-info/requires.txt +1 -0
- {metripy-0.3.0 → metripy-0.3.2}/pyproject.toml +3 -2
- metripy-0.3.0/metripy/Application/Config/File/ConfigFileReaderInterface.py +0 -14
- metripy-0.3.0/metripy/Report/Html/Reporter.py +0 -424
- {metripy-0.3.0 → metripy-0.3.2}/LICENSE +0 -0
- {metripy-0.3.0 → metripy-0.3.2}/README.md +0 -0
- {metripy-0.3.0 → metripy-0.3.2}/metripy/Application/Analyzer.py +0 -0
- {metripy-0.3.0 → metripy-0.3.2}/metripy/Application/Config/GitConfig.py +0 -0
- {metripy-0.3.0 → metripy-0.3.2}/metripy/Application/Config/ReportConfig.py +0 -0
- {metripy-0.3.0 → metripy-0.3.2}/metripy/Application/__init__.py +0 -0
- {metripy-0.3.0 → metripy-0.3.2}/metripy/Component/Debug/Debugger.py +0 -0
- {metripy-0.3.0 → metripy-0.3.2}/metripy/Component/File/Finder.py +0 -0
- {metripy-0.3.0 → metripy-0.3.2}/metripy/Component/Output/CliOutput.py +0 -0
- {metripy-0.3.0 → metripy-0.3.2}/metripy/Component/Output/ProgressBar.py +0 -0
- {metripy-0.3.0 → metripy-0.3.2}/metripy/Dependency/Composer/Composer.py +0 -0
- {metripy-0.3.0 → metripy-0.3.2}/metripy/Dependency/Composer/Packegist.py +0 -0
- {metripy-0.3.0 → metripy-0.3.2}/metripy/Dependency/Dependency.py +0 -0
- {metripy-0.3.0 → metripy-0.3.2}/metripy/Dependency/Npm/Npm.py +0 -0
- {metripy-0.3.0 → metripy-0.3.2}/metripy/Dependency/Npm/NpmOrg.py +0 -0
- {metripy-0.3.0 → metripy-0.3.2}/metripy/Dependency/Pip/Pip.py +0 -0
- {metripy-0.3.0 → metripy-0.3.2}/metripy/Dependency/Pip/PyPi.py +0 -0
- {metripy-0.3.0 → metripy-0.3.2}/metripy/Git/GitAnalyzer.py +0 -0
- {metripy-0.3.0 → metripy-0.3.2}/metripy/Import/Json/JsonImporter.py +0 -0
- {metripy-0.3.0 → metripy-0.3.2}/metripy/LangAnalyzer/AbstractLangAnalyzer.py +0 -0
- {metripy-0.3.0 → metripy-0.3.2}/metripy/LangAnalyzer/Generic/HalSteadAnalyzer.py +0 -0
- {metripy-0.3.0 → metripy-0.3.2}/metripy/LangAnalyzer/Generic/__init__.py +0 -0
- {metripy-0.3.0 → metripy-0.3.2}/metripy/LangAnalyzer/Php/PhpAnalyzer.py +0 -0
- {metripy-0.3.0 → metripy-0.3.2}/metripy/LangAnalyzer/Php/PhpBasicAstParser.py +0 -0
- {metripy-0.3.0 → metripy-0.3.2}/metripy/LangAnalyzer/Php/PhpBasicLocAnalyzer.py +0 -0
- {metripy-0.3.0 → metripy-0.3.2}/metripy/LangAnalyzer/Php/PhpHalSteadAnalyzer.py +0 -0
- {metripy-0.3.0 → metripy-0.3.2}/metripy/LangAnalyzer/Python/PythonHalSteadAnalyzer.py +0 -0
- {metripy-0.3.0 → metripy-0.3.2}/metripy/LangAnalyzer/Typescript/TypescriptAnalyzer.py +0 -0
- {metripy-0.3.0 → metripy-0.3.2}/metripy/LangAnalyzer/Typescript/TypescriptAstParser.py +0 -0
- {metripy-0.3.0 → metripy-0.3.2}/metripy/LangAnalyzer/Typescript/TypescriptBasicComplexityAnalyzer.py +0 -0
- {metripy-0.3.0 → metripy-0.3.2}/metripy/LangAnalyzer/Typescript/TypescriptBasicLocAnalyzer.py +0 -0
- {metripy-0.3.0 → metripy-0.3.2}/metripy/LangAnalyzer/Typescript/TypescriptHalSteadAnalyzer.py +0 -0
- {metripy-0.3.0 → metripy-0.3.2}/metripy/LangAnalyzer/__init__.py +0 -0
- {metripy-0.3.0 → metripy-0.3.2}/metripy/Metric/Code/AggregatedMetrics.py +0 -0
- {metripy-0.3.0 → metripy-0.3.2}/metripy/Metric/Code/FileMetrics.py +0 -0
- {metripy-0.3.0 → metripy-0.3.2}/metripy/Metric/Code/ModuleMetrics.py +0 -0
- {metripy-0.3.0 → metripy-0.3.2}/metripy/Metric/Code/SegmentedMetrics.py +0 -0
- {metripy-0.3.0 → metripy-0.3.2}/metripy/Metric/FileTree/FileTree.py +0 -0
- {metripy-0.3.0 → metripy-0.3.2}/metripy/Metric/FileTree/FileTreeParser.py +0 -0
- {metripy-0.3.0 → metripy-0.3.2}/metripy/Metric/Git/GitCodeHotspot.py +0 -0
- {metripy-0.3.0 → metripy-0.3.2}/metripy/Metric/Git/GitContributor.py +0 -0
- {metripy-0.3.0 → metripy-0.3.2}/metripy/Metric/Git/GitKnowledgeSilo.py +0 -0
- {metripy-0.3.0 → metripy-0.3.2}/metripy/Metric/Git/GitMetrics.py +0 -0
- {metripy-0.3.0 → metripy-0.3.2}/metripy/Metric/ProjectMetrics.py +0 -0
- {metripy-0.3.0 → metripy-0.3.2}/metripy/Metric/Trend/AggregatedTrendMetric.py +0 -0
- {metripy-0.3.0 → metripy-0.3.2}/metripy/Metric/Trend/ClassTrendMetric.py +0 -0
- {metripy-0.3.0 → metripy-0.3.2}/metripy/Metric/Trend/FileTrendMetric.py +0 -0
- {metripy-0.3.0 → metripy-0.3.2}/metripy/Metric/Trend/FunctionTrendMetric.py +0 -0
- {metripy-0.3.0 → metripy-0.3.2}/metripy/Metric/Trend/SegmentedTrendMetric.py +0 -0
- {metripy-0.3.0 → metripy-0.3.2}/metripy/Report/Csv/Reporter.py +0 -0
- {metripy-0.3.0 → metripy-0.3.2}/metripy/Report/Json/AbstractJsonReporter.py +0 -0
- {metripy-0.3.0 → metripy-0.3.2}/metripy/Report/Json/GitJsonReporter.py +0 -0
- {metripy-0.3.0 → metripy-0.3.2}/metripy/Report/Json/JsonReporter.py +0 -0
- {metripy-0.3.0 → metripy-0.3.2}/metripy/Report/ReporterInterface.py +0 -0
- {metripy-0.3.0 → metripy-0.3.2}/metripy/Tree/ClassNode.py +0 -0
- {metripy-0.3.0 → metripy-0.3.2}/metripy/Tree/FunctionNode.py +0 -0
- {metripy-0.3.0 → metripy-0.3.2}/metripy/Tree/ModuleNode.py +0 -0
- {metripy-0.3.0 → metripy-0.3.2}/metripy/Trend/TrendAnalyzer.py +0 -0
- {metripy-0.3.0 → metripy-0.3.2}/metripy/__init__.py +0 -0
- {metripy-0.3.0 → metripy-0.3.2}/metripy/metripy.py +0 -0
- {metripy-0.3.0 → metripy-0.3.2}/metripy.egg-info/dependency_links.txt +0 -0
- {metripy-0.3.0 → metripy-0.3.2}/metripy.egg-info/entry_points.txt +0 -0
- {metripy-0.3.0 → metripy-0.3.2}/metripy.egg-info/top_level.txt +0 -0
- {metripy-0.3.0 → metripy-0.3.2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: metripy
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
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
|
|
@@ -26,6 +26,7 @@ Requires-Dist: packaging==25.0
|
|
|
26
26
|
Requires-Dist: toml==0.10.2
|
|
27
27
|
Requires-Dist: tree-sitter==0.21.3
|
|
28
28
|
Requires-Dist: tree-sitter-languages==1.10.2
|
|
29
|
+
Requires-Dist: PyYAML==6.0.3
|
|
29
30
|
Provides-Extra: dev
|
|
30
31
|
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
31
32
|
Requires-Dist: pytest-cov==7.0.0; extra == "dev"
|
|
@@ -2,14 +2,13 @@ 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
|
|
8
9
|
from metripy.Report.ReporterFactory import ReporterFactory
|
|
9
10
|
from metripy.Report.ReporterInterface import ReporterInterface
|
|
10
11
|
|
|
11
|
-
from metripy.Application.Info import Info
|
|
12
|
-
|
|
13
12
|
|
|
14
13
|
class Application:
|
|
15
14
|
def run(self, argv) -> None:
|
|
@@ -61,7 +60,7 @@ class Application:
|
|
|
61
60
|
)
|
|
62
61
|
for report_config in project_config.reports:
|
|
63
62
|
reporter: ReporterInterface = ReporterFactory.create(
|
|
64
|
-
report_config, output
|
|
63
|
+
report_config, output, project_config.name
|
|
65
64
|
)
|
|
66
65
|
reporter.generate(project_metrics)
|
|
67
66
|
output.writeln(
|
|
@@ -17,7 +17,6 @@ class Config:
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
def set(self, param: str, value: any) -> None:
|
|
20
|
-
print(f"Setting {param} to {value}")
|
|
21
20
|
if param == "quiet":
|
|
22
21
|
self.quiet = value
|
|
23
22
|
elif param == "version":
|
|
@@ -27,7 +26,7 @@ class Config:
|
|
|
27
26
|
elif param == "debug":
|
|
28
27
|
self.debug = value
|
|
29
28
|
elif param.startswith("configs."):
|
|
30
|
-
self._set_project_value(param[len("configs."):], value)
|
|
29
|
+
self._set_project_value(param[len("configs.") :], value)
|
|
31
30
|
else:
|
|
32
31
|
# ignore unknown parameters
|
|
33
32
|
return
|
|
@@ -35,7 +34,9 @@ class Config:
|
|
|
35
34
|
def _set_project_value(self, param: str, value: any) -> None:
|
|
36
35
|
keys = param.split(".")
|
|
37
36
|
project_name = keys[0]
|
|
38
|
-
project_config = next(
|
|
37
|
+
project_config = next(
|
|
38
|
+
(pc for pc in self.project_configs if pc.name == project_name), None
|
|
39
|
+
)
|
|
39
40
|
if not project_config:
|
|
40
41
|
project_config = ProjectConfig(project_name)
|
|
41
42
|
self.project_configs.append(project_config)
|
|
@@ -5,6 +5,7 @@ from metripy.Application.Config.File.ConfigFileReaderInterface import (
|
|
|
5
5
|
ConfigFileReaderInterface,
|
|
6
6
|
)
|
|
7
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,45 +1,39 @@
|
|
|
1
|
-
import json
|
|
2
1
|
import os
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
3
3
|
|
|
4
4
|
from metripy.Application.Config.Config import Config
|
|
5
|
-
from metripy.Application.Config.File.ConfigFileReaderInterface import (
|
|
6
|
-
ConfigFileReaderInterface,
|
|
7
|
-
)
|
|
8
5
|
from metripy.Application.Config.GitConfig import GitConfig
|
|
9
6
|
from metripy.Application.Config.ProjectConfig import ProjectConfig
|
|
10
7
|
from metripy.Application.Config.ReportConfig import ReportConfig
|
|
11
8
|
|
|
12
9
|
|
|
13
|
-
class
|
|
10
|
+
class ConfigFileReaderInterface(ABC):
|
|
14
11
|
def __init__(self, filename: str):
|
|
15
12
|
self.filename = filename
|
|
16
13
|
|
|
14
|
+
@abstractmethod
|
|
17
15
|
def read(self, config: Config) -> None:
|
|
18
|
-
|
|
19
|
-
json_data = json.load(file)
|
|
20
|
-
|
|
21
|
-
self.parse_json(json_data, config)
|
|
16
|
+
pass
|
|
22
17
|
|
|
23
18
|
def resolve_path(self, path: str) -> str:
|
|
24
19
|
return os.path.join(os.path.dirname(self.filename), path)
|
|
25
20
|
|
|
26
|
-
def
|
|
27
|
-
|
|
21
|
+
def parse_data(self, data: dict, config: Config) -> None:
|
|
28
22
|
# configs
|
|
29
|
-
if configs :=
|
|
23
|
+
if configs := data.get("configs"):
|
|
30
24
|
for project_name, project_config in configs.items():
|
|
31
25
|
project_config = self.parse_config_json(project_name, project_config)
|
|
32
26
|
config.project_configs.append(project_config)
|
|
33
27
|
|
|
34
|
-
def parse_config_json(self, project_name: str,
|
|
28
|
+
def parse_config_json(self, project_name: str, data: dict) -> ProjectConfig:
|
|
35
29
|
project_config = ProjectConfig(project_name)
|
|
36
30
|
|
|
37
31
|
# extensions
|
|
38
|
-
if base_path :=
|
|
32
|
+
if base_path := data.get("base_path"):
|
|
39
33
|
project_config.base_path = base_path
|
|
40
34
|
|
|
41
35
|
# includes
|
|
42
|
-
if includes :=
|
|
36
|
+
if includes := data.get("includes"):
|
|
43
37
|
files = []
|
|
44
38
|
# with config file, includes are relative to the config file
|
|
45
39
|
for include in includes:
|
|
@@ -49,39 +43,39 @@ class JsonConfigFileReader(ConfigFileReaderInterface):
|
|
|
49
43
|
project_config.includes = files
|
|
50
44
|
|
|
51
45
|
# extensions
|
|
52
|
-
if extensions :=
|
|
46
|
+
if extensions := data.get("extensions"):
|
|
53
47
|
project_config.extensions = extensions
|
|
54
48
|
|
|
55
49
|
# excludes
|
|
56
|
-
if excludes :=
|
|
50
|
+
if excludes := data.get("excludes"):
|
|
57
51
|
project_config.excludes = excludes
|
|
58
52
|
|
|
59
53
|
# reports
|
|
60
|
-
if reports :=
|
|
54
|
+
if reports := data.get("reports"):
|
|
61
55
|
for report_type, path in reports.items():
|
|
62
56
|
path = self.resolve_path(path)
|
|
63
57
|
project_config.reports.append(ReportConfig(report_type, path))
|
|
64
58
|
|
|
65
59
|
# git
|
|
66
|
-
if git :=
|
|
60
|
+
if git := data.get("git"):
|
|
67
61
|
project_config.git = GitConfig()
|
|
68
62
|
project_config.git.repo = project_config.base_path
|
|
69
63
|
project_config.git.branch = git.get("branch", project_config.git.branch)
|
|
70
64
|
|
|
71
65
|
# composer
|
|
72
|
-
if composer :=
|
|
66
|
+
if composer := data.get("composer"):
|
|
73
67
|
project_config.composer = composer
|
|
74
68
|
|
|
75
69
|
# pip
|
|
76
|
-
if pip :=
|
|
70
|
+
if pip := data.get("pip"):
|
|
77
71
|
project_config.pip = pip
|
|
78
72
|
|
|
79
73
|
# npm
|
|
80
|
-
if npm :=
|
|
74
|
+
if npm := data.get("npm"):
|
|
81
75
|
project_config.npm = npm
|
|
82
76
|
|
|
83
77
|
# trends
|
|
84
|
-
if history_path :=
|
|
78
|
+
if history_path := data.get("trends"):
|
|
85
79
|
project_config.history_path = self.resolve_path(history_path)
|
|
86
80
|
|
|
87
81
|
return project_config
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
from metripy.Application.Config.Config import Config
|
|
4
|
+
from metripy.Application.Config.File.ConfigFileReaderInterface import (
|
|
5
|
+
ConfigFileReaderInterface,
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class JsonConfigFileReader(ConfigFileReaderInterface):
|
|
10
|
+
def __init__(self, filename: str):
|
|
11
|
+
super().__init__(filename)
|
|
12
|
+
|
|
13
|
+
def read(self, config: Config) -> None:
|
|
14
|
+
with open(self.filename, "r") as file:
|
|
15
|
+
json_data = json.load(file)
|
|
16
|
+
|
|
17
|
+
self.parse_data(json_data, config)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import yaml
|
|
2
|
+
|
|
3
|
+
from metripy.Application.Config.Config import Config
|
|
4
|
+
from metripy.Application.Config.File.ConfigFileReaderInterface import (
|
|
5
|
+
ConfigFileReaderInterface,
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class YamlConfigFileReader(ConfigFileReaderInterface):
|
|
10
|
+
def __init__(self, filename: str):
|
|
11
|
+
super().__init__(filename)
|
|
12
|
+
|
|
13
|
+
def read(self, config: Config) -> None:
|
|
14
|
+
with open(self.filename, "r") as file:
|
|
15
|
+
yaml_data = yaml.safe_load(file)
|
|
16
|
+
|
|
17
|
+
self.parse_data(yaml_data, config)
|
|
@@ -9,7 +9,6 @@ from metripy.Application.Config.File.ConfigFileReaderFactory import (
|
|
|
9
9
|
class Parser:
|
|
10
10
|
def parse(self, argv: list[str]) -> Config:
|
|
11
11
|
config = Config()
|
|
12
|
-
print(argv)
|
|
13
12
|
|
|
14
13
|
if argv[0].endswith("metripy.py") or argv[0].endswith("metripy"):
|
|
15
14
|
argv.pop(0)
|
|
@@ -18,7 +17,6 @@ class Parser:
|
|
|
18
17
|
key = 0
|
|
19
18
|
while key < len(argv):
|
|
20
19
|
arg = argv[key]
|
|
21
|
-
print(f"Key: {key} Arg: '{arg}'")
|
|
22
20
|
if matches := re.search(r"^--config=(.+)$", arg):
|
|
23
21
|
fileReader = ConfigFileReaderFactory.createFromFileName(
|
|
24
22
|
matches.group(1)
|
|
@@ -41,7 +41,6 @@ class ProjectConfig:
|
|
|
41
41
|
if len(keys) == 0:
|
|
42
42
|
return
|
|
43
43
|
primary_key = keys[0]
|
|
44
|
-
print(f"Setting primary {primary_key} to {value}")
|
|
45
44
|
# single value
|
|
46
45
|
if primary_key == "base_path":
|
|
47
46
|
self.base_path = value
|
|
@@ -84,7 +83,9 @@ class ProjectConfig:
|
|
|
84
83
|
if value != "":
|
|
85
84
|
self.reports.append(ReportConfig(report_type, report_path))
|
|
86
85
|
else:
|
|
87
|
-
report_config = next(
|
|
86
|
+
report_config = next(
|
|
87
|
+
(rc for rc in self.reports if rc.type == report_type), None
|
|
88
|
+
)
|
|
88
89
|
if not report_config:
|
|
89
90
|
return
|
|
90
|
-
self.reports.remove(report_config)
|
|
91
|
+
self.reports.remove(report_config)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import toml
|
|
2
2
|
|
|
3
|
+
|
|
3
4
|
class Info:
|
|
4
5
|
def __init__(self):
|
|
5
6
|
data = self._get_data()
|
|
@@ -11,14 +12,19 @@ class Info:
|
|
|
11
12
|
data = toml.load(file)
|
|
12
13
|
return data
|
|
13
14
|
|
|
15
|
+
def get_version(self) -> str:
|
|
16
|
+
return self.version
|
|
17
|
+
|
|
14
18
|
def get_version_info(self) -> str:
|
|
15
19
|
return f"""
|
|
16
|
-
Metripy {self.
|
|
20
|
+
Metripy {self.get_version()}
|
|
17
21
|
{self.url}
|
|
18
22
|
"""
|
|
19
23
|
|
|
20
24
|
def get_help(self) -> str:
|
|
21
|
-
return
|
|
25
|
+
return (
|
|
26
|
+
self.get_version_info()
|
|
27
|
+
+ """
|
|
22
28
|
Usage: metripy [options]
|
|
23
29
|
Options:
|
|
24
30
|
--config=<file> Use a custom config file
|
|
@@ -27,3 +33,4 @@ Options:
|
|
|
27
33
|
--debug Enable debug mode
|
|
28
34
|
--quiet Disable output
|
|
29
35
|
"""
|
|
36
|
+
)
|
|
@@ -122,7 +122,7 @@ class PythonAnalyzer(AbstractLangAnalyzer):
|
|
|
122
122
|
continue
|
|
123
123
|
# if MI is 0, we want to take another look, radon does not like boring functions
|
|
124
124
|
|
|
125
|
-
lines = code_lines[function_node.lineno:function_node.line_end]
|
|
125
|
+
lines = code_lines[function_node.lineno : function_node.line_end]
|
|
126
126
|
function_metrics = (
|
|
127
127
|
self.fallback_halstead_analyzer.calculate_halstead_metrics(
|
|
128
128
|
"\n".join(lines)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from metripy.Dependency.Dependency import Dependency
|
|
2
|
+
from metripy.Metric.ProjectMetrics import ProjectMetrics
|
|
3
|
+
from metripy.Report.Html.PageRenderer import PageRenderer
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class DependencyPageRenderer(PageRenderer):
|
|
7
|
+
def __init__(self, template_dir: str, output_dir: str, project_name: str):
|
|
8
|
+
super().__init__(template_dir, output_dir, project_name)
|
|
9
|
+
|
|
10
|
+
def render(self, metrics: ProjectMetrics):
|
|
11
|
+
dependencies = metrics.dependencies if metrics.dependencies is not None else []
|
|
12
|
+
license_by_type = Dependency.get_lisence_distribution(dependencies)
|
|
13
|
+
|
|
14
|
+
self.render_template(
|
|
15
|
+
"dependencies.html",
|
|
16
|
+
{
|
|
17
|
+
"has_dependencies_data": bool(metrics.dependencies),
|
|
18
|
+
"dependencies": [d.to_dict() for d in dependencies],
|
|
19
|
+
"license_distribution": license_by_type,
|
|
20
|
+
},
|
|
21
|
+
)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
from metripy.Metric.FileTree.FileTreeParser import FileTreeParser
|
|
4
|
+
from metripy.Metric.ProjectMetrics import ProjectMetrics
|
|
5
|
+
from metripy.Report.Html.PageRenderer import PageRenderer
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class FilesPageRenderer(PageRenderer):
|
|
9
|
+
def __init__(self, template_dir: str, output_dir: str, project_name: str):
|
|
10
|
+
super().__init__(template_dir, output_dir, project_name)
|
|
11
|
+
|
|
12
|
+
def render(self, metrics: ProjectMetrics):
|
|
13
|
+
file_names = []
|
|
14
|
+
file_details = {}
|
|
15
|
+
for file_metrics in metrics.file_metrics:
|
|
16
|
+
file_name = file_metrics.full_name
|
|
17
|
+
file_details[file_name] = file_metrics.to_dict()
|
|
18
|
+
file_names.append(file_name)
|
|
19
|
+
|
|
20
|
+
filetree = FileTreeParser.parse(file_names, shorten=True)
|
|
21
|
+
|
|
22
|
+
self.render_template(
|
|
23
|
+
"files.html",
|
|
24
|
+
{
|
|
25
|
+
"filetree": json.dumps(filetree.to_dict()),
|
|
26
|
+
"file_details": json.dumps(file_details),
|
|
27
|
+
},
|
|
28
|
+
)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
from metripy.Metric.ProjectMetrics import ProjectMetrics
|
|
4
|
+
from metripy.Report.Html.PageRenderer import PageRenderer
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class GitAnalysisPageRenderer(PageRenderer):
|
|
8
|
+
def __init__(self, template_dir: str, output_dir: str, project_name: str):
|
|
9
|
+
super().__init__(template_dir, output_dir, project_name)
|
|
10
|
+
|
|
11
|
+
def _render_empty_template(self):
|
|
12
|
+
self.render_template(
|
|
13
|
+
"git_analysis.html",
|
|
14
|
+
{
|
|
15
|
+
"has_git_analysis_data": False,
|
|
16
|
+
"git_analysis": {},
|
|
17
|
+
"git_analysis_json": "{}",
|
|
18
|
+
"git_stats_data": "{}",
|
|
19
|
+
"git_churn_data": "{}",
|
|
20
|
+
"git_silos_data": [],
|
|
21
|
+
"git_contributors": [],
|
|
22
|
+
"git_hotspots_data": [],
|
|
23
|
+
},
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
def render(self, metrics: ProjectMetrics):
|
|
27
|
+
if not metrics.git_metrics:
|
|
28
|
+
self._render_empty_template()
|
|
29
|
+
return
|
|
30
|
+
|
|
31
|
+
self.render_template(
|
|
32
|
+
"git_analysis.html",
|
|
33
|
+
{
|
|
34
|
+
"has_git_analysis_data": bool(metrics.git_metrics),
|
|
35
|
+
"git_analysis": metrics.git_metrics.to_dict(),
|
|
36
|
+
"git_analysis_json": json.dumps(
|
|
37
|
+
metrics.git_metrics.get_contributors_dict(), indent=4
|
|
38
|
+
),
|
|
39
|
+
"git_stats_data": json.dumps(
|
|
40
|
+
metrics.git_metrics.get_commit_stats_per_month(), indent=4
|
|
41
|
+
), # git commit graph
|
|
42
|
+
"git_churn_data": json.dumps(
|
|
43
|
+
metrics.git_metrics.get_churn_per_month(), indent=4
|
|
44
|
+
), # git chrun graph
|
|
45
|
+
"git_silos_data": metrics.git_metrics.get_silos_list()[
|
|
46
|
+
:10
|
|
47
|
+
], # silos list
|
|
48
|
+
"git_contributors": metrics.git_metrics.get_contributors_list()[
|
|
49
|
+
:10
|
|
50
|
+
], # contributors list
|
|
51
|
+
"git_hotspots_data": metrics.git_metrics.get_hotspots_list()[
|
|
52
|
+
:10
|
|
53
|
+
], # hotspots list
|
|
54
|
+
},
|
|
55
|
+
)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
from metripy.Metric.ProjectMetrics import ProjectMetrics
|
|
4
|
+
from metripy.Report.Html.PageRenderer import PageRenderer
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class IndexPageRenderer(PageRenderer):
|
|
8
|
+
def __init__(self, template_dir: str, output_dir: str, project_name: str):
|
|
9
|
+
super().__init__(template_dir, output_dir, project_name)
|
|
10
|
+
|
|
11
|
+
def render(self, metrics: ProjectMetrics):
|
|
12
|
+
git_stats_data = {}
|
|
13
|
+
if metrics.git_metrics:
|
|
14
|
+
git_stats_data = metrics.git_metrics.get_commit_stats_per_month()
|
|
15
|
+
|
|
16
|
+
self.render_template(
|
|
17
|
+
"index.html",
|
|
18
|
+
{
|
|
19
|
+
"git_stats_data": json.dumps(git_stats_data, indent=4),
|
|
20
|
+
"total_code_metrics": metrics.total_code_metrics.to_dict(),
|
|
21
|
+
"has_total_code_metrics_trend": metrics.total_code_metrics.trend
|
|
22
|
+
is not None,
|
|
23
|
+
"total_code_metrics_trend": (
|
|
24
|
+
metrics.total_code_metrics.trend.to_dict()
|
|
25
|
+
if metrics.total_code_metrics.trend
|
|
26
|
+
else None
|
|
27
|
+
),
|
|
28
|
+
"segmentation_data": json.dumps(
|
|
29
|
+
metrics.total_code_metrics.to_dict_segmentation(), indent=4
|
|
30
|
+
),
|
|
31
|
+
"segmentation_data_trend": (
|
|
32
|
+
json.dumps(
|
|
33
|
+
metrics.total_code_metrics.trend.to_dict_segmentation(),
|
|
34
|
+
indent=4,
|
|
35
|
+
)
|
|
36
|
+
if metrics.total_code_metrics.trend
|
|
37
|
+
else None
|
|
38
|
+
),
|
|
39
|
+
},
|
|
40
|
+
)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
|
|
4
|
+
from py_template_engine import TemplateEngine
|
|
5
|
+
|
|
6
|
+
from metripy.Application.Info import Info
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class PageRenderer:
|
|
10
|
+
def __init__(self, template_dir: str, output_dir: str, project_name: str):
|
|
11
|
+
self.template_dir = template_dir
|
|
12
|
+
self.output_dir = output_dir
|
|
13
|
+
|
|
14
|
+
self.global_template_args = {
|
|
15
|
+
"project_name": project_name,
|
|
16
|
+
"last_updated": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
|
17
|
+
"date": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
|
18
|
+
"author": "Metripy",
|
|
19
|
+
"version": Info().get_version(),
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@staticmethod
|
|
23
|
+
def _stringify_values(obj):
|
|
24
|
+
if isinstance(obj, dict):
|
|
25
|
+
return {
|
|
26
|
+
key: PageRenderer._stringify_values(value) for key, value in obj.items()
|
|
27
|
+
}
|
|
28
|
+
elif isinstance(obj, list):
|
|
29
|
+
return [PageRenderer._stringify_values(item) for item in obj]
|
|
30
|
+
else:
|
|
31
|
+
return str(obj)
|
|
32
|
+
|
|
33
|
+
def render_template(self, template_name: str, data: dict):
|
|
34
|
+
data = self._stringify_values(
|
|
35
|
+
{
|
|
36
|
+
**self.global_template_args,
|
|
37
|
+
**data,
|
|
38
|
+
}
|
|
39
|
+
)
|
|
40
|
+
engine = TemplateEngine(os.path.join(self.template_dir, template_name))
|
|
41
|
+
content = engine.render(**data)
|
|
42
|
+
with open(os.path.join(self.output_dir, template_name), "w") as file:
|
|
43
|
+
file.write(content)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from metripy.Report.Html.DependencyPageRenderer import DependencyPageRenderer
|
|
2
|
+
from metripy.Report.Html.FilesPageRenderer import FilesPageRenderer
|
|
3
|
+
from metripy.Report.Html.GitAnalysisPageRenderer import GitAnalysisPageRenderer
|
|
4
|
+
from metripy.Report.Html.IndexPageRenderer import IndexPageRenderer
|
|
5
|
+
from metripy.Report.Html.TopOffendersPageRenderer import TopOffendersPageRenderer
|
|
6
|
+
from metripy.Report.Html.TrendsPageRenderer import TrendsPageRenderer
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class PageRendererFactory:
|
|
10
|
+
def __init__(self, template_dir: str, output_dir: str, project_name: str):
|
|
11
|
+
self.template_dir = template_dir
|
|
12
|
+
self.output_dir = output_dir
|
|
13
|
+
self.project_name = project_name
|
|
14
|
+
|
|
15
|
+
def create_index_page_renderer(self) -> IndexPageRenderer:
|
|
16
|
+
return IndexPageRenderer(self.template_dir, self.output_dir, self.project_name)
|
|
17
|
+
|
|
18
|
+
def create_files_page_renderer(self) -> FilesPageRenderer:
|
|
19
|
+
return FilesPageRenderer(self.template_dir, self.output_dir, self.project_name)
|
|
20
|
+
|
|
21
|
+
def create_top_offenders_page_renderer(self) -> TopOffendersPageRenderer:
|
|
22
|
+
return TopOffendersPageRenderer(
|
|
23
|
+
self.template_dir, self.output_dir, self.project_name
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
def create_git_analysis_page_renderer(self) -> GitAnalysisPageRenderer:
|
|
27
|
+
return GitAnalysisPageRenderer(
|
|
28
|
+
self.template_dir, self.output_dir, self.project_name
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
def create_dependency_page_renderer(self) -> DependencyPageRenderer:
|
|
32
|
+
return DependencyPageRenderer(
|
|
33
|
+
self.template_dir, self.output_dir, self.project_name
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
def create_trends_page_renderer(self) -> TrendsPageRenderer:
|
|
37
|
+
return TrendsPageRenderer(self.template_dir, self.output_dir, self.project_name)
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import shutil
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
from metripy.Application.Config.ReportConfig import ReportConfig
|
|
7
|
+
from metripy.Component.Output.CliOutput import CliOutput
|
|
8
|
+
from metripy.Metric.ProjectMetrics import ProjectMetrics
|
|
9
|
+
from metripy.Report.Html.PageRendererFactory import PageRendererFactory
|
|
10
|
+
from metripy.Report.ReporterInterface import ReporterInterface
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Reporter(ReporterInterface):
|
|
14
|
+
def __init__(
|
|
15
|
+
self, config: ReportConfig, output: CliOutput, project_name: str = "foobar"
|
|
16
|
+
):
|
|
17
|
+
self.config: ReportConfig = config
|
|
18
|
+
self.output = output
|
|
19
|
+
self.template_dir = os.path.join(os.getcwd(), "templates/html_report")
|
|
20
|
+
self.project_name = project_name
|
|
21
|
+
|
|
22
|
+
self.page_renderer_factory = PageRendererFactory(
|
|
23
|
+
self.template_dir, self.config.path, self.project_name
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
self.global_template_args = {
|
|
27
|
+
"project_name": project_name,
|
|
28
|
+
"last_updated": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
def generate(self, metrics: ProjectMetrics):
|
|
32
|
+
|
|
33
|
+
self.output.writeln("<info>Generating HTML report...</info>")
|
|
34
|
+
|
|
35
|
+
# copy sources
|
|
36
|
+
if not os.path.exists(os.path.join(self.config.path, "js")):
|
|
37
|
+
os.makedirs(os.path.join(self.config.path, "js"))
|
|
38
|
+
if not os.path.exists(os.path.join(self.config.path, "css")):
|
|
39
|
+
os.makedirs(os.path.join(self.config.path, "css"))
|
|
40
|
+
if not os.path.exists(os.path.join(self.config.path, "images")):
|
|
41
|
+
os.makedirs(os.path.join(self.config.path, "images"))
|
|
42
|
+
if not os.path.exists(os.path.join(self.config.path, "fonts")):
|
|
43
|
+
os.makedirs(os.path.join(self.config.path, "fonts"))
|
|
44
|
+
if not os.path.exists(os.path.join(self.config.path, "data")):
|
|
45
|
+
os.makedirs(os.path.join(self.config.path, "data"))
|
|
46
|
+
|
|
47
|
+
# shutil.copy(os.path.join(self.template_dir, "favicon.ico"), os.path.join(self.config.path, "favicon.ico"))
|
|
48
|
+
|
|
49
|
+
shutil.copytree(
|
|
50
|
+
os.path.join(self.template_dir, "js"),
|
|
51
|
+
os.path.join(self.config.path, "js"),
|
|
52
|
+
dirs_exist_ok=True,
|
|
53
|
+
)
|
|
54
|
+
shutil.copytree(
|
|
55
|
+
os.path.join(self.template_dir, "css"),
|
|
56
|
+
os.path.join(self.config.path, "css"),
|
|
57
|
+
dirs_exist_ok=True,
|
|
58
|
+
)
|
|
59
|
+
shutil.copytree(
|
|
60
|
+
os.path.join(self.template_dir, "images"),
|
|
61
|
+
os.path.join(self.config.path, "images"),
|
|
62
|
+
dirs_exist_ok=True,
|
|
63
|
+
)
|
|
64
|
+
# shutil.copytree(os.path.join(self.template_dir, "fonts"), os.path.join(self.config.path, "fonts"), dirs_exist_ok=True)
|
|
65
|
+
|
|
66
|
+
# copy logo, lies 2 down from the templates directory
|
|
67
|
+
shutil.copy(
|
|
68
|
+
os.path.join(self.template_dir, "../..", "logo.svg"),
|
|
69
|
+
os.path.join(self.config.path, "images", "logo.svg"),
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# Render main pages
|
|
73
|
+
self.render_index_page(metrics)
|
|
74
|
+
self.render_files_page(metrics)
|
|
75
|
+
self.render_top_offenders_page(metrics)
|
|
76
|
+
self.render_git_analysis_page(metrics)
|
|
77
|
+
self.render_dependencies_page(metrics)
|
|
78
|
+
self.render_trends_page(metrics)
|
|
79
|
+
|
|
80
|
+
self.output.writeln(
|
|
81
|
+
f"<success>HTML report generated in {self.config.path} directory</success>"
|
|
82
|
+
)
|
|
83
|
+
self.output.writeln(
|
|
84
|
+
f"<success>Open HTML report: {self.config.path}/index.html</success>"
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
def render_index_page(self, metrics: ProjectMetrics):
|
|
88
|
+
self.output.writeln("<info>Rendering index page</info>")
|
|
89
|
+
self.page_renderer_factory.create_index_page_renderer().render(metrics)
|
|
90
|
+
self.output.writeln("<success>Done rendering index page</success>")
|
|
91
|
+
|
|
92
|
+
def render_files_page(self, metrics: ProjectMetrics):
|
|
93
|
+
"""Render the files page with file details and analysis"""
|
|
94
|
+
self.output.writeln("<info>Rendering files page</info>")
|
|
95
|
+
self.page_renderer_factory.create_files_page_renderer().render(metrics)
|
|
96
|
+
self.output.writeln("<success>Files page generated successfully</success>")
|
|
97
|
+
|
|
98
|
+
def render_top_offenders_page(self, metrics: ProjectMetrics):
|
|
99
|
+
self.output.writeln("<info>Rendering top offenders page</info>")
|
|
100
|
+
self.page_renderer_factory.create_top_offenders_page_renderer().render(metrics)
|
|
101
|
+
self.output.writeln(
|
|
102
|
+
"<success>Top offenders page generated successfully</success>"
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
def render_git_analysis_page(self, metrics: ProjectMetrics):
|
|
106
|
+
"""Render the git analysis page with comprehensive git data"""
|
|
107
|
+
self.output.writeln("<info>Rendering git analysis page</info>")
|
|
108
|
+
self.page_renderer_factory.create_git_analysis_page_renderer().render(metrics)
|
|
109
|
+
self.output.writeln(
|
|
110
|
+
"<success>Git analysis page generated successfully</success>"
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
def render_dependencies_page(self, metrics: ProjectMetrics):
|
|
114
|
+
"""Render the dependencies page with dependency details and stats"""
|
|
115
|
+
self.output.writeln("<info>Rendering dependencies page</info>")
|
|
116
|
+
self.page_renderer_factory.create_dependency_page_renderer().render(metrics)
|
|
117
|
+
self.output.writeln(
|
|
118
|
+
"<success>Dependencies page generated successfully</success>"
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
def render_trends_page(self, metrics: ProjectMetrics):
|
|
122
|
+
self.output.writeln("<info>Rendering trends page</info>")
|
|
123
|
+
self.page_renderer_factory.create_trends_page_renderer().render(metrics)
|
|
124
|
+
self.output.writeln("<success>Trends page generated successfully</success>")
|