Coverage for sources/ictruck/recipes/rich.py: 100%
72 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-03-31 03:43 +0000
« prev ^ index » next coverage.py v7.8.0, created at 2025-03-31 03:43 +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 ''' Produces truck and installs it into builtins with alias.
90 Replaces an existing truck, preserving its module configurations.
92 Library developers should call :py:func:`__.register_module` instead.
93 '''
94 truck = produce_truck(
95 flavors = flavors,
96 active_flavors = active_flavors,
97 trace_levels = trace_levels,
98 mode = mode,
99 stderr = stderr )
100 return truck.install( alias = alias )
103@_validate_arguments
104def produce_console_formatter(
105 console: _Console,
106 # pylint: disable=unused-argument
107 control: __.FormatterControl,
108 mname: str,
109 flavor: int | str,
110 # pylint: enable=unused-argument
111) -> __.Formatter:
112 ''' Produces formatter which uses Rich highlighter and prettier. '''
113 return __.funct.partial( _console_format, console )
116@_validate_arguments
117def produce_console_printer(
118 console: _Console,
119 # pylint: disable=unused-argument
120 mname: str,
121 flavor: __.Flavor,
122 # pylint: enable=unused-argument
123) -> __.Printer:
124 ''' Produces a printer that uses Rich console printing.
126 .. note::
128 May reprocess ANSI SGR codes or markup from formatters, potentially
129 causing visual artifacts. Be careful to use this only with "safe"
130 formatters.
131 '''
132 return __.funct.partial( _console_print, console )
135@_validate_arguments
136def produce_pretty_formatter(
137 # pylint: disable=unused-argument
138 control: __.FormatterControl,
139 mname: str,
140 flavor: int | str,
141 # pylint: enable=unused-argument
142) -> __.Formatter:
143 ''' Produces formatter which uses Rich prettier. '''
144 return _pretty_repr
147@_validate_arguments
148def produce_simple_printer(
149 target: __.io.TextIOBase,
150 # pylint: disable=unused-argument
151 mname: str,
152 flavor: __.Flavor,
153 # pylint: enable=unused-argument
154) -> __.Printer:
155 ''' Produces printer which uses standard Python 'print'. '''
156 return __.funct.partial( _simple_print, target = target )
159@_validate_arguments
160def produce_truck(
161 flavors: __.ProduceTruckFlavorsArgument = __.absent,
162 active_flavors: __.ProduceTruckActiveFlavorsArgument = __.absent,
163 trace_levels: __.ProduceTruckTraceLevelsArgument = __.absent,
164 mode: ProduceTruckModeArgument = Modes.Formatter,
165 stderr: ProduceTruckStderrArgument = True,
166) -> __.Truck:
167 ''' Produces icecream truck which integrates with Rich. '''
168 match mode:
169 case Modes.Formatter: factory = _produce_formatter_truck
170 case Modes.Printer: factory = _produce_printer_truck
171 return factory(
172 flavors = flavors,
173 active_flavors = active_flavors,
174 trace_levels = trace_levels,
175 stderr = stderr )
178@_validate_arguments
179def register_module(
180 name: __.RegisterModuleNameArgument = __.absent,
181 flavors: __.ProduceTruckFlavorsArgument = __.absent,
182 include_context: __.RegisterModuleIncludeContextArgument = __.absent,
183 prefix_emitter: __.RegisterModulePrefixEmitterArgument = __.absent,
184) -> None:
185 ''' Registers module with Rich prettier to format arguments.
187 Intended for library developers to configure debugging flavors without
188 overriding anything set by the application or other libraries.
189 '''
190 __.register_module(
191 name = name,
192 flavors = flavors,
193 formatter_factory = produce_pretty_formatter,
194 include_context = include_context,
195 prefix_emitter = prefix_emitter )
198def _console_format( console: _Console, value: __.typx.Any ) -> str:
199 with console.capture( ) as capture:
200 console.print( value )
201 return capture.get( )
204def _console_print( console: _Console, text: str ) -> None:
205 with _windows_replace_ansi_sgr( ):
206 # console.print( text, markup = False )
207 console.print( text )
210def _produce_formatter_truck(
211 flavors: __.ProduceTruckFlavorsArgument = __.absent,
212 active_flavors: __.ProduceTruckActiveFlavorsArgument = __.absent,
213 trace_levels: __.ProduceTruckTraceLevelsArgument = __.absent,
214 stderr: ProduceTruckStderrArgument = True,
215) -> __.Truck:
216 console = _Console( stderr = stderr )
217 gc_nomargs = { }
218 if not __.is_absent( flavors ): gc_nomargs[ 'flavors' ] = flavors
219 generalcfg = __.VehicleConfiguration(
220 formatter_factory = __.funct.partial(
221 produce_console_formatter, console ),
222 **gc_nomargs ) # pyright: ignore
223 target = __.sys.stderr if stderr else __.sys.stdout
224 if not isinstance( target, __.io.TextIOBase ):
225 raise ConsoleTextIoInvalidity( target )
226 nomargs: dict[ str, __.typx.Any ] = dict(
227 active_flavors = active_flavors,
228 generalcfg = generalcfg,
229 printer_factory = __.funct.partial( produce_simple_printer, target ),
230 trace_levels = trace_levels )
231 return __.produce_truck( **nomargs )
234def _produce_printer_truck(
235 flavors: __.ProduceTruckFlavorsArgument = __.absent,
236 active_flavors: __.ProduceTruckActiveFlavorsArgument = __.absent,
237 trace_levels: __.ProduceTruckTraceLevelsArgument = __.absent,
238 stderr: ProduceTruckStderrArgument = True,
239) -> __.Truck:
240 console = _Console( stderr = stderr )
241 gc_nomargs = { }
242 if not __.is_absent( flavors ): gc_nomargs[ 'flavors' ] = flavors
243 generalcfg = __.VehicleConfiguration(
244 formatter_factory = produce_pretty_formatter,
245 **gc_nomargs ) # pyright: ignore
246 target = __.sys.stderr if stderr else __.sys.stdout
247 if not isinstance( target, __.io.TextIOBase ): # pragma: no cover
248 raise ConsoleTextIoInvalidity( target )
249 nomargs: dict[ str, __.typx.Any ] = dict(
250 active_flavors = active_flavors,
251 generalcfg = generalcfg,
252 printer_factory = __.funct.partial( produce_console_printer, console ),
253 trace_levels = trace_levels )
254 return __.produce_truck( **nomargs )
257# def _produce_prefix( console: _Console, mname: str, flavor: _Flavor ) -> str:
258# # TODO: Detect if terminal supports 256 colors or true color.
259# # Make spectrum of hues for trace depths, if so.
260# return _icecream.DEFAULT_PREFIX
263def _simple_print( text: str, target: __.io.TextIOBase ) -> None:
264 with _windows_replace_ansi_sgr( ):
265 print( text, file = target )
268@__.ctxl.contextmanager
269def _windows_replace_ansi_sgr( ) -> __.typx.Generator[ None, None, None ]:
270 # Note: Copied from the 'icecream' sources.
271 # Converts ANSI SGR sequences to Windows API calls on older
272 # command terminals which do not have proper ANSI SGR support.
273 # Otherwise, rendering on terminal occurs normally.
274 _colorama.init( )
275 yield
276 _colorama.deinit( )