chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,195 @@
|
||||
"""Output formatting utilities for CLI."""
|
||||
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
import click
|
||||
|
||||
|
||||
def format_output(data: Any, format_type: str = "text") -> str:
|
||||
"""Format output based on requested format type.
|
||||
|
||||
Args:
|
||||
data: Data to format
|
||||
format_type: One of 'text', 'json', 'table'
|
||||
|
||||
Returns:
|
||||
Formatted string
|
||||
"""
|
||||
if format_type == "json":
|
||||
return format_as_json(data)
|
||||
elif format_type == "table":
|
||||
return format_as_table(data)
|
||||
else:
|
||||
return format_as_text(data)
|
||||
|
||||
|
||||
def format_as_json(data: Any) -> str:
|
||||
"""Format data as pretty-printed JSON."""
|
||||
try:
|
||||
return json.dumps(data, indent=2, default=str)
|
||||
except (TypeError, ValueError) as e:
|
||||
return json.dumps({"error": f"JSON serialization failed: {e}", "raw": str(data)})
|
||||
|
||||
|
||||
def format_as_text(data: Any, indent: int = 0) -> str:
|
||||
"""Format data as human-readable text."""
|
||||
prefix = " " * indent
|
||||
|
||||
if data is None:
|
||||
return f"{prefix}(none)"
|
||||
|
||||
if isinstance(data, dict):
|
||||
# Check for error response
|
||||
if "success" in data and not data.get("success"):
|
||||
error = data.get("error") or data.get("message") or "Unknown error"
|
||||
return f"{prefix}❌ Error: {error}"
|
||||
|
||||
# Check for success response with data
|
||||
if "success" in data and data.get("success"):
|
||||
result = data.get("data") or data.get("result") or data
|
||||
if result != data:
|
||||
return format_as_text(result, indent)
|
||||
|
||||
lines = []
|
||||
for key, value in data.items():
|
||||
if key in ("success", "error", "message") and "success" in data:
|
||||
continue # Skip meta fields
|
||||
if isinstance(value, dict):
|
||||
lines.append(f"{prefix}{key}:")
|
||||
lines.append(format_as_text(value, indent + 1))
|
||||
elif isinstance(value, list):
|
||||
lines.append(f"{prefix}{key}: [{len(value)} items]")
|
||||
if len(value) <= 10:
|
||||
for i, item in enumerate(value):
|
||||
lines.append(
|
||||
f"{prefix} [{i}] {_format_list_item(item)}")
|
||||
else:
|
||||
for i, item in enumerate(value[:5]):
|
||||
lines.append(
|
||||
f"{prefix} [{i}] {_format_list_item(item)}")
|
||||
lines.append(f"{prefix} ... ({len(value) - 10} more)")
|
||||
for i, item in enumerate(value[-5:], len(value) - 5):
|
||||
lines.append(
|
||||
f"{prefix} [{i}] {_format_list_item(item)}")
|
||||
else:
|
||||
lines.append(f"{prefix}{key}: {value}")
|
||||
return "\n".join(lines)
|
||||
|
||||
if isinstance(data, list):
|
||||
if not data:
|
||||
return f"{prefix}(empty list)"
|
||||
lines = [f"{prefix}[{len(data)} items]"]
|
||||
for i, item in enumerate(data[:20]):
|
||||
lines.append(f"{prefix} [{i}] {_format_list_item(item)}")
|
||||
if len(data) > 20:
|
||||
lines.append(f"{prefix} ... ({len(data) - 20} more)")
|
||||
return "\n".join(lines)
|
||||
|
||||
return f"{prefix}{data}"
|
||||
|
||||
|
||||
def _format_list_item(item: Any) -> str:
|
||||
"""Format a single list item."""
|
||||
if isinstance(item, dict):
|
||||
# Try to find a name/id field for display
|
||||
name = item.get("name") or item.get(
|
||||
"Name") or item.get("id") or item.get("Id")
|
||||
if name:
|
||||
extra = ""
|
||||
if "instanceID" in item:
|
||||
extra = f" (ID: {item['instanceID']})"
|
||||
elif "path" in item:
|
||||
extra = f" ({item['path']})"
|
||||
return f"{name}{extra}"
|
||||
# Fallback to compact representation
|
||||
return json.dumps(item, default=str)[:80]
|
||||
return str(item)[:80]
|
||||
|
||||
|
||||
def format_as_table(data: Any) -> str:
|
||||
"""Format data as an ASCII table."""
|
||||
if isinstance(data, dict):
|
||||
# Check for success response with data
|
||||
if "success" in data and data.get("success"):
|
||||
result = data.get("data") or data.get(
|
||||
"result") or data.get("items")
|
||||
if isinstance(result, list):
|
||||
return _build_table(result)
|
||||
|
||||
# Single dict as key-value table
|
||||
rows = [[str(k), str(v)[:60]] for k, v in data.items()]
|
||||
return _build_table(rows, headers=["Key", "Value"])
|
||||
|
||||
if isinstance(data, list):
|
||||
return _build_table(data)
|
||||
|
||||
return str(data)
|
||||
|
||||
|
||||
def _build_table(data: list[Any], headers: list[str] | None = None) -> str:
|
||||
"""Build an ASCII table from list data."""
|
||||
if not data:
|
||||
return "(no data)"
|
||||
|
||||
# Convert list of dicts to rows
|
||||
if isinstance(data[0], dict):
|
||||
if headers is None:
|
||||
headers = list(data[0].keys())
|
||||
rows = [[str(item.get(h, ""))[:40] for h in headers] for item in data]
|
||||
elif isinstance(data[0], (list, tuple)):
|
||||
rows = [[str(cell)[:40] for cell in row] for row in data]
|
||||
if headers is None:
|
||||
headers = [f"Col{i}" for i in range(len(data[0]))]
|
||||
else:
|
||||
rows = [[str(item)[:60]] for item in data]
|
||||
headers = headers or ["Value"]
|
||||
|
||||
# Calculate column widths
|
||||
col_widths = [len(h) for h in headers]
|
||||
for row in rows:
|
||||
for i, cell in enumerate(row):
|
||||
if i < len(col_widths):
|
||||
col_widths[i] = max(col_widths[i], len(cell))
|
||||
|
||||
# Build table
|
||||
lines = []
|
||||
|
||||
# Header
|
||||
header_line = " | ".join(
|
||||
h.ljust(col_widths[i]) for i, h in enumerate(headers))
|
||||
lines.append(header_line)
|
||||
lines.append("-+-".join("-" * w for w in col_widths))
|
||||
|
||||
# Rows
|
||||
for row in rows[:50]: # Limit rows
|
||||
row_line = " | ".join(
|
||||
(row[i] if i < len(row) else "").ljust(col_widths[i])
|
||||
for i in range(len(headers))
|
||||
)
|
||||
lines.append(row_line)
|
||||
|
||||
if len(rows) > 50:
|
||||
lines.append(f"... ({len(rows) - 50} more rows)")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def print_success(message: str) -> None:
|
||||
"""Print a success message."""
|
||||
click.echo(f"✅ {message}")
|
||||
|
||||
|
||||
def print_error(message: str) -> None:
|
||||
"""Print an error message to stderr."""
|
||||
click.echo(f"❌ {message}", err=True)
|
||||
|
||||
|
||||
def print_warning(message: str) -> None:
|
||||
"""Print a warning message."""
|
||||
click.echo(f"⚠️ {message}")
|
||||
|
||||
|
||||
def print_info(message: str) -> None:
|
||||
"""Print an info message."""
|
||||
click.echo(f"ℹ️ {message}")
|
||||
Reference in New Issue
Block a user