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

57 statements  

« prev     ^ index     » next       coverage.py v7.9.1, created at 2025-07-01 16:01 +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 

29from __future__ import annotations 

30 

31from rich.console import Console as _Console 

32from rich.pretty import pretty_repr as _pretty_repr 

33 

34from . import __ 

35 

36 

37_validate_arguments = ( 

38 __.validate_arguments( 

39 globalvars = globals( ), 

40 errorclass = __.exceptions.ArgumentClassInvalidity ) ) 

41 

42 

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

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

45 

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

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

48 

49 

50class Modes( __.enum.Enum ): 

51 ''' Operation modes for Rich truck. ''' 

52 

53 Formatter = 'formatter' 

54 Printer = 'printer' 

55 

56 

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

58 Modes, 

59 __.typx.Doc( 

60 ''' Operation mode. 

61 

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

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

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

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

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

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

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

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

70 but more vibrant option. 

71 ''' ), 

72] 

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

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

75] 

76 

77 

78@_validate_arguments 

79def install( # noqa: PLR0913 

80 alias: __.InstallAliasArgument = __.builtins_alias_default, 

81 flavors: __.ProduceTruckFlavorsArgument = __.absent, 

82 active_flavors: __.ProduceTruckActiveFlavorsArgument = __.absent, 

83 trace_levels: __.ProduceTruckTraceLevelsArgument = __.absent, 

84 mode: ProduceTruckModeArgument = Modes.Formatter, 

85 stderr: ProduceTruckStderrArgument = True, 

86) -> __.Truck: 

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

88 

89 Replaces an existing truck, preserving global module configurations. 

90 

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

92 ''' 

93 truck = produce_truck( 

94 flavors = flavors, 

95 active_flavors = active_flavors, 

96 trace_levels = trace_levels, 

97 mode = mode, 

98 stderr = stderr ) 

99 return truck.install( alias = alias ) 

100 

101 

102@_validate_arguments 

103def produce_console_formatter( 

104 console: _Console, 

105 control: __.FormatterControl, 

106 mname: str, 

107 flavor: int | str, 

108) -> __.Formatter: 

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

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

111 

112 

113@_validate_arguments 

114def produce_console_printer( 

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

116) -> __.Printer: 

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

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

119 

120 .. note:: 

121 

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

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

124 formatters. 

125 ''' 

126 return console.print 

127 

128 

129@_validate_arguments 

130def produce_pretty_formatter( 

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

132) -> __.Formatter: 

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

134 return _pretty_repr 

135 

136 

137@_validate_arguments 

138def produce_truck( 

139 flavors: __.ProduceTruckFlavorsArgument = __.absent, 

140 active_flavors: __.ProduceTruckActiveFlavorsArgument = __.absent, 

141 trace_levels: __.ProduceTruckTraceLevelsArgument = __.absent, 

142 mode: ProduceTruckModeArgument = Modes.Formatter, 

143 stderr: ProduceTruckStderrArgument = True, 

144) -> __.Truck: 

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

146 match mode: 

147 case Modes.Formatter: factory = _produce_formatter_truck 

148 case Modes.Printer: factory = _produce_printer_truck 

149 return factory( 

150 flavors = flavors, 

151 active_flavors = active_flavors, 

152 trace_levels = trace_levels, 

153 stderr = stderr ) 

154 

155 

156@_validate_arguments 

157def register_module( 

158 name: __.RegisterModuleNameArgument = __.absent, 

159 flavors: __.ProduceTruckFlavorsArgument = __.absent, 

160 include_context: __.RegisterModuleIncludeContextArgument = __.absent, 

161 prefix_emitter: __.RegisterModulePrefixEmitterArgument = __.absent, 

162) -> None: 

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

164 

165 Intended for library developers to configure debugging flavors without 

166 overriding anything set by the application or other libraries. 

167 ''' 

168 __.register_module( 

169 name = name, 

170 flavors = flavors, 

171 formatter_factory = produce_pretty_formatter, 

172 include_context = include_context, 

173 prefix_emitter = prefix_emitter ) 

174 

175 

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

177 with console.capture( ) as capture: 

178 console.print( value ) 

179 return capture.get( ) 

180 

181 

182def _produce_formatter_truck( 

183 flavors: __.ProduceTruckFlavorsArgument = __.absent, 

184 active_flavors: __.ProduceTruckActiveFlavorsArgument = __.absent, 

185 trace_levels: __.ProduceTruckTraceLevelsArgument = __.absent, 

186 stderr: ProduceTruckStderrArgument = True, 

187) -> __.Truck: 

188 console = _Console( stderr = stderr ) 

189 gc_nomargs = { } 

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

191 generalcfg = __.VehicleConfiguration( 

192 formatter_factory = __.funct.partial( 

193 produce_console_formatter, console ), 

194 **gc_nomargs ) # pyright: ignore 

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

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

197 raise ConsoleTextIoInvalidity( target ) 

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

199 active_flavors = active_flavors, 

200 generalcfg = generalcfg, 

201 printer_factory = __.funct.partial( 

202 __.produce_simple_printer, target ), 

203 trace_levels = trace_levels ) 

204 return __.produce_truck( **nomargs ) 

205 

206 

207def _produce_printer_truck( 

208 flavors: __.ProduceTruckFlavorsArgument = __.absent, 

209 active_flavors: __.ProduceTruckActiveFlavorsArgument = __.absent, 

210 trace_levels: __.ProduceTruckTraceLevelsArgument = __.absent, 

211 stderr: ProduceTruckStderrArgument = True, 

212) -> __.Truck: 

213 console = _Console( stderr = stderr ) 

214 gc_nomargs = { } 

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

216 generalcfg = __.VehicleConfiguration( 

217 formatter_factory = produce_pretty_formatter, 

218 **gc_nomargs ) # pyright: ignore 

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

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

221 raise ConsoleTextIoInvalidity( target ) 

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

223 active_flavors = active_flavors, 

224 generalcfg = generalcfg, 

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

226 trace_levels = trace_levels ) 

227 return __.produce_truck( **nomargs ) 

228 

229 

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

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

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

233# return _icecream.DEFAULT_PREFIX