Public Access
Post-merge / detect-type (push) Successful in 13s
Post-merge / validate-commit-msg (push) Successful in 10s
Post-merge / configure-repo (push) Successful in 28s
Post-merge / vikunja (push) Successful in 44s
Post-merge / sync-wiki (push) Successful in 58s
Post-merge / release (push) Successful in 1m7s
Post-merge / publish (push) Successful in 44s
Post-merge / badges (push) Successful in 1m6s
103 lines
2.6 KiB
Python
103 lines
2.6 KiB
Python
"""Operation step tracking with translated reports.
|
|
|
|
Provides a context manager that tracks multi-step operations and prints
|
|
a status report on exit. Steps are marked as pending, in_progress,
|
|
completed, or failed. On exception, the last in-progress step is
|
|
marked as failed.
|
|
|
|
Usage::
|
|
|
|
from devx.utils.step_tracker import track_steps
|
|
|
|
with track_steps() as tracker:
|
|
tracker.begin("Install dependencies")
|
|
install_deps()
|
|
tracker.done()
|
|
|
|
tracker.begin("Run tests")
|
|
run_tests()
|
|
tracker.done()
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections.abc import Generator
|
|
from contextlib import contextmanager
|
|
|
|
import click
|
|
|
|
_STATUS_ICONS = {
|
|
"completed": "✓",
|
|
"failed": "✗",
|
|
"pending": "○",
|
|
"in_progress": "◌",
|
|
}
|
|
|
|
_STATUS_COLORS = {
|
|
"completed": "green",
|
|
"failed": "red",
|
|
"in_progress": "yellow",
|
|
"pending": "white",
|
|
}
|
|
|
|
|
|
class Step:
|
|
"""A single tracked step in an operation."""
|
|
|
|
def __init__(self, name: str) -> None:
|
|
self.name = name
|
|
self.status = "pending"
|
|
|
|
|
|
class StepTracker:
|
|
"""Tracks steps of an operation and prints a report on exit."""
|
|
|
|
def __init__(self) -> None:
|
|
self.steps: list[Step] = []
|
|
|
|
def begin(self, name: str) -> None:
|
|
"""Start a new step.
|
|
|
|
Args:
|
|
name: Human-readable step name.
|
|
"""
|
|
step = Step(name)
|
|
self.steps.append(step)
|
|
step.status = "in_progress"
|
|
|
|
def done(self) -> None:
|
|
"""Mark the most recent in-progress step as completed."""
|
|
if self.steps and self.steps[-1].status == "in_progress":
|
|
self.steps[-1].status = "completed"
|
|
|
|
|
|
@contextmanager
|
|
def track_steps() -> Generator[StepTracker, None, None]:
|
|
"""Context manager that tracks steps and prints a report on exit.
|
|
|
|
On exception the last in-progress step is marked as failed.
|
|
The report is printed in the ``finally`` block so it always appears.
|
|
|
|
Yields:
|
|
A :class:`StepTracker` instance to track steps with.
|
|
"""
|
|
tracker = StepTracker()
|
|
try:
|
|
yield tracker
|
|
except Exception:
|
|
for step in reversed(tracker.steps):
|
|
if step.status == "in_progress":
|
|
step.status = "failed"
|
|
raise
|
|
finally:
|
|
_print_report(tracker.steps)
|
|
|
|
|
|
def _print_report(steps: list[Step]) -> None:
|
|
"""Print an operation report to stdout."""
|
|
click.secho("=== Operation Report ===", fg="bright_cyan")
|
|
for step in steps:
|
|
icon = _STATUS_ICONS.get(step.status, "?")
|
|
color = _STATUS_COLORS.get(step.status)
|
|
click.secho(f" {icon} {step.name} ({step.status})", fg=color)
|