Coverage for sources / ictr / standard / introducers.py: 83%
67 statements
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-12 01:33 +0000
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-12 01:33 +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''' Standard introducer with support for decorations and styles. '''
24from . import __
25from . import core as _core
28class Introducer( __.Introducer ):
29 ''' Standard introducer. '''
31 configuration: __.typx.Annotated[
32 _core.IntroducerConfiguration,
33 __.ddoc.Doc(
34 ''' Default behaviors and format for introductory text. ''' ),
35 ] = __.dcls.field( default_factory = _core.IntroducerConfiguration )
37 def __call__(
38 self,
39 control: __.TextualizationControl,
40 record: __.Record,
41 columns_max: __.Absential[ int ] = __.absent,
42 ) -> str:
43 configuration = self.configuration
44 auxdata = _core.IntroducerState.from_configuration(
45 configuration = configuration,
46 control = control,
47 columns_max = columns_max )
48 if isinstance( record.flavor, int ):
49 return _render_trace_label( auxdata, record )
50 return _render_nominal_label( auxdata, record )
53def _render_nominal_label(
54 auxdata: _core.IntroducerState, record: __.Record
55) -> str:
56 configuration = auxdata.configuration
57 styles = dict( configuration.styles )
58 flavor = record.flavor
59 if isinstance( flavor, int ): 59 ↛ 60line 59 didn't jump to line 60 because the condition on line 59 was never true
60 raise __.FlavorMisclassification( flavor, expectation = 'string' )
61 name = __.flavor_aliases_standard.get( flavor, flavor )
62 spec = __.flavor_specifications_standard[ name ]
63 label = ''
64 if configuration.label_as & _core.LabelPresentations.Emoji:
65 if configuration.label_as & _core.LabelPresentations.Words: 65 ↛ 66line 65 didn't jump to line 66 because the condition on line 65 was never true
66 label = f"{spec.emoji} {spec.label}"
67 else: label = f"{spec.emoji}"
68 elif configuration.label_as & _core.LabelPresentations.Words: 68 ↛ 70line 68 didn't jump to line 70 because the condition on line 68 was always true
69 label = f"{spec.label}"
70 if auxdata.colorize:
71 styles[ 'flavor' ] = _core.Style( fgcolor = spec.color )
72 return _render_common( auxdata, record, styles, label )
75def _render_trace_label(
76 auxdata: _core.IntroducerState, record: __.Record
77) -> str:
78 # TODO? Option to render indentation guides.
79 configuration = auxdata.configuration
80 styles = dict( configuration.styles )
81 flavor = record.flavor
82 if not isinstance( flavor, int ): 82 ↛ 83line 82 didn't jump to line 83 because the condition on line 82 was never true
83 raise __.FlavorMisclassification( flavor, expectation = 'int' )
84 level = flavor
85 label = ''
86 if configuration.label_as & _core.LabelPresentations.Emoji: 86 ↛ 87line 86 didn't jump to line 87 because the condition on line 86 was never true
87 if configuration.label_as & _core.LabelPresentations.Words:
88 label = f"🔎 TRACE{level}"
89 else: label = '🔎'
90 elif configuration.label_as & _core.LabelPresentations.Words: 90 ↛ 92line 90 didn't jump to line 92 because the condition on line 90 was always true
91 label = f"TRACE{level}"
92 if auxdata.colorize and level < len( _trace_color_names ): 92 ↛ 93line 92 didn't jump to line 93 because the condition on line 92 was never true
93 styles[ 'flavor' ] = (
94 _core.Style( fgcolor = _trace_color_names[ level ] ) )
95 return _render_common( auxdata, record, styles, label )
98def _render_common(
99 auxdata: _core.IntroducerState,
100 record: __.Record,
101 styles: __.cabc.Mapping[ str, _core.Style ],
102 label: str
103) -> str:
104 # TODO? Performance optimization: Only compute and interpolate PID, thread,
105 # and timestamp, if capabilities set permits.
106 configuration = auxdata.configuration
107 auxiliaries = configuration.auxiliaries
108 thread = auxiliaries.thread_discoverer( )
109 interpolants: dict[ str, str ] = {
110 'flavor': label,
111 'address': record.address,
112 'timestamp': auxiliaries.time_formatter( configuration.ts_format ),
113 'process_id': str( auxiliaries.pid_discoverer( ) ),
114 'thread_id': str( thread.ident ),
115 'thread_name': thread.name,
116 }
117 if auxdata.colorize:
118 _stylize_interpolants( auxdata, interpolants, styles )
119 return configuration.template.format( **interpolants )
122def _stylize_interpolants(
123 auxdata: _core.IntroducerState,
124 interpolants: dict[ str, str ],
125 styles: __.cabc.Mapping[ str, _core.Style ],
126) -> None:
127 style_default = styles.get( 'flavor' )
128 interpolants_: dict[ str, str ] = { }
129 for iname, ivalue in interpolants.items( ):
130 style = styles.get( iname, style_default )
131 if not style: continue # pragma: no branch
132 capture = __.io.StringIO( )
133 console = __.produce_rich_console(
134 auxdata.control, capture, auxdata.columns_max )
135 style_ = __.rich_style.Style( color = style.fgcolor )
136 console.print( ivalue, end = '', highlight = False, style = style_ )
137 interpolants_[ iname ] = capture.getvalue( )
138 interpolants.update( interpolants_ )
141_trace_color_names: tuple[ str, ... ] = (
142 'grey85', 'grey82', 'grey78', 'grey74', 'grey70',
143 'grey66', 'grey62', 'grey58', 'grey54', 'grey50' )
145_trace_prefix_styles: tuple[ _core.Style, ... ] = tuple(
146 _core.Style( fgcolor = name ) for name in _trace_color_names )