"""
Module to copy the README.md, LICENCE, .gitignore and .pre-commit-config.yaml files
to the build directory and add author information.
"""
import shutil
from argparse import Namespace
from pathlib import Path
from rich import print as rprint
from uv_start.config import load_config
from uv_start.exceptions import TemplateError
TEMPLATE_DIR = Path(__file__).resolve().parent / "template"
[docs]
def parse_docs(args: Namespace, project_dir: Path) -> None:
"""Parse the README.md file and update the content with project information."""
for template in [
"README.md",
"LICENSE",
".gitignore",
".pre-commit-config.yaml",
".env.example",
]:
_copy_template(template, project_dir)
# Copy config.py to src/project_name
module_name = args.project_name.replace("-", "_")
src_dir = project_dir / "src" / module_name
src_dir.mkdir(parents=True, exist_ok=True)
_copy_template("config.py", src_dir)
vs_code_dir = project_dir / ".vscode"
vs_code_dir.mkdir(parents=True, exist_ok=True)
_copy_template("settings.json", vs_code_dir)
_copy_template("launch.json", vs_code_dir)
if args.github:
_add_github_workflows(project_dir)
_update_content(project_dir, args, ".github/workflows/ci.yml")
_update_content(project_dir, args, ".github/workflows/release.yml")
_update_configs(project_dir, args)
_init_version(args, project_dir)
[docs]
def parse_docs_data(args: Namespace, project_dir: Path) -> None:
"""Set up template files for a data analysis project.
Copies .gitignore, .env.example, the hhlab matplotlib style, and a
starter sample.ipynb into the project root. No src/ structure is created.
"""
for template in [".gitignore", ".env.example", "README.md"]:
_copy_template(template, project_dir)
_copy_template("hhlab_style01.mplstyle", project_dir)
_copy_template("colors.py", project_dir)
_copy_template("sample.ipynb", project_dir)
# CLAUDE.md is stored as data-CLAUDE.md in the template dir to avoid
# conflicting with this project's own CLAUDE.md during development.
src = TEMPLATE_DIR / "data-CLAUDE.md"
dest = project_dir / "CLAUDE.md"
shutil.copy(src, dest)
rprint("[green]CLAUDE.md copied to project root[/green]")
_update_content(project_dir, args, "README.md")
_update_content(project_dir, args, "sample.ipynb")
_update_content(project_dir, args, "CLAUDE.md")
vs_code_dir = project_dir / ".vscode"
vs_code_dir.mkdir(parents=True, exist_ok=True)
_copy_template("settings.json", vs_code_dir)
_copy_template("launch.json", vs_code_dir)
if args.github:
_add_github_workflows(project_dir)
_update_content(project_dir, args, ".github/workflows/ci.yml")
_update_content(project_dir, args, ".github/workflows/release.yml")
rprint("[green]Data project template files copied successfully.[/green]")
def _copy_template(template: str, project_dir: Path) -> None:
"""Copy template files to the build directory"""
try:
copy_path = TEMPLATE_DIR / template
paste_path = project_dir / f"{template}"
shutil.copy(copy_path, paste_path)
rprint(f"[green]{template} copied to root project[/green]")
if template == "README.md" and (project_dir / "packages").exists():
for package in (project_dir / "packages").iterdir():
if package.is_dir():
shutil.copy(copy_path, package)
rprint(f"[green]{template} successfully copied[/green]")
except FileNotFoundError as e:
raise TemplateError(f"{template} template not found") from e
def _update_configs(project_dir: Path, args: Namespace) -> None:
"""Update the configuration files with project information."""
for template in ["README.md", "LICENSE", "pyproject.toml"]:
_update_content(project_dir, args, template)
def _parse_replacement(args: Namespace, content_path: Path) -> dict[str, str]:
"""Load replacements for the README.md files into dictionary."""
user_config = load_config()
AUTHOR_NAME = user_config.author_name
AUTHOR_EMAIL = user_config.author_email
target_version = args.python
parent_dir_name = content_path.parent.name
module_name = parent_dir_name.replace("-", "_")
return {
"# Title": f"# {parent_dir_name}",
"{project_name}": parent_dir_name,
"{python_version}": args.python,
"{author}": AUTHOR_NAME,
"{email}": AUTHOR_EMAIL,
"{package_name}": parent_dir_name,
"{module_name}": module_name,
"src/{module_name}/__init__.py": f"src/{module_name}/__init__.py",
'python-version: ["3.12"]': f'python-version: ["{target_version}"]',
"python-version: '3.12'": f"python-version: '{target_version}'",
}
def _update_content(
project_dir: Path, args: Namespace, content_type: str
) -> None:
"""Update the content of a file with project information."""
try:
# Files that should only exist in root directory
root_only_files = [
"LICENSE",
".github/workflows/ci.yml",
".github/workflows/release.yml",
]
content_path = [project_dir / content_type] + (
[
package / content_type
for package in (project_dir / "packages").iterdir()
if package.is_dir()
]
if (project_dir / "packages").exists()
and content_type not in root_only_files
else []
)
for file in content_path:
replacements = _parse_replacement(args, file)
with file.open("r") as f:
content = f.read()
for old, new in replacements.items():
content = content.replace(old, new)
with file.open("w") as f:
f.write(content)
rprint(f"[green]{content_type} successfully updated[/green]")
except FileNotFoundError as e:
raise TemplateError(f"Failed to update {content_type}: {e}") from e
def _init_version(args: Namespace, project_dir: Path) -> None:
"""Initialize the version file with imports and version."""
try:
package_name = args.project_name.replace("-", "_")
# Handle root project's __init__.py
root_init = project_dir / "src" / package_name / "__init__.py"
with root_init.open("w") as f:
f.write(
'__version__ = "0.1.0"\n\n'
"from .config import set_env_vars\n\n"
"# Initialize environment variables\n"
"set_env_vars()\n"
)
rprint("[green]Root __init__.py initialized with config setup[/green]")
# Handle sub-packages (if workspace)
if (project_dir / "packages").exists():
for sub_package in (project_dir / "packages").iterdir():
if sub_package.is_dir():
sub_package_name = sub_package.name.replace("-", "_")
sub_init = (
sub_package / f"src/{sub_package_name}/__init__.py"
)
with sub_init.open("w") as f:
f.write('__version__ = "0.1.0"\n')
rprint(
f"[green]Version file initialized for {sub_package_name}[/green]"
)
except FileNotFoundError as e:
raise TemplateError("Version file not found") from e
def _add_github_workflows(project_dir: Path) -> None:
"""Add GitHub workflow configurations to the project."""
try:
# Create .github/workflows directory
workflows_dir = project_dir / ".github" / "workflows"
workflows_dir.mkdir(parents=True, exist_ok=True)
# Copy workflow files
for workflow in ["ci.yml", "release.yml"]:
source = TEMPLATE_DIR / ".github" / "workflows" / workflow
dest = workflows_dir / workflow
shutil.copy(source, dest)
rprint(
"[green]GitHub workflow configurations added successfully[/green]"
)
except FileNotFoundError as e:
raise TemplateError(f"Failed to add GitHub workflows: {e}") from e