301 lines
10 KiB
Python
301 lines
10 KiB
Python
"""
|
|
Export handler for PyUCC GUI.
|
|
Handles exporting results to CSV and other formats, including UCC-style reports.
|
|
"""
|
|
|
|
from tkinter import filedialog, messagebox
|
|
from pathlib import Path
|
|
from datetime import datetime
|
|
|
|
|
|
class ExportHandler:
|
|
"""Handles export functionality for results."""
|
|
|
|
def __init__(self, gui_app):
|
|
"""
|
|
Args:
|
|
gui_app: Reference to the main GUI application instance
|
|
"""
|
|
self.app = gui_app
|
|
|
|
def export_to_csv(self):
|
|
"""Export current results tree to CSV file."""
|
|
from ..utils.csv_exporter import export_rows_to_csv
|
|
|
|
path = filedialog.asksaveasfilename(
|
|
defaultextension=".csv",
|
|
filetypes=[("CSV files", "*.csv"), ("All files", "*")],
|
|
)
|
|
|
|
if not path:
|
|
return
|
|
|
|
headers = [c for c in self.app.results_tree["columns"]]
|
|
rows = (
|
|
self.app.results_tree.item(child, "values")
|
|
for child in self.app.results_tree.get_children()
|
|
)
|
|
|
|
try:
|
|
written = export_rows_to_csv(path, headers, rows)
|
|
messagebox.showinfo("Export", f"Exported {written} rows to {path}")
|
|
except Exception as e:
|
|
messagebox.showerror("Export Error", str(e))
|
|
|
|
def save_ucc_report(self):
|
|
"""Save UCC-style report based on current action and results."""
|
|
# Determine which action is currently active
|
|
current_action = getattr(self.app, "_current_action", None)
|
|
|
|
if not current_action:
|
|
messagebox.showwarning("Save Report", "No results available to save")
|
|
return
|
|
|
|
# Get default filename based on action
|
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
default_name = f"PyUcc_{current_action}_{timestamp}_outfile.txt"
|
|
|
|
path = filedialog.asksaveasfilename(
|
|
defaultextension=".txt",
|
|
initialfile=default_name,
|
|
filetypes=[("Text files", "*.txt"), ("All files", "*")],
|
|
)
|
|
|
|
if not path:
|
|
return
|
|
|
|
try:
|
|
if current_action == "countings":
|
|
self._save_counting_report(Path(path))
|
|
elif current_action == "differ":
|
|
self._save_differ_report(Path(path))
|
|
elif current_action == "duplicates":
|
|
self._save_duplicates_report(Path(path))
|
|
elif current_action == "metrics":
|
|
self._save_metrics_report(Path(path))
|
|
elif current_action == "scan":
|
|
self._save_scan_report(Path(path))
|
|
else:
|
|
messagebox.showwarning(
|
|
"Save Report", f"Report not implemented for {current_action}"
|
|
)
|
|
return
|
|
|
|
# Show success dialog with option to open the report
|
|
self._show_report_saved_dialog(path)
|
|
except Exception as e:
|
|
messagebox.showerror("Save Report Error", str(e))
|
|
|
|
def _show_report_saved_dialog(self, report_path: str):
|
|
"""Show dialog with option to open the saved report."""
|
|
import tkinter as tk
|
|
from tkinter import ttk
|
|
import subprocess
|
|
import os
|
|
|
|
# Create custom dialog
|
|
dialog = tk.Toplevel(self.app)
|
|
dialog.title("Report Saved")
|
|
dialog.transient(self.app)
|
|
dialog.grab_set()
|
|
|
|
# Center the dialog
|
|
dialog.geometry("500x180")
|
|
dialog.resizable(False, False)
|
|
|
|
# Icon
|
|
try:
|
|
dialog.iconbitmap(
|
|
default=str(Path(__file__).parent.parent / "assets" / "icon.ico")
|
|
)
|
|
except:
|
|
pass
|
|
|
|
# Main frame
|
|
main_frame = ttk.Frame(dialog, padding=20)
|
|
main_frame.pack(fill=tk.BOTH, expand=True)
|
|
|
|
# Success icon and message
|
|
ttk.Label(
|
|
main_frame,
|
|
text="✅ Report saved successfully!",
|
|
font=("Segoe UI", 11, "bold"),
|
|
).pack(pady=(0, 10))
|
|
|
|
# File path (with text wrapping)
|
|
path_frame = ttk.Frame(main_frame)
|
|
path_frame.pack(fill=tk.X, pady=(0, 20))
|
|
|
|
ttk.Label(path_frame, text="Location:", font=("Segoe UI", 9)).pack(anchor=tk.W)
|
|
path_text = tk.Text(
|
|
path_frame,
|
|
height=2,
|
|
wrap=tk.WORD,
|
|
font=("Consolas", 9),
|
|
bg="#f0f0f0",
|
|
relief=tk.FLAT,
|
|
)
|
|
path_text.insert("1.0", report_path)
|
|
path_text.config(state=tk.DISABLED)
|
|
path_text.pack(fill=tk.X, pady=(5, 0))
|
|
|
|
# Buttons frame
|
|
btn_frame = ttk.Frame(main_frame)
|
|
btn_frame.pack(fill=tk.X)
|
|
|
|
def open_report():
|
|
"""Open report file with default text editor."""
|
|
try:
|
|
if os.name == "nt": # Windows
|
|
os.startfile(report_path)
|
|
elif os.name == "posix": # macOS/Linux
|
|
subprocess.run(["xdg-open", report_path])
|
|
dialog.destroy()
|
|
except Exception as e:
|
|
messagebox.showerror(
|
|
"Open Error", f"Could not open file:\n{e}", parent=dialog
|
|
)
|
|
|
|
def close_dialog():
|
|
dialog.destroy()
|
|
|
|
# Open Report button (primary)
|
|
open_btn = ttk.Button(
|
|
btn_frame, text="📄 Open Report", command=open_report, width=20
|
|
)
|
|
open_btn.pack(side=tk.LEFT, padx=(0, 10))
|
|
|
|
# Close button
|
|
close_btn = ttk.Button(btn_frame, text="Close", command=close_dialog, width=15)
|
|
close_btn.pack(side=tk.LEFT)
|
|
|
|
# Center dialog on parent
|
|
dialog.update_idletasks()
|
|
x = (
|
|
self.app.winfo_x()
|
|
+ (self.app.winfo_width() // 2)
|
|
- (dialog.winfo_width() // 2)
|
|
)
|
|
y = (
|
|
self.app.winfo_y()
|
|
+ (self.app.winfo_height() // 2)
|
|
- (dialog.winfo_height() // 2)
|
|
)
|
|
dialog.geometry(f"+{x}+{y}")
|
|
|
|
# Set focus to Open button
|
|
open_btn.focus_set()
|
|
|
|
# Bind Enter key to open report
|
|
dialog.bind("<Return>", lambda e: open_report())
|
|
dialog.bind("<Escape>", lambda e: close_dialog())
|
|
|
|
def _save_counting_report(self, output_path: Path):
|
|
"""Save counting results as UCC-style report."""
|
|
from ..utils.ucc_report_generator import UCCReportGenerator
|
|
|
|
# Collect results from cache
|
|
results_cache = getattr(self.app, "_results_cache", None)
|
|
if not results_cache:
|
|
raise ValueError("No counting results available")
|
|
|
|
# Convert cache to list if it's a dict
|
|
if isinstance(results_cache, dict):
|
|
results = list(results_cache.values())
|
|
elif isinstance(results_cache, list):
|
|
results = results_cache
|
|
else:
|
|
raise ValueError("Invalid results cache format")
|
|
|
|
# Get project root
|
|
project_root = getattr(self.app, "_last_scan_root", "")
|
|
|
|
# Generate command description
|
|
cmd_desc = f"PyUcc Counting Analysis - Project: {project_root}"
|
|
|
|
UCCReportGenerator.generate_counting_report(
|
|
results=results,
|
|
output_path=output_path,
|
|
command_description=cmd_desc,
|
|
base_path=project_root,
|
|
)
|
|
|
|
def _save_differ_report(self, output_path: Path):
|
|
"""Save differ results as UCC-style report."""
|
|
from ..utils.ucc_report_generator import UCCReportGenerator
|
|
|
|
# Get differ results from _current_results
|
|
differ_result = getattr(self.app, "_current_results", None)
|
|
if not differ_result:
|
|
raise ValueError("No differ results available")
|
|
|
|
# Extract matched pairs
|
|
pairs = differ_result.get("pairs", [])
|
|
baseline_id = differ_result.get("baseline_id", "unknown")
|
|
|
|
# Generate command description
|
|
cmd_desc = f"PyUcc Differential Analysis - Baseline: {baseline_id}"
|
|
|
|
UCCReportGenerator.generate_differ_report(
|
|
diff_results=pairs,
|
|
output_path=output_path,
|
|
baseline_id=baseline_id,
|
|
command_description=cmd_desc,
|
|
)
|
|
|
|
def _save_metrics_report(self, output_path: Path):
|
|
"""Save metrics results as UCC-style report (Cyclomatic Complexity)."""
|
|
from ..utils.ucc_report_generator import UCCReportGenerator
|
|
|
|
# Collect results from cache
|
|
results_cache = getattr(self.app, "_results_cache", None)
|
|
if not results_cache:
|
|
raise ValueError("No metrics results available")
|
|
|
|
# Convert cache to list if it's a dict
|
|
if isinstance(results_cache, dict):
|
|
results = list(results_cache.values())
|
|
elif isinstance(results_cache, list):
|
|
results = results_cache
|
|
else:
|
|
raise ValueError("Invalid results cache format")
|
|
|
|
# Get project root
|
|
project_root = getattr(self.app, "_last_scan_root", "")
|
|
|
|
# Generate command description
|
|
cmd_desc = f"PyUcc Cyclomatic Complexity Analysis - Project: {project_root}"
|
|
|
|
UCCReportGenerator.generate_metrics_report(
|
|
results=results,
|
|
output_path=output_path,
|
|
command_description=cmd_desc,
|
|
base_path=project_root,
|
|
)
|
|
|
|
def _save_scan_report(self, output_path: Path):
|
|
"""Save scan results as UCC-style report."""
|
|
# Scan is just a file list, can reuse counting format
|
|
self._save_counting_report(output_path)
|
|
|
|
def _save_duplicates_report(self, output_path: Path):
|
|
"""Save duplicates results in UCC-style report using UCCReportGenerator."""
|
|
from ..utils.ucc_report_generator import UCCReportGenerator
|
|
|
|
# Expect cached results in app._results_cache as list of dicts
|
|
results_cache = getattr(self.app, "_results_cache", None)
|
|
if not results_cache:
|
|
raise ValueError("No duplicates results available")
|
|
|
|
# Generate a descriptive command text
|
|
project_root = getattr(self.app, "_last_scan_root", "")
|
|
cmd_desc = f"PyUcc Duplicate Analysis - Project: {project_root}"
|
|
|
|
UCCReportGenerator.generate_duplicates_report(
|
|
duplicates=results_cache,
|
|
output_path=output_path,
|
|
command_description=cmd_desc,
|
|
base_path=project_root,
|
|
params=getattr(self.app, "_current_results", {}).get("params"),
|
|
)
|