j
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user