set export := true
set dotenv-load := true

alias update := upgrade
alias tox := tests

EXAMPLE_DIRNAME := "example"
VERSION := `sed -n 's/^ *version.*=.*"\([^"]*\)".*/\1/p' pyproject.toml`

# Default is to list commands and check if uv is installed
[private]
@default:
  just --list
  if ! command -v uv >/dev/null; then \
    echo "Error - Command 'uv' is not available. Please install uv."; \
    exit 1; \
  fi

# Upgrade and install all dependencies
@upgrade:
    uv sync --all-groups --upgrade

# Regenerate uv.lock without upgrading anything (use after bumping version)
@relock:
    uv lock

# Install for development (uses uv.lock)
install:
    uv sync --frozen --all-groups

# Format source code
@format:
    uvx ruff format
    uvx ruff check --fix

# Check formatting of source code
@lint:
    uvx ruff format --check
    uvx ruff check
    uvx typos
    just _no-invisible-characters

# Fail if any tracked file holds an invisible or bidirectional control character.
# They do not show up in review, which is what makes them useful for hiding code
# (Trojan Source, CVE-2021-42574). If one is ever genuinely needed, add its path
# as a git pathspec exclusion below and say in a comment why it is there.
[private]
@_no-invisible-characters:
    if git grep -nIP '[\x{00AD}\x{200B}\x{200C}\x{200D}\x{2060}\x{202A}-\x{202E}\x{2066}-\x{2069}\x{FEFF}]' -- .; then \
        echo "Error - invisible or bidi control characters found in the lines above."; \
        exit 1; \
    fi

# Run test with coverage
@test-cov *ARGS:
    uv run --no-sync coverage run manage.py test {{ARGS}}
    uv run --no-sync coverage report

# Run test
@test *ARGS:
    uv run --no-sync manage.py test {{ARGS}}

# Run all tests (invokes tox)
@tests *ARGS:
    uvx --with tox-uv tox {{ARGS}}

# Fail if uv.lock does not name the version in pyproject.toml.
# `uv sync --frozen` does not check the project's own version, so without this a build
# passes with a stale lock: django-bootstrap4 shipped a bump with the previous version
# still in the lock, and `just build` reported everything green. Fix with `just relock`.
[private]
@_lockfile-matches-version:
    LOCKED=`awk -F'"' '/^version = /{v=$2} /editable = "\."/{print v; exit}' uv.lock`; \
    if [ "$LOCKED" != "{{ VERSION }}" ]; then \
        echo "Error - pyproject.toml says {{ VERSION }}, uv.lock says $LOCKED. Run 'just relock'."; \
        exit 1; \
    fi

# Build artefacts + packaging checks (local preflight)
@build: clean-build install _lockfile-matches-version
    uv build
    uvx twine check dist/*
    uvx check-manifest
    uvx pyroma .
    uvx check-wheel-contents dist/*.whl
    uv run --isolated --no-project --with dist/*.whl tests/smoke_test.py
    uv run --isolated --no-project --with dist/*.tar.gz tests/smoke_test.py

# Build the documentation
@docs: clean-docs install
    uv run -m sphinx -T -b html -d docs/_build/doctrees -D language=en docs docs/_build/html

# Run the example project
@example:
    if test -e {{ EXAMPLE_DIRNAME }}; then \
        cd "{{ EXAMPLE_DIRNAME }}" && python manage.py runserver; \
    else \
        echo "Example not found."; \
    fi

# Show changes and changelog entries since the last tag (release preflight)
@release-check:
    TAG=$(git describe --tags --abbrev=0); \
    echo "Commits touching src/ since $TAG. Each needs an entry, or is a no-op for users:"; \
    git log --format='  %h %s' "$TAG"..HEAD -- src/ | grep . || echo "  (none)"; \
    echo ""; \
    echo "Commits touching nothing in src/. Tooling, CI and docs still ship, so read these too:"; \
    git log --format='  %h %s' "$TAG"..HEAD -- . ':(exclude)src/' | grep . || echo "  (none)"; \
    echo ""; \
    echo "Entries in the Unreleased section of CHANGELOG.md:"; \
    awk '/^## Unreleased/{f=1;next} /^## /{f=0} f' CHANGELOG.md | grep '^- ' || echo "  (none)"; \
    echo ""; \
    echo "Version: pyproject.toml {{ VERSION }}, uv.lock `awk -F'\"' '/^version = /{v=$2} /editable = \"\\.\"/{print v; exit}' uv.lock`"; \
    echo ""; \
    echo "A commit touching src/ and other paths appears in both lists."

# Create and push a release tag (publishing happens in GitHub Actions)
@release-tag: porcelain branch build
    git tag -a v{{ VERSION }} -m "Release {{ VERSION }}"
    git push origin v{{ VERSION }}

# Backwards-compatible alias (kept so muscle memory doesn't publish locally)
@publish:
    echo "Local publishing is disabled."
    echo "Use: just release-tag"
    exit 1

# Show package version number
@version:
    echo "{{ VERSION }}"

# Delete the build directory and other build artefacts
[private]
@clean-build:
    rm -rf build dist src/*.egg-info .coverage*

# Delete the documentation build
[private]
@clean-docs:
    rm -rf docs/_build

# Check if the current Git branch is 'main'
[private]
@branch:
    if [ "`git rev-parse --abbrev-ref HEAD`" != "main" ]; then \
        echo "Error - Not on branch main."; \
        exit 1; \
    fi
    echo "On branch main.";

# Fail if working directory contains uncommitted or untracked changes
[private]
@porcelain:
    if [ -n "`git status --porcelain --untracked-files=all`" ]; then \
        echo "Error - Working directory is not clean. Commit your changes."; \
        exit 1; \
    fi
    echo "Working directory is clean.";
