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

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''' Standard introducer with support for decorations and styles. ''' 

22 

23 

24from . import __ 

25from . import core as _core 

26 

27 

28class Introducer( __.Introducer ): 

29 ''' Standard introducer. ''' 

30 

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 ) 

36 

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 ) 

51 

52 

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 ) 

73 

74 

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 ) 

96 

97 

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 ) 

120 

121 

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_ ) 

139 

140 

141_trace_color_names: tuple[ str, ... ] = ( 

142 'grey85', 'grey82', 'grey78', 'grey74', 'grey70', 

143 'grey66', 'grey62', 'grey58', 'grey54', 'grey50' ) 

144 

145_trace_prefix_styles: tuple[ _core.Style, ... ] = tuple( 

146 _core.Style( fgcolor = name ) for name in _trace_color_names )