# 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