Coverage for sources/ictruck/recipes/rich.py: 100%

56 statements  

« prev     ^ index     » next       coverage.py v7.9.2, created at 2025-07-05 05:21 +0000

1# vim: set filetype=python fileencoding=utf-8: 

2# -*- coding: utf-8 -*- 

3 

4#============================================================================# 

5# # 

6# Licensed under the Apache License, Version 2.0 (the "License"); # 

7# you may not use this file except in compliance with the License. # 

8# You may obtain a copy of the License at # 

9# # 

10# http://www.apache.org/licenses/LICENSE-2.0 # 

11# # 

12# Unless required by applicable law or agreed to in writing, software # 

13# distributed under the License is distributed on an "AS IS" BASIS, # 

14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # 

15# See the License for the specific language governing permissions and # 

16# limitations under the License. # 

17# # 

18#============================================================================# 

19 

20 

21''' Recipes for Rich formatters and printers. 

22 

23 .. note:: 

24 

25 To use this module, you must have the ``rich`` package installed. 

26''' 

27 

28 

29 

30from rich.console import Console as _Console 

31from rich.pretty import pretty_repr as _pretty_repr 

32 

33from . import __ 

34 

35 

36_validate_arguments = ( 

37 __.validate_arguments( 

38 globalvars = globals( ), 

39 errorclass = __.exceptions.ArgumentClassInvalidity ) ) 

40 

41 

42class ConsoleTextIoInvalidity( __.exceptions.Omnierror, TypeError ): 

43 ''' Text stream invalid for use with Rich console. ''' 

44 

45 def __init__( self, stream: __.typx.Any ): 

46 super( ).__init__( f"Invalid stream for Rich console: {stream!r}" ) 

47 

48 

49class Modes( __.enum.Enum ): 

50 ''' Operation modes for Rich truck. ''' 

51 

52 Formatter = 'formatter' 

53 Printer = 'printer' 

54 

55 

56ProduceTruckModeArgument: __.typx.TypeAlias = __.typx.Annotated[ 

57 Modes, 

58 __.typx.Doc( 

59 ''' Operation mode. 

60 

61 ``Formatter`` uses Rich to highlight and pretty text prior to 

62 printing (output). Text from non-Rich formatters will be printed 

63 as-is. Safer, but slightly more boring option. 

64 ``Printer`` uses Rich to highlight and pretty text while printing 

65 (output). Text from non-Rich formatters will be potentially be 

66 highlighted and prettied. If the text already contains ANSI SGR 

67 sequences (e.g., terminal colorization), then it might be 

68 reprocessed by the printer, causing visual artifacts. Less safe, 

69 but more vibrant option. 

70 ''' ), 

71] 

72ProduceTruckStderrArgument: __.typx.TypeAlias = __.typx.Annotated[ 

73 bool, __.typx.Doc( ''' Output to standard diagnostic stream? ''' ) 

74] 

75 

76 

77@_validate_arguments 

78def install( # noqa: PLR0913 

79 alias: __.InstallAliasArgument = __.builtins_alias_default, 

80 flavors: __.ProduceTruckFlavorsArgument = __.absent, 

81 active_flavors: __.ProduceTruckActiveFlavorsArgument = __.absent, 

82 trace_levels: __.ProduceTruckTraceLevelsArgument = __.absent, 

83 mode: ProduceTruckModeArgument = Modes.Formatter, 

84 stderr: ProduceTruckStderrArgument = True, 

85) -> __.Truck: 

86 ''' Produces truck and installs it into builtins with alias. 

87 

88 Replaces an existing truck, preserving global module configurations. 

89 

90 Library developers should call :py:func:`__.register_module` instead. 

91 ''' 

92 truck = produce_truck( 

93 flavors = flavors, 

94 active_flavors = active_flavors, 

95 trace_levels = trace_levels, 

96 mode = mode, 

97 stderr = stderr ) 

98 return truck.install( alias = alias ) 

99 

100 

101@_validate_arguments 

102def produce_console_formatter( 

103 console: _Console, 

104 control: __.FormatterControl, 

105 mname: str, 

106 flavor: int | str, 

107) -> __.Formatter: 

108 ''' Produces formatter which uses Rich highlighter and prettier. ''' 

109 return __.funct.partial( _console_format, console ) 

110 

111 

112@_validate_arguments 

113def produce_console_printer( 

114 console: _Console, mname: str, flavor: __.Flavor 

115) -> __.Printer: 

116 # TODO: Remove from recipe. Should always use simple printer. 

117 ''' Produces a printer that uses Rich console printing. 

118 

119 .. note:: 

120 

121 May reprocess ANSI SGR codes or markup from formatters, potentially 

122 causing visual artifacts. Be careful to use this only with "safe" 

123 formatters. 

124 ''' 

125 return console.print 

126 

127 

128@_validate_arguments 

129def produce_pretty_formatter( 

130 control: __.FormatterControl, mname: str, flavor: int | str 

131) -> __.Formatter: 

132 ''' Produces formatter which uses Rich prettier. ''' 

133 return _pretty_repr 

134 

135 

136@_validate_arguments 

137def produce_truck( 

138 flavors: __.ProduceTruckFlavorsArgument = __.absent, 

139 active_flavors: __.ProduceTruckActiveFlavorsArgument = __.absent, 

140 trace_levels: __.ProduceTruckTraceLevelsArgument = __.absent, 

141 mode: ProduceTruckModeArgument = Modes.Formatter, 

142 stderr: ProduceTruckStderrArgument = True, 

143) -> __.Truck: 

144 ''' Produces icecream truck which integrates with Rich. ''' 

145 match mode: 

146 case Modes.Formatter: factory = _produce_formatter_truck 

147 case Modes.Printer: factory = _produce_printer_truck 

148 return factory( 

149 flavors = flavors, 

150 active_flavors = active_flavors, 

151 trace_levels = trace_levels, 

152 stderr = stderr ) 

153 

154 

155@_validate_arguments 

156def register_module( 

157 name: __.RegisterModuleNameArgument = __.absent, 

158 flavors: __.ProduceTruckFlavorsArgument = __.absent, 

159 include_context: __.RegisterModuleIncludeContextArgument = __.absent, 

160 prefix_emitter: __.RegisterModulePrefixEmitterArgument = __.absent, 

161) -> None: 

162 ''' Registers module with Rich prettier to format arguments. 

163 

164 Intended for library developers to configure debugging flavors without 

165 overriding anything set by the application or other libraries. 

166 ''' 

167 __.register_module( 

168 name = name, 

169 flavors = flavors, 

170 formatter_factory = produce_pretty_formatter, 

171 include_context = include_context, 

172 prefix_emitter = prefix_emitter ) 

173 

174 

175def _console_format( console: _Console, value: __.typx.Any ) -> str: 

176 with console.capture( ) as capture: 

177 console.print( value ) 

178 return capture.get( ) 

179 

180 

181def _produce_formatter_truck( 

182 flavors: __.ProduceTruckFlavorsArgument = __.absent, 

183 active_flavors: __.ProduceTruckActiveFlavorsArgument = __.absent, 

184 trace_levels: __.ProduceTruckTraceLevelsArgument = __.absent, 

185 stderr: ProduceTruckStderrArgument = True, 

186) -> __.Truck: 

187 console = _Console( stderr = stderr ) 

188 gc_nomargs = { } 

189 if not __.is_absent( flavors ): gc_nomargs[ 'flavors' ] = flavors 

190 generalcfg = __.VehicleConfiguration( 

191 formatter_factory = __.funct.partial( 

192 produce_console_formatter, console ), 

193 **gc_nomargs ) # pyright: ignore 

194 target = __.sys.stderr if stderr else __.sys.stdout 

195 if not isinstance( target, __.io.TextIOBase ): 

196 raise ConsoleTextIoInvalidity( target ) 

197 nomargs: dict[ str, __.typx.Any ] = dict( 

198 active_flavors = active_flavors, 

199 generalcfg = generalcfg, 

200 printer_factory = __.funct.partial( 

201 __.produce_simple_printer, target ), 

202 trace_levels = trace_levels ) 

203 return __.produce_truck( **nomargs ) 

204 

205 

206def _produce_printer_truck( 

207 flavors: __.ProduceTruckFlavorsArgument = __.absent, 

208 active_flavors: __.ProduceTruckActiveFlavorsArgument = __.absent, 

209 trace_levels: __.ProduceTruckTraceLevelsArgument = __.absent, 

210 stderr: ProduceTruckStderrArgument = True, 

211) -> __.Truck: 

212 console = _Console( stderr = stderr ) 

213 gc_nomargs = { } 

214 if not __.is_absent( flavors ): gc_nomargs[ 'flavors' ] = flavors 

215 generalcfg = __.VehicleConfiguration( 

216 formatter_factory = produce_pretty_formatter, 

217 **gc_nomargs ) # pyright: ignore 

218 target = __.sys.stderr if stderr else __.sys.stdout 

219 if not isinstance( target, __.io.TextIOBase ): # pragma: no cover 

220 raise ConsoleTextIoInvalidity( target ) 

221 nomargs: dict[ str, __.typx.Any ] = dict( 

222 active_flavors = active_flavors, 

223 generalcfg = generalcfg, 

224 printer_factory = __.funct.partial( produce_console_printer, console ), 

225 trace_levels = trace_levels ) 

226 return __.produce_truck( **nomargs ) 

227 

228 

229# def _produce_prefix( console: _Console, mname: str, flavor: _Flavor ) -> str: 

230# # TODO: Detect if terminal supports 256 colors or true color. 

231# # Make spectrum of hues for trace depths, if so. 

232# return _icecream.DEFAULT_PREFIX