Plain is headed towards 1.0! Subscribe for development updates โ†’

  1import os
  2import subprocess
  3import sys
  4import tomllib
  5from importlib.util import find_spec
  6from pathlib import Path
  7
  8import click
  9
 10from plain.cli import register_cli
 11from plain.cli.print import print_event
 12
 13
 14def install_git_hook() -> None:
 15    hook_path = os.path.join(".git", "hooks", "pre-commit")
 16    if os.path.exists(hook_path):
 17        print("pre-commit hook already exists")
 18    else:
 19        with open(hook_path, "w") as f:
 20            f.write(
 21                """#!/bin/sh
 22plain pre-commit"""
 23            )
 24        os.chmod(hook_path, 0o755)
 25        print("pre-commit hook installed")
 26
 27
 28@register_cli("pre-commit")
 29@click.command()
 30@click.option("--install", is_flag=True)
 31def cli(install: bool) -> None:
 32    """Git pre-commit checks"""
 33    if install:
 34        install_git_hook()
 35        return
 36
 37    pyproject = Path("pyproject.toml")
 38
 39    if pyproject.exists():
 40        with open(pyproject, "rb") as f:
 41            pyproject = tomllib.load(f)
 42        for name, data in (
 43            pyproject.get("tool", {})
 44            .get("plain", {})
 45            .get("pre-commit", {})
 46            .get("run", {})
 47        ).items():
 48            cmd = data["cmd"]
 49            print_event(
 50                click.style(f"Custom[{name}]:", bold=True)
 51                + click.style(f" {cmd}", dim=True),
 52                newline=False,
 53            )
 54            result = subprocess.run(cmd, shell=True)
 55            if result.returncode != 0:
 56                sys.exit(result.returncode)
 57
 58    # Run this first since it's probably the most likely to fail
 59    if find_spec("plain.code"):
 60        check_short(
 61            click.style("Code:", bold=True)
 62            + click.style(" plain code check", dim=True),
 63            "plain",
 64            "code",
 65            "check",
 66        )
 67
 68    if Path("uv.lock").exists():
 69        check_short(
 70            click.style("Dependencies:", bold=True)
 71            + click.style(" uv lock --check", dim=True),
 72            "uv",
 73            "lock",
 74            "--check",
 75        )
 76
 77    if plain_db_connected():
 78        check_short(
 79            click.style("Preflight:", bold=True)
 80            + click.style(" plain preflight", dim=True),
 81            "plain",
 82            "preflight",
 83            "--quiet",
 84        )
 85        check_short(
 86            click.style("Migrate:", bold=True)
 87            + click.style(" plain migrate --check", dim=True),
 88            "plain",
 89            "migrate",
 90            "--check",
 91        )
 92        check_short(
 93            click.style("Migrations:", bold=True)
 94            + click.style(" plain makemigrations --dry-run --check", dim=True),
 95            "plain",
 96            "makemigrations",
 97            "--dry-run",
 98            "--check",
 99        )
100    else:
101        check_short(
102            click.style("Preflight:", bold=True)
103            + click.style(" plain preflight", dim=True),
104            "plain",
105            "preflight",
106            "--quiet",
107        )
108        click.secho("--> Skipping migration checks", bold=True, fg="yellow")
109
110    check_short(
111        click.style("Build:", bold=True) + click.style(" plain build", dim=True),
112        "plain",
113        "build",
114    )
115
116    if find_spec("plain.pytest"):
117        print_event(
118            click.style("Test:", bold=True) + click.style(" plain test", dim=True)
119        )
120        result = subprocess.run(["plain", "test"])
121        if result.returncode != 0:
122            sys.exit(result.returncode)
123
124
125def plain_db_connected() -> bool:
126    result = subprocess.run(
127        [
128            "plain",
129            "migrations",
130            "list",
131        ],
132        stdout=subprocess.DEVNULL,
133        stderr=subprocess.DEVNULL,
134    )
135    return result.returncode == 0
136
137
138def check_short(message: str, *args: str) -> None:
139    print_event(message, newline=False)
140    result = subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
141    if result.returncode != 0:
142        click.secho("โœ˜", fg="red")
143        click.secho(result.stdout.decode("utf-8"))
144        sys.exit(1)
145    else:
146        click.secho("โœ”", fg="green")