196 lines
9.8 KiB
Python
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 |