Coverage for lintro / ai / display / shared.py: 100%
28 statements
« prev ^ index » next coverage.py v7.13.0, created at 2026-04-03 18:53 +0000
« prev ^ index » next coverage.py v7.13.0, created at 2026-04-03 18:53 +0000
1"""Shared helpers for AI display rendering.
3Cross-module utilities used by fix, summary, and validation renderers.
4"""
6from __future__ import annotations
8import os
9import re
11from rich.console import Console, RenderableType
12from rich.panel import Panel
14from lintro.ai.cost import format_cost, format_token_count
15from lintro.utils.console.constants import BORDER_LENGTH
17# Pattern to strip leading number prefixes like "1. ", "2) " from AI responses
18LEADING_NUMBER_RE = re.compile(r"^\d+[\.\)]\s*")
21def is_github_actions() -> bool:
22 """Check if running inside GitHub Actions.
24 Returns:
25 True if GITHUB_ACTIONS environment variable is set.
26 """
27 return os.environ.get("GITHUB_ACTIONS") == "true"
30def cost_str(
31 input_tokens: int,
32 output_tokens: int,
33 cost: float,
34) -> str:
35 """Build a cost summary string for the section header.
37 Args:
38 input_tokens: Total input tokens consumed.
39 output_tokens: Total output tokens generated.
40 cost: Estimated cost in USD.
42 Returns:
43 Cost summary string, or empty if cost is zero.
44 """
45 if cost <= 0:
46 return ""
47 tokens = format_token_count(input_tokens + output_tokens)
48 return f" {tokens} tokens, est. {format_cost(cost)}"
51def print_section_header(
52 console: Console,
53 emoji: str,
54 label: str,
55 detail: str,
56 *,
57 cost_info: str = "",
58) -> None:
59 """Print a terminal section header with bars.
61 Args:
62 console: Rich Console to print to.
63 emoji: Section emoji (e.g. "brain", "robot").
64 label: Tool or section name.
65 detail: Summary text (e.g. "3 issues explained (2 codes)").
66 cost_info: Optional cost/token info to include in header.
67 """
68 border = "\u2501" * BORDER_LENGTH
69 console.print()
70 console.print(f"[cyan]{border}[/cyan]")
71 console.print(f"[bold cyan]{emoji} {label}[/bold cyan] \u2014 {detail}")
72 if cost_info:
73 console.print(f"[dim]{cost_info}[/dim]")
74 console.print(f"[cyan]{border}[/cyan]")
77def print_code_panel(
78 console: Console,
79 *,
80 code: str,
81 index: int,
82 total: int,
83 count: int,
84 count_label: str,
85 content: RenderableType,
86 tool_name: str = "",
87) -> None:
88 """Print a Rich Panel for one error-code group.
90 Shared by both explanation and fix rendering to ensure
91 consistent Panel styling across chk and fmt.
93 Args:
94 console: Rich Console to print to.
95 code: Error code (e.g. "B101", "D107").
96 index: 1-based group index.
97 total: Total number of groups.
98 count: Number of occurrences/files.
99 count_label: Label for count (e.g. "occurrence", "file").
100 content: Rich renderable content for the panel body.
101 tool_name: Optional tool name to show next to the error code.
102 """
103 plural = "s" if count != 1 else ""
104 tool_part = f" [dim]{tool_name}[/dim]" if tool_name else ""
105 title = (
106 f"[bold cyan]\\[{index}/{total}][/bold cyan] "
107 f"[bold yellow]{code}[/bold yellow]{tool_part} "
108 f"[dim]({count} {count_label}{plural})[/dim]"
109 )
110 console.print(
111 Panel(
112 content,
113 title=title,
114 title_align="left",
115 border_style="cyan",
116 padding=(0, 1),
117 ),
118 )