diff --git a/.gitignore b/.gitignore index e261124..132ce2c 100644 --- a/.gitignore +++ b/.gitignore @@ -170,3 +170,6 @@ cython_debug/ # PyPI configuration file .pypirc + +# AI agent logs +.[jJ]ules diff --git a/Makefile b/Makefile index 27718a0..1c8d0a3 100644 --- a/Makefile +++ b/Makefile @@ -5,29 +5,22 @@ DESCRIPTION ?= Python Project Template AUTHOR ?= Amr Abed EMAIL ?= amrabed GITHUB ?= amrabed -SOURCE ?= $(shell echo ${NAME} | tr '-' '_' | tr '[:upper:]' '[:lower:]') .PHONY: help help: # Show help @grep -E '^[a-zA-Z_-]+:.*?# .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?# "}; {printf "\033[36m%-15s\033[0m %s\n", $$1, $$2}' .PHONY: project -project: # Rename project (run once) - @if [ -d project ]; then mv project ${SOURCE}; fi - @sed -i '' 's/^::: project\.app/::: ${SOURCE}\.app/' docs/reference/app.md - @sed -i '' 's/^repo_name: .*/repo_name: ${GITHUB}\/${NAME}/' mkdocs.yml - @sed -i '' 's/^repo_url: .*/repo_url: https:\/\/github.com\/${GITHUB}\/${NAME}/' mkdocs.yml - @sed -i '' 's/^source = \[.*\]/source = \["${SOURCE}"\]/' pyproject.toml - @sed -i '' 's/^app = "project\.app:main"/app = "${SOURCE}\.app:main"/' pyproject.toml - @sed -i '' 's/^name = ".*"/name = "${SOURCE}"/' pyproject.toml - @sed -i '' 's/^description = ".*"/description = "${DESCRIPTION}"/' pyproject.toml - @sed -i '' 's/^authors = \[.*\]/authors = \["${AUTHOR} <${EMAIL}>"\]/' pyproject.toml - @sed -i '' 's/^# .*/# ${DESCRIPTION}/' docs/README.md - @sed -i '' 's/@.*/@${GITHUB}/' .github/CODEOWNERS - @sed -i '' 's/^github: \[.*\]/github: \[${GITHUB}\]/' .github/FUNDING.yml +project: uv # Rename project (run once) + @uv run rename \ + --name '$(subst ','\'',$(NAME))' \ + --description '$(subst ','\'',$(DESCRIPTION))' \ + --author '$(subst ','\'',$(AUTHOR))' \ + --email '$(subst ','\'',$(EMAIL))' \ + --github '$(subst ','\'',$(GITHUB))' uv: # Install uv - pipx install -f uv + @command -v uv >/dev/null 2>&1 || pipx install uv venv: # Create and activate virtual environment and install dependencies uv sync diff --git a/pyproject.toml b/pyproject.toml index 7b1fdf4..d59bc4f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,6 +12,7 @@ dependencies = [ [project.scripts] app = "project.app:main" +rename = "scripts.rename:main" [dependency-groups] dev = [ diff --git a/scripts/__init__.py b/scripts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/scripts/rename.py b/scripts/rename.py new file mode 100644 index 0000000..4388ac2 --- /dev/null +++ b/scripts/rename.py @@ -0,0 +1,62 @@ +import os +import re +import shutil +from pathlib import Path + +from click import ClickException, UsageError, command, echo, option + + +@command() +@option("--name", required=True, help="Project new name") +@option("--description", required=True, help="Project short description") +@option("--author", required=True, help="Author name") +@option("--email", required=True, help="Author email") +@option("--github", required=True, help="GitHub username") +def main(name: str, description: str, author: str, email: str, github: str): + # Validate name to prevent directory traversal or other injection + if not re.match(r"^[a-zA-Z0-9_-]+$", name): + raise UsageError( + f"Invalid project name '{name}'. Only alphanumeric characters, dashes, and underscores are allowed." + ) + + source = name.replace("-", "_").lower() + + echo(f"Initializing project '{name}' (source: '{source}')...") + + # 1. Rename project directory + if os.path.isdir("project"): + shutil.move("project", source) + elif not os.path.isdir(source): + raise ClickException(f"Error: Neither 'project' nor '{source}' directory found.") + + # 2. File modifications + replacements = [ + ("docs/reference/app.md", r"^::: project\.app", f"::: {source}.app"), + ("mkdocs.yml", r"^repo_name: .*", f"repo_name: {github}/{name}"), + ("mkdocs.yml", r"^repo_url: .*", f"repo_url: https://github.com/{github}/{name}"), + ("pyproject.toml", r"^source = \[.*\]", f'source = ["{source}"]'), + ("pyproject.toml", r'^app = "project\.app:main"', f'app = "{source}.app:main"'), + ("pyproject.toml", r'^name = ".*"', f'name = "{source}"'), + ("pyproject.toml", r'^description = ".*"', f'description = "{description}"'), + ("pyproject.toml", r"^authors = \[.*\]", f'authors = ["{author} <{email}>"]'), + ("docs/README.md", r"^# .*", f"# {description}"), + (".github/CODEOWNERS", r"@.*", f"@{github}"), + (".github/FUNDING.yml", r"^github: \[.*\]", f"github: [{github}]"), + ] + + for filepath, pattern, replacement in replacements: + path = Path(filepath) + if not path.exists(): + echo(f"Warning: File {filepath} not found, skipping.") + continue + + content = path.read_text() + new_content = re.sub(pattern, replacement, content, flags=re.MULTILINE) + path.write_text(new_content) + echo(f"Updated {filepath}") + + echo("Project initialization complete.") + + +if __name__ == "__main__": + main()