Cybersecurity-Projects/PROJECTS/beginner/base64-tool/src/base64_tool/formatter.py

255 lines
6.7 KiB
Python

"""
©AngelaMos | 2026
formatter.py
Rich terminal output for all CLI commands
Handles all display logic for encoded strings, decoded bytes,
detection tables, peel layer summaries, and chain step results.
Detects whether stdout is a TTY or a pipe and switches between
Rich-formatted panels and raw text output. All Rich output goes
to stderr so piped stdout stays machine-readable.
Key exports:
print_encoded() - Displays an encoded string in a Rich panel or raw to stdout
print_decoded() - Displays decoded bytes as text or hex fallback
print_detection() - Renders a confidence table for detect results
print_peel_result() - Renders each peel layer and a final output panel
print_chain_result() - Renders each encoding step and the final chain result
print_score_breakdown() - Renders per-format score table for verbose mode
is_piped() - Returns True when stdout is not a TTY
Connects to:
constants.py - imports CONFIDENCE_THRESHOLD, PREVIEW_LENGTH, EncodingFormat
detector.py - imports DetectionResult
peeler.py - imports PeelResult
utils.py - imports safe_bytes_preview
cli.py - imports all print_* functions
"""
import sys
from rich.console import Console
from rich.panel import Panel
from rich.table import Table
from rich.text import Text
from base64_tool.constants import (
CONFIDENCE_THRESHOLD,
EncodingFormat,
PREVIEW_LENGTH,
)
from base64_tool.detector import DetectionResult
from base64_tool.peeler import PeelResult
from base64_tool.utils import safe_bytes_preview
console = Console(stderr = True)
def is_piped() -> bool:
return not sys.stdout.isatty()
def write_raw(text: str) -> None:
sys.stdout.write(text)
sys.stdout.flush()
def print_encoded(result: str, fmt: EncodingFormat) -> None:
if is_piped():
write_raw(result)
return
panel = Panel(
Text(result,
style = "green"),
title = f"[bold cyan]{fmt.value}[/bold cyan] encoded",
border_style = "cyan",
)
console.print(panel)
def print_decoded(result: bytes) -> None:
preview = safe_bytes_preview(result, length = 4096)
if is_piped():
write_raw(preview)
return
panel = Panel(
Text(preview,
style = "green"),
title = "[bold cyan]Decoded[/bold cyan]",
border_style = "cyan",
)
console.print(panel)
def print_score_breakdown(
scores: dict[EncodingFormat,
float],
) -> None:
table = Table(
title = "Score Breakdown",
show_header = True,
header_style = "bold magenta",
)
table.add_column("Format", style = "cyan", min_width = 10)
table.add_column(
"Score",
justify = "right",
min_width = 8,
)
table.add_column("Status", min_width = 10)
sorted_scores = sorted(
scores.items(),
key = lambda x: x[1],
reverse = True,
)
for fmt, score in sorted_scores:
color = _confidence_color(score)
if score >= CONFIDENCE_THRESHOLD:
status = "[green]detected[/green]"
elif score > 0:
status = "[yellow]below threshold[/yellow]"
else:
status = "[dim]no match[/dim]"
table.add_row(
fmt.value,
f"[{color}]{score:.0%}[/{color}]",
status,
)
console.print(table)
def print_detection(
results: list[DetectionResult],
*,
verbose_scores: dict[EncodingFormat,
float] | None = None,
) -> None:
if verbose_scores is not None:
print_score_breakdown(verbose_scores)
console.print()
if not results:
console.print("[yellow]No encoding format detected.[/yellow]")
return
table = Table(
title = "Detection Results",
show_header = True,
header_style = "bold magenta",
)
table.add_column("Format", style = "cyan", min_width = 10)
table.add_column(
"Confidence",
justify = "right",
style = "green",
min_width = 12,
)
table.add_column("Decoded Preview", style = "dim")
for result in results:
confidence_str = f"{result.confidence:.0%}"
preview = ""
if result.decoded is not None:
preview = safe_bytes_preview(
result.decoded,
PREVIEW_LENGTH,
)
table.add_row(
result.format.value,
confidence_str,
preview,
)
console.print(table)
def print_peel_result(
result: PeelResult,
*,
verbose: bool = False,
) -> None:
if not result.success:
console.print("[yellow]No encoding layers detected.[/yellow]")
return
layer_count = len(result.layers)
suffix = "s" if layer_count > 1 else ""
console.print()
console.print(
f"[bold cyan]Peeled {layer_count} encoding "
f"layer{suffix}[/bold cyan]"
)
console.print()
for layer in result.layers:
color = _confidence_color(layer.confidence)
console.print(
f" [bold]Layer {layer.depth}[/bold] "
f"[cyan]{layer.format.value}[/cyan] "
f"[{color}]{layer.confidence:.0%}[/{color}]"
)
console.print(f" [dim]{layer.decoded_preview}[/dim]")
if verbose and layer.all_scores:
console.print()
print_score_breakdown(dict(layer.all_scores))
console.print()
console.print()
preview = safe_bytes_preview(result.final_output, length = 4096)
panel = Panel(
Text(preview,
style = "bold green"),
title = "[bold]Final Output[/bold]",
border_style = "green",
subtitle = (f"[dim]{layer_count} layer{suffix} peeled[/dim]"),
)
console.print(panel)
def print_chain_result(
steps: list[tuple[EncodingFormat,
str]],
final: str,
) -> None:
if is_piped():
write_raw(final)
return
console.print()
console.print("[bold cyan]Encoding Chain[/bold cyan]")
console.print()
for i, (fmt, intermediate) in enumerate(steps):
marker = "start" if i == 0 else "step"
arrow = f" [{marker}] " if i == 0 else " -> "
truncated = intermediate[: PREVIEW_LENGTH]
ellipsis = "..." if len(intermediate) > PREVIEW_LENGTH else ""
console.print(
f"{arrow}[cyan]{fmt.value}[/cyan] "
f"[dim]{truncated}{ellipsis}[/dim]"
)
console.print()
panel = Panel(
Text(final,
style = "green"),
title = "[bold]Chain Result[/bold]",
border_style = "cyan",
subtitle = f"[dim]{len(steps)} steps[/dim]",
)
console.print(panel)
def _confidence_color(confidence: float) -> str:
if confidence >= 0.9:
return "green"
if confidence >= 0.7:
return "yellow"
return "red"