sistemata la funzione di normalizzazione con anche le librerie custom importate in maniera assoluta, rivisitazione della gui
This commit is contained in:
parent
6ce186efbb
commit
a91820848d
@ -33,6 +33,10 @@ MODULE_NAME_TO_PACKAGE_NAME_MAP: Dict[str, str] = {
|
|||||||
# Modules that are often incorrectly identified as external.
|
# Modules that are often incorrectly identified as external.
|
||||||
FALSE_POSITIVE_EXTERNAL_MODULES: Set[str] = {"mpl_toolkits"}
|
FALSE_POSITIVE_EXTERNAL_MODULES: Set[str] = {"mpl_toolkits"}
|
||||||
|
|
||||||
|
# Modules that are known to be local to the project's ecosystem but are
|
||||||
|
# not in the standard scan path (e.g., sibling directories).
|
||||||
|
PROJECT_SPECIFIC_LOCAL_MODULES: Set[str] = {"geoelevation"}
|
||||||
|
|
||||||
|
|
||||||
class ImportExtractor(ast.NodeVisitor):
|
class ImportExtractor(ast.NodeVisitor):
|
||||||
"""
|
"""
|
||||||
@ -163,6 +167,13 @@ def find_project_modules_and_dependencies(
|
|||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# Ignore known project-specific local modules that are not on PyPI
|
||||||
|
if imp_module in PROJECT_SPECIFIC_LOCAL_MODULES:
|
||||||
|
logger.info(
|
||||||
|
f"Skipping known project-specific local module: '{imp_module}'"
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
# Ignore known false positives
|
# Ignore known false positives
|
||||||
if imp_module in FALSE_POSITIVE_EXTERNAL_MODULES:
|
if imp_module in FALSE_POSITIVE_EXTERNAL_MODULES:
|
||||||
logger.info(f"Skipping known false positive: '{imp_module}'")
|
logger.info(f"Skipping known false positive: '{imp_module}'")
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
# dependency_analyzer/core/project_normalizer.py
|
# dependency_analyzer/core/project_normalizer.py
|
||||||
"""
|
"""
|
||||||
Handles the logic for normalizing a Python project structure.
|
Handles the logic for normalizing a Python project structure.
|
||||||
|
|
||||||
This includes creating virtual environments, generating configuration files,
|
This includes creating virtual environments, generating configuration files,
|
||||||
and installing dependencies in an isolated manner.
|
and installing dependencies in an isolated manner.
|
||||||
"""
|
"""
|
||||||
@ -9,201 +8,140 @@ import logging
|
|||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict, List, Tuple
|
from typing import Callable, Dict, List, Optional, Tuple
|
||||||
|
|
||||||
# Import functions from other core modules that we will reuse
|
|
||||||
from . import analyzer, package_manager
|
from . import analyzer, package_manager
|
||||||
|
|
||||||
# --- Logger Configuration ---
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def check_project_status(project_path: Path) -> Dict[str, bool]:
|
def check_project_status(project_path: Path) -> Dict[str, bool]:
|
||||||
"""
|
|
||||||
Analyzes a project directory to check for key normalization indicators.
|
|
||||||
"""
|
|
||||||
if not project_path or not project_path.is_dir():
|
if not project_path or not project_path.is_dir():
|
||||||
return {'has_venv': False, 'has_requirements': False, 'has_pyproject': False}
|
return {'has_venv': False, 'has_requirements': False, 'has_pyproject': False}
|
||||||
|
status = {'has_venv': (project_path / '.venv').is_dir(),
|
||||||
status = {
|
'has_requirements': (project_path / 'requirements.txt').is_file(),
|
||||||
'has_venv': (project_path / '.venv').is_dir(),
|
'has_pyproject': (project_path / 'pyproject.toml').is_file()}
|
||||||
'has_requirements': (project_path / 'requirements.txt').is_file(),
|
|
||||||
'has_pyproject': (project_path / 'pyproject.toml').is_file(),
|
|
||||||
}
|
|
||||||
logger.info(f"Project status check for '{project_path.name}': {status}")
|
logger.info(f"Project status check for '{project_path.name}': {status}")
|
||||||
return status
|
return status
|
||||||
|
|
||||||
|
|
||||||
def _run_command(
|
def _run_command(command: List[str], cwd: Path, description: str) -> Tuple[bool, str]:
|
||||||
command: List[str], cwd: Path, description: str
|
|
||||||
) -> Tuple[bool, str]:
|
|
||||||
"""
|
|
||||||
A helper to run a shell command and return its status and output.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
command: The command to run as a list of strings.
|
|
||||||
cwd: The working directory for the command.
|
|
||||||
description: A brief description of the action for logging.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
A tuple (success_flag, message).
|
|
||||||
"""
|
|
||||||
logger.info(f"Running task: {description}...")
|
logger.info(f"Running task: {description}...")
|
||||||
logger.debug(f"Executing command: {' '.join(command)}")
|
logger.debug(f"Executing command: {' '.join(command)}")
|
||||||
try:
|
try:
|
||||||
proc = subprocess.run(
|
proc = subprocess.run(
|
||||||
command,
|
command, cwd=cwd, capture_output=True, text=True, encoding="utf-8",
|
||||||
cwd=cwd,
|
errors="ignore", check=True, timeout=600,
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
encoding="utf-8",
|
|
||||||
errors="ignore",
|
|
||||||
check=True, # Raise an exception for non-zero exit codes
|
|
||||||
timeout=600, # 10-minute timeout
|
|
||||||
creationflags=(subprocess.CREATE_NO_WINDOW if sys.platform == "win32" else 0),
|
creationflags=(subprocess.CREATE_NO_WINDOW if sys.platform == "win32" else 0),
|
||||||
)
|
)
|
||||||
message = f"Task '{description}' completed successfully."
|
output = proc.stdout.strip() or f"Task '{description}' completed successfully."
|
||||||
if proc.stdout and proc.stdout.strip():
|
return True, output
|
||||||
logger.debug(f"Output from '{description}':\n{proc.stdout.strip()}")
|
|
||||||
return True, message
|
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
msg = f"Error: Command '{command[0]}' not found. Is Python correctly installed and in PATH?"
|
msg = f"Error: Command '{command[0]}' not found. Is Python in PATH?"
|
||||||
logger.error(msg)
|
|
||||||
return False, msg
|
return False, msg
|
||||||
except subprocess.TimeoutExpired:
|
except subprocess.TimeoutExpired:
|
||||||
msg = f"Error: Task '{description}' timed out after 10 minutes."
|
msg = f"Error: Task '{description}' timed out."
|
||||||
logger.error(msg)
|
|
||||||
return False, msg
|
return False, msg
|
||||||
except subprocess.CalledProcessError as e:
|
except subprocess.CalledProcessError as e:
|
||||||
error_details = e.stderr.strip() or e.stdout.strip()
|
msg = f"Error during '{description}':\n{e.stderr.strip() or e.stdout.strip()}"
|
||||||
msg = f"Error during '{description}':\n{error_details}"
|
|
||||||
logger.error(msg)
|
|
||||||
return False, msg
|
return False, msg
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
msg = f"An unexpected error occurred during '{description}': {e}"
|
msg = f"An unexpected error occurred during '{description}': {e}"
|
||||||
logger.exception(msg)
|
|
||||||
return False, msg
|
return False, msg
|
||||||
|
|
||||||
|
|
||||||
def _get_venv_python_path(project_path: Path) -> Path:
|
def _get_venv_python_path(project_path: Path) -> Path:
|
||||||
"""
|
return project_path / ".venv" / ("Scripts" if sys.platform == "win32" else "bin") / "python.exe"
|
||||||
Determines the path to the Python executable inside the .venv directory.
|
|
||||||
"""
|
|
||||||
if sys.platform == "win32":
|
|
||||||
return project_path / ".venv" / "Scripts" / "python.exe"
|
|
||||||
else:
|
|
||||||
return project_path / ".venv" / "bin" / "python"
|
|
||||||
|
|
||||||
|
|
||||||
def normalize_project(project_path: Path) -> Dict[str, Tuple[bool, str]]:
|
def normalize_project(
|
||||||
|
project_path: Path,
|
||||||
|
progress_callback: Optional[Callable[[str, bool, str], None]] = None
|
||||||
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Executes the full project normalization workflow.
|
Executes the full project normalization workflow, reporting progress via a callback.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
project_path: The root path of the project to normalize.
|
project_path: The root path of the project to normalize.
|
||||||
|
progress_callback: A function to call with updates. It should accept
|
||||||
Returns:
|
(step_key, success_flag, message).
|
||||||
A dictionary containing the success status and a message for each step.
|
|
||||||
"""
|
"""
|
||||||
results: Dict[str, Tuple[bool, str]] = {}
|
def report_progress(step_key: str, success: bool, message: str):
|
||||||
|
if progress_callback:
|
||||||
|
progress_callback(step_key, success, message)
|
||||||
|
# Log to file/console as well
|
||||||
|
log_level = logging.INFO if success else logging.ERROR
|
||||||
|
logging.log(log_level, f"Step '{step_key}': {message}")
|
||||||
|
|
||||||
# --- Step 1: Create Virtual Environment ---
|
# --- Step 1: Create Virtual Environment ---
|
||||||
venv_path = project_path / ".venv"
|
venv_path = project_path / ".venv"
|
||||||
if venv_path.exists():
|
if venv_path.exists():
|
||||||
logger.info("Virtual environment '.venv' already exists. Skipping creation.")
|
report_progress("venv_creation", True, "Virtual environment '.venv' already exists.")
|
||||||
results["venv_creation"] = (True, "Virtual environment '.venv' already exists.")
|
|
||||||
else:
|
else:
|
||||||
success, message = _run_command(
|
success, message = _run_command(
|
||||||
[sys.executable, "-m", "venv", ".venv"],
|
[sys.executable, "-m", "venv", ".venv"], cwd=project_path, description="Create virtual environment"
|
||||||
cwd=project_path,
|
|
||||||
description="Create virtual environment",
|
|
||||||
)
|
)
|
||||||
results["venv_creation"] = (success, message)
|
report_progress("venv_creation", success, message)
|
||||||
if not success:
|
if not success: return
|
||||||
return results # Stop if venv creation fails
|
|
||||||
|
|
||||||
# --- Step 2: Analyze Dependencies ---
|
# --- Step 2: Analyze Dependencies ---
|
||||||
logger.info("Analyzing project dependencies using AST...")
|
|
||||||
try:
|
try:
|
||||||
# Determine scan path (same logic as in the GUI)
|
|
||||||
scan_path = project_path
|
scan_path = project_path
|
||||||
potential_sub_pkg = project_path / project_path.name.lower()
|
potential_sub_pkg = project_path / project_path.name.lower()
|
||||||
if potential_sub_pkg.is_dir():
|
if potential_sub_pkg.is_dir(): scan_path = potential_sub_pkg
|
||||||
scan_path = potential_sub_pkg
|
|
||||||
|
|
||||||
std_lib_info, external_info = analyzer.find_project_modules_and_dependencies(
|
std_lib_info, external_info = analyzer.find_project_modules_and_dependencies(
|
||||||
repo_path=project_path, scan_path=scan_path
|
repo_path=project_path, scan_path=scan_path
|
||||||
)
|
)
|
||||||
results["analysis"] = (True, f"Analysis found {len(external_info)} external dependencies.")
|
report_progress("analysis", True, f"Analysis found {len(external_info)} external dependencies.")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
msg = f"Failed to analyze project dependencies: {e}"
|
report_progress("analysis", False, f"Failed to analyze project dependencies: {e}")
|
||||||
logger.exception(msg)
|
return
|
||||||
results["analysis"] = (False, msg)
|
|
||||||
return results
|
|
||||||
|
|
||||||
# --- Step 3: Generate requirements.txt ---
|
# --- Step 3: Generate requirements.txt ---
|
||||||
logger.info("Generating requirements.txt file...")
|
|
||||||
try:
|
try:
|
||||||
req_file = package_manager.generate_requirements_file(
|
req_file = package_manager.generate_requirements_file(
|
||||||
project_path, external_info, std_lib_info
|
project_path, external_info, std_lib_info
|
||||||
)
|
)
|
||||||
results["requirements_generation"] = (True, f"Successfully created '{req_file.name}'.")
|
report_progress("requirements_generation", True, f"Successfully created '{req_file.name}'.")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
msg = f"Failed to generate requirements.txt: {e}"
|
report_progress("requirements_generation", False, f"Failed to generate requirements.txt: {e}")
|
||||||
logger.exception(msg)
|
return
|
||||||
results["requirements_generation"] = (False, msg)
|
|
||||||
return results
|
|
||||||
|
|
||||||
# --- Step 4: Install Dependencies into .venv ---
|
# --- Step 4: Install Dependencies into .venv ---
|
||||||
if not external_info:
|
if not external_info:
|
||||||
logger.info("No external dependencies to install.")
|
report_progress("dependency_installation", True, "No external dependencies found to install.")
|
||||||
results["dependency_installation"] = (True, "No external dependencies found to install.")
|
|
||||||
else:
|
else:
|
||||||
venv_python = _get_venv_python_path(project_path)
|
venv_python = _get_venv_python_path(project_path)
|
||||||
success, message = _run_command(
|
# We now install one by one for better feedback
|
||||||
[str(venv_python), "-m", "pip", "install", "-r", "requirements.txt"],
|
all_ok = True
|
||||||
cwd=project_path,
|
error_messages = []
|
||||||
description="Install dependencies into .venv",
|
for package_name in sorted(external_info.keys()):
|
||||||
)
|
report_progress("dependency_installation", True, f"Installing '{package_name}'...")
|
||||||
results["dependency_installation"] = (success, message)
|
success, message = _run_command(
|
||||||
if not success:
|
[str(venv_python), "-m", "pip", "install", package_name],
|
||||||
return results # Stop if installation fails
|
cwd=project_path, description=f"Install {package_name}"
|
||||||
|
)
|
||||||
|
if not success:
|
||||||
|
all_ok = False
|
||||||
|
error_messages.append(message)
|
||||||
|
|
||||||
|
if all_ok:
|
||||||
|
report_progress("dependency_installation", True, "All dependencies installed successfully.")
|
||||||
|
else:
|
||||||
|
final_error = "Failed to install one or more dependencies:\n" + "\n\n".join(error_messages)
|
||||||
|
report_progress("dependency_installation", False, final_error)
|
||||||
|
# We don't return here, to allow pyproject.toml creation anyway
|
||||||
|
|
||||||
# --- Step 5: Create pyproject.toml ---
|
# --- Step 5: Create pyproject.toml ---
|
||||||
pyproject_path = project_path / "pyproject.toml"
|
pyproject_path = project_path / "pyproject.toml"
|
||||||
if pyproject_path.exists():
|
if pyproject_path.exists():
|
||||||
logger.info("'pyproject.toml' already exists. Skipping creation.")
|
report_progress("pyproject_creation", True, "'pyproject.toml' already exists.")
|
||||||
results["pyproject_creation"] = (True, "'pyproject.toml' already exists.")
|
|
||||||
else:
|
else:
|
||||||
logger.info("Creating a minimal pyproject.toml file...")
|
|
||||||
project_name = project_path.name.lower().replace("_", "-")
|
project_name = project_path.name.lower().replace("_", "-")
|
||||||
pyproject_content = f"""[build-system]
|
pyproject_content = f"""[build-system]\nrequires = ["setuptools>=61.0"]\nbuild-backend = "setuptools.build_meta"\n\n[project]\nname = "{project_name}"\nversion = "0.1.0"\n"""
|
||||||
requires = ["setuptools>=61.0"]
|
|
||||||
build-backend = "setuptools.build_meta"
|
|
||||||
|
|
||||||
[project]
|
|
||||||
name = "{project_name}"
|
|
||||||
version = "0.1.0"
|
|
||||||
description = "A short description of the project."
|
|
||||||
readme = "README.md"
|
|
||||||
requires-python = ">=3.8"
|
|
||||||
# Add other classifiers or dependencies here if needed
|
|
||||||
# dependencies = [
|
|
||||||
# "dependency1",
|
|
||||||
# "dependency2",
|
|
||||||
# ]
|
|
||||||
|
|
||||||
[project.urls]
|
|
||||||
Homepage = "https://example.com"
|
|
||||||
"""
|
|
||||||
try:
|
try:
|
||||||
pyproject_path.write_text(pyproject_content, encoding="utf-8")
|
pyproject_path.write_text(pyproject_content, encoding="utf-8")
|
||||||
msg = "Successfully created a minimal 'pyproject.toml'."
|
report_progress("pyproject_creation", True, "Successfully created a minimal 'pyproject.toml'.")
|
||||||
logger.info(msg)
|
|
||||||
results["pyproject_creation"] = (True, msg)
|
|
||||||
except IOError as e:
|
except IOError as e:
|
||||||
msg = f"Failed to write 'pyproject.toml': {e}"
|
report_progress("pyproject_creation", False, f"Failed to write 'pyproject.toml': {e}")
|
||||||
logger.error(msg)
|
|
||||||
results["pyproject_creation"] = (False, msg)
|
|
||||||
|
|
||||||
return results
|
|
||||||
@ -344,17 +344,82 @@ class DependencyAnalyzerApp(tk.Frame):
|
|||||||
logging.log(logging.INFO if success else logging.ERROR, f"{task_name}: {message}")
|
logging.log(logging.INFO if success else logging.ERROR, f"{task_name}: {message}")
|
||||||
if not success: messagebox.showerror(f"{task_name} Failed", message)
|
if not success: messagebox.showerror(f"{task_name} Failed", message)
|
||||||
|
|
||||||
|
# --- Methods for "Project Normalizer" Tab ---
|
||||||
# --- Methods for "Project Normalizer" Tab ---
|
# --- Methods for "Project Normalizer" Tab ---
|
||||||
def _normalize_project_threaded(self) -> None:
|
def _normalize_project_threaded(self) -> None:
|
||||||
"""Starts the project normalization process in a background thread."""
|
"""Starts the project normalization process in a background thread."""
|
||||||
if not self.selected_repository_path:
|
if not self.selected_repository_path:
|
||||||
messagebox.showwarning("Warning", "Please select a project repository first.")
|
messagebox.showwarning("Warning", "Please select a project repository first.")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Clear previous results before starting
|
||||||
|
self.normalizer_steps_list.delete(0, tk.END)
|
||||||
|
self.normalizer_details_text.config(state=tk.NORMAL)
|
||||||
|
self.normalizer_details_text.delete('1.0', tk.END)
|
||||||
|
self.normalizer_details_text.config(state=tk.DISABLED)
|
||||||
|
self.normalizer_step_messages.clear()
|
||||||
|
|
||||||
|
# The backend function no longer returns a value.
|
||||||
|
# It reports progress via the callback.
|
||||||
self._run_long_task_threaded(
|
self._run_long_task_threaded(
|
||||||
project_normalizer.normalize_project, self.selected_repository_path,
|
project_normalizer.normalize_project,
|
||||||
callback_success=self._normalize_project_callback,
|
self.selected_repository_path,
|
||||||
|
self._update_normalizer_progress # Pass the progress callback function
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _update_normalizer_progress(self, step_key: str, success: bool, message: str) -> None:
|
||||||
|
"""
|
||||||
|
Thread-safe method to update the normalizer UI with progress.
|
||||||
|
This function is called by the background thread.
|
||||||
|
"""
|
||||||
|
# Ensure GUI updates happen in the main thread
|
||||||
|
self.master.after(0, self._populate_normalizer_step, step_key, success, message)
|
||||||
|
|
||||||
|
def _populate_normalizer_step(self, step_key: str, success: bool, message: str):
|
||||||
|
"""The actual GUI update logic."""
|
||||||
|
step_map = {
|
||||||
|
"venv_creation": "1. Create Virtual Environment", "analysis": "2. Analyze Dependencies",
|
||||||
|
"requirements_generation": "3. Generate requirements.txt", "dependency_installation": "4. Install Dependencies",
|
||||||
|
"pyproject_creation": "5. Create pyproject.toml",
|
||||||
|
}
|
||||||
|
|
||||||
|
# This logic handles both new steps and updates to existing steps (like installation)
|
||||||
|
step_name = step_map.get(step_key, step_key.replace("_", " ").title())
|
||||||
|
icon = "✓" if success else "✗"
|
||||||
|
|
||||||
|
# Find if the step is already in the list
|
||||||
|
list_items = self.normalizer_steps_list.get(0, tk.END)
|
||||||
|
try:
|
||||||
|
# Find the index of the main step (e.g., "4. Install Dependencies")
|
||||||
|
main_step_name_prefix = step_name.split(" ")[0]
|
||||||
|
index = next(i for i, item in enumerate(list_items) if item.strip().startswith(main_step_name_prefix))
|
||||||
|
|
||||||
|
# Update the main step's final status
|
||||||
|
self.normalizer_steps_list.delete(index)
|
||||||
|
self.normalizer_steps_list.insert(index, f" {icon} {step_name}")
|
||||||
|
self.normalizer_steps_list.itemconfig(index, {'fg': 'green' if success else 'red'})
|
||||||
|
self.normalizer_step_messages[index] = message
|
||||||
|
|
||||||
|
except StopIteration:
|
||||||
|
# If step is not found, it's a new main step
|
||||||
|
index = self.normalizer_steps_list.size()
|
||||||
|
self.normalizer_steps_list.insert(tk.END, f" {icon} {step_name}")
|
||||||
|
self.normalizer_steps_list.itemconfig(index, {'fg': 'green' if success else 'red'})
|
||||||
|
self.normalizer_step_messages.append(message)
|
||||||
|
|
||||||
|
# Handle sub-step messages (like individual package installations)
|
||||||
|
if "Installing '" in message and success:
|
||||||
|
self.normalizer_steps_list.insert(tk.END, f" - {message.split(' ')[1]}")
|
||||||
|
self.normalizer_steps_list.itemconfig(tk.END, {'fg': 'grey'})
|
||||||
|
self.normalizer_step_messages.append(message) # Add message for sub-step too
|
||||||
|
|
||||||
|
# Auto-select the last updated/added item
|
||||||
|
last_index = self.normalizer_steps_list.size() - 1
|
||||||
|
self.normalizer_steps_list.selection_clear(0, tk.END)
|
||||||
|
self.normalizer_steps_list.selection_set(last_index)
|
||||||
|
self.normalizer_steps_list.see(last_index) # Scroll to the new item
|
||||||
|
self._on_normalizer_step_select(None) # Show details
|
||||||
|
|
||||||
def _normalize_project_callback(self, results: Dict[str, Tuple[bool, str]]) -> None:
|
def _normalize_project_callback(self, results: Dict[str, Tuple[bool, str]]) -> None:
|
||||||
"""Handles the results of the normalization, populating the new UI."""
|
"""Handles the results of the normalization, populating the new UI."""
|
||||||
logging.info("Normalization process finished. Displaying results.")
|
logging.info("Normalization process finished. Displaying results.")
|
||||||
@ -383,9 +448,9 @@ class DependencyAnalyzerApp(tk.Frame):
|
|||||||
|
|
||||||
def _on_normalizer_step_select(self, event: Optional[tk.Event]) -> None:
|
def _on_normalizer_step_select(self, event: Optional[tk.Event]) -> None:
|
||||||
"""Displays the details for the currently selected normalization step."""
|
"""Displays the details for the currently selected normalization step."""
|
||||||
|
# This function remains largely the same
|
||||||
selected_indices = self.normalizer_steps_list.curselection()
|
selected_indices = self.normalizer_steps_list.curselection()
|
||||||
if not selected_indices:
|
if not selected_indices: return
|
||||||
return
|
|
||||||
|
|
||||||
selected_index = selected_indices[0]
|
selected_index = selected_indices[0]
|
||||||
try:
|
try:
|
||||||
@ -395,7 +460,6 @@ class DependencyAnalyzerApp(tk.Frame):
|
|||||||
self.normalizer_details_text.insert('1.0', message)
|
self.normalizer_details_text.insert('1.0', message)
|
||||||
self.normalizer_details_text.config(state=tk.DISABLED)
|
self.normalizer_details_text.config(state=tk.DISABLED)
|
||||||
except IndexError:
|
except IndexError:
|
||||||
# This should not happen, but it's a safe fallback
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user