Chore: Stop tracking files based on .gitignore update.
Untracked files matching the following rules: - Rule "*.pyc": 10 files
This commit is contained in:
commit
dc0d4b18d1
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
.svn
|
||||
*.pyc
|
||||
_dist/
|
||||
_build/
|
||||
15
.vscode/launch.json
vendored
Normal file
15
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
|
||||
{
|
||||
"name": "Python Debugger: Module",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "projectinitializer"
|
||||
}
|
||||
]
|
||||
}
|
||||
74
README.md
Normal file
74
README.md
Normal file
@ -0,0 +1,74 @@
|
||||
# Project Initializer Tool
|
||||
|
||||
A Python application to initialize standard project structures for new Python projects,
|
||||
suitable for use with Git.
|
||||
|
||||
## Features
|
||||
|
||||
- Creates a defined directory structure for source code (`project_name/core`, `project_name/gui`).
|
||||
- Generates basic files: `__init__.py`, `__main__.py`, `core.py`, `gui.py`.
|
||||
- Creates a `doc/` directory with template `English-manual.md` and `Italian-manual.md`.
|
||||
- Generates a basic `README.md` for the new project.
|
||||
- Generates a `.spec` file template for PyInstaller.
|
||||
- Copies a default `.ico` file.
|
||||
- Creates a standard `.gitignore` file.
|
||||
- Initializes a local Git repository with an initial commit.
|
||||
- Supports both Command Line Interface (CLI) and Graphical User Interface (GUI) via Tkinter.
|
||||
- Remembers the last used root directory for convenience in GUI mode.
|
||||
|
||||
## Structure of this Tool (`ProjectInitializerTool`)
|
||||
|
||||
This tool itself is organized as follows:
|
||||
|
||||
- `project_initializer/`: Main Python package for the tool.
|
||||
- `__main__.py`: Entry point (`python -m project_initializer`).
|
||||
- `core/`: Core logic for project creation.
|
||||
- `project_creator.py`: Handles the actual file and directory generation.
|
||||
- `config/`: Configuration and file templates.
|
||||
- `settings.py`: Manages app config (like last used directory) and stores file templates.
|
||||
- `gui/`: Tkinter GUI components.
|
||||
- `app_window.py`: Defines the main application window.
|
||||
- `cli/`: Command-line interface components.
|
||||
- `interface.py`: Handles argument parsing and CLI interaction.
|
||||
- `assets/`: Static assets for the tool.
|
||||
- `default_icon.ico`: The default icon copied to new projects.
|
||||
|
||||
## Usage
|
||||
|
||||
### Prerequisites
|
||||
- Python 3.7+ (ideally 3.8+ for `pathlib` features and type hinting usage)
|
||||
- Git (optional, for repository initialization in new projects)
|
||||
|
||||
### Running the Tool
|
||||
|
||||
1. Navigate to the `ProjectInitializerTool` directory (the one containing this README).
|
||||
2. Execute the tool using the Python module execution:
|
||||
|
||||
**GUI Mode (Recommended for interactive use):**
|
||||
```bash
|
||||
python -m project_initializer
|
||||
```
|
||||
This will launch the graphical interface.
|
||||
|
||||
**CLI Mode (For scripting or command-line preference):**
|
||||
```bash
|
||||
python -m project_initializer <root_directory> <ProjectName>
|
||||
```
|
||||
- `<root_directory>`: The directory where the new project folder (named `<ProjectName>`) will be created.
|
||||
- `<ProjectName>`: The desired name for your new project (e.g., "MyNewApp").
|
||||
|
||||
Example:
|
||||
```bash
|
||||
python -m project_initializer /home/user/dev/projects MyCoolProject
|
||||
```
|
||||
|
||||
This will create a folder `/home/user/dev/projects/MyCoolProject` with the standard structure.
|
||||
|
||||
### Configuration
|
||||
The tool saves the last used root directory in a configuration file (`.project_initializer_config.json`) in your user's home directory.
|
||||
|
||||
## Development (of this `ProjectInitializerTool`)
|
||||
|
||||
- Ensure you have Python installed.
|
||||
- The `default_icon.ico` file should be present in `project_initializer/assets/`.
|
||||
- To run during development: `python -m project_initializer` from the `ProjectInitializerTool` directory.
|
||||
0
projectinitializer/__init__.py
Normal file
0
projectinitializer/__init__.py
Normal file
124
projectinitializer/__main__.py
Normal file
124
projectinitializer/__main__.py
Normal file
@ -0,0 +1,124 @@
|
||||
# ProjectInitializerTool/project_initializer/__main__.py
|
||||
|
||||
import sys
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
|
||||
# Per lanciare il tool, la directory ProjectInitializerTool deve essere nel PYTHONPATH
|
||||
# o si deve essere in ProjectInitializerTool/ e lanciare python -m project_initializer
|
||||
# Questo permette gli import relativi corretti.
|
||||
from projectinitializer.gui import app_window # type: ignore
|
||||
from projectinitializer.cli import interface # type: ignore
|
||||
from projectinitializer.config import settings # type: ignore
|
||||
|
||||
|
||||
def ensure_default_icon_exists():
|
||||
"""
|
||||
Checks if the default icon exists in the package's assets.
|
||||
Creates a dummy one if not, for development convenience of the tool itself.
|
||||
This helps prevent errors if the asset is accidentally deleted during development.
|
||||
"""
|
||||
icon_path = settings.DEFAULT_ICON_PATH
|
||||
if not icon_path.exists():
|
||||
print(f"Warning: Default icon '{icon_path}' not found in package assets.", file=sys.stderr)
|
||||
print(f"Attempting to create a dummy empty icon file at that location.", file=sys.stderr)
|
||||
try:
|
||||
icon_path.parent.mkdir(parents=True, exist_ok=True) # Ensure assets directory exists
|
||||
icon_path.touch()
|
||||
print(f"Dummy '{settings.DEFAULT_ICON_FILE_NAME}' created at '{icon_path}'. "
|
||||
"Please replace it with a real .ico file for the tool to function correctly.", file=sys.stderr)
|
||||
except OSError as e:
|
||||
print(f"Critical Error: Could not create dummy icon at '{icon_path}': {e}. "
|
||||
"The tool might not be able to copy the icon to new projects.", file=sys.stderr)
|
||||
|
||||
def main():
|
||||
"""
|
||||
Main entry point for the Project Initializer tool.
|
||||
Determines whether to run in CLI or GUI mode.
|
||||
"""
|
||||
# Ensure the default icon asset is available for copying
|
||||
ensure_default_icon_exists()
|
||||
|
||||
# Argument parser to decide mode (and for CLI args if that's the mode)
|
||||
# We use a basic parser here just to check for presence of CLI-specific args
|
||||
# or a potential --gui / --cli flag if we add one.
|
||||
# The full CLI parsing is delegated to cli.interface.
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Python Project Initializer Tool.",
|
||||
add_help=False # We'll add help in a context-specific way or let sub-parsers do it
|
||||
)
|
||||
# These arguments are for the CLI mode. If they are present, we assume CLI.
|
||||
# nargs='?' makes them optional for this initial parse, so GUI mode doesn't fail.
|
||||
parser.add_argument("root_directory", nargs="?", help=argparse.SUPPRESS) # Suppress from top-level help
|
||||
parser.add_argument("project_name", nargs="?", help=argparse.SUPPRESS) # Suppress from top-level help
|
||||
|
||||
# Optional explicit mode flags (could be useful in future)
|
||||
# mode_group = parser.add_mutually_exclusive_group()
|
||||
# mode_group.add_argument("--cli", action="store_true", help="Force Command Line Interface mode.")
|
||||
# mode_group.add_argument("--gui", action="store_true", help="Force Graphical User Interface mode.")
|
||||
|
||||
# Parse known arguments without exiting on error for missing positional ones yet.
|
||||
# This allows us to check if any arguments were passed that might indicate CLI intent.
|
||||
# All arguments including script name are in sys.argv
|
||||
|
||||
# Heuristic: if there are arguments beyond the script name itself,
|
||||
# and they are not flags like --help (which cli.interface would handle),
|
||||
# it's likely an attempt to run CLI.
|
||||
# A very simple check: if sys.argv has more than 1 element (script name + something else)
|
||||
# A slightly better one: check if root_directory and project_name are likely provided.
|
||||
|
||||
# If sys.argv has more than 1 element (meaning some args were passed)
|
||||
# AND those arguments are not specific GUI launch flags (if we add them later)
|
||||
# then, we assume it's for the CLI. Otherwise, default to GUI.
|
||||
|
||||
# Let's try parsing the arguments. If root_directory and project_name are provided,
|
||||
# it's CLI mode. Otherwise, it's GUI. This relies on argparse's behavior.
|
||||
# sys.argv[0] is script name or -m module_name
|
||||
|
||||
# If any arguments are passed (beyond script name), assume CLI intent.
|
||||
# The cli.interface.handle_cli_invocation will perform full parsing and error handling.
|
||||
# If no arguments are passed, launch GUI.
|
||||
# This is a simplification. A --cli or --gui flag would be more robust.
|
||||
|
||||
# Check if sys.argv contains arguments that are likely positional arguments for CLI
|
||||
# sys.argv = ['project_initializer/__main__.py', 'arg1', 'arg2'] for `python -m project_initializer arg1 arg2`
|
||||
# sys.argv = ['script.py'] for `python script.py`
|
||||
|
||||
# A common pattern for this is to try to parse, and if specific args are found, run one mode.
|
||||
# For simplicity now: if arguments are passed, assume CLI. cli.interface will validate them.
|
||||
# If no arguments are passed (only script name), assume GUI.
|
||||
|
||||
args_to_parse = sys.argv[1:]
|
||||
|
||||
# Heuristic: if the first argument doesn't start with '-' and there are two arguments,
|
||||
# it's highly probable they are the positional 'root_directory' and 'project_name'.
|
||||
# This avoids accidentally going to CLI if someone types `python -m project_initializer --some-unknown-gui-flag`.
|
||||
is_cli_intent = False
|
||||
if len(args_to_parse) >= 2:
|
||||
# Check if the first two args are likely our positional CLI args
|
||||
# (i.e., they don't look like options/flags)
|
||||
if not args_to_parse[0].startswith('-') and not args_to_parse[1].startswith('-'):
|
||||
is_cli_intent = True
|
||||
elif len(args_to_parse) == 1 and (args_to_parse[0] == "--help" or args_to_parse[0] == "-h"):
|
||||
# If only help is requested, let CLI handler show its help message
|
||||
is_cli_intent = True
|
||||
|
||||
|
||||
if is_cli_intent:
|
||||
# Pass all arguments (sys.argv[1:]) to the CLI handler
|
||||
interface.handle_cli_invocation(args_to_parse)
|
||||
else:
|
||||
# No arguments or arguments that don't match CLI pattern, launch GUI
|
||||
if args_to_parse and not (len(args_to_parse) == 1 and (args_to_parse[0] == "--help" or args_to_parse[0] == "-h")):
|
||||
# Arguments were passed that didn't fit the CLI pattern,
|
||||
# and it wasn't a simple help request. Show a warning/help.
|
||||
print("Info: Unrecognized arguments for CLI mode. Defaulting to GUI mode.", file=sys.stdout)
|
||||
print("For CLI, provide: <root_directory> <project_name>", file=sys.stdout)
|
||||
print("Example: python -m project_initializer /path/to/projects MyNewApp\n", file=sys.stdout)
|
||||
|
||||
print("Launching GUI mode...") # Useful feedback if launched from terminal
|
||||
app_window.launch_gui_application()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
0
projectinitializer/assets/default_icon.ico
Normal file
0
projectinitializer/assets/default_icon.ico
Normal file
0
projectinitializer/cli/__init__.py
Normal file
0
projectinitializer/cli/__init__.py
Normal file
70
projectinitializer/cli/interface.py
Normal file
70
projectinitializer/cli/interface.py
Normal file
@ -0,0 +1,70 @@
|
||||
# ProjectInitializerTool/project_initializer/cli/interface.py
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from projectinitializer.core import project_creator # type: ignore
|
||||
# Settings non è direttamente usato qui, ma il core sì
|
||||
# from project_initializer.config import settings
|
||||
|
||||
def handle_cli_invocation(cli_args: list[str] | None = None) -> None:
|
||||
"""
|
||||
Parses command-line arguments and triggers project creation.
|
||||
Manages CLI-specific output and error handling.
|
||||
|
||||
Args:
|
||||
cli_args (list[str] | None, optional): A list of command line arguments
|
||||
(e.g. from sys.argv[1:]).
|
||||
If None, sys.argv[1:] is used.
|
||||
"""
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Python Project Initializer Tool (CLI Mode). "
|
||||
"Creates a standard Python project structure."
|
||||
)
|
||||
parser.add_argument(
|
||||
"root_directory",
|
||||
type=str,
|
||||
help="The absolute or relative root directory where the new project folder will be created."
|
||||
)
|
||||
parser.add_argument(
|
||||
"project_name",
|
||||
type=str,
|
||||
help="The name of the new project (e.g., 'MyAwesomeProject'). "
|
||||
"This will be used for the main project folder and, in a sanitized form, "
|
||||
"for the Python package name."
|
||||
)
|
||||
# Aggiungere un flag per forzare la modalità CLI se __main__.py diventa più complesso
|
||||
# parser.add_argument("--cli", action="store_true", help="Force CLI mode if auto-detection is ambiguous.")
|
||||
|
||||
if cli_args is None:
|
||||
args = parser.parse_args() # Uses sys.argv[1:] by default
|
||||
else:
|
||||
args = parser.parse_args(cli_args)
|
||||
|
||||
|
||||
root_dir_path = Path(args.root_directory).resolve() # Resolve to absolute path for clarity
|
||||
project_name_str = args.project_name.strip()
|
||||
|
||||
if not project_name_str:
|
||||
print("Error: Project name cannot be empty.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# La validazione di root_dir_path (se esiste ed è una directory)
|
||||
# e la validazione più approfondita di project_name (es. isidentifier)
|
||||
# sono gestite dalla funzione project_creator.create_project.
|
||||
# Qui facciamo solo controlli di base sugli argomenti forniti.
|
||||
|
||||
try:
|
||||
print(f"Attempting to create project '{project_name_str}' in '{root_dir_path}'...")
|
||||
success_message = project_creator.create_project(str(root_dir_path), project_name_str)
|
||||
# Il core creator stampa già i suoi messaggi di info/successo.
|
||||
# Potremmo voler sopprimere quelli e stampare solo il messaggio finale qui,
|
||||
# ma per ora va bene così.
|
||||
# print(f"\n{success_message}") # Già stampato dal core.
|
||||
sys.exit(0) # Success
|
||||
except project_creator.ProjectCreationError as e:
|
||||
print(f"Error: Project creation failed: {e}", file=sys.stderr)
|
||||
sys.exit(1) # Failure
|
||||
except Exception as e: # Catch-all for other unexpected errors from core or here
|
||||
print(f"Error: An unexpected error occurred: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
0
projectinitializer/config/__init__.py
Normal file
0
projectinitializer/config/__init__.py
Normal file
353
projectinitializer/config/settings.py
Normal file
353
projectinitializer/config/settings.py
Normal file
@ -0,0 +1,353 @@
|
||||
# ProjectInitializerTool/project_initializer/config/settings.py
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Dict, Any
|
||||
|
||||
# --- Constants ---
|
||||
CONFIG_FILE_NAME: str = ".project_initializer_config.json"
|
||||
DEFAULT_ICON_FILE_NAME: str = "default_icon.ico" # Nome del file icona in assets/
|
||||
|
||||
# Path to the assets directory within the package
|
||||
# Path(__file__) is the path to this settings.py file
|
||||
# .parent is the config/ directory
|
||||
# .parent is the project_initializer/ directory
|
||||
# / "assets" gives project_initializer/assets/
|
||||
PACKAGE_ASSETS_PATH: Path = Path(__file__).resolve().parent.parent / "assets"
|
||||
DEFAULT_ICON_PATH: Path = PACKAGE_ASSETS_PATH / DEFAULT_ICON_FILE_NAME
|
||||
|
||||
|
||||
# --- Configuration Management ---
|
||||
|
||||
def get_config_file_path() -> Path:
|
||||
"""
|
||||
Gets the path to the configuration file.
|
||||
Stores it in the user's home directory.
|
||||
"""
|
||||
return Path.home() / CONFIG_FILE_NAME
|
||||
|
||||
def load_app_configuration() -> Dict[str, Any]:
|
||||
"""
|
||||
Loads the application configuration (e.g., last used root directory).
|
||||
Returns a dictionary with the configuration or an empty dictionary if not found/error.
|
||||
"""
|
||||
config_path = get_config_file_path()
|
||||
if config_path.exists():
|
||||
try:
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
except json.JSONDecodeError:
|
||||
print(f"Warning: Configuration file {config_path} is corrupted. Using defaults.")
|
||||
return {}
|
||||
except IOError:
|
||||
print(f"Warning: Could not read configuration file {config_path}. Using defaults.")
|
||||
return {}
|
||||
return {}
|
||||
|
||||
def save_app_configuration(config_data: Dict[str, Any]) -> None:
|
||||
"""
|
||||
Saves the application configuration to a JSON file.
|
||||
"""
|
||||
config_path = get_config_file_path()
|
||||
try:
|
||||
with open(config_path, "w", encoding="utf-8") as f:
|
||||
json.dump(config_data, f, indent=4)
|
||||
except IOError:
|
||||
print(f"Error: Could not write configuration file {config_path}.")
|
||||
|
||||
# --- File Templates ---
|
||||
|
||||
def get_gitignore_template() -> str:
|
||||
"""Returns the .gitignore template string."""
|
||||
return """# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
pip-wheel-metadata/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a CI server in a temp folder.
|
||||
# Then everything is copied to shipping folder during release.
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# PEP 582; __pypackages__
|
||||
__pypackages__/
|
||||
|
||||
# PEP 621; pyproject.toml sections
|
||||
.pdm.toml
|
||||
.pdm.lock
|
||||
# .venv
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# static analysis tool
|
||||
.flake8
|
||||
|
||||
# VS Code
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
.history/
|
||||
|
||||
# sublime
|
||||
*.sublime-workspace
|
||||
*.sublime-project
|
||||
|
||||
# Kate
|
||||
.kateproject
|
||||
.kateproject.lock
|
||||
.katenewfile. Neuen Filenamensvorschlag merken.
|
||||
|
||||
# Temporary files
|
||||
*.swp
|
||||
*~
|
||||
|
||||
_dist/
|
||||
_build/
|
||||
"""
|
||||
|
||||
def get_readme_template(project_name_original: str) -> str:
|
||||
"""Returns the README.md template string, formatted with the project name."""
|
||||
return f"""# {project_name_original}
|
||||
|
||||
A brief description of {project_name_original}.
|
||||
|
||||
## Features
|
||||
- Feature 1
|
||||
- Feature 2
|
||||
|
||||
## Getting Started
|
||||
...
|
||||
|
||||
## Contributing
|
||||
...
|
||||
|
||||
## License
|
||||
...
|
||||
"""
|
||||
|
||||
def get_main_py_template(project_name_original: str, project_name_lower: str) -> str:
|
||||
"""Returns the __main__.py template string for the new project."""
|
||||
return f"""# {project_name_lower}/__main__.py
|
||||
|
||||
# Example import assuming your main logic is in a 'main' function
|
||||
# within a 'app' module in your '{project_name_lower}.core' package.
|
||||
# from {project_name_lower}.core.app import main as start_application
|
||||
#
|
||||
# Or, if you have a function in {project_name_lower}.core.core:
|
||||
# from {project_name_lower}.core.core import main_function
|
||||
|
||||
def main():
|
||||
print(f"Running {project_name_original}...")
|
||||
# Placeholder: Replace with your application's entry point
|
||||
# Example: start_application()
|
||||
print("To customize, edit '{project_name_lower}/__main__.py' and your core modules.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
"""
|
||||
|
||||
def get_spec_file_template(project_name_original: str, project_name_lower: str) -> str:
|
||||
"""Returns the .spec file template string for PyInstaller."""
|
||||
return f"""# -*- mode: python ; coding: utf-8 -*-
|
||||
|
||||
block_cipher = None
|
||||
|
||||
a = Analysis(
|
||||
['{project_name_lower}/__main__.py'], # Main script of the project being built
|
||||
pathex=['.'], # Current directory, where the .spec file is, and project_name_lower is a subfolder
|
||||
binaries=[],
|
||||
datas=[('{DEFAULT_ICON_FILE_NAME}', '.')], # Icon file relative to project root
|
||||
hiddenimports=[],
|
||||
hookspath=[],
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
win_no_prefer_redirects=False,
|
||||
win_private_assemblies=False,
|
||||
cipher=block_cipher,
|
||||
noarchive=False
|
||||
)
|
||||
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
|
||||
|
||||
exe = EXE(
|
||||
pyz,
|
||||
a.scripts,
|
||||
a.binaries,
|
||||
a.zipfiles,
|
||||
a.datas,
|
||||
[],
|
||||
name='{project_name_original}',
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
upx_exclude=[],
|
||||
runtime_tmpdir=None,
|
||||
console=True, # Set to False for GUI-only applications
|
||||
icon='{DEFAULT_ICON_FILE_NAME}' # Icon relative to project root
|
||||
)
|
||||
"""
|
||||
|
||||
def get_english_manual_template(project_name_original: str) -> str:
|
||||
"""Returns the English manual template string."""
|
||||
return f"""# {project_name_original} - English Manual
|
||||
|
||||
## Introduction
|
||||
Welcome to {project_name_original}. This document provides an overview of how to install, use, and understand the project.
|
||||
|
||||
## Installation
|
||||
Describe the installation steps here. For example:
|
||||
1. Clone the repository: `git clone <repository_url>`
|
||||
2. Navigate to the project directory: `cd {project_name_original}`
|
||||
3. Install dependencies: `pip install -r requirements.txt` (if applicable)
|
||||
|
||||
## Usage
|
||||
Explain how to run and use the application.
|
||||
- To run the application: `python -m {project_name_original.lower().replace(" ", "_").replace("-", "_")}`
|
||||
- Command-line arguments (if any).
|
||||
- GUI interaction (if any).
|
||||
|
||||
## Development
|
||||
Information for developers contributing to the project.
|
||||
- Code structure.
|
||||
- How to run tests.
|
||||
|
||||
## Troubleshooting
|
||||
Common issues and their solutions.
|
||||
"""
|
||||
|
||||
def get_italian_manual_template(project_name_original: str) -> str:
|
||||
"""Returns the Italian manual template string."""
|
||||
return f"""# {project_name_original} - Manuale Italiano
|
||||
|
||||
## Introduzione
|
||||
Benvenuto in {project_name_original}. Questo documento fornisce una panoramica su come installare, utilizzare e comprendere il progetto.
|
||||
|
||||
## Installazione
|
||||
Descrivi i passaggi di installazione qui. Ad esempio:
|
||||
1. Clona il repository: `git clone <repository_url>`
|
||||
2. Naviga nella directory del progetto: `cd {project_name_original}`
|
||||
3. Installa le dipendenze: `pip install -r requirements.txt` (se applicabile)
|
||||
|
||||
## Utilizzo
|
||||
Spiega come eseguire e utilizzare l'applicazione.
|
||||
- Per eseguire l'applicazione: `python -m {project_name_original.lower().replace(" ", "_").replace("-", "_")}`
|
||||
- Argomenti da riga di comando (se presenti).
|
||||
- Interazione con la GUI (se presente).
|
||||
|
||||
## Sviluppo
|
||||
Informazioni per gli sviluppatori che contribuiscono al progetto.
|
||||
- Struttura del codice.
|
||||
- Come eseguire i test.
|
||||
|
||||
## Risoluzione dei problemi
|
||||
Problemi comuni e relative soluzioni.
|
||||
"""
|
||||
0
projectinitializer/core/__init__.py
Normal file
0
projectinitializer/core/__init__.py
Normal file
195
projectinitializer/core/project_creator.py
Normal file
195
projectinitializer/core/project_creator.py
Normal file
@ -0,0 +1,195 @@
|
||||
# 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
|
||||
# Sanitize project_name_original for use as Python module/package name
|
||||
project_name_lower = project_name_original.lower().replace("-", "_").replace(" ", "_")
|
||||
# Basic validation for a Python module name (simplistic)
|
||||
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() # Empty core logic file
|
||||
|
||||
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() # Empty gui logic file
|
||||
|
||||
(src_package_path / "__init__.py").touch() # Make project_name_lower a package
|
||||
|
||||
# Create __main__.py for the new project
|
||||
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'}")
|
||||
|
||||
# 6) Create .spec file for PyInstaller for the new project
|
||||
spec_content = settings.get_spec_file_template(project_name_original, project_name_lower)
|
||||
# The .spec file should be in the project_root_path
|
||||
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
|
||||
# The icon in the .spec file is referenced relative to the project root.
|
||||
destination_icon_path = project_root_path / settings.DEFAULT_ICON_FILE_NAME
|
||||
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:
|
||||
# This case should ideally be handled by ensuring the tool's assets are present.
|
||||
# For robustness, we can create an empty placeholder if it's truly missing.
|
||||
destination_icon_path.touch()
|
||||
print(f"Warning: Default icon '{settings.DEFAULT_ICON_PATH}' not found. "
|
||||
f"Created empty placeholder: {destination_icon_path}")
|
||||
|
||||
|
||||
# 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.")
|
||||
# Optional: Add all files and make an initial commit
|
||||
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:
|
||||
# Git command not found, not a fatal error for project creation itself.
|
||||
# The user can initialize git manually later.
|
||||
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 during Git operation.
|
||||
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:
|
||||
# Catch other OS-level errors (permissions, disk full, etc.)
|
||||
# Attempt to clean up if partial creation occurred
|
||||
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: # Catch any other unexpected error
|
||||
error_msg = f"An unexpected error occurred: {e}"
|
||||
print(f"Error: {error_msg}")
|
||||
if project_root_path.exists() and project_root_path.is_dir(): # Defensive cleanup
|
||||
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
|
||||
0
projectinitializer/gui/__init__.py
Normal file
0
projectinitializer/gui/__init__.py
Normal file
129
projectinitializer/gui/app_window.py
Normal file
129
projectinitializer/gui/app_window.py
Normal file
@ -0,0 +1,129 @@
|
||||
# ProjectInitializerTool/project_initializer/gui/app_window.py
|
||||
|
||||
import tkinter as tk
|
||||
from tkinter import filedialog, messagebox
|
||||
from pathlib import Path
|
||||
from projectinitializer.config import settings # type: ignore
|
||||
from projectinitializer.core import project_creator # type: ignore
|
||||
|
||||
class AppWindow:
|
||||
def __init__(self, master: tk.Tk):
|
||||
self.master = master
|
||||
master.title("Project Initializer Tool")
|
||||
# master.geometry("500x200") # Optional: set a default size
|
||||
|
||||
self.app_config = settings.load_app_configuration()
|
||||
self.last_root_dir = self.app_config.get("last_root_directory", str(Path.home()))
|
||||
|
||||
# --- Widgets ---
|
||||
# Root Directory Frame
|
||||
root_dir_frame = tk.Frame(master, padx=5, pady=5)
|
||||
root_dir_frame.pack(fill=tk.X, expand=False)
|
||||
|
||||
tk.Label(root_dir_frame, text="Root Directory:").pack(side=tk.LEFT, padx=(0, 5))
|
||||
self.root_dir_entry = tk.Entry(root_dir_frame)
|
||||
self.root_dir_entry.pack(side=tk.LEFT, fill=tk.X, expand=True)
|
||||
self.root_dir_entry.insert(0, self.last_root_dir)
|
||||
self.browse_button = tk.Button(root_dir_frame, text="Browse...", command=self._browse_root_directory)
|
||||
self.browse_button.pack(side=tk.LEFT, padx=(5, 0))
|
||||
|
||||
# Project Name Frame
|
||||
project_name_frame = tk.Frame(master, padx=5, pady=5)
|
||||
project_name_frame.pack(fill=tk.X, expand=False)
|
||||
|
||||
tk.Label(project_name_frame, text="Project Name:").pack(side=tk.LEFT, padx=(0, 5))
|
||||
self.project_name_entry = tk.Entry(project_name_frame)
|
||||
self.project_name_entry.pack(side=tk.LEFT, fill=tk.X, expand=True)
|
||||
|
||||
# Ensure project name entry also expands if window is resized
|
||||
project_name_frame.grid_columnconfigure(1, weight=1)
|
||||
|
||||
|
||||
# Proceed Button Frame (for centering or specific layout)
|
||||
button_frame = tk.Frame(master, pady=10)
|
||||
button_frame.pack()
|
||||
|
||||
self.proceed_button = tk.Button(button_frame, text="Create Project", command=self._proceed_action, width=20)
|
||||
self.proceed_button.pack()
|
||||
|
||||
# Set focus to project name entry initially
|
||||
self.project_name_entry.focus_set()
|
||||
|
||||
# Configure resizing behavior for the main window content
|
||||
# Let the entry fields expand with the window width
|
||||
# Not strictly necessary with pack, but good practice for grid or more complex layouts
|
||||
# master.grid_columnconfigure(0, weight=1) # if using grid for frames
|
||||
|
||||
def _browse_root_directory(self) -> None:
|
||||
"""Opens a dialog to select the root directory."""
|
||||
initial_dir = self.root_dir_entry.get() or str(Path.home())
|
||||
if not Path(initial_dir).is_dir(): # check if path is valid, otherwise default to home
|
||||
initial_dir = str(Path.home())
|
||||
|
||||
directory = filedialog.askdirectory(
|
||||
initialdir=initial_dir,
|
||||
title="Select Root Directory"
|
||||
)
|
||||
if directory: # If a directory is selected (not cancelled)
|
||||
self.root_dir_entry.delete(0, tk.END)
|
||||
self.root_dir_entry.insert(0, directory)
|
||||
|
||||
def _validate_inputs(self) -> bool:
|
||||
"""Validates user inputs from the GUI."""
|
||||
self.root_dir = self.root_dir_entry.get().strip()
|
||||
self.project_name = self.project_name_entry.get().strip()
|
||||
|
||||
if not self.root_dir:
|
||||
messagebox.showerror("Input Error", "Root directory cannot be empty.")
|
||||
self.root_dir_entry.focus_set()
|
||||
return False
|
||||
if not Path(self.root_dir).is_dir():
|
||||
messagebox.showerror("Input Error", "The selected root directory is not a valid directory.")
|
||||
self.root_dir_entry.focus_set()
|
||||
return False
|
||||
if not self.project_name:
|
||||
messagebox.showerror("Input Error", "Project name cannot be empty.")
|
||||
self.project_name_entry.focus_set()
|
||||
return False
|
||||
|
||||
# Basic check for project name characters.
|
||||
# More complex validation (like isidentifier for the sanitized version)
|
||||
# is handled in the core, but a simple GUI check can be useful.
|
||||
# For now, we rely on the core's validation.
|
||||
|
||||
return True
|
||||
|
||||
def _proceed_action(self) -> None:
|
||||
"""Handles the 'Create Project' button click."""
|
||||
if not self._validate_inputs():
|
||||
return
|
||||
|
||||
# Save current root_dir for next time if it's valid and different
|
||||
if Path(self.root_dir).is_dir() and self.root_dir != self.last_root_dir:
|
||||
self.app_config["last_root_directory"] = self.root_dir
|
||||
settings.save_app_configuration(self.app_config)
|
||||
self.last_root_dir = self.root_dir # Update internal state
|
||||
|
||||
self.proceed_button.config(state=tk.DISABLED)
|
||||
self.master.update_idletasks() # Ensure button state updates immediately
|
||||
|
||||
try:
|
||||
# Call the core logic
|
||||
success_message = project_creator.create_project(self.root_dir, self.project_name)
|
||||
messagebox.showinfo("Success", success_message)
|
||||
# Optionally clear fields or perform other actions on success
|
||||
self.project_name_entry.delete(0, tk.END) # Clear project name for the next one
|
||||
self.project_name_entry.focus_set()
|
||||
except project_creator.ProjectCreationError as e:
|
||||
messagebox.showerror("Project Creation Failed", str(e))
|
||||
except Exception as e: # Catch any other unexpected errors
|
||||
messagebox.showerror("Unexpected Error", f"An unexpected error occurred: {str(e)}")
|
||||
finally:
|
||||
# Re-enable button
|
||||
self.proceed_button.config(state=tk.NORMAL)
|
||||
|
||||
def launch_gui_application() -> None:
|
||||
"""Initializes and runs the Tkinter GUI application."""
|
||||
root = tk.Tk()
|
||||
app = AppWindow(root)
|
||||
root.mainloop()
|
||||
0
requirements.txt
Normal file
0
requirements.txt
Normal file
Loading…
Reference in New Issue
Block a user