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

1"""Shared helpers for AI display rendering. 

2 

3Cross-module utilities used by fix, summary, and validation renderers. 

4""" 

5 

6from __future__ import annotations 

7 

8import os 

9import re 

10 

11from rich.console import Console, RenderableType 

12from rich.panel import Panel 

13 

14from lintro.ai.cost import format_cost, format_token_count 

15from lintro.utils.console.constants import BORDER_LENGTH 

16 

17# Pattern to strip leading number prefixes like "1. ", "2) " from AI responses 

18LEADING_NUMBER_RE = re.compile(r"^\d+[\.\)]\s*") 

19 

20 

21def is_github_actions() -> bool: 

22 """Check if running inside GitHub Actions. 

23 

24 Returns: 

25 True if GITHUB_ACTIONS environment variable is set. 

26 """ 

27 return os.environ.get("GITHUB_ACTIONS") == "true" 

28 

29 

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. 

36 

37 Args: 

38 input_tokens: Total input tokens consumed. 

39 output_tokens: Total output tokens generated. 

40 cost: Estimated cost in USD. 

41 

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)}" 

49 

50 

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. 

60 

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]") 

75 

76 

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. 

89 

90 Shared by both explanation and fix rendering to ensure 

91 consistent Panel styling across chk and fmt. 

92 

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 )