greenmining 1.1.9__py3-none-any.whl → 1.2.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.
- greenmining/__init__.py +29 -10
- greenmining/analyzers/__init__.py +0 -8
- greenmining/controllers/repository_controller.py +83 -88
- greenmining/services/local_repo_analyzer.py +15 -8
- greenmining-1.2.1.dist-info/METADATA +311 -0
- {greenmining-1.1.9.dist-info → greenmining-1.2.1.dist-info}/RECORD +9 -15
- greenmining/analyzers/power_regression.py +0 -211
- greenmining/analyzers/qualitative_analyzer.py +0 -394
- greenmining/analyzers/version_power_analyzer.py +0 -246
- greenmining/config.py +0 -91
- greenmining/presenters/__init__.py +0 -7
- greenmining/presenters/console_presenter.py +0 -143
- greenmining-1.1.9.dist-info/METADATA +0 -865
- {greenmining-1.1.9.dist-info → greenmining-1.2.1.dist-info}/WHEEL +0 -0
- {greenmining-1.1.9.dist-info → greenmining-1.2.1.dist-info}/licenses/LICENSE +0 -0
- {greenmining-1.1.9.dist-info → greenmining-1.2.1.dist-info}/top_level.txt +0 -0
|
@@ -1,246 +0,0 @@
|
|
|
1
|
-
# Version-by-version power analysis.
|
|
2
|
-
# Measure power consumption across multiple software versions/tags.
|
|
3
|
-
|
|
4
|
-
from __future__ import annotations
|
|
5
|
-
|
|
6
|
-
import subprocess
|
|
7
|
-
from dataclasses import dataclass, field
|
|
8
|
-
from typing import Any, Dict, List, Optional
|
|
9
|
-
|
|
10
|
-
from greenmining.utils import colored_print
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
@dataclass
|
|
14
|
-
class VersionPowerProfile:
|
|
15
|
-
# Power profile for a single version.
|
|
16
|
-
|
|
17
|
-
version: str
|
|
18
|
-
commit_sha: str
|
|
19
|
-
energy_joules: float = 0.0
|
|
20
|
-
power_watts_avg: float = 0.0
|
|
21
|
-
duration_seconds: float = 0.0
|
|
22
|
-
iterations: int = 0
|
|
23
|
-
energy_std: float = 0.0 # Standard deviation across iterations
|
|
24
|
-
|
|
25
|
-
def to_dict(self) -> Dict[str, Any]:
|
|
26
|
-
return {
|
|
27
|
-
"version": self.version,
|
|
28
|
-
"commit_sha": self.commit_sha,
|
|
29
|
-
"energy_joules": round(self.energy_joules, 4),
|
|
30
|
-
"power_watts_avg": round(self.power_watts_avg, 4),
|
|
31
|
-
"duration_seconds": round(self.duration_seconds, 4),
|
|
32
|
-
"iterations": self.iterations,
|
|
33
|
-
"energy_std": round(self.energy_std, 4),
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
@dataclass
|
|
38
|
-
class VersionPowerReport:
|
|
39
|
-
# Complete power analysis report across versions.
|
|
40
|
-
|
|
41
|
-
versions: List[VersionPowerProfile] = field(default_factory=list)
|
|
42
|
-
trend: str = "stable" # increasing, decreasing, stable
|
|
43
|
-
total_change_percent: float = 0.0
|
|
44
|
-
most_efficient: str = ""
|
|
45
|
-
least_efficient: str = ""
|
|
46
|
-
|
|
47
|
-
def to_dict(self) -> Dict[str, Any]:
|
|
48
|
-
return {
|
|
49
|
-
"versions": [v.to_dict() for v in self.versions],
|
|
50
|
-
"trend": self.trend,
|
|
51
|
-
"total_change_percent": round(self.total_change_percent, 2),
|
|
52
|
-
"most_efficient": self.most_efficient,
|
|
53
|
-
"least_efficient": self.least_efficient,
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
def summary(self) -> str:
|
|
57
|
-
# Generate human-readable summary.
|
|
58
|
-
lines = [
|
|
59
|
-
"Version Power Analysis Report",
|
|
60
|
-
"-" * 40,
|
|
61
|
-
f"Versions analyzed: {len(self.versions)}",
|
|
62
|
-
f"Trend: {self.trend}",
|
|
63
|
-
f"Total change: {self.total_change_percent:+.2f}%",
|
|
64
|
-
f"Most efficient: {self.most_efficient}",
|
|
65
|
-
f"Least efficient: {self.least_efficient}",
|
|
66
|
-
"",
|
|
67
|
-
"Per-version breakdown:",
|
|
68
|
-
]
|
|
69
|
-
for v in self.versions:
|
|
70
|
-
lines.append(
|
|
71
|
-
f" {v.version}: {v.energy_joules:.4f}J "
|
|
72
|
-
f"({v.power_watts_avg:.2f}W avg, {v.duration_seconds:.2f}s)"
|
|
73
|
-
)
|
|
74
|
-
return "\n".join(lines)
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
class VersionPowerAnalyzer:
|
|
78
|
-
# Measure and compare power consumption across software versions.
|
|
79
|
-
|
|
80
|
-
def __init__(
|
|
81
|
-
self,
|
|
82
|
-
test_command: str = "pytest tests/",
|
|
83
|
-
energy_backend: str = "rapl",
|
|
84
|
-
iterations: int = 10,
|
|
85
|
-
warmup_iterations: int = 2,
|
|
86
|
-
):
|
|
87
|
-
# Initialize version power analyzer.
|
|
88
|
-
# Args:
|
|
89
|
-
# test_command: Shell command to run for measurement
|
|
90
|
-
# energy_backend: Energy measurement backend
|
|
91
|
-
# iterations: Number of measurement iterations per version
|
|
92
|
-
# warmup_iterations: Warmup runs before measuring
|
|
93
|
-
self.test_command = test_command
|
|
94
|
-
self.energy_backend = energy_backend
|
|
95
|
-
self.iterations = iterations
|
|
96
|
-
self.warmup_iterations = warmup_iterations
|
|
97
|
-
self._meter = None
|
|
98
|
-
|
|
99
|
-
def _get_energy_meter(self):
|
|
100
|
-
# Get energy meter instance.
|
|
101
|
-
if self._meter is None:
|
|
102
|
-
from greenmining.energy.base import get_energy_meter
|
|
103
|
-
|
|
104
|
-
self._meter = get_energy_meter(self.energy_backend)
|
|
105
|
-
return self._meter
|
|
106
|
-
|
|
107
|
-
def _measure_version(self, repo_path: str, version: str) -> VersionPowerProfile:
|
|
108
|
-
# Measure power consumption for a specific version.
|
|
109
|
-
meter = self._get_energy_meter()
|
|
110
|
-
|
|
111
|
-
# Checkout version
|
|
112
|
-
sha = self._checkout_version(repo_path, version)
|
|
113
|
-
colored_print(f" Measuring {version} ({sha[:8]})...", "cyan")
|
|
114
|
-
|
|
115
|
-
# Warmup
|
|
116
|
-
for i in range(self.warmup_iterations):
|
|
117
|
-
subprocess.run(
|
|
118
|
-
self.test_command,
|
|
119
|
-
shell=True,
|
|
120
|
-
cwd=repo_path,
|
|
121
|
-
capture_output=True,
|
|
122
|
-
text=True,
|
|
123
|
-
timeout=600,
|
|
124
|
-
)
|
|
125
|
-
|
|
126
|
-
# Measure iterations
|
|
127
|
-
measurements = []
|
|
128
|
-
for i in range(self.iterations):
|
|
129
|
-
meter.start()
|
|
130
|
-
subprocess.run(
|
|
131
|
-
self.test_command,
|
|
132
|
-
shell=True,
|
|
133
|
-
cwd=repo_path,
|
|
134
|
-
capture_output=True,
|
|
135
|
-
text=True,
|
|
136
|
-
timeout=600,
|
|
137
|
-
)
|
|
138
|
-
metrics = meter.stop()
|
|
139
|
-
measurements.append(metrics)
|
|
140
|
-
|
|
141
|
-
# Aggregate
|
|
142
|
-
import numpy as np
|
|
143
|
-
|
|
144
|
-
energies = [m.joules for m in measurements]
|
|
145
|
-
avg_energy = float(np.mean(energies))
|
|
146
|
-
std_energy = float(np.std(energies))
|
|
147
|
-
avg_power = float(np.mean([m.watts_avg for m in measurements]))
|
|
148
|
-
avg_duration = float(np.mean([m.duration_seconds for m in measurements]))
|
|
149
|
-
|
|
150
|
-
profile = VersionPowerProfile(
|
|
151
|
-
version=version,
|
|
152
|
-
commit_sha=sha,
|
|
153
|
-
energy_joules=avg_energy,
|
|
154
|
-
power_watts_avg=avg_power,
|
|
155
|
-
duration_seconds=avg_duration,
|
|
156
|
-
iterations=self.iterations,
|
|
157
|
-
energy_std=std_energy,
|
|
158
|
-
)
|
|
159
|
-
|
|
160
|
-
colored_print(
|
|
161
|
-
f" {version}: {avg_energy:.4f}J +/-{std_energy:.4f} "
|
|
162
|
-
f"({avg_power:.2f}W, {avg_duration:.2f}s)",
|
|
163
|
-
"green",
|
|
164
|
-
)
|
|
165
|
-
return profile
|
|
166
|
-
|
|
167
|
-
def analyze_versions(
|
|
168
|
-
self,
|
|
169
|
-
repo_path: str,
|
|
170
|
-
versions: List[str],
|
|
171
|
-
) -> VersionPowerReport:
|
|
172
|
-
# Analyze power consumption across multiple versions.
|
|
173
|
-
# Args:
|
|
174
|
-
# repo_path: Path to local git repository
|
|
175
|
-
# versions: List of version tags or commit references
|
|
176
|
-
colored_print(f"\nAnalyzing {len(versions)} versions for power consumption", "cyan")
|
|
177
|
-
colored_print(f" Test: {self.test_command}", "cyan")
|
|
178
|
-
colored_print(f" Iterations: {self.iterations} (+{self.warmup_iterations} warmup)", "cyan")
|
|
179
|
-
|
|
180
|
-
profiles = []
|
|
181
|
-
for version in versions:
|
|
182
|
-
try:
|
|
183
|
-
profile = self._measure_version(repo_path, version)
|
|
184
|
-
profiles.append(profile)
|
|
185
|
-
except Exception as e:
|
|
186
|
-
colored_print(f" Error measuring {version}: {e}", "red")
|
|
187
|
-
|
|
188
|
-
if not profiles:
|
|
189
|
-
return VersionPowerReport()
|
|
190
|
-
|
|
191
|
-
# Determine trend
|
|
192
|
-
first_energy = profiles[0].energy_joules
|
|
193
|
-
last_energy = profiles[-1].energy_joules
|
|
194
|
-
|
|
195
|
-
if first_energy > 0:
|
|
196
|
-
total_change = ((last_energy - first_energy) / first_energy) * 100
|
|
197
|
-
else:
|
|
198
|
-
total_change = 0.0
|
|
199
|
-
|
|
200
|
-
if total_change > 5:
|
|
201
|
-
trend = "increasing"
|
|
202
|
-
elif total_change < -5:
|
|
203
|
-
trend = "decreasing"
|
|
204
|
-
else:
|
|
205
|
-
trend = "stable"
|
|
206
|
-
|
|
207
|
-
# Find most/least efficient
|
|
208
|
-
most_efficient = min(profiles, key=lambda p: p.energy_joules)
|
|
209
|
-
least_efficient = max(profiles, key=lambda p: p.energy_joules)
|
|
210
|
-
|
|
211
|
-
report = VersionPowerReport(
|
|
212
|
-
versions=profiles,
|
|
213
|
-
trend=trend,
|
|
214
|
-
total_change_percent=total_change,
|
|
215
|
-
most_efficient=most_efficient.version,
|
|
216
|
-
least_efficient=least_efficient.version,
|
|
217
|
-
)
|
|
218
|
-
|
|
219
|
-
colored_print(f"\nTrend: {trend} ({total_change:+.2f}%)", "cyan")
|
|
220
|
-
colored_print(f"Most efficient: {most_efficient.version}", "green")
|
|
221
|
-
colored_print(f"Least efficient: {least_efficient.version}", "red")
|
|
222
|
-
|
|
223
|
-
# Restore to latest version
|
|
224
|
-
try:
|
|
225
|
-
self._checkout_version(repo_path, versions[-1])
|
|
226
|
-
except Exception:
|
|
227
|
-
pass
|
|
228
|
-
|
|
229
|
-
return report
|
|
230
|
-
|
|
231
|
-
@staticmethod
|
|
232
|
-
def _checkout_version(repo_path: str, version: str) -> str:
|
|
233
|
-
# Checkout a version and return its SHA.
|
|
234
|
-
subprocess.run(
|
|
235
|
-
["git", "checkout", version, "--quiet"],
|
|
236
|
-
cwd=repo_path,
|
|
237
|
-
capture_output=True,
|
|
238
|
-
text=True,
|
|
239
|
-
)
|
|
240
|
-
result = subprocess.run(
|
|
241
|
-
["git", "rev-parse", "HEAD"],
|
|
242
|
-
cwd=repo_path,
|
|
243
|
-
capture_output=True,
|
|
244
|
-
text=True,
|
|
245
|
-
)
|
|
246
|
-
return result.stdout.strip()
|
greenmining/config.py
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
from pathlib import Path
|
|
3
|
-
from typing import Any, Dict, List
|
|
4
|
-
|
|
5
|
-
from dotenv import load_dotenv
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def _load_yaml_config(yaml_path: Path) -> Dict[str, Any]:
|
|
9
|
-
# Load configuration from YAML file if it exists.
|
|
10
|
-
if not yaml_path.exists():
|
|
11
|
-
return {}
|
|
12
|
-
try:
|
|
13
|
-
import yaml
|
|
14
|
-
|
|
15
|
-
with open(yaml_path, "r") as f:
|
|
16
|
-
return yaml.safe_load(f) or {}
|
|
17
|
-
except ImportError:
|
|
18
|
-
return {}
|
|
19
|
-
except Exception:
|
|
20
|
-
return {}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
class Config:
|
|
24
|
-
# Configuration class for loading from env vars and YAML.
|
|
25
|
-
|
|
26
|
-
def __init__(self, env_file: str = ".env", yaml_file: str = "greenmining.yaml"):
|
|
27
|
-
# Initialize configuration from environment and YAML file.
|
|
28
|
-
env_path = Path(env_file)
|
|
29
|
-
if env_path.exists():
|
|
30
|
-
load_dotenv(env_path)
|
|
31
|
-
else:
|
|
32
|
-
load_dotenv()
|
|
33
|
-
|
|
34
|
-
# Load YAML config
|
|
35
|
-
yaml_path = Path(yaml_file)
|
|
36
|
-
self._yaml_config = _load_yaml_config(yaml_path)
|
|
37
|
-
|
|
38
|
-
# GitHub API Configuration
|
|
39
|
-
self.GITHUB_TOKEN = os.getenv("GITHUB_TOKEN")
|
|
40
|
-
if not self.GITHUB_TOKEN or self.GITHUB_TOKEN == "your_github_pat_here":
|
|
41
|
-
raise ValueError("GITHUB_TOKEN not set. Please set it in .env file or environment.")
|
|
42
|
-
|
|
43
|
-
# Search Configuration (YAML: sources.search.*)
|
|
44
|
-
yaml_search = self._yaml_config.get("sources", {}).get("search", {})
|
|
45
|
-
|
|
46
|
-
self.SUPPORTED_LANGUAGES: List[str] = yaml_search.get(
|
|
47
|
-
"languages",
|
|
48
|
-
[
|
|
49
|
-
"Python",
|
|
50
|
-
"JavaScript",
|
|
51
|
-
"TypeScript",
|
|
52
|
-
"Java",
|
|
53
|
-
"C++",
|
|
54
|
-
"C#",
|
|
55
|
-
"Go",
|
|
56
|
-
"Rust",
|
|
57
|
-
"PHP",
|
|
58
|
-
"Ruby",
|
|
59
|
-
"Swift",
|
|
60
|
-
"Kotlin",
|
|
61
|
-
"Scala",
|
|
62
|
-
"R",
|
|
63
|
-
"MATLAB",
|
|
64
|
-
"Dart",
|
|
65
|
-
"Lua",
|
|
66
|
-
"Perl",
|
|
67
|
-
"Haskell",
|
|
68
|
-
"Elixir",
|
|
69
|
-
],
|
|
70
|
-
)
|
|
71
|
-
|
|
72
|
-
# Repository Limits
|
|
73
|
-
self.MIN_STARS = yaml_search.get("min_stars", int(os.getenv("MIN_STARS", "100")))
|
|
74
|
-
self.MAX_REPOS = int(os.getenv("MAX_REPOS", "100"))
|
|
75
|
-
|
|
76
|
-
# Output Configuration (YAML: output.directory)
|
|
77
|
-
yaml_output = self._yaml_config.get("output", {})
|
|
78
|
-
self.OUTPUT_DIR = Path(yaml_output.get("directory", os.getenv("OUTPUT_DIR", "./data")))
|
|
79
|
-
self.OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
|
|
80
|
-
|
|
81
|
-
# File Paths
|
|
82
|
-
self.REPOS_FILE = self.OUTPUT_DIR / "repositories.json"
|
|
83
|
-
|
|
84
|
-
def __repr__(self) -> str:
|
|
85
|
-
# String representation of configuration (hiding sensitive data).
|
|
86
|
-
return (
|
|
87
|
-
f"Config("
|
|
88
|
-
f"MAX_REPOS={self.MAX_REPOS}, "
|
|
89
|
-
f"OUTPUT_DIR={self.OUTPUT_DIR}"
|
|
90
|
-
f")"
|
|
91
|
-
)
|
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
# Console Presenter - Handles console output formatting.
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from typing import Any, Dict, List
|
|
6
|
-
|
|
7
|
-
from tabulate import tabulate
|
|
8
|
-
|
|
9
|
-
from greenmining.utils import colored_print
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class ConsolePresenter:
|
|
13
|
-
# Presenter for console/terminal output.
|
|
14
|
-
|
|
15
|
-
@staticmethod
|
|
16
|
-
def show_banner():
|
|
17
|
-
# Display application banner.
|
|
18
|
-
banner = """
|
|
19
|
-
|
|
20
|
-
Green Microservices Mining
|
|
21
|
-
|
|
22
|
-
"""
|
|
23
|
-
colored_print(banner, "green")
|
|
24
|
-
|
|
25
|
-
@staticmethod
|
|
26
|
-
def show_repositories(repositories: list[dict], limit: int = 10):
|
|
27
|
-
# Display repository table.
|
|
28
|
-
if not repositories:
|
|
29
|
-
colored_print("No repositories to display", "yellow")
|
|
30
|
-
return
|
|
31
|
-
|
|
32
|
-
colored_print(f"\n Top {min(limit, len(repositories))} Repositories:\n", "cyan")
|
|
33
|
-
|
|
34
|
-
table_data = []
|
|
35
|
-
for repo in repositories[:limit]:
|
|
36
|
-
table_data.append(
|
|
37
|
-
[
|
|
38
|
-
repo.get("full_name", "N/A"),
|
|
39
|
-
repo.get("language", "N/A"),
|
|
40
|
-
f"{repo.get('stars', 0):,}",
|
|
41
|
-
(
|
|
42
|
-
repo.get("description", "")[:50] + "..."
|
|
43
|
-
if len(repo.get("description", "")) > 50
|
|
44
|
-
else repo.get("description", "")
|
|
45
|
-
),
|
|
46
|
-
]
|
|
47
|
-
)
|
|
48
|
-
|
|
49
|
-
headers = ["Repository", "Language", "Stars", "Description"]
|
|
50
|
-
print(tabulate(table_data, headers=headers, tablefmt="grid"))
|
|
51
|
-
|
|
52
|
-
@staticmethod
|
|
53
|
-
def show_commit_stats(stats: dict[str, Any]):
|
|
54
|
-
# Display commit statistics.
|
|
55
|
-
colored_print("\n Commit Statistics:\n", "cyan")
|
|
56
|
-
|
|
57
|
-
table_data = [
|
|
58
|
-
["Total Commits", f"{stats.get('total_commits', 0):,}"],
|
|
59
|
-
["Repositories", stats.get("total_repos", 0)],
|
|
60
|
-
["Avg per Repo", f"{stats.get('avg_per_repo', 0):.1f}"],
|
|
61
|
-
["Date Range", stats.get("date_range", "N/A")],
|
|
62
|
-
]
|
|
63
|
-
|
|
64
|
-
print(tabulate(table_data, headers=["Metric", "Value"], tablefmt="grid"))
|
|
65
|
-
|
|
66
|
-
@staticmethod
|
|
67
|
-
def show_analysis_results(results: dict[str, Any]):
|
|
68
|
-
# Display analysis results.
|
|
69
|
-
colored_print("\n Analysis Results:\n", "cyan")
|
|
70
|
-
|
|
71
|
-
summary = results.get("summary", {})
|
|
72
|
-
table_data = [
|
|
73
|
-
["Total Commits Analyzed", f"{summary.get('total_commits', 0):,}"],
|
|
74
|
-
["Green-Aware Commits", f"{summary.get('green_commits', 0):,}"],
|
|
75
|
-
["Green Rate", f"{summary.get('green_commit_rate', 0):.1%}"],
|
|
76
|
-
["Patterns Detected", len(results.get("known_patterns", {}))],
|
|
77
|
-
]
|
|
78
|
-
|
|
79
|
-
print(tabulate(table_data, headers=["Metric", "Value"], tablefmt="grid"))
|
|
80
|
-
|
|
81
|
-
@staticmethod
|
|
82
|
-
def show_pattern_distribution(patterns: dict[str, Any], limit: int = 10):
|
|
83
|
-
# Display pattern distribution.
|
|
84
|
-
if not patterns:
|
|
85
|
-
colored_print("No patterns to display", "yellow")
|
|
86
|
-
return
|
|
87
|
-
|
|
88
|
-
colored_print(f"\n Top {limit} Green Patterns:\n", "cyan")
|
|
89
|
-
|
|
90
|
-
# Sort by count
|
|
91
|
-
sorted_patterns = sorted(
|
|
92
|
-
patterns.items(), key=lambda x: x[1].get("count", 0), reverse=True
|
|
93
|
-
)[:limit]
|
|
94
|
-
|
|
95
|
-
table_data = []
|
|
96
|
-
for pattern_name, data in sorted_patterns:
|
|
97
|
-
table_data.append(
|
|
98
|
-
[
|
|
99
|
-
pattern_name,
|
|
100
|
-
data.get("count", 0),
|
|
101
|
-
f"{data.get('percentage', 0):.1f}%",
|
|
102
|
-
data.get("confidence_distribution", {}).get("HIGH", 0),
|
|
103
|
-
]
|
|
104
|
-
)
|
|
105
|
-
|
|
106
|
-
headers = ["Pattern", "Count", "Percentage", "High Confidence"]
|
|
107
|
-
print(tabulate(table_data, headers=headers, tablefmt="grid"))
|
|
108
|
-
|
|
109
|
-
@staticmethod
|
|
110
|
-
def show_pipeline_status(status: dict[str, Any]):
|
|
111
|
-
# Display pipeline status.
|
|
112
|
-
colored_print("\n Pipeline Status:\n", "cyan")
|
|
113
|
-
|
|
114
|
-
table_data = []
|
|
115
|
-
for phase, info in status.items():
|
|
116
|
-
status_icon = "done" if info.get("completed") else "pending"
|
|
117
|
-
table_data.append(
|
|
118
|
-
[status_icon, phase, info.get("file", "N/A"), info.get("size", "N/A")]
|
|
119
|
-
)
|
|
120
|
-
|
|
121
|
-
headers = ["Status", "Phase", "Output File", "Size"]
|
|
122
|
-
print(tabulate(table_data, headers=headers, tablefmt="grid"))
|
|
123
|
-
|
|
124
|
-
@staticmethod
|
|
125
|
-
def show_progress_message(phase: str, current: int, total: int):
|
|
126
|
-
# Display progress message.
|
|
127
|
-
percentage = (current / total * 100) if total > 0 else 0
|
|
128
|
-
colored_print(f"[{phase}] Progress: {current}/{total} ({percentage:.1f}%)", "cyan")
|
|
129
|
-
|
|
130
|
-
@staticmethod
|
|
131
|
-
def show_error(message: str):
|
|
132
|
-
# Display error message.
|
|
133
|
-
colored_print(f" Error: {message}", "red")
|
|
134
|
-
|
|
135
|
-
@staticmethod
|
|
136
|
-
def show_success(message: str):
|
|
137
|
-
# Display success message.
|
|
138
|
-
colored_print(f" {message}", "green")
|
|
139
|
-
|
|
140
|
-
@staticmethod
|
|
141
|
-
def show_warning(message: str):
|
|
142
|
-
# Display warning message.
|
|
143
|
-
colored_print(f" Warning: {message}", "yellow")
|