SXXXXXXX_ProjectInitializer/projectinitializer/core/project_creator.py
2025-05-08 10:47:04 +02:00

196 lines
9.8 KiB
Python

# ProjectInitializerTool/project_initializer/core/project_creator.py
import os
import shutil
import subprocess
from pathlib import Path
# Importa i settings per accedere ai template e al percorso dell'icona
from projectinitializer.config import settings # type: ignore
# Tkinter messagebox è specifico della GUI.
# Per il core, dovremmo restituire successo/fallimento e messaggi,
# lasciando che sia il chiamante (GUI o CLI) a visualizzarli.
# Tuttavia, per mantenere una certa familiarità con il codice precedente
# e dato che l'utente potrebbe voler vedere feedback immediato anche se chiamato
# da una GUI che poi mostra un suo popup, lo lascio per ora,
# ma idealmente questo modulo non dovrebbe dipendere da tkinter.
# Una soluzione migliore sarebbe un sistema di logging o callback.
# Per semplicità, al momento, se la GUI è in uso, i messaggi appariranno
# anche dalla console se la GUI non sopprime stdout.
# Se la GUI non è in uso (CLI), print() è l'output atteso.
# Rimuoviamo la dipendenza diretta da messagebox per il core.
# Il chiamante (GUI/CLI) gestirà la notifica utente.
class ProjectCreationError(Exception):
"""Custom exception for errors during project creation."""
pass
def create_project(root_directory_str: str, project_name_original: str) -> str:
"""
Creates the project directory structure and initial files.
Args:
root_directory_str (str): The root directory where the project folder will be created.
project_name_original (str): The name of the project (can have mixed case).
Returns:
str: A success message.
Raises:
ProjectCreationError: If any error occurs during project creation.
"""
root_directory = Path(root_directory_str)
if not root_directory.is_dir():
raise ProjectCreationError(f"Root directory '{root_directory}' does not exist or is not a directory.")
project_root_path = root_directory / project_name_original
project_name_lower = project_name_original.lower().replace("-", "_").replace(" ", "_")
# Sanitize project_name_original for use as a filename (e.g., for the icon)
# Rimuovi spazi e caratteri problematici, ma mantieni il case originale se possibile per il nome file .ico
# Per il file .spec e l'icona, è meglio usare un nome file "pulito".
# Usiamo project_name_original ma rimuovendo solo gli spazi, o potremmo usare project_name_lower
# per coerenza con il file .spec. Scegliamo project_name_original ma sanitizzato.
# Un nome file come "Mio Progetto.ico" è valido su Windows, ma meno portabile.
# "MioProgetto.ico" è meglio.
project_icon_filename_base = "".join(c if c.isalnum() or c in ['_', '-'] else '' for c in project_name_original)
if not project_icon_filename_base: # Se il nome originale era tipo "!@#$%"
project_icon_filename_base = project_name_lower # Fallback a nome lowercase
project_icon_filename = f"{project_icon_filename_base}.ico"
if not project_name_lower.isidentifier():
raise ProjectCreationError(
f"The sanitized project name '{project_name_lower}' (derived from '{project_name_original}') "
"is not a valid Python identifier. Please use letters, numbers, and underscores, "
"not starting with a number."
)
if project_root_path.exists():
raise ProjectCreationError(f"Project directory '{project_root_path}' already exists.")
try:
# 1) Create main project folder
project_root_path.mkdir(parents=True, exist_ok=False)
print(f"Info: Created directory: {project_root_path}")
# 2) Create source code subfolder (package name)
src_package_path = project_root_path / project_name_lower
src_package_path.mkdir()
print(f"Info: Created directory: {src_package_path}")
# 3) Create source subdirectories and __init__.py files for the new project
core_subpath = src_package_path / "core"
core_subpath.mkdir()
print(f"Info: Created directory: {core_subpath}")
(core_subpath / "__init__.py").touch()
(core_subpath / "core.py").touch()
gui_subpath = src_package_path / "gui"
gui_subpath.mkdir()
print(f"Info: Created directory: {gui_subpath}")
(gui_subpath / "__init__.py").touch()
(gui_subpath / "gui.py").touch()
(src_package_path / "__init__.py").touch()
main_py_content = settings.get_main_py_template(project_name_original, project_name_lower)
with open(src_package_path / "__main__.py", "w", encoding="utf-8") as f:
f.write(main_py_content)
print(f"Info: Created file: {src_package_path / '__main__.py'}")
# 4) Create 'doc' folder and manual files
doc_path = project_root_path / "doc"
doc_path.mkdir()
print(f"Info: Created directory: {doc_path}")
english_manual_content = settings.get_english_manual_template(project_name_original)
with open(doc_path / "English-manual.md", "w", encoding="utf-8") as f:
f.write(english_manual_content)
print(f"Info: Created file: {doc_path / 'English-manual.md'}")
italian_manual_content = settings.get_italian_manual_template(project_name_original)
with open(doc_path / "Italian-manual.md", "w", encoding="utf-8") as f:
f.write(italian_manual_content)
print(f"Info: Created file: {doc_path / 'Italian-manual.md'}")
# 5) Create README.md for the new project
readme_content = settings.get_readme_template(project_name_original)
with open(project_root_path / "README.md", "w", encoding="utf-8") as f:
f.write(readme_content)
print(f"Info: Created file: {project_root_path / 'README.md'}")
# --- MODIFICA QUI per usare project_icon_filename ---
# 6) Create .spec file for PyInstaller for the new project
# Passa project_icon_filename al template del file .spec
spec_content = settings.get_spec_file_template(project_name_original, project_name_lower, project_icon_filename)
with open(project_root_path / f"{project_name_lower}.spec", "w", encoding="utf-8") as f:
f.write(spec_content)
print(f"Info: Created file: {project_root_path / f'{project_name_lower}.spec'}")
# 7) Copy default .ico file to the new project's root with the new name
# Usa project_icon_filename come nome del file di destinazione
destination_icon_path = project_root_path / project_icon_filename
if settings.DEFAULT_ICON_PATH.exists():
shutil.copy2(settings.DEFAULT_ICON_PATH, destination_icon_path)
print(f"Info: Copied icon: {destination_icon_path} from {settings.DEFAULT_ICON_PATH}")
else:
destination_icon_path.touch()
print(f"Warning: Default icon '{settings.DEFAULT_ICON_PATH}' not found. "
f"Created empty placeholder: {destination_icon_path}")
# --- FINE MODIFICA ICONA ---
# 8) Create .gitignore for the new project
gitignore_content = settings.get_gitignore_template()
with open(project_root_path / ".gitignore", "w", encoding="utf-8") as f:
f.write(gitignore_content)
print(f"Info: Created file: {project_root_path / '.gitignore'}")
# 9) Initialize Git repository
try:
print(f"Info: Initializing Git repository in {project_root_path}...")
subprocess.run(["git", "init"], cwd=project_root_path, check=True, capture_output=True, text=True)
print(f"Info: Git repository initialized.")
subprocess.run(["git", "add", "."], cwd=project_root_path, check=True, capture_output=True, text=True)
print(f"Info: All files added to Git staging area.")
subprocess.run(
["git", "commit", "-m", "Initial project structure created by ProjectInitializerTool"],
cwd=project_root_path, check=True, capture_output=True, text=True
)
print("Info: Initial commit made.")
except FileNotFoundError:
print("Warning: Git command not found. Please install Git to initialize a repository automatically.")
print(" The project structure has been created, but a Git repository was not initialized.")
except subprocess.CalledProcessError as e:
error_message = f"Error during Git operation: {e.stderr}"
if e.stdout: error_message += f"\nGit stdout: {e.stdout}"
print(f"Warning: {error_message}")
print(" The project structure has been created, but there was an issue with Git initialization.")
success_message = (
f"Project '{project_name_original}' created successfully in '{project_root_path}'.\n"
f"To run your new project (example): python -m {project_name_lower}"
)
print(f"\n{success_message}")
return success_message
except OSError as e:
error_msg = f"OS error during project creation: {e}"
print(f"Error: {error_msg}")
if project_root_path.exists() and project_root_path.is_dir():
try:
shutil.rmtree(project_root_path)
print(f"Info: Cleaned up partially created project at {project_root_path}")
except OSError as cleanup_e:
print(f"Error: Error during cleanup of '{project_root_path}': {cleanup_e}")
raise ProjectCreationError(error_msg) from e
except Exception as e:
error_msg = f"An unexpected error occurred: {e}"
print(f"Error: {error_msg}")
if project_root_path.exists() and project_root_path.is_dir():
try:
shutil.rmtree(project_root_path)
print(f"Info: Cleaned up partially created project at {project_root_path} due to unexpected error.")
except OSError as cleanup_e:
print(f"Error: Error during cleanup of '{project_root_path}': {cleanup_e}")
raise ProjectCreationError(error_msg) from e