Coverage for sources / ictr / standard / core.py: 100%
91 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''' Core data structures and utilities. '''
24from . import __
27class Auxiliaries( __.immut.DataclassObject ):
28 ''' Auxiliary functions used by textualizers and interpolation.
30 Typically used by unit tests to inject mock dependencies,
31 but can also be used to deeply customize output.
32 '''
34 pid_discoverer: __.typx.Annotated[
35 __.typx.Callable[ [ ], int ],
36 __.ddoc.Doc( ''' Returns ID of current process. ''' ),
37 ] = __.os.getpid
38 thread_discoverer: __.typx.Annotated[
39 __.typx.Callable[ [ ], __.threads.Thread ],
40 __.ddoc.Doc( ''' Returns current thread. ''' ),
41 ] = __.threads.current_thread
42 time_formatter: __.typx.Annotated[
43 __.typx.Callable[ [ str ], str ],
44 __.ddoc.Doc( ''' Returns current time in specified format. ''' ),
45 ] = lambda fmt: __.Datetime.now( __.Timezone.utc ).strftime( fmt )
48class ColumnsConstraints( __.enum.Enum ):
49 ''' How to constrain text which exceeds maximum columns. '''
51 Complect = __.enum.auto( ) # fold/wrap
52 Exceed = __.enum.auto( ) # overflow
53 # Truncate = __.enum.auto( ) # chop/cut
56class IncisionBoundaries( __.enum.Enum ):
57 ''' Where to constrain text which exceeds maximum columns. '''
59 Nowhere = __.enum.auto( )
60 Whitespace = __.enum.auto( ) # horizontal spaces and tabs
61 Wordsplits = __.enum.auto( ) # hyphens + whitespace
62 Anywhere = __.enum.auto( )
65class Style( __.immut.DataclassObject ):
66 ''' Style for text. Corresponds to terminal attributes. '''
68 bgcolor: __.typx.Optional[ str ] = None
69 fgcolor: __.typx.Optional[ str ] = None
70 # TODO: Int flag enum for bold, blink, etc...
73InterpolantsStylesRegistry: __.typx.TypeAlias = (
74 __.accret.Dictionary[ str, Style ] )
77class LabelPresentations( __.enum.IntFlag ):
78 ''' How introduction labels should be presented. '''
80 Nothing = 0
81 Words = __.enum.auto( )
82 Emoji = __.enum.auto( )
85class ExceptionsConfiguration( __.immut.DataclassObject ):
86 ''' Configuration pertaining to exceptions. '''
88 discoverer: __.typx.Annotated[
89 __.typx.Callable[ [ ], __.ExceptionInfo ],
90 __.ddoc.Doc( ''' Returns information on current exception. ''' ),
91 ] = __.sys.exc_info
92 enable_discovery: __.typx.Annotated[
93 bool, __.ddoc.Doc( ''' Discover active exception? ''' )
94 ] = False
95 enable_stacktraces: __.typx.Annotated[
96 bool, __.ddoc.Doc( ''' Render tracebacks? ''' )
97 ] = False
98 recursive_stacktraces: __.typx.Annotated[
99 bool, __.ddoc.Doc(
100 ''' Render traceback for each exception group member? ''' ),
101 ] = False
102 template: __.typx.Annotated[
103 str, __.ddoc.Doc( ''' Template for exception message. ''' )
104 ] = '[{name}] {message}'
106 def discover( self ) -> __.typx.Optional[ BaseException ]:
107 ''' Discovers active exception. '''
108 return self.discoverer( )[ 1 ] if self.enable_discovery else None
110 def interpolate( self, exception: BaseException ) -> tuple[ str, ... ]:
111 ''' Interpolates exception attributes into message template. '''
112 eclass = type( exception )
113 name = eclass.__name__
114 qname = eclass.__qualname__
115 mname = eclass.__module__
116 interpolants = dict(
117 name = name, qname = qname, mname = mname,
118 message = str( exception ) )
119 interpolants[ 'fqname' ] = f"{mname}.{qname}"
120 return tuple( self.template.format( **interpolants ).split( '\n' ) )
123class IntroducerConfiguration( __.immut.DataclassObject ):
124 ''' Behaviors and format for text from standard introducer. '''
126 auxiliaries: __.typx.Annotated[
127 Auxiliaries, __.typx.Doc( ''' Auxiliaries for interpolation. ''' )
128 ] = __.dcls.field( default_factory = Auxiliaries )
129 colorize: __.typx.Annotated[
130 bool, __.typx.Doc( ''' Attempt to colorize? ''' )
131 ] = True
132 label_as: __.typx.Annotated[
133 LabelPresentations,
134 __.ddoc.Doc(
135 ''' How to present prefix label.
137 ``Words``: As words like ``TRACE0`` or ``ERROR``.
138 ``Emoji``: As emoji like ``🔎`` or ``❌``.
140 For both emoji and words: ``Emoji | Words``.
141 ''' )
142 ] = LabelPresentations.Words
143 styles: __.typx.Annotated[
144 InterpolantsStylesRegistry,
145 __.ddoc.Doc(
146 ''' Mapping of interpolant names to style objects. ''' ),
147 ] = __.dcls.field( default_factory = InterpolantsStylesRegistry )
148 template: __.typx.Annotated[
149 str,
150 __.ddoc.Doc(
151 ''' String format for prefix.
153 The following interpolants are supported:
154 ``flavor``: Decorated flavor.
155 ``address``: Address of invoker.
156 ``timestamp``: Current timestamp, formatted as string.
157 ``process_id``: ID of current process according to OS kernel.
158 ``thread_id``: ID of current thread.
159 ``thread_name``: Name of current thread.
160 ''' ),
161 ] = "{flavor}| " # "{timestamp} [{module_qname}] {flavor}| "
162 ts_format: __.typx.Annotated[
163 str,
164 __.ddoc.Doc(
165 ''' String format for prefix timestamp.
167 Used by :py:func:`time.strftime` or equivalent.
168 ''' ),
169 ] = '%Y-%m-%d %H:%M:%S.%f'
172class IntroducerState( __.immut.DataclassObject ):
173 ''' Data transfer object for introducer state. '''
175 configuration: IntroducerConfiguration
176 control: __.TextualizationControl
177 colorize: __.typx.Annotated[ bool, __.ddoc.Doc( ''' Colorize? ''' ) ]
178 columns_max: __.typx.Annotated[
179 __.Absential[ int ],
180 __.ddoc.Doc(
181 ''' Available line length (maximum columns) of target. ''' ),
182 ] = __.absent
184 @classmethod
185 def from_configuration(
186 cls,
187 configuration: IntroducerConfiguration,
188 control: __.TextualizationControl,
189 columns_max: __.Absential[ int ] = __.absent,
190 ) -> __.typx.Self:
191 colorize = __.ENRICH and control.colorize and configuration.colorize
192 return cls(
193 configuration = configuration,
194 control = control,
195 colorize = colorize,
196 columns_max = columns_max )
199class LinearizerConfiguration( __.immut.DataclassObject ):
200 ''' Behaviors for standard textual linearizer. '''
202 colorize: __.typx.Annotated[
203 bool, __.typx.Doc( ''' Attempt to colorize? ''' )
204 ] = True
205 columns_constraint: __.typx.Annotated[
206 ColumnsConstraints,
207 __.ddoc.Doc(
208 ''' How to constrain text which exceeds maximum columns. ''' ),
209 ] = ColumnsConstraints.Complect
210 columns_max: __.typx.Annotated[
211 __.typx.Optional[ int ],
212 __.ddoc.Doc(
213 ''' How many columns per line to assume if printer does not tell.
215 If ``None``, then infinite number of columns is assumed.
216 ''' ),
217 ] = None
218 exceptionscfg: __.typx.Annotated[
219 ExceptionsConfiguration,
220 __.ddoc.Doc( ''' Configuration pertaining to exceptions. ''' ),
221 ] = __.dcls.field( default_factory = ExceptionsConfiguration )
222 incision_boundary: __.typx.Annotated[
223 IncisionBoundaries,
224 __.ddoc.Doc(
225 ''' Where to constrain text which exceeds maximum columns. ''' ),
226 ] = IncisionBoundaries.Wordsplits
229class LinearizerState( __.immut.DataclassObject ):
230 ''' Data transfer object for linearizer state. '''
232 configuration: LinearizerConfiguration
233 control: __.TextualizationControl
234 colorize: __.typx.Annotated[ bool, __.ddoc.Doc( ''' Colorize? ''' ) ]
235 columns_constraint: __.typx.Annotated[
236 ColumnsConstraints,
237 __.ddoc.Doc( ''' Effective columns constraint for lines. ''' ),
238 ] = ColumnsConstraints.Exceed
239 columns_max: __.typx.Annotated[
240 __.Absential[ int ],
241 __.ddoc.Doc(
242 ''' Available line length (maximum columns) of target. ''' ),
243 ] = __.absent
245 @classmethod
246 def from_configuration(
247 cls,
248 configuration: LinearizerConfiguration,
249 control: __.TextualizationControl,
250 ) -> __.typx.Self:
251 colorize = __.ENRICH and control.colorize and configuration.colorize
252 columns_constraint = configuration.columns_constraint
253 columns_max = control.columns_max or configuration.columns_max
254 if columns_max is None:
255 columns_constraint = ColumnsConstraints.Exceed
256 columns_max = __.absent
257 return cls(
258 configuration = configuration,
259 control = control,
260 colorize = colorize,
261 columns_constraint = columns_constraint,
262 columns_max = columns_max )
265class CompositorConfiguration( __.immut.DataclassObject ):
266 ''' Behaviors and format for text from standard compositor. '''
268 detail_prefix_initial: __.typx.Annotated[
269 str, __.ddoc.Doc( ''' Initial prefix for message detail. ''' )
270 ] = ''
271 detail_prefix_subsequent: __.typx.Annotated[
272 __.typx.Optional[ str ],
273 __.ddoc.Doc(
274 ''' Subsequent prefix for message detail.
276 If ``None``, then automatic padding is calculated based on the
277 visual width of the initial prefix for message detail.
278 ''' ),
279 ] = None
280 # TODO? 'details_maximum'
281 details_separator: __.typx.Annotated[
282 str, __.ddoc.Doc( ''' Separator between details. ''' )
283 ] = '\n\n'
284 line_prefix_initial: __.typx.Annotated[
285 str, __.ddoc.Doc( ''' Prefix before first line. ''' )
286 ] = ''
287 line_prefix_subsequent: __.typx.Annotated[
288 __.typx.Optional[ str ],
289 __.ddoc.Doc(
290 ''' Prefix before each line after the first.
292 If ``None``, then automatic padding is calculated based on the
293 visual width of the initial line prefix.
294 ''' ),
295 ] = None
296 linearizercfg: __.typx.Annotated[
297 LinearizerConfiguration,
298 __.ddoc.Doc(
299 ''' Text linearization and pretty-formatting behaviors. ''' ),
300 ] = __.dcls.field( default_factory = LinearizerConfiguration )
301 summary_incision_ratio: __.typx.Annotated[
302 float,
303 __.ddoc.Doc(
304 ''' Ratio of introduction width to full width at which to split.
306 If ratio is met or exceeded, then introduction and summary are
307 split onto consecutive lines.
308 ''' ),
309 ] = 0.3
312class CompositorState( __.immut.DataclassObject ):
313 ''' Data transfer object for textualizer state. '''
315 configuration: CompositorConfiguration
316 linearizer: LinearizerState
318 @classmethod
319 def from_configuration(
320 cls,
321 configuration: CompositorConfiguration,
322 control: __.TextualizationControl,
323 ) -> __.typx.Self:
324 linearizer = LinearizerState.from_configuration(
325 configuration = configuration.linearizercfg, control = control )
326 return cls( configuration = configuration, linearizer = linearizer )
329COMPOSITOR_CONFIGURATION_DEFAULT = CompositorConfiguration( )
330INTRODUCER_CONFIGURATION_DEFAULT = IntroducerConfiguration( )