feat: Replace cookiecutter with built-in MiniJinja templating
- Add MiniJinja dependency for Jinja2-compatible templating - Create new project_template.rs module for template rendering - Bundle snowflake template in templates/snowflake/ directory - Remove dependency on external cookiecutter Python tool - Handle Gitea publishing natively in Rust instead of Python hooks - Update default template path to use bundled template with fallback - Update README to reflect new built-in templating - All tests pass Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
"""Post-generation hook: optionally publish the new project to Gitea.
|
||||
|
||||
Cookiecutter renders this file as a Jinja template before running it, so a
|
||||
doubled opening brace anywhere outside a cookiecutter variable is read as the
|
||||
start of a Jinja expression and breaks the render. The JSON body below is
|
||||
therefore built with ``json.dumps`` rather than written out with literal braces.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
# This one comes from cookiecutter.json.
|
||||
PROJECT_NAME = "{{cookiecutter.project_name}}"
|
||||
|
||||
GITEA_URL = os.environ.get("GITEA_URL") # e.g. https://gitea.example.com
|
||||
GITEA_USER = os.environ.get("GITEA_USER")
|
||||
GITEA_TOKEN = os.environ.get("GITEA_TOKEN")
|
||||
|
||||
|
||||
def run(cmd, **kwargs):
|
||||
"""Run a command given as an argument list, raising if it fails."""
|
||||
return subprocess.run(cmd, check=True, **kwargs)
|
||||
|
||||
|
||||
def create_gitea_repo():
|
||||
# 1. Create the repo via the Gitea API. `--fail` makes curl exit non-zero on
|
||||
# an HTTP error, so a bad token surfaces here instead of silently
|
||||
# "succeeding" and leaving a push to fail later.
|
||||
api_url = f"{GITEA_URL.rstrip('/')}/api/v1/user/repos"
|
||||
payload = json.dumps({"name": PROJECT_NAME, "private": False, "auto_init": False})
|
||||
run([
|
||||
"curl", "-sS", "--fail", "-X", "POST", api_url,
|
||||
"-H", "Content-Type: application/json",
|
||||
"-H", f"Authorization: token {GITEA_TOKEN}",
|
||||
"-d", payload,
|
||||
])
|
||||
|
||||
# 2. Init git and push. `-b main` matters because the push below names
|
||||
# `main`, while git's built-in default is still `master`.
|
||||
repo_url = f"{GITEA_URL.rstrip('/')}/{GITEA_USER}/{PROJECT_NAME}.git"
|
||||
auth_url = repo_url.replace("://", f"://{GITEA_USER}:{GITEA_TOKEN}@", 1)
|
||||
|
||||
run(["git", "init", "-b", "main"])
|
||||
run(["git", "add", "."])
|
||||
run(["git", "commit", "-m", "Initial commit from cookiecutter"])
|
||||
# The stored remote keeps the plain URL: embedding the token would write it
|
||||
# in cleartext into .git/config for the life of the project. It is passed to
|
||||
# this one push only.
|
||||
run(["git", "remote", "add", "origin", repo_url])
|
||||
run(["git", "push", auth_url, "main"])
|
||||
run(["git", "fetch", "origin"])
|
||||
run(["git", "branch", "--set-upstream-to=origin/main", "main"])
|
||||
print(f"✅ Repo created and pushed: {repo_url}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
missing = [
|
||||
name
|
||||
for name, value in (
|
||||
("GITEA_URL", GITEA_URL),
|
||||
("GITEA_USER", GITEA_USER),
|
||||
("GITEA_TOKEN", GITEA_TOKEN),
|
||||
)
|
||||
if not value
|
||||
]
|
||||
if missing:
|
||||
# Publishing is opt-in. Without credentials the project is still
|
||||
# perfectly good on disk, so skip rather than fail the generation and
|
||||
# have cookiecutter throw the whole tree away.
|
||||
print(f"ℹ Skipping Gitea publish; not set: {', '.join(missing)}")
|
||||
sys.exit(0)
|
||||
try:
|
||||
create_gitea_repo()
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"❌ Error: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
Reference in New Issue
Block a user