Coverage for lintro / formatters / core / format_registry.py: 88%

48 statements  

« prev     ^ index     » next       coverage.py v7.13.0, created at 2026-04-03 18:53 +0000

1"""Core formatting abstractions and style registry. 

2 

3This module provides: 

4- OutputStyle: Abstract base class for output style renderers 

5- TableDescriptor: Interface for describing table columns and rows 

6- Style registry functions for looking up format styles 

7""" 

8 

9from __future__ import annotations 

10 

11from abc import ABC, abstractmethod 

12from functools import lru_cache 

13from typing import TYPE_CHECKING, Any 

14 

15from lintro.enums.output_format import OutputFormat 

16 

17if TYPE_CHECKING: 

18 pass 

19 

20 

21# ============================================================================= 

22# Abstract Base Classes 

23# ============================================================================= 

24 

25 

26class OutputStyle(ABC): 

27 """Abstract base class for output style renderers. 

28 

29 Implementations convert tabular data into a concrete textual 

30 representation (e.g., grid, markdown, plain). 

31 """ 

32 

33 @abstractmethod 

34 def format( 

35 self, 

36 columns: list[str], 

37 rows: list[list[Any]], 

38 tool_name: str | None = None, 

39 **kwargs: Any, 

40 ) -> str: 

41 """Format a table given columns and rows. 

42 

43 Args: 

44 columns: List of column header names. 

45 rows: List of rows, where each row is a list of values. 

46 tool_name: Optional tool name for metadata-rich formats. 

47 **kwargs: Additional renderer-specific context. 

48 

49 Returns: 

50 str: Formatted table as a string. 

51 """ 

52 pass 

53 

54 

55class TableDescriptor(ABC): 

56 """Describe how to extract tabular data for a tool's issues. 

57 

58 Concrete implementations define column ordering and how to map issue 

59 objects into a list of column values. 

60 """ 

61 

62 @abstractmethod 

63 def get_columns(self) -> list[str]: 

64 """Return the list of column names in order.""" 

65 pass 

66 

67 @abstractmethod 

68 def get_rows( 

69 self, 

70 issues: list[Any], 

71 ) -> list[list[Any]]: 

72 """Return the values for each column for a list of issues. 

73 

74 Args: 

75 issues: List of issue objects to extract data from. 

76 

77 Returns: 

78 list[list]: Nested list representing table rows and columns. 

79 """ 

80 pass 

81 

82 

83# ============================================================================= 

84# Style Registry 

85# ============================================================================= 

86 

87 

88@lru_cache(maxsize=1) 

89def _create_style_instances() -> dict[OutputFormat, OutputStyle]: 

90 """Create singleton instances of all output styles. 

91 

92 Uses lazy imports to avoid circular dependencies and improve startup time. 

93 Results are cached to ensure style instances are reused. 

94 

95 Returns: 

96 dict[OutputFormat, OutputStyle]: Mapping of format to style instance. 

97 """ 

98 from lintro.formatters.styles.csv import CsvStyle 

99 from lintro.formatters.styles.github import GitHubStyle 

100 from lintro.formatters.styles.grid import GridStyle 

101 from lintro.formatters.styles.html import HtmlStyle 

102 from lintro.formatters.styles.json import JsonStyle 

103 from lintro.formatters.styles.markdown import MarkdownStyle 

104 from lintro.formatters.styles.plain import PlainStyle 

105 

106 return { 

107 OutputFormat.PLAIN: PlainStyle(), 

108 OutputFormat.GRID: GridStyle(), 

109 OutputFormat.MARKDOWN: MarkdownStyle(), 

110 OutputFormat.HTML: HtmlStyle(), 

111 OutputFormat.JSON: JsonStyle(), 

112 OutputFormat.CSV: CsvStyle(), 

113 OutputFormat.GITHUB: GitHubStyle(), 

114 } 

115 

116 

117def get_style(format_key: OutputFormat | str) -> OutputStyle: 

118 """Get the output style for a given format. 

119 

120 Args: 

121 format_key: Output format as enum or string (e.g., "grid", "plain"). 

122 

123 Returns: 

124 OutputStyle: The appropriate style instance for formatting. 

125 Falls back to GridStyle for unknown formats to maintain 

126 backward compatibility. 

127 """ 

128 styles = _create_style_instances() 

129 

130 # Handle string keys for backward compatibility 

131 if isinstance(format_key, str): 

132 format_key_lower = format_key.lower() 

133 try: 

134 format_key = OutputFormat(format_key_lower) 

135 except ValueError: 

136 # Try matching by name 

137 for fmt in OutputFormat: 

138 if ( 

139 fmt.value == format_key_lower 

140 or fmt.name.lower() == format_key_lower 

141 ): 

142 format_key = fmt 

143 break 

144 else: 

145 # Fallback to cached GridStyle for unknown formats 

146 return styles[OutputFormat.GRID] 

147 

148 style = styles.get(format_key) 

149 if style is None: 

150 # Fallback to cached grid style 

151 return styles[OutputFormat.GRID] 

152 

153 return style 

154 

155 

156def get_format_map() -> dict[OutputFormat, OutputStyle]: 

157 """Get the complete format map for direct access. 

158 

159 This provides backward compatibility for code that expects a FORMAT_MAP dict. 

160 

161 Returns: 

162 dict[OutputFormat, OutputStyle]: Complete mapping of all formats to styles. 

163 """ 

164 return _create_style_instances() 

165 

166 

167def get_string_format_map() -> dict[str, OutputStyle]: 

168 """Get format map with string keys for backward compatibility. 

169 

170 Some formatters use string keys like "grid" instead of OutputFormat.GRID. 

171 

172 Returns: 

173 dict[str, OutputStyle]: Mapping with string keys. 

174 """ 

175 styles = _create_style_instances() 

176 return {fmt.value: style for fmt, style in styles.items()} 

177 

178 

179# Convenience constants for common use cases 

180DEFAULT_FORMAT = OutputFormat.GRID