Coverage for sources/ictruck/recipes/rich.py: 100%
73 statements
« prev ^ index » next coverage.py v7.7.1, created at 2025-03-21 21:53 +0000
« prev ^ index » next coverage.py v7.7.1, created at 2025-03-21 21:53 +0000
1# vim: set filetype=python fileencoding=utf-8:
2# -*- coding: utf-8 -*-
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#============================================================================#
21''' Recipes for Rich formatters and printers.
23 .. note::
25 To use this module, you must have the ``rich`` package installed.
26'''
29from __future__ import annotations
31import colorama as _colorama
33from rich.console import Console as _Console
34from rich.pretty import pretty_repr as _pretty_repr
36from . import __
39_validate_arguments = (
40 __.validate_arguments(
41 globalvars = globals( ),
42 errorclass = __.exceptions.ArgumentClassInvalidity ) )
45class ConsoleTextIoInvalidity( __.exceptions.Omnierror, TypeError ):
46 ''' Text stream invalid for use with Rich console. '''
48 def __init__( self, stream: __.typx.Any ):
49 super( ).__init__( f"Invalid stream for Rich console: {stream!r}" )
52class Modes( __.enum.Enum ):
53 ''' Operation modes for Rich truck. '''
54 Formatter = 'formatter'
55 Printer = 'printer'
58ProduceTruckModeArgument: __.typx.TypeAlias = __.typx.Annotated[
59 Modes,
60 __.typx.Doc(
61 ''' Operation mode.
63 ``Formatter`` uses Rich to highlight and pretty text prior to
64 printing (output). Text from non-Rich formatters will be printed
65 as-is. Safer, but slightly more boring option.
66 ``Printer`` uses Rich to highlight and pretty text while printing
67 (output). Text from non-Rich formatters will be potentially be
68 highlighted and prettied. If the text already contains ANSI SGR
69 sequences (e.g., terminal colorization), then it might be
70 reprocessed by the printer, causing visual artifacts. Less safe,
71 but more vibrant option.
72 ''' ),
73]
74ProduceTruckStderrArgument: __.typx.TypeAlias = __.typx.Annotated[
75 bool, __.typx.Doc( ''' Output to standard diagnostic stream? ''' )
76]
79@_validate_arguments
80def install( # pylint: disable=too-many-arguments
81 alias: __.InstallAliasArgument = __.builtins_alias_default,
82 flavors: __.ProduceTruckFlavorsArgument = __.absent,
83 active_flavors: __.ProduceTruckActiveFlavorsArgument = __.absent,
84 trace_levels: __.ProduceTruckTraceLevelsArgument = __.absent,
85 mode: ProduceTruckModeArgument = Modes.Formatter,
86 stderr: ProduceTruckStderrArgument = True,
87) -> __.Truck:
88 ''' Installs configured truck into builtins.
90 Application developers should call this early before importing
91 library packages which may also use the builtin truck.
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 __.install_builtin_safely(
100 alias, truck, __.exceptions.AttributeNondisplacement )
101 return truck
104@_validate_arguments
105def produce_console_formatter(
106 console: _Console,
107 # pylint: disable=unused-argument
108 control: __.FormatterControl,
109 mname: str,
110 flavor: int | str,
111 # pylint: enable=unused-argument
112) -> __.Formatter:
113 ''' Produces formatter which uses Rich highlighter and prettier. '''
114 return __.funct.partial( _console_format, console )
117@_validate_arguments
118def produce_console_printer(
119 console: _Console,
120 # pylint: disable=unused-argument
121 mname: str,
122 flavor: __.Flavor,
123 # pylint: enable=unused-argument
124) -> __.Printer:
125 ''' Produces a printer that uses Rich console printing.
127 .. note::
129 May reprocess ANSI SGR codes or markup from formatters, potentially
130 causing visual artifacts. Be careful to use this only with "safe"
131 formatters.
132 '''
133 return __.funct.partial( _console_print, console )
136@_validate_arguments
137def produce_pretty_formatter(
138 # pylint: disable=unused-argument
139 control: __.FormatterControl,
140 mname: str,
141 flavor: int | str,
142 # pylint: enable=unused-argument
143) -> __.Formatter:
144 ''' Produces formatter which uses Rich prettier. '''
145 return _pretty_repr
148@_validate_arguments
149def produce_simple_printer(
150 target: __.io.TextIOBase,
151 # pylint: disable=unused-argument
152 mname: str,
153 flavor: __.Flavor,
154 # pylint: enable=unused-argument
155) -> __.Printer:
156 ''' Produces printer which uses standard Python 'print'. '''
157 return __.funct.partial( _simple_print, target = target )
160@_validate_arguments
161def produce_truck(
162 flavors: __.ProduceTruckFlavorsArgument = __.absent,
163 active_flavors: __.ProduceTruckActiveFlavorsArgument = __.absent,
164 trace_levels: __.ProduceTruckTraceLevelsArgument = __.absent,
165 mode: ProduceTruckModeArgument = Modes.Formatter,
166 stderr: ProduceTruckStderrArgument = True,
167) -> __.Truck:
168 ''' Produces icecream truck which integrates with Rich. '''
169 match mode:
170 case Modes.Formatter: factory = _produce_formatter_truck
171 case Modes.Printer: factory = _produce_printer_truck
172 return factory(
173 flavors = flavors,
174 active_flavors = active_flavors,
175 trace_levels = trace_levels,
176 stderr = stderr )
179@_validate_arguments
180def register_module(
181 name: __.RegisterModuleNameArgument = __.absent,
182 flavors: __.ProduceTruckFlavorsArgument = __.absent,
183 include_context: __.RegisterModuleIncludeContextArgument = __.absent,
184 prefix_emitter: __.RegisterModulePrefixEmitterArgument = __.absent,
185) -> None:
186 ''' Registers module with Rich prettier to format arguments.
188 Intended for library developers to configure debugging flavors without
189 overriding anything set by the application or other libraries.
190 '''
191 __.register_module(
192 name = name,
193 flavors = flavors,
194 formatter_factory = produce_pretty_formatter,
195 include_context = include_context,
196 prefix_emitter = prefix_emitter )
199def _console_format( console: _Console, value: __.typx.Any ) -> str:
200 with console.capture( ) as capture:
201 console.print( value )
202 return capture.get( )
205def _console_print( console: _Console, text: str ) -> None:
206 with _windows_replace_ansi_sgr( ):
207 # console.print( text, markup = False )
208 console.print( text )
211def _produce_formatter_truck(
212 flavors: __.ProduceTruckFlavorsArgument = __.absent,
213 active_flavors: __.ProduceTruckActiveFlavorsArgument = __.absent,
214 trace_levels: __.ProduceTruckTraceLevelsArgument = __.absent,
215 stderr: ProduceTruckStderrArgument = True,
216) -> __.Truck:
217 console = _Console( stderr = stderr )
218 gc_nomargs = { }
219 if not __.is_absent( flavors ): gc_nomargs[ 'flavors' ] = flavors
220 generalcfg = __.VehicleConfiguration(
221 formatter_factory = __.funct.partial(
222 produce_console_formatter, console ),
223 **gc_nomargs ) # pyright: ignore
224 target = __.sys.stderr if stderr else __.sys.stdout
225 if not isinstance( target, __.io.TextIOBase ):
226 raise ConsoleTextIoInvalidity( target )
227 nomargs: dict[ str, __.typx.Any ] = dict(
228 active_flavors = active_flavors,
229 generalcfg = generalcfg,
230 printer_factory = __.funct.partial( produce_simple_printer, target ),
231 trace_levels = trace_levels )
232 return __.produce_truck( **nomargs )
235def _produce_printer_truck(
236 flavors: __.ProduceTruckFlavorsArgument = __.absent,
237 active_flavors: __.ProduceTruckActiveFlavorsArgument = __.absent,
238 trace_levels: __.ProduceTruckTraceLevelsArgument = __.absent,
239 stderr: ProduceTruckStderrArgument = True,
240) -> __.Truck:
241 console = _Console( stderr = stderr )
242 gc_nomargs = { }
243 if not __.is_absent( flavors ): gc_nomargs[ 'flavors' ] = flavors
244 generalcfg = __.VehicleConfiguration(
245 formatter_factory = produce_pretty_formatter,
246 **gc_nomargs ) # pyright: ignore
247 target = __.sys.stderr if stderr else __.sys.stdout
248 if not isinstance( target, __.io.TextIOBase ): # pragma: no cover
249 raise ConsoleTextIoInvalidity( target )
250 nomargs: dict[ str, __.typx.Any ] = dict(
251 active_flavors = active_flavors,
252 generalcfg = generalcfg,
253 printer_factory = __.funct.partial( produce_console_printer, console ),
254 trace_levels = trace_levels )
255 return __.produce_truck( **nomargs )
258# def _produce_prefix( console: _Console, mname: str, flavor: _Flavor ) -> str:
259# # TODO: Detect if terminal supports 256 colors or true color.
260# # Make spectrum of hues for trace depths, if so.
261# return _icecream.DEFAULT_PREFIX
264def _simple_print( text: str, target: __.io.TextIOBase ) -> None:
265 with _windows_replace_ansi_sgr( ):
266 print( text, file = target )
269@__.ctxl.contextmanager
270def _windows_replace_ansi_sgr( ) -> __.typx.Generator[ None, None, None ]:
271 # Note: Copied from the 'icecream' sources.
272 # Converts ANSI SGR sequences to Windows API calls on older
273 # command terminals which do not have proper ANSI SGR support.
274 # Otherwise, rendering on terminal occurs normally.
275 _colorama.init( )
276 yield
277 _colorama.deinit( )