This commit is contained in:
2026-02-12 17:19:24 -08:00
parent ccadea0576
commit a92a7fdc7d
8 changed files with 1132 additions and 39 deletions

View File

@@ -6,7 +6,7 @@ specialized handling for planets plus generic list/dict/object renderers.
from __future__ import annotations
from typing import Any, Dict, List, Optional, Sequence
from typing import Any, Dict, List, Optional, Sequence, Tuple
from rich import box
from rich.console import Console, Group
@@ -15,6 +15,8 @@ from rich.table import Table
from tarot import Card
from utils.attributes import Color, Planet
from utils.dates import format_month_day
from utils.object_formatting import format_value, get_object_attributes, is_nested_object
class Renderer:
@@ -69,27 +71,10 @@ class Renderer:
self.console.print(table)
def print_cards_table(self, cards: List[Card]) -> None:
table = Table(title="Cards", show_lines=True)
table.add_column("#", justify="right")
table.add_column("Name")
table.add_column("Arcana")
table.add_column("Suit")
table.add_column("Pip/Court")
for card in cards:
suit = getattr(getattr(card, "suit", None), "name", "")
pip = str(getattr(card, "pip", "")) if getattr(card, "pip", None) else ""
court = getattr(card, "court_rank", "")
pip_display = court or pip
table.add_row(
str(getattr(card, "number", "")),
getattr(card, "name", ""),
getattr(card, "arcana", ""),
suit,
pip_display,
)
self.console.print(table)
for idx, card in enumerate(cards):
self._render_card(card)
if idx < len(cards) - 1:
self.console.print()
# Internal renderers -------------------------------------------------
def _render_card(self, card: Card) -> None:
@@ -99,7 +84,6 @@ class Renderer:
arcana = getattr(card, "arcana", "")
suit_obj = getattr(card, "suit", None)
suit = getattr(suit_obj, "name", "") if suit_obj is not None else ""
pip = getattr(card, "pip", None)
number = getattr(card, "number", "")
keywords = getattr(card, "keywords", None) or []
meaning = getattr(card, "meaning", None)
@@ -112,18 +96,7 @@ class Renderer:
top_right = f"#{number}" if number not in (None, "") else ""
top.add_row(top_left, top_center, top_right)
body_lines = [f"Arcana: {arcana or '-'}"]
if suit:
body_lines.append(f"Suit: {suit}")
if pip:
body_lines.append(f"Pip: {pip}")
if getattr(card, "court_rank", ""):
body_lines.append(f"Court Rank: {card.court_rank}")
if keywords:
body_lines.append(f"Keywords: {', '.join(keywords)}")
guidance = getattr(card, "guidance", None)
if guidance:
body_lines.append(f"Guidance: {guidance}")
body_lines = self._card_field_lines(card)
body = Panel("\n".join(body_lines), box=box.MINIMAL, padding=(1, 1))
bottom = Table(
@@ -292,6 +265,68 @@ class Renderer:
safe_val = value if value not in (None, "") else ""
return f"{label}:\n{safe_val}"
def _card_field_lines(self, card: Card) -> List[str]:
exclude = {"name", "number", "meaning", "keywords"}
lines: List[str] = []
for attr_name, attr_value in get_object_attributes(card):
if attr_name in exclude:
continue
if attr_value in (None, "", [], {}, ()): # Skip empty values
continue
label = self._humanize_key(attr_name).title()
if is_nested_object(attr_value) and not hasattr(attr_value, "name"):
lines.append(f"{label}:")
nested = format_value(attr_value, indent=2)
lines.extend(nested.splitlines())
continue
lines.append(f"{label}: {self._format_card_value(attr_value)}")
if not lines:
lines.append("(no fields)")
return lines
def _format_card_value(self, value: Any) -> str:
if value is None:
return ""
if isinstance(value, (str, int, float, bool)):
return str(value)
if isinstance(value, tuple):
date_range = self._format_date_range(value)
if date_range is not None:
return date_range
return ", ".join(self._format_card_value(v) for v in value)
if isinstance(value, list):
return ", ".join(self._format_card_value(v) for v in value)
if isinstance(value, dict):
return ", ".join(
f"{self._humanize_key(str(k)).title()}: {self._format_card_value(v)}"
for k, v in value.items()
)
if hasattr(value, "name"):
return str(getattr(value, "name", value))
if is_nested_object(value):
return format_value(value, indent=2)
return str(value)
@staticmethod
def _format_date_range(value: Tuple[Any, ...]) -> Optional[str]:
if len(value) != 2:
return None
start, end = value
if not (
isinstance(start, tuple)
and isinstance(end, tuple)
and len(start) == 2
and len(end) == 2
):
return None
try:
return f"{format_month_day(start)} - {format_month_day(end)}"
except Exception:
return None
class PlanetRenderer:
"""Type-specific table rendering for planets."""