DEVX-97: fix: wrap all user-facing strings with _() for i18n completeness

This commit is contained in:
emil
2026-06-28 15:01:07 +00:00
parent 1a89738dd4
commit 40a65cb0a6
9 changed files with 953 additions and 389 deletions
+4 -3
View File
@@ -18,6 +18,7 @@ import subprocess # nosec B404
import click import click
from devx.ci._shared import write_github_output from devx.ci._shared import write_github_output
from devx.i18n import _
RELEASE_RE = re.compile(r"^release: v\d+\.\d+\.\d+") RELEASE_RE = re.compile(r"^release: v\d+\.\d+\.\d+")
@@ -44,13 +45,13 @@ def is_release_commit(message: str) -> bool:
def main() -> None: def main() -> None:
"""Detect if the latest commit is a release commit and set GITHUB_OUTPUT.""" """Detect if the latest commit is a release commit and set GITHUB_OUTPUT."""
msg = get_commit_message() msg = get_commit_message()
click.echo(f"Commit message: {msg}") click.echo(_("Commit message: {msg}", msg=msg))
is_release = is_release_commit(msg) is_release = is_release_commit(msg)
write_github_output("is-release", "true" if is_release else "false") write_github_output("is-release", "true" if is_release else "false")
if is_release: if is_release:
click.echo("Release commit — skipping all post-merge jobs.") click.echo(_("Release commit — skipping all post-merge jobs."))
else: else:
click.echo("Regular merge commit — running all post-merge jobs.") click.echo(_("Regular merge commit — running all post-merge jobs."))
if __name__ == "__main__": # pragma: no cover if __name__ == "__main__": # pragma: no cover
+14 -10
View File
@@ -30,6 +30,7 @@ import click
import requests import requests
from devx.config import GITEA_API_URL, REPO_NAME, REPO_OWNER from devx.config import GITEA_API_URL, REPO_NAME, REPO_OWNER
from devx.i18n import _
DEFAULT_MAX_RUNNERS = 3 DEFAULT_MAX_RUNNERS = 3
@@ -55,9 +56,9 @@ def query_runners(api_url: str, token: str, owner: str, repo: str) -> int:
data = r.json() data = r.json()
total += data.get("total_count", 0) total += data.get("total_count", 0)
else: else:
click.echo(f"Warning: repo-level runners query returned HTTP {r.status_code}", err=True) click.echo(_("Warning: repo-level runners query returned HTTP {status}", status=r.status_code), err=True)
except (requests.RequestException, ValueError) as e: except (requests.RequestException, ValueError) as e:
click.echo(f"Warning: repo-level runners query failed: {e}", err=True) click.echo(_("Warning: repo-level runners query failed: {error}", error=e), err=True)
# 2. Organization-level runners # 2. Organization-level runners
try: try:
@@ -70,9 +71,9 @@ def query_runners(api_url: str, token: str, owner: str, repo: str) -> int:
data = r.json() data = r.json()
total += data.get("total_count", 0) total += data.get("total_count", 0)
else: else:
click.echo(f"Warning: org-level runners query returned HTTP {r.status_code}", err=True) click.echo(_("Warning: org-level runners query returned HTTP {status}", status=r.status_code), err=True)
except (requests.RequestException, ValueError) as e: except (requests.RequestException, ValueError) as e:
click.echo(f"Warning: org-level runners query failed: {e}", err=True) click.echo(_("Warning: org-level runners query failed: {error}", error=e), err=True)
# 3. Instance-level runners (requires admin scope) # 3. Instance-level runners (requires admin scope)
try: try:
@@ -85,9 +86,12 @@ def query_runners(api_url: str, token: str, owner: str, repo: str) -> int:
data = r.json() data = r.json()
total += data.get("total_count", 0) total += data.get("total_count", 0)
elif r.status_code != 403: # 403 is expected without admin scope elif r.status_code != 403: # 403 is expected without admin scope
click.echo(f"Warning: instance-level runners query returned HTTP {r.status_code}", err=True) click.echo(
_("Warning: instance-level runners query returned HTTP {status}", status=r.status_code),
err=True,
)
except (requests.RequestException, ValueError) as e: except (requests.RequestException, ValueError) as e:
click.echo(f"Warning: instance-level runners query failed: {e}", err=True) click.echo(_("Warning: instance-level runners query failed: {error}", error=e), err=True)
return total return total
@@ -165,8 +169,8 @@ def main(
with open(gh_output, "a", encoding="utf-8") as f: # noqa: PTH123 with open(gh_output, "a", encoding="utf-8") as f: # noqa: PTH123
f.write(f"runner-count={count}\n") f.write(f"runner-count={count}\n")
f.write(f"runner-indices={json.dumps(indices)}\n") f.write(f"runner-indices={json.dumps(indices)}\n")
click.echo(f"Runner count: {count}") click.echo(_("Runner count: {count}", count=count))
click.echo(f"Runner indices: {indices}") click.echo(_("Runner indices: {indices}", indices=indices))
return return
if output_count: if output_count:
@@ -178,8 +182,8 @@ def main(
return return
# Default: output both as key=value pairs for CI consumption # Default: output both as key=value pairs for CI consumption
click.echo(f"count={count}") click.echo(_("count={count}", count=count))
click.echo(f"indices={json.dumps(indices)}") click.echo(_("indices={indices}", indices=json.dumps(indices)))
if __name__ == "__main__": # pragma: no cover if __name__ == "__main__": # pragma: no cover
+12 -4
View File
@@ -102,17 +102,25 @@ def main(pattern: str, runner_index: int | None, max_runners: int, github_env: b
groups = distribute(files, max_runners) groups = distribute(files, max_runners)
for i, group in enumerate(groups): for i, group in enumerate(groups):
labels = " ".join(group) if group else "(none)" labels = " ".join(group) if group else "(none)"
click.echo(f"Runner {i}: {labels}") click.echo(_("Runner {i}: {labels}", i=i, labels=labels))
return return
if skip_if_excess and github_env and runner_index > max_runners: if skip_if_excess and github_env and runner_index > max_runners:
click.echo(f"Skipping — runner index {runner_index} > max runners {max_runners}") click.echo(
_(
"Skipping — runner index {runner_index} > max runners {max_runners}",
runner_index=runner_index,
max_runners=max_runners,
)
)
write_github_env("ASSIGNED_FILES", "") write_github_env("ASSIGNED_FILES", "")
write_github_env("SKIP", "true") write_github_env("SKIP", "true")
return return
if runner_index < 1: if runner_index < 1:
raise click.ClickException(f"Runner index {runner_index} is out of range (must be >= 1)") raise click.ClickException(
_("Runner index {runner_index} is out of range (must be >= 1)", runner_index=runner_index)
)
zero_based = runner_index - 1 zero_based = runner_index - 1
assigned = files_for_runner(files, zero_based, max_runners) assigned = files_for_runner(files, zero_based, max_runners)
@@ -121,7 +129,7 @@ def main(pattern: str, runner_index: int | None, max_runners: int, github_env: b
if github_env: if github_env:
write_github_env("ASSIGNED_FILES", encoded) write_github_env("ASSIGNED_FILES", encoded)
write_github_env("SKIP", "false") write_github_env("SKIP", "false")
click.echo(f"Assigned {len(assigned)} files to runner {runner_index}") click.echo(_("Assigned {count} files to runner {runner_index}", count=len(assigned), runner_index=runner_index))
return return
click.echo(encoded) click.echo(encoded)
+19 -4
View File
@@ -162,17 +162,25 @@ def main(
groups = distribute(items, weights, max_runners) groups = distribute(items, weights, max_runners)
for i, group in enumerate(groups): for i, group in enumerate(groups):
labels = " ".join(group) if group else "(none)" labels = " ".join(group) if group else "(none)"
click.echo(f"Runner {i}: {labels}") click.echo(_("Runner {i}: {labels}", i=i, labels=labels))
return return
if skip_if_excess and github_env and runner_index > max_runners: if skip_if_excess and github_env and runner_index > max_runners:
click.echo(f"Skipping — runner index {runner_index} > max runners {max_runners}") click.echo(
_(
"Skipping — runner index {runner_index} > max runners {max_runners}",
runner_index=runner_index,
max_runners=max_runners,
)
)
write_github_env("ASSIGNED_ITEMS", "") write_github_env("ASSIGNED_ITEMS", "")
write_github_env("SKIP", "true") write_github_env("SKIP", "true")
return return
if runner_index < 1: if runner_index < 1:
raise click.ClickException(f"Runner index {runner_index} is out of range (must be >= 1)") raise click.ClickException(
_("Runner index {runner_index} is out of range (must be >= 1)", runner_index=runner_index)
)
zero_based = runner_index - 1 zero_based = runner_index - 1
assigned = items_for_runner(items, weights, zero_based, max_runners) assigned = items_for_runner(items, weights, zero_based, max_runners)
@@ -181,7 +189,14 @@ def main(
if github_env: if github_env:
write_github_env("ASSIGNED_ITEMS", encoded) write_github_env("ASSIGNED_ITEMS", encoded)
write_github_env("SKIP", "false") write_github_env("SKIP", "false")
click.echo(f"Assigned {len(assigned)} items to runner {runner_index}: {encoded}") click.echo(
_(
"Assigned {count} items to runner {runner_index}: {encoded}",
count=len(assigned),
runner_index=runner_index,
encoded=encoded,
)
)
return return
click.echo(encoded) click.echo(encoded)
+1 -1
View File
@@ -86,7 +86,7 @@ def cli(pytest_args: tuple[str, ...]) -> None:
cmd = [sys.executable, "-m", "pytest"] cmd = [sys.executable, "-m", "pytest"]
cmd.extend(pytest_args) cmd.extend(pytest_args)
click.echo(f"Running: {' '.join(cmd)}") click.echo(_("Running: {cmd}", cmd=" ".join(cmd)))
process = subprocess.Popen( # nosec B603 process = subprocess.Popen( # nosec B603
cmd, cmd,
+22 -11
View File
@@ -28,6 +28,8 @@ from typing import Any
import click import click
from devx.i18n import _
def _repo_root() -> Path: def _repo_root() -> Path:
"""Resolve repo root from GITHUB_WORKSPACE or cwd.""" """Resolve repo root from GITHUB_WORKSPACE or cwd."""
@@ -68,7 +70,7 @@ def fetch_latest_master(branch: str = "master") -> None:
""" """
_run(["git", "fetch", "origin", branch]) # nosec B607 _run(["git", "fetch", "origin", branch]) # nosec B607
_run(["git", "reset", "--hard", f"origin/{branch}"]) # nosec B607 _run(["git", "reset", "--hard", f"origin/{branch}"]) # nosec B607
click.echo(f"Synced to latest origin/{branch}") click.echo(_("Synced to latest origin/{branch}", branch=branch))
def generate_badges(output_dir: str) -> None: def generate_badges(output_dir: str) -> None:
@@ -76,8 +78,8 @@ def generate_badges(output_dir: str) -> None:
_run([sys.executable, "-m", "devx.tools.generate_badges", "--output-dir", output_dir]) _run([sys.executable, "-m", "devx.tools.generate_badges", "--output-dir", output_dir])
badges = list(Path(output_dir).glob("*.svg")) badges = list(Path(output_dir).glob("*.svg"))
if not badges: if not badges:
raise click.ClickException("No badge SVG files generated") raise click.ClickException(_("No badge SVG files generated"))
click.echo(f"Generated {len(badges)} badge files") click.echo(_("Generated {count} badge files", count=len(badges)))
def push_to_badges_branch(badges_dir: str) -> str: def push_to_badges_branch(badges_dir: str) -> str:
@@ -99,12 +101,12 @@ def push_to_badges_branch(badges_dir: str) -> str:
_run(["git", "add", "./*.svg"]) # nosec B607 _run(["git", "add", "./*.svg"]) # nosec B607
_run(["git", "commit", "--no-verify", "-m", "Update badges [skip ci]"]) # nosec B607 _run(["git", "commit", "--no-verify", "-m", "Update badges [skip ci]"]) # nosec B607
_run(["git", "push", "origin", "badges", "--force"]) # nosec B607 _run(["git", "push", "origin", "badges", "--force"]) # nosec B607
click.echo("Badges pushed to badges branch") click.echo(_("Badges pushed to badges branch"))
# Get the commit SHA of the badges branch # Get the commit SHA of the badges branch
result = _run_capture(["git", "rev-parse", "HEAD"]) # nosec B607 result = _run_capture(["git", "rev-parse", "HEAD"]) # nosec B607
sha = result.stdout.strip() sha = result.stdout.strip()
click.echo(f"Badges commit SHA: {sha}") click.echo(_("Badges commit SHA: {sha}", sha=sha))
return sha return sha
@@ -142,11 +144,11 @@ def update_readme_with_badge_sha(badges_sha: str, repo_root: Path | None = None)
new_content = update_badge_urls(content, badges_sha) new_content = update_badge_urls(content, badges_sha)
if new_content != content: if new_content != content:
filepath.write_text(new_content) filepath.write_text(new_content)
click.echo(f"Updated badge URLs in {filename}") click.echo(_("Updated badge URLs in {filename}", filename=filename))
updated_any = True updated_any = True
if not updated_any: if not updated_any:
click.echo("No badge URLs found to update — README already up to date") click.echo(_("No badge URLs found to update — README already up to date"))
return return
_run(["git", "add", "README.md", "docs/index.md"]) # nosec B607 _run(["git", "add", "README.md", "docs/index.md"]) # nosec B607
@@ -160,7 +162,7 @@ def update_readme_with_badge_sha(badges_sha: str, repo_root: Path | None = None)
] ]
) # nosec B607 ) # nosec B607
_run(["git", "push", "origin", "master"]) # nosec B607 _run(["git", "push", "origin", "master"]) # nosec B607
click.echo(f"Pushed README update with badge SHA {badges_sha[:8]}") click.echo(_("Pushed README update with badge SHA {sha}", sha=badges_sha[:8]))
@click.command() @click.command()
@@ -193,13 +195,22 @@ def main(output_dir: str, branch: str, no_readme_update: bool, retries: int) ->
except (subprocess.CalledProcessError, RuntimeError) as exc: except (subprocess.CalledProcessError, RuntimeError) as exc:
last_error = exc last_error = exc
if attempt < retries: if attempt < retries:
click.echo(f"Badge push attempt {attempt}/{retries} failed — retrying: {exc}") click.echo(
_(
"Badge push attempt {attempt}/{retries} failed — retrying: {error}",
attempt=attempt,
retries=retries,
error=exc,
)
)
time.sleep(10) time.sleep(10)
with contextlib.suppress(subprocess.CalledProcessError): with contextlib.suppress(subprocess.CalledProcessError):
fetch_latest_master(branch) fetch_latest_master(branch)
else: else:
click.echo(f"Badge push failed after {retries} attempts: {exc}") click.echo(_("Badge push failed after {retries} attempts: {error}", retries=retries, error=exc))
raise click.ClickException(f"Badge push failed after {retries} attempts: {last_error}") raise click.ClickException(
_("Badge push failed after {retries} attempts: {error}", retries=retries, error=last_error)
)
if __name__ == "__main__": # pragma: no cover if __name__ == "__main__": # pragma: no cover
+19 -10
View File
@@ -200,9 +200,9 @@ def main(
total_kept = 0 total_kept = 0
total_failed = 0 total_failed = 0
for name in names: for name in names:
click.echo(f"\n{'=' * 60}") click.echo(_("\n{separator}", separator="=" * 60))
click.echo(f"Package: {owner}/{name}") click.echo(_("Package: {owner}/{name}", owner=owner, name=name))
click.echo(f"{'=' * 60}") click.echo(_("{separator}", separator="=" * 60))
try: try:
versions = list_package_versions(base_url, owner, name, token) versions = list_package_versions(base_url, owner, name, token)
except requests.RequestException as exc: except requests.RequestException as exc:
@@ -217,17 +217,19 @@ def main(
click.echo(_("No versions found.")) click.echo(_("No versions found."))
continue continue
click.echo(f"Found {len(versions)} version(s):") click.echo(_("Found {count} version(s):", count=len(versions)))
for v in sort_versions_by_date(versions): for v in sort_versions_by_date(versions):
click.echo(f" {v.get('version', '?')} (created: {v.get('created_at', '?')})") click.echo(
_(" {version} (created: {created})", version=v.get("version", "?"), created=v.get("created_at", "?"))
)
to_delete = select_for_deletion(versions, keep) to_delete = select_for_deletion(versions, keep)
kept_count = len(versions) - len(to_delete) kept_count = len(versions) - len(to_delete)
click.echo(f"\nKeeping {kept_count}, would delete {len(to_delete)}") click.echo(_("\nKeeping {kept}, would delete {count}", kept=kept_count, count=len(to_delete)))
if dry_run: if dry_run:
for v in to_delete: for v in to_delete:
click.echo(f" [dry-run] Would delete: {v.get('version', '?')}") click.echo(_(" [dry-run] Would delete: {version}", version=v.get("version", "?")))
total_kept += kept_count total_kept += kept_count
continue continue
@@ -236,17 +238,24 @@ def main(
for v in to_delete: for v in to_delete:
version = str(v.get("version", "")) version = str(v.get("version", ""))
if delete_package_version(base_url, owner, name, version, token): if delete_package_version(base_url, owner, name, version, token):
click.echo(f" Deleted: {version}") click.echo(_(" Deleted: {version}", version=version))
deleted_count += 1 deleted_count += 1
else: else:
click.echo(f" FAILED to delete: {version}", err=True) click.echo(_(" FAILED to delete: {version}", version=version), err=True)
failed_count += 1 failed_count += 1
total_deleted += deleted_count total_deleted += deleted_count
total_kept += kept_count total_kept += kept_count
total_failed += failed_count total_failed += failed_count
click.echo(f"\nDone. Deleted {total_deleted}, kept {total_kept}, failed {total_failed}.") click.echo(
_(
"\nDone. Deleted {deleted}, kept {kept}, failed {failed}.",
deleted=total_deleted,
kept=total_kept,
failed=total_failed,
)
)
if total_failed > 0: if total_failed > 0:
raise click.ClickException(_("Failed to delete {count} image version(s)", count=total_failed)) raise click.ClickException(_("Failed to delete {count} image version(s)", count=total_failed))
+39 -27
View File
@@ -27,6 +27,8 @@ from pathlib import Path
import click import click
from devx.i18n import _
# Coverage regex matches "TOTAL ... NN%" or "TOTAL ... NN.NN%" # Coverage regex matches "TOTAL ... NN%" or "TOTAL ... NN.NN%"
_COVERAGE_RE = re.compile(r"TOTAL.*?(\d+(?:\.\d+)?)%") _COVERAGE_RE = re.compile(r"TOTAL.*?(\d+(?:\.\d+)?)%")
_PASSED_RE = re.compile(r"(\d+) passed") _PASSED_RE = re.compile(r"(\d+) passed")
@@ -197,17 +199,19 @@ def read_version(repo_root: Path) -> str:
""" """
pkg = detect_package_name(repo_root) pkg = detect_package_name(repo_root)
if pkg is None: if pkg is None:
click.echo(" WARNING: No Python package found under src/ — version badge will show 'unknown'") click.echo(_(" WARNING: No Python package found under src/ — version badge will show 'unknown'"))
return "unknown" return "unknown"
init_file = repo_root / "src" / pkg / "__init__.py" init_file = repo_root / "src" / pkg / "__init__.py"
if not init_file.exists(): if not init_file.exists():
click.echo(f" WARNING: {init_file} not found — version badge will show 'unknown'") click.echo(_(" WARNING: {init_file} not found — version badge will show 'unknown'", init_file=init_file))
return "unknown" return "unknown"
content = init_file.read_text() content = init_file.read_text()
match = re.search(r'__version__\s*=\s*["\']([^"\']+)["\']', content) match = re.search(r'__version__\s*=\s*["\']([^"\']+)["\']', content)
if match: if match:
return match.group(1) return match.group(1)
click.echo(f" WARNING: No __version__ found in {init_file} — version badge will show 'unknown'") click.echo(
_(" WARNING: No __version__ found in {init_file} — version badge will show 'unknown'", init_file=init_file)
)
return "unknown" return "unknown"
@@ -278,11 +282,11 @@ def collect_coverage_and_tests(repo_root: Path) -> tuple[dict[str, str | int], d
""" """
cov_target = detect_coverage_target(repo_root) cov_target = detect_coverage_target(repo_root)
if cov_target is None: if cov_target is None:
click.echo(" WARNING: No coverage target detected (no src/ package, no --cov in pyproject.toml)") click.echo(_(" WARNING: No coverage target detected (no src/ package, no --cov in pyproject.toml)"))
return make_badge("coverage", "unknown", "lightgrey"), make_badge("tests", "unknown", "lightgrey") return make_badge("coverage", "unknown", "lightgrey"), make_badge("tests", "unknown", "lightgrey")
testpaths = detect_testpaths(repo_root) testpaths = detect_testpaths(repo_root)
click.echo(f" Test paths: {testpaths or '(pytest defaults)'}") click.echo(_(" Test paths: {testpaths}", testpaths=testpaths or "(pytest defaults)"))
cmd = [ cmd = [
sys.executable, sys.executable,
@@ -302,18 +306,18 @@ def collect_coverage_and_tests(repo_root: Path) -> tuple[dict[str, str | int], d
if coverage is not None: if coverage is not None:
cov_badge = make_badge("coverage", f"{coverage:.0f}%", coverage_color(coverage)) cov_badge = make_badge("coverage", f"{coverage:.0f}%", coverage_color(coverage))
else: else:
click.echo(f" WARNING: Could not extract coverage from pytest output (rc={rc})") click.echo(_(" WARNING: Could not extract coverage from pytest output (rc={rc})", rc=rc))
click.echo(f" pytest stdout (last 300 chars): {stdout.strip()[-300:]}") click.echo(_(" pytest stdout (last 300 chars): {stdout}", stdout=stdout.strip()[-300:]))
click.echo(f" pytest stderr (last 300 chars): {stderr.strip()[-300:]}") click.echo(_(" pytest stderr (last 300 chars): {stderr}", stderr=stderr.strip()[-300:]))
cov_badge = make_badge("coverage", "unknown", "red") cov_badge = make_badge("coverage", "unknown", "red")
test_count = extract_test_count(combined) test_count = extract_test_count(combined)
if test_count is not None: if test_count is not None:
tests_badge = make_badge("tests", f"{test_count} passing", "brightgreen" if rc == 0 else "red") tests_badge = make_badge("tests", f"{test_count} passing", "brightgreen" if rc == 0 else "red")
else: else:
click.echo(f" WARNING: Could not extract test count from pytest output (rc={rc})") click.echo(_(" WARNING: Could not extract test count from pytest output (rc={rc})", rc=rc))
click.echo(f" pytest stdout (last 300 chars): {stdout.strip()[-300:]}") click.echo(_(" pytest stdout (last 300 chars): {stdout}", stdout=stdout.strip()[-300:]))
click.echo(f" pytest stderr (last 300 chars): {stderr.strip()[-300:]}") click.echo(_(" pytest stderr (last 300 chars): {stderr}", stderr=stderr.strip()[-300:]))
tests_badge = make_badge("tests", "unknown", "red") tests_badge = make_badge("tests", "unknown", "red")
return cov_badge, tests_badge return cov_badge, tests_badge
@@ -328,8 +332,8 @@ def collect_doc_coverage(repo_root: Path) -> dict[str, str | int]:
doc_pct = extract_doc_coverage(stdout) doc_pct = extract_doc_coverage(stdout)
if doc_pct is not None: if doc_pct is not None:
return make_badge("docs", f"{doc_pct}%", doc_coverage_color(doc_pct)) return make_badge("docs", f"{doc_pct}%", doc_coverage_color(doc_pct))
click.echo(f" WARNING: Could not extract doc coverage (rc={rc})") click.echo(_(" WARNING: Could not extract doc coverage (rc={rc})", rc=rc))
click.echo(f" stderr: {stderr.strip()[:200]}") click.echo(_(" stderr: {stderr}", stderr=stderr.strip()[:200]))
return make_badge("docs", "unknown", "red") return make_badge("docs", "unknown", "red")
@@ -356,16 +360,16 @@ def collect_quality(repo_root: Path) -> dict[str, str | int]:
results.append(False) results.append(False)
# Distinguish "tool not installed" from "tool found issues" # Distinguish "tool not installed" from "tool found issues"
if "No module named" in stderr or "not found" in stderr.lower(): if "No module named" in stderr or "not found" in stderr.lower():
click.echo(f" WARNING: {name} not installed — skipping (counted as pass)") click.echo(_(" WARNING: {name} not installed — skipping (counted as pass)", name=name))
results[-1] = True results[-1] = True
tool_names.append(f"{name}: not installed (skipped)") tool_names.append(f"{name}: not installed (skipped)")
else: else:
tool_names.append(f"{name}: FAIL") tool_names.append(f"{name}: FAIL")
click.echo(f" WARNING: {name} failed (rc={rc})") click.echo(_(" WARNING: {name} failed (rc={rc})", name=name, rc=rc))
click.echo(f" stderr: {stderr.strip()[:200]}") click.echo(_(" stderr: {stderr}", stderr=stderr.strip()[:200]))
all_pass = all(results) all_pass = all(results)
click.echo(f" Quality checks: {', '.join(tool_names)}") click.echo(_(" Quality checks: {checks}", checks=", ".join(tool_names)))
return make_badge("code quality", "A" if all_pass else "F", "brightgreen" if all_pass else "red") return make_badge("code quality", "A" if all_pass else "F", "brightgreen" if all_pass else "red")
@@ -377,28 +381,28 @@ def generate_badges(output_dir: Path, repo_root: Path | None = None) -> dict[str
repo_root: Repository root (auto-detected if None). repo_root: Repository root (auto-detected if None).
""" """
root = repo_root or resolve_repo_root() root = repo_root or resolve_repo_root()
click.echo(f" Repo root: {root}") click.echo(_(" Repo root: {root}", root=root))
pkg = detect_package_name(root) pkg = detect_package_name(root)
click.echo(f" Package: {pkg or 'none'}") click.echo(_(" Package: {pkg}", pkg=pkg or "none"))
badges: dict[str, dict[str, str | int]] = {} badges: dict[str, dict[str, str | int]] = {}
# 1. Code coverage + test count (single pytest-cov run) # 1. Code coverage + test count (single pytest-cov run)
click.echo(" Collecting coverage and tests...") click.echo(_(" Collecting coverage and tests..."))
cov_badge, tests_badge = collect_coverage_and_tests(root) cov_badge, tests_badge = collect_coverage_and_tests(root)
badges["coverage"] = cov_badge badges["coverage"] = cov_badge
badges["tests"] = tests_badge badges["tests"] = tests_badge
# 2. Documentation coverage # 2. Documentation coverage
click.echo(" Collecting doc coverage...") click.echo(_(" Collecting doc coverage..."))
badges["docs"] = collect_doc_coverage(root) badges["docs"] = collect_doc_coverage(root)
# 3. Code quality (ruff + pyright + bandit) # 3. Code quality (ruff + pyright + bandit)
click.echo(" Collecting code quality...") click.echo(_(" Collecting code quality..."))
badges["quality"] = collect_quality(root) badges["quality"] = collect_quality(root)
# 4. Version # 4. Version
click.echo(" Collecting version...") click.echo(_(" Collecting version..."))
version = read_version(root) version = read_version(root)
badges["version"] = make_badge("version", f"v{version}", "blue") badges["version"] = make_badge("version", f"v{version}", "blue")
@@ -411,7 +415,7 @@ def generate_badges(output_dir: Path, repo_root: Path | None = None) -> dict[str
svg = render_svg(str(badge["label"]), str(badge["message"]), str(badge["color"])) svg = render_svg(str(badge["label"]), str(badge["message"]), str(badge["color"]))
path = output_dir / f"{name}.svg" path = output_dir / f"{name}.svg"
path.write_text(svg) path.write_text(svg)
click.echo(f" Generated: {path}") click.echo(_(" Generated: {path}", path=path))
return badges return badges
@@ -431,11 +435,19 @@ def cli(output_dir: str, repo_root: str | None) -> None:
"""Generate self-contained SVG badge files from project metrics.""" """Generate self-contained SVG badge files from project metrics."""
out = Path(output_dir) out = Path(output_dir)
root = Path(repo_root) if repo_root else None root = Path(repo_root) if repo_root else None
click.echo(f"Generating badges in {out}...") click.echo(_("Generating badges in {out}...", out=out))
badges = generate_badges(out, repo_root=root) badges = generate_badges(out, repo_root=root)
click.echo(f"\nGenerated {len(badges)} badges:") click.echo(_("\nGenerated {count} badges:", count=len(badges)))
for name, badge in badges.items(): for name, badge in badges.items():
click.echo(f" {name}: {badge['label']}={badge['message']} ({badge['color']})") click.echo(
_(
" {name}: {label}={message} ({color})",
name=name,
label=badge["label"],
message=badge["message"],
color=badge["color"],
)
)
if __name__ == "__main__": # pragma: no cover if __name__ == "__main__": # pragma: no cover
+823 -319
View File
File diff suppressed because it is too large Load Diff