Last active
September 7, 2023 14:52
-
-
Save kfsone/15b37b28eb93a6aa74a750530a10313f to your computer and use it in GitHub Desktop.
ChecklistTracker for rich.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from __future__ import annotations | |
from typing import List, Optional | |
from rich.console import Console, ConsoleOptions, RenderableType | |
from rich.live import Live | |
from rich.spinner import Spinner | |
from rich.table import Table | |
from rich.text import Text | |
class ChecklistTracker: | |
""" | |
Provides a running checklist based on a rich Table. | |
When a new row is added, it has no check state, and it shows a spinner to let the | |
user know it is working. | |
Once a row is finished, it is assigned a check state (ok/nok) and a color to match, | |
and the spinner is replaced with the result of the check. | |
""" | |
columns: List[str] # column identities | |
rows: List[List[str]] # rows of columns | |
checks: Optional[List[bool]] # ok/fail status of each row | |
ok_style: str # rich style for OK rows | |
nok_style: str # rich style for non-OK rows | |
table: Table | |
def __init__(self, fields: List[str], ok: str="green", nok: str="red") -> None: | |
""" | |
Construct a checklist tracker from a list of fields to show, and styling | |
for rows that check as 'ok' vs 'nok' (not ok). | |
""" | |
self.columns = ["Check"] + (fields if isinstance(fields, list) else list(fields)) + ["Result"] | |
self.rows = [] | |
self.checks = [] | |
self.styles = { None: "dim", True: ok, False: nok } | |
self.table = None | |
def build_table(self) -> None: | |
""" | |
Internal method: Constructs the table, so we don't do it every refresh. | |
""" | |
self.table = Table(*self.columns, show_header=False, show_lines=False, show_edge=False, box=None) | |
styles = self.styles | |
for (row, check) in zip(self.rows, self.checks): | |
self.table.add_row(*row, style=styles[check]) | |
def start_list_item(self, *props: List[str], extra: str="") -> None: | |
""" | |
Begins a new row in the checklist. | |
""" | |
self.rows.append([Spinner("dots")] + list(Text(p) for p in props) + [Text(extra or "")]) | |
self.checks.append(None) | |
self.build_table() | |
def stop_list_item(self, ok: bool, result: str) -> None: | |
""" | |
Updates the last entry in the list with an ok/nok state and replaces the spinner with result text. | |
""" | |
self.rows[-1][0] = "✓" if ok else "✗" | |
self.rows[-1][-1] = Text(result) | |
self.checks[-1] = ok | |
self.build_table() | |
def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderableType: | |
if self.table: | |
yield self.table | |
if __name__ == "__main__": | |
import time | |
files = [ | |
"fred.txt", | |
"barney.txt", | |
"over9000.lines", | |
] | |
items = { | |
"first": [ ["some", "command"], True, "over the rainbow" ], | |
"second": [ ["sudo", "fetch", "beer"], False, "none in fridge"] | |
} | |
styles = { True: "green", False: "red" } | |
tracker = ChecklistTracker(["Filename", "Step", "Result"]) | |
with Live(tracker, auto_refresh=True, refresh_per_second=5) as live: # We'll refresh manually | |
for filepath in files: | |
for step, detail in items.items(): | |
ident = f"{filepath}/{step}" | |
active = tracker.start_list_item(filepath, step, extra=f"cmd: {' '.join(detail[0])}") | |
time.sleep(1.4) | |
tracker.stop_list_item(detail[1], detail[2]) | |
live.refresh() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment