This commit is contained in:
2026-01-12 20:01:45 -08:00
parent be55e6e450
commit 2870abf4de
7 changed files with 2083 additions and 52 deletions

View File

@@ -1828,3 +1828,153 @@ def format_result(result: Any, title: str = "") -> str:
table.add_result(result)
return str(table)
def extract_item_metadata(item: Any) -> Dict[str, Any]:
"""Extract a comprehensive set of metadata from an item for the ItemDetailView."""
if item is None:
return {}
out = {}
# Use existing extractors from match-standard result table columns
title = extract_title_value(item)
if title: out["Title"] = title
hv = extract_hash_value(item)
if hv: out["Hash"] = hv
store = extract_store_value(item)
if store: out["Store"] = store
# Path/Target
data = _as_dict(item) or {}
path = data.get("path") or data.get("target") or data.get("filename")
if path: out["Path"] = path
ext = extract_ext_value(item)
if ext: out["Ext"] = ext
size = extract_size_bytes_value(item)
if size: out["Size"] = size
# Duration
dur = _get_first_dict_value(data, ["duration_seconds", "duration"])
if dur:
out["Duration"] = _format_duration_hms(dur)
# URL
url = _get_first_dict_value(data, ["url", "URL"])
if url: out["Url"] = url
# Relationships
rels = _get_first_dict_value(data, ["relationships", "rel"])
if rels: out["Relations"] = rels
# Tags Summary
tags = _get_first_dict_value(data, ["tags", "tag"])
if tags: out["Tags"] = tags
return out
class ItemDetailView(ResultTable):
"""A specialized view that displays item details alongside a list of related items (tags, urls, etc).
This is used for 'get-tag', 'get-url' and similar cmdlets where we want to contextually show
what is being operated on (the main item) along with the selection list.
"""
def __init__(
self,
title: str = "",
item_metadata: Optional[Dict[str, Any]] = None,
**kwargs
):
super().__init__(title, **kwargs)
self.item_metadata = item_metadata or {}
def to_rich(self):
"""Render the item details panel above the standard results table."""
from rich.table import Table as RichTable
from rich.panel import Panel
from rich.console import Group, Columns
from rich.text import Text
# 1. Create Detail Grid
details_table = RichTable(show_header=False, box=None, padding=(0, 2), expand=True)
details_table.add_column("Key", style="bold cyan", justify="right", width=12)
details_table.add_column("Value")
# Canonical display order for metadata
order = ["Title", "Hash", "Store", "Path", "Ext", "Size", "Duration", "Url", "Relations"]
has_details = False
# Add ordered items first
for key in order:
val = self.item_metadata.get(key) or self.item_metadata.get(key.lower()) or self.item_metadata.get(key.upper())
# Special formatting for certain types
if key == "Size" and val and isinstance(val, (int, float, str)) and str(val).isdigit():
val = _format_size(int(val), integer_only=False)
if key == "Relations" and isinstance(val, list) and val:
if isinstance(val[0], dict):
val = "\n".join([f"[dim]→[/dim] {r.get('type','rel')}: {r.get('title','?')}" for r in val])
else:
val = "\n".join([f"[dim]→[/dim] {r}" for r in val])
if val:
details_table.add_row(f"{key}:", str(val))
has_details = True
elif key in ["Url", "Relations"]:
# User requested <null> for these if blank
details_table.add_row(f"{key}:", "[dim]<null>[/dim]")
has_details = True
# Add any remaining metadata not in the canonical list
for k, v in self.item_metadata.items():
k_norm = k.lower()
if k_norm not in [x.lower() for x in order] and v and k_norm not in ["tags", "tag"]:
details_table.add_row(f"{k.capitalize()}:", str(v))
has_details = True
# Tags Summary
tags = self.item_metadata.get("Tags") or self.item_metadata.get("tags") or self.item_metadata.get("tag")
if tags and isinstance(tags, (list, str)):
if isinstance(tags, str):
tags = [t.strip() for t in tags.split(",") if t.strip()]
tags_sorted = sorted(map(str, tags))
tag_cols = Columns([f"[dim]#[/dim]{t}" for t in tags_sorted], equal=True, expand=True)
details_table.add_row("", "") # Spacer
details_table.add_row("Tags:", tag_cols)
has_details = True
# 2. Get the standard table render
original_title = self.title
original_header_lines = self.header_lines
self.title = ""
self.header_lines = []
try:
results_renderable = super().to_rich()
finally:
self.title = original_title
self.header_lines = original_header_lines
# 3. Assemble components
elements = []
if has_details:
elements.append(Panel(details_table, title="Item Details", border_style="blue"))
# Wrap the results in a titled panel
display_title = "Items"
if original_title:
display_title = original_title
# Add a bit of padding
results_group = Group(Text(""), results_renderable, Text(""))
elements.append(Panel(results_group, title=display_title, border_style="green"))
return Group(*elements)

1831
SYS/result_table_new.py Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -265,6 +265,7 @@ def render_item_details_panel(item: Dict[str, Any]) -> None:
"""Render a comprehensive details panel for a result item."""
from rich.table import Table
from rich.columns import Columns
from rich.panel import Panel
title = (
item.get("title")
@@ -274,31 +275,35 @@ def render_item_details_panel(item: Dict[str, Any]) -> None:
)
# Main layout table for the panel
details_table = Table.grid(expand=True)
details_table.add_column(style="cyan", no_wrap=True, width=15)
details_table = Table.grid(expand=True, padding=(0, 2))
details_table.add_column(style="cyan", no_wrap=True, width=15, justify="right")
details_table.add_column(style="white")
# Basic Info
details_table.add_row("Title", f"[bold]{title}[/bold]")
# Canonical order
details_table.add_row("Title:", f"[bold]{title}[/bold]")
if "store" in item:
details_table.add_row("Store", str(item["store"]))
if "hash" in item or "hash_hex" in item or "file_hash" in item:
h = item.get("hash") or item.get("hash_hex") or item.get("file_hash")
details_table.add_row("Hash:", str(h))
if "store" in item or "table" in item:
s = item.get("store") or item.get("table")
details_table.add_row("Store:", str(s))
if "hash" in item:
details_table.add_row("Hash", str(item["hash"]))
# Metadata / Path
if "path" in item or "target" in item:
path = item.get("path") or item.get("target")
details_table.add_row("Path", str(path))
# Only show if it doesn't look like a URL (which would go in Url row)
if path and not str(path).startswith(("http://", "https://")):
details_table.add_row("Path:", str(path))
if "ext" in item or "extension" in item:
ext = item.get("ext") or item.get("extension")
details_table.add_row("Extension", str(ext))
details_table.add_row("Ext:", str(ext))
if "size_bytes" in item or "size" in item:
size = item.get("size_bytes") or item.get("size")
if isinstance(size, (int, float)):
if isinstance(size, (int, float, str)) and str(size).isdigit():
size = int(size)
if size > 1024 * 1024 * 1024:
size_str = f"{size / (1024*1024*1024):.1f} GB"
elif size > 1024 * 1024:
@@ -307,33 +312,40 @@ def render_item_details_panel(item: Dict[str, Any]) -> None:
size_str = f"{size / 1024:.1f} KB"
else:
size_str = f"{size} bytes"
details_table.add_row("Size", size_str)
details_table.add_row("Size:", size_str)
# URL(s)
urls = item.get("url") or item.get("URL") or []
if isinstance(urls, str):
urls = [urls]
if isinstance(urls, list) and urls:
url_text = "\n".join(map(str, urls))
details_table.add_row("URL(s)", url_text)
valid_urls = [str(u).strip() for u in urls if str(u).strip()]
if valid_urls:
url_text = "\n".join(valid_urls)
details_table.add_row("Url:", url_text)
else:
details_table.add_row("Url:", "[dim]<null>[/dim]")
# Tags
tags = item.get("tag") or item.get("tags") or []
if isinstance(tags, str):
tags = [tags]
tags = [t.strip() for t in tags.split(",") if t.strip()]
if isinstance(tags, list) and tags:
# Sort and filter tags to look nice
tags_sorted = sorted(map(str, tags))
# Group tags by namespace if they have them
tag_cols = Columns([f"[dim]#[/dim]{t}" for t in tags_sorted], equal=True, expand=True)
details_table.add_row("", "") # Spacer
details_table.add_row("Tags", tag_cols)
details_table.add_row("Tags:", tag_cols)
# Relationships (if any)
rels = item.get("relationships") or item.get("rel") or []
if isinstance(rels, list) and rels:
rel_text = "\n".join([f"[dim]→[/dim] {r}" for r in rels])
details_table.add_row("Relations", rel_text)
# Check for list of dicts (from get-relationship) or list of strings
if rels and isinstance(rels[0], dict):
rel_text = "\n".join([f"[dim]→[/dim] {r.get('type','rel')}: {r.get('title','?')}" for r in rels])
else:
rel_text = "\n".join([f"[dim]→[/dim] {r}" for r in rels])
details_table.add_row("Relations:", rel_text)
else:
details_table.add_row("Relations:", "[dim]<null>[/dim]")
panel = Panel(
details_table,