-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinit_project.py
More file actions
executable file
·805 lines (662 loc) · 25 KB
/
init_project.py
File metadata and controls
executable file
·805 lines (662 loc) · 25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
#!/usr/bin/env python3
"""
Project Initialization Script
Initializes a new project from the python-template.
Creates a clean git history and customizes project details.
"""
from dataclasses import dataclass
from pathlib import Path
import re
import shutil
import subprocess
import sys
from typing import Final
# Constants
TEMPLATE_NAME: Final[str] = "python-template"
TEMPLATE_DESCRIPTION: Final[str] = "A modern Python project template"
DEFAULT_DESCRIPTION: Final[str] = "A modern Python project"
DEFAULT_COMMIT_MSG: Final[str] = "chore: initialize repository"
# Python version configuration
# This is the single source of truth for the default Python version.
# To bump the version, update this constant and the template's pyproject.toml
# (requires-python, [tool.ruff] target-version, and [tool.mypy] python_version)
DEFAULT_PYTHON_VERSION: Final[str] = "3.13"
# Required files for template validation
REQUIRED_FILES: Final[list[str]] = [
"pyproject.toml",
"src/cli/main.py",
"Makefile",
".pre-commit-config.yaml",
]
# Template files to remove during cleanup
TEMPLATE_CLEANUP_FILES: Final[list[str]] = [
"init_project.py",
"CHANGELOG.md",
"LICENSE",
]
# MkDocs files to remove if not keeping
MKDOCS_FILES: Final[list[str]] = [
"mkdocs.yml",
"docs/",
]
@dataclass
class ProjectConfig:
"""Configuration for the new project."""
name: str
description: str
author: str
commit_msg: str
keep_mkdocs: bool
cleanup_template: bool
install_dependencies: bool
python_version: str
def print_banner() -> None:
"""Print a welcome banner."""
print("=" * 60)
print("🚀 Python Template Project Initializer")
print("=" * 60)
print()
def validate_template() -> bool:
"""Validate that we're running from a template repository."""
missing_files = [f for f in REQUIRED_FILES if not Path(f).exists()]
if missing_files:
print("❌ Error: This doesn't appear to be a python-template repository.")
print(f"Missing required files: {', '.join(missing_files)}")
print()
print("💡 Make sure you're running this script from the root of the")
print(" python-template repository. If you cloned it, ensure all files")
print(" were downloaded correctly.")
return False
# Verify git repository exists
if not Path(".git").exists():
print("❌ Error: Not a git repository.")
print()
print("💡 This script requires a git repository to work.")
print(" Please clone the template first:")
print(" git clone <repository-url> <project-name>")
print(" cd <project-name>")
print(" python init_project.py")
return False
return True
def validate_project_name(name: str) -> bool:
"""Validate project name format."""
if not name:
return False
return bool(re.match(r"^[a-zA-Z0-9_-]+$", name))
def validate_author_email(author: str) -> bool:
"""Validate author email format."""
if not author or "<" not in author or ">" not in author:
return True # Allow empty or simple names
email_part = author.split("<")[1].split(">")[0].strip()
return "@" in email_part and "." in email_part.split("@")[1]
def get_choice_input(prompt: str, options: list[str], error_msg: str) -> int:
"""Get a choice input from user."""
while True:
choice = input(f"{prompt} ").strip()
if choice in [str(i) for i in range(1, len(options) + 1)]:
return int(choice)
print(f"❌ {error_msg}")
def get_user_input() -> ProjectConfig:
"""Get user input for project customization."""
print("Let's customize your new project!")
print()
# Collect project name
while True:
project_name = input(
"📝 Enter your project name (e.g., 'my-awesome-project'): "
).strip()
if not project_name:
print("❌ Error: Project name cannot be empty.")
print("💡 Please enter a valid project name (e.g., 'my-awesome-project').")
continue
if not validate_project_name(project_name):
print("❌ Error: Invalid project name format.")
print("💡 Project names can only contain:")
print(" - Letters (a-z, A-Z)")
print(" - Numbers (0-9)")
print(" - Hyphens (-)")
print(" - Underscores (_)")
print(f" Example: 'my-project' or 'my_project' (not '{project_name}')")
continue
break
# Collect project description
project_description = input(
f"📝 Enter project description (default: '{DEFAULT_DESCRIPTION}'): "
).strip()
if not project_description:
project_description = DEFAULT_DESCRIPTION
# Collect author information
print()
while True:
author = input(
"👤 Enter your name and email (optional, press Enter to skip): "
).strip()
if not author:
break
if not validate_author_email(author):
print("❌ Error: Invalid email format.")
print("💡 Please use one of these formats:")
print(" - 'Your Name <[email protected]>'")
print(" - Or press Enter to skip")
print(f" You entered: '{author}'")
continue
break
# Collect commit message
commit_msg = input(
f"📝 Enter initial commit message (default: '{DEFAULT_COMMIT_MSG}'): "
).strip()
if not commit_msg:
commit_msg = DEFAULT_COMMIT_MSG
# Prompt for MkDocs preference
print()
print("📚 Documentation options:")
print("1. Keep MkDocs (recommended for most projects)")
print("2. Remove MkDocs (if you prefer other documentation tools)")
mkdocs_choice = get_choice_input(
"Choose documentation option (1 or 2):",
["Keep MkDocs", "Remove MkDocs"],
"Please enter 1 or 2.",
)
keep_mkdocs = mkdocs_choice == 1
# Prompt for cleanup preference
print()
print("🧹 Template cleanup options:")
print("1. Remove template-specific files (recommended)")
print("2. Keep all files")
cleanup_choice = get_choice_input(
"Choose cleanup option (1 or 2):",
["Remove template files", "Keep all files"],
"Please enter 1 or 2.",
)
cleanup_template = cleanup_choice == 1
# Prompt for dependency installation
print()
print("📦 Dependency installation:")
print("1. Run 'uv sync' now (recommended)")
print("2. Skip dependency installation")
install_choice = get_choice_input(
"Install dependencies now? (1 or 2):",
["Install dependencies", "Skip installation"],
"Please enter 1 or 2.",
)
install_dependencies = install_choice == 1
python_version = DEFAULT_PYTHON_VERSION
return ProjectConfig(
name=project_name,
description=project_description,
author=author,
commit_msg=commit_msg,
keep_mkdocs=keep_mkdocs,
cleanup_template=cleanup_template,
install_dependencies=install_dependencies,
python_version=python_version,
)
def confirm_changes(config: ProjectConfig) -> bool:
"""Display summary and confirm changes before proceeding."""
print()
print("📋 Summary of changes:")
print("=" * 40)
print(f"Project name: {config.name}")
print(f"Description: {config.description}")
print(f"Author: {config.author if config.author else 'Not specified'}")
print(f"Python version: {config.python_version}")
print(f"Commit message: {config.commit_msg}")
print(f"Keep MkDocs: {'Yes' if config.keep_mkdocs else 'No'}")
print(f"Cleanup template files: {'Yes' if config.cleanup_template else 'No'}")
print(f"Install dependencies: {'Yes' if config.install_dependencies else 'No'}")
print("=" * 40)
print()
while True:
confirm = input("Proceed with initialization? (y/N): ").strip().lower()
if confirm in ["y", "yes"]:
return True
elif confirm in ["n", "no", ""]:
return False
print("❌ Please enter 'y' for yes or 'n' for no.")
class FileUpdater:
"""Manages file updates with consistent patterns."""
@staticmethod
def format_project_title(project_name: str) -> str:
"""Format project name as a title."""
return project_name.replace("-", " ").title()
@staticmethod
def update_file(file_path: str, replacements: list[tuple[str, str]]) -> None:
"""Update a file with multiple replacements."""
path = Path(file_path)
content = path.read_text()
for pattern, replacement in replacements:
content = re.sub(pattern, replacement, content)
path.write_text(content)
print(f"✅ Updated {file_path}")
@staticmethod
def update_pyproject_toml(config: ProjectConfig) -> None:
"""Update pyproject.toml with new project details."""
replacements = [
(r'name = "python-template"', f'name = "{config.name}"'),
(
r'description = "A modern Python project template"',
f'description = "{config.description}"',
),
]
if config.author:
replacements.append(
(
r'authors = \["Your Name <your\.email@example\.com>"\]',
f'authors = ["{config.author}"]',
)
)
FileUpdater.update_file("pyproject.toml", replacements)
@staticmethod
def update_makefile(config: ProjectConfig) -> None:
"""Update Makefile with new project name."""
replacements = [
(r"PACKAGE_NAME \?= python-template", f"PACKAGE_NAME ?= {config.name}"),
(r"SCRIPT_NAME \?= python-template", f"SCRIPT_NAME ?= {config.name}"),
]
FileUpdater.update_file("Makefile", replacements)
@staticmethod
def update_readme(config: ProjectConfig) -> None:
"""Update README.md with new project name."""
project_title = FileUpdater.format_project_title(config.name)
replacements = [
(r"# Python Project Template", f"# {project_title}"),
(r"poetry run python-template", f"uv run {config.name}"),
(r"make run\s+# Default package name", f"make run # Run {config.name}"),
]
FileUpdater.update_file("README.md", replacements)
@staticmethod
def update_cli_script(config: ProjectConfig) -> None:
"""Update the CLI script name in pyproject.toml."""
replacements = [
(
r'python-template = "src\.cli\.main:cli"',
f'{config.name} = "src.cli.main:cli"',
),
]
FileUpdater.update_file("pyproject.toml", replacements)
@staticmethod
def update_cli_module(config: ProjectConfig) -> None:
"""Update the CLI module with project details."""
replacements = [
(r'PROJECT_NAME = "python-template"', f'PROJECT_NAME = "{config.name}"'),
(
r'PROJECT_DESCRIPTION = "A modern Python project template"',
f'PROJECT_DESCRIPTION = "{config.description}"',
),
]
FileUpdater.update_file("src/cli/main.py", replacements)
@staticmethod
def update_mkdocs_config(config: ProjectConfig) -> None:
"""Update mkdocs.yml with project details."""
project_title = FileUpdater.format_project_title(config.name)
replacements = [
(
r"site_name: Python Project Template",
f"site_name: {project_title}",
),
(
r"site_description: A modern Python project template",
f"site_description: {config.description}",
),
(
r"site_url: https://your-username\.github\.io/python-template",
f"site_url: https://your-username.github.io/{config.name}",
),
(
r"repo_name: your-username/python-template",
f"repo_name: your-username/{config.name}",
),
(
r"repo_url: https://github\.com/your-username/python-template",
f"repo_url: https://github.com/your-username/{config.name}",
),
]
if config.author:
replacements.append(
(
r"site_author: Your Name",
f"site_author: {config.author.split('<')[0].strip()}",
)
)
FileUpdater.update_file("mkdocs.yml", replacements)
@staticmethod
def remove_mkdocs_files() -> None:
"""Remove MkDocs-related files."""
for file_path in MKDOCS_FILES:
if Path(file_path).exists():
if Path(file_path).is_dir():
shutil.rmtree(file_path)
print(f"🗑️ Removed directory: {file_path}")
else:
Path(file_path).unlink()
print(f"🗑️ Removed file: {file_path}")
@staticmethod
def _python_version_to_ruff_format(version: str) -> str:
"""Convert Python version (e.g., '3.13') to Ruff format (e.g., 'py313')."""
# Remove dots and add 'py' prefix
return f"py{version.replace('.', '')}"
@staticmethod
def update_python_version(config: ProjectConfig) -> None:
"""Update Python version in .python-version and pyproject.toml."""
# Update .python-version file
Path(".python-version").write_text(f"{config.python_version}\n")
print(f"✅ Updated .python-version to Python {config.python_version}")
# Convert version for Ruff format (e.g., "3.13" -> "py313")
ruff_version = FileUpdater._python_version_to_ruff_format(config.python_version)
# Update pyproject.toml: requires-python, ruff target-version, and mypy python_version
replacements = [
# Update requires-python
(
r'requires-python = ">=\d+\.\d+,<4\.0"',
f'requires-python = ">={config.python_version},<4.0"',
),
# Update Ruff target-version
(
r'target-version = "py\d+"',
f'target-version = "{ruff_version}"',
),
# Update MyPy python_version
(
r'python_version = "\d+\.\d+"',
f'python_version = "{config.python_version}"',
),
]
FileUpdater.update_file("pyproject.toml", replacements)
@staticmethod
def _generate_readme_content(config: ProjectConfig) -> str:
"""Generate README content from template."""
project_title = FileUpdater.format_project_title(config.name)
# Build description section (only if description is provided)
description_section = ""
if config.description and config.description.strip():
description_section = f"\n{config.description}\n"
return f"""# {project_title}{description_section}
## Requirements
- **Python**: {config.python_version} or higher
- **uv**: For dependency management
### Installing uv
Install uv with the official installer:
```bash
# macOS and Linux
curl -LsSf https://astral.sh/uv/install.sh | sh
# Windows
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
```
Or install via pip:
```bash
pip install uv
```
## Development
Use `make help` to see all available commands, or run directly:
**Setup & Dependencies:**
- **Setup:** `make setup` - Complete development setup (install deps + pre-commit)
- **Install:** `make install` - Install main dependencies
- **Install dev:** `make install-dev` - Install all dependencies including dev
- **Add package:** `make add PACKAGE=name` - Add a new dependency
- **Remove package:** `make remove PACKAGE=name` - Remove a dependency
- **Dependency tree:** `make tree` - Show dependency tree
**Code Quality:**
- **All checks:** `make check` - Run linting, type checking, and tests
- **Fast checks:** `make check-fast` - Run fast checks (lint only)
- **Full checks:** `make check-full` - Run all checks including coverage
- **Lint:** `make lint` - Run Ruff linter
- **Format:** `make format` - Format code with Ruff
- **Format check:** `make format-check` - Check if code is formatted correctly
- **Format fix:** `make format-fix` - Format code and fix issues automatically
- **Type check:** `make type-check` - Run mypy type checking
**Testing & Building:**
- **Test:** `make test` - Run tests
- **Test with coverage:** `make test-cov` - Run tests with coverage report
- **Build:** `make build` - Build package for distribution
- **Publish:** `make publish` - Publish to PyPI
**Application:**
- **Run:** `make run` - Run the application
- **Debug:** `make debug` - Run in debug mode
**Documentation:**
- **Build docs:** `make docs` - Build documentation
- **Serve docs:** `make serve-docs` - Serve documentation locally
**Performance:**
- **Benchmark:** `make benchmark` - Benchmark linting and formatting performance
- **Profile:** `make profile` - Profile dependency resolution
### CLI Usage
The project includes a CLI built with Typer:
```bash
# Show project information
uv run {config.name}
# Show help
uv run {config.name} --help
# Run application (multiple ways)
make run # Default package name
make run ARGS="--help" # With arguments
make debug # Debug mode
```
### Makefile
The Makefile provides commands for installing dependencies, running tests, linting, formatting, building, publishing, and managing versioning.
- **Configurable**: Update `MAIN_MODULE` and `MAIN_FUNCTION` in Makefile for different entry points
- **Flexible**: Supports running any package with `make run <package-name>`
- **Consistent**: Same commands work regardless of application type
- **Extensible**: Easy to add new targets for different project needs
### Security
The project includes security scanning tools:
- **Safety:** `uv run safety scan` - Check for known vulnerabilities
- **Bandit:** `uv run bandit -r src/` - Security linting for Python code
## Configuration
All tools are configured in `pyproject.toml`. See the file for specific settings.
## Versioning and Changelog
The project uses [Commitizen](https://commitizen-tools.github.io/commitizen/) for automated versioning and changelog generation.
- **Versioning**: Follows [Semantic Versioning](https://semver.org/) (MAJOR.MINOR.PATCH)
- **Changelog**: Generated from commit messages
- **Commits**: Use [Conventional Commits](https://www.conventionalcommits.org/) format
- **Releases**: Independent of CI/CD platforms
### Versioning Commands
- `make version` - Show current version
- `make bump` - Bump version based on conventional commits
- `make bump-patch` - Patch version bump (0.0.0 → 0.0.1)
- `make bump-minor` - Minor version bump (0.0.0 → 0.1.0)
- `make bump-major` - Major version bump (0.0.0 → 1.0.0)
### Commit Format
```
<type>[optional scope]: <description>
[optional body]
[optional footer(s)]
```
Common types: `feat`, `fix`, `docs`, `style`, `refactor`, `test`, `chore`
"""
@staticmethod
def create_new_readme(config: ProjectConfig) -> None:
"""Create a new README with project-specific content."""
readme_content = FileUpdater._generate_readme_content(config)
try:
Path("README.md").write_text(readme_content, encoding="utf-8")
print("✅ Created new README.md")
except OSError as e:
print("❌ Error: Failed to create README.md")
print(f" Details: {e}")
print("💡 Check that you have write permissions in the current directory.")
raise
@staticmethod
def cleanup_template_files(config: ProjectConfig) -> None:
"""Remove template-specific files and create new README."""
# Create new README first
FileUpdater.create_new_readme(config)
# Then remove template files
for file_path in TEMPLATE_CLEANUP_FILES:
try:
if Path(file_path).exists():
Path(file_path).unlink()
print(f"🗑️ Removed template file: {file_path}")
except OSError as e:
print(f"⚠️ Warning: Failed to remove {file_path}")
print(f" Details: {e}")
print("💡 You may need to remove this file manually later.")
# Continue with other files even if one fails
def install_dependencies() -> None:
"""Install project dependencies using uv."""
print()
print("📦 Installing dependencies...")
try:
subprocess.run(["uv", "sync", "--extra", "dev"], check=True)
print("✅ Dependencies installed successfully")
except subprocess.CalledProcessError as e:
print("❌ Error: Failed to install dependencies")
print(f" Exit code: {e.returncode}")
if e.stderr:
print(
f" Error output: {e.stderr.decode() if isinstance(e.stderr, bytes) else e.stderr}"
)
print()
print("💡 Troubleshooting steps:")
print(" 1. Check your internet connection")
print(" 2. Verify uv is up to date: uv --version")
print(" 3. Try running manually: uv sync --extra dev")
print(" 4. Check for conflicting packages in pyproject.toml")
except FileNotFoundError:
print("❌ Error: 'uv' command not found")
print()
print("💡 Install uv using one of these methods:")
print(" macOS/Linux: curl -LsSf https://astral.sh/uv/install.sh | sh")
print(
' Windows: powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"'
)
print(" Or via pip: pip install uv")
print(" Visit: https://docs.astral.sh/uv/getting-started/installation/")
def create_clean_git_history(commit_msg: str) -> None:
"""Create a clean git history with a single initial commit."""
print()
print("🔄 Creating clean git history...")
# Remove existing git history
shutil.rmtree(".git")
print("✅ Removed existing git history")
# Initialize git repository
subprocess.run(["git", "init"], check=True)
print("✅ Initialized new git repository")
# Stage all files
subprocess.run(["git", "add", "."], check=True)
print("✅ Staged all files")
# Create initial commit
subprocess.run(["git", "commit", "-m", commit_msg], check=True)
print(f"✅ Created initial commit: '{commit_msg}'")
def show_next_steps(
project_name: str, keep_mkdocs: bool, dependencies_installed: bool
) -> None:
"""Show next steps to the user."""
print()
print("🎉 Project initialization complete!")
print("=" * 60)
print()
print("Next steps:")
print()
step_num = 1
if not dependencies_installed:
print(f"{step_num}. 📦 Install dependencies:")
print(" uv sync --extra dev")
print()
step_num += 1
print(f"{step_num}. 🔧 Set up pre-commit hooks:")
print(" uv run pre-commit install")
print()
step_num += 1
print(f"{step_num}. 🧪 Run tests:")
print(" uv run pytest")
print(" # or use: make test")
print()
step_num += 1
print(f"{step_num}. 🚀 Run your application:")
print(f" uv run {project_name}")
print(" # or use: make run")
print()
step_num += 1
if keep_mkdocs:
print(f"{step_num}. 📚 Build documentation:")
print(" make docs")
print()
step_num += 1
print(f"{step_num}. 🔗 Add remote repository (optional):")
print(" git remote add origin <your-repo-url>")
print(" git push -u origin main")
print()
print("Happy coding! 🚀")
def main() -> None:
"""Main function."""
print_banner()
# Validate template repository
if not validate_template():
sys.exit(1)
# Collect user input
config = get_user_input()
# Confirm changes before proceeding
if not confirm_changes(config):
print("❌ Initialization cancelled.")
sys.exit(0)
print()
print("🔄 Initializing project...")
print()
try:
# Update configuration files
FileUpdater.update_pyproject_toml(config)
FileUpdater.update_makefile(config)
FileUpdater.update_cli_script(config)
FileUpdater.update_cli_module(config)
FileUpdater.update_python_version(config)
# Update or remove MkDocs based on user preference
if config.keep_mkdocs:
FileUpdater.update_mkdocs_config(config)
else:
FileUpdater.remove_mkdocs_files()
# Remove template files if requested (creates new README)
if config.cleanup_template:
FileUpdater.cleanup_template_files(config)
else:
# Only update README if we're not creating a new one
FileUpdater.update_readme(config)
# Install dependencies if requested
dependencies_installed = False
if config.install_dependencies:
install_dependencies()
dependencies_installed = True
# Create clean git history
create_clean_git_history(config.commit_msg)
# Display next steps
show_next_steps(config.name, config.keep_mkdocs, dependencies_installed)
except KeyboardInterrupt:
print("\n❌ Initialization cancelled by user.")
print("💡 No changes were made. You can run the script again when ready.")
sys.exit(1)
except subprocess.CalledProcessError as e:
print("❌ Error: Git command failed")
print(f" Command: {' '.join(e.cmd) if e.cmd else 'unknown'}")
print(f" Exit code: {e.returncode}")
if e.stderr:
error_output = e.stderr.decode() if isinstance(e.stderr, bytes) else str(e.stderr)
print(f" Error: {error_output}")
print()
print("💡 Troubleshooting steps:")
print(" 1. Verify git is installed: git --version")
print(" 2. Check you have write permissions in this directory")
print(" 3. Ensure git is configured: git config --global user.name")
print(" 4. Try running the git command manually to see the full error")
sys.exit(1)
except PermissionError as e:
print("❌ Error: Permission denied")
print(f" Details: {e}")
print()
print("💡 Troubleshooting steps:")
print(" 1. Check file/directory permissions: ls -la")
print(" 2. Ensure you have write access to the current directory")
print(" 3. On Unix systems, you may need to use: chmod +w .")
print(" 4. Try running from a different directory with proper permissions")
sys.exit(1)
except Exception as e:
print("❌ Error: Unexpected error during initialization")
print(f" Error type: {type(e).__name__}")
print(f" Details: {e}")
print()
print("💡 Troubleshooting steps:")
print(" 1. Check the error message above for clues")
print(" 2. Verify all required files are present")
print(" 3. Ensure you have sufficient disk space")
print(" 4. Try running the script again")
print(" 5. If the problem persists, check the project's issue tracker")
sys.exit(1)
if __name__ == "__main__":
main()