Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/stack_data/formatting.py: 19%
128 statements
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-20 06:09 +0000
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-20 06:09 +0000
1import inspect
2import sys
3import traceback
4from types import FrameType, TracebackType
5from typing import Union, Iterable
7from stack_data import (style_with_executing_node, Options, Line, FrameInfo, LINE_GAP,
8 Variable, RepeatedFrames, BlankLineRange, BlankLines)
9from stack_data.utils import assert_
12class Formatter:
13 def __init__(
14 self, *,
15 options=None,
16 pygmented=False,
17 show_executing_node=True,
18 pygments_formatter_cls=None,
19 pygments_formatter_kwargs=None,
20 pygments_style="monokai",
21 executing_node_modifier="bg:#005080",
22 executing_node_underline="^",
23 current_line_indicator="-->",
24 line_gap_string="(...)",
25 line_number_gap_string=":",
26 line_number_format_string="{:4} | ",
27 show_variables=False,
28 use_code_qualname=True,
29 show_linenos=True,
30 strip_leading_indent=True,
31 html=False,
32 chain=True,
33 collapse_repeated_frames=True
34 ):
35 if options is None:
36 options = Options()
38 if pygmented and not options.pygments_formatter:
39 if show_executing_node:
40 pygments_style = style_with_executing_node(
41 pygments_style, executing_node_modifier
42 )
44 if pygments_formatter_cls is None:
45 from pygments.formatters.terminal256 import Terminal256Formatter \
46 as pygments_formatter_cls
48 options.pygments_formatter = pygments_formatter_cls(
49 style=pygments_style,
50 **pygments_formatter_kwargs or {},
51 )
53 self.pygmented = pygmented
54 self.show_executing_node = show_executing_node
55 assert_(
56 len(executing_node_underline) == 1,
57 ValueError("executing_node_underline must be a single character"),
58 )
59 self.executing_node_underline = executing_node_underline
60 self.current_line_indicator = current_line_indicator or ""
61 self.line_gap_string = line_gap_string
62 self.line_number_gap_string = line_number_gap_string
63 self.line_number_format_string = line_number_format_string
64 self.show_variables = show_variables
65 self.show_linenos = show_linenos
66 self.use_code_qualname = use_code_qualname
67 self.strip_leading_indent = strip_leading_indent
68 self.html = html
69 self.chain = chain
70 self.options = options
71 self.collapse_repeated_frames = collapse_repeated_frames
72 if not self.show_linenos and self.options.blank_lines == BlankLines.SINGLE:
73 raise ValueError(
74 "BlankLines.SINGLE option can only be used when show_linenos=True"
75 )
77 def set_hook(self):
78 def excepthook(_etype, evalue, _tb):
79 self.print_exception(evalue)
81 sys.excepthook = excepthook
83 def print_exception(self, e=None, *, file=None):
84 self.print_lines(self.format_exception(e), file=file)
86 def print_stack(self, frame_or_tb=None, *, file=None):
87 if frame_or_tb is None:
88 frame_or_tb = inspect.currentframe().f_back
90 self.print_lines(self.format_stack(frame_or_tb), file=file)
92 def print_lines(self, lines, *, file=None):
93 if file is None:
94 file = sys.stderr
95 for line in lines:
96 print(line, file=file, end="")
98 def format_exception(self, e=None) -> Iterable[str]:
99 if e is None:
100 e = sys.exc_info()[1]
102 if self.chain:
103 if e.__cause__ is not None:
104 yield from self.format_exception(e.__cause__)
105 yield traceback._cause_message
106 elif (e.__context__ is not None
107 and not e.__suppress_context__):
108 yield from self.format_exception(e.__context__)
109 yield traceback._context_message
111 yield 'Traceback (most recent call last):\n'
112 yield from self.format_stack(e.__traceback__)
113 yield from traceback.format_exception_only(type(e), e)
115 def format_stack(self, frame_or_tb=None) -> Iterable[str]:
116 if frame_or_tb is None:
117 frame_or_tb = inspect.currentframe().f_back
119 yield from self.format_stack_data(
120 FrameInfo.stack_data(
121 frame_or_tb,
122 self.options,
123 collapse_repeated_frames=self.collapse_repeated_frames,
124 )
125 )
127 def format_stack_data(
128 self, stack: Iterable[Union[FrameInfo, RepeatedFrames]]
129 ) -> Iterable[str]:
130 for item in stack:
131 if isinstance(item, FrameInfo):
132 yield from self.format_frame(item)
133 else:
134 yield self.format_repeated_frames(item)
136 def format_repeated_frames(self, repeated_frames: RepeatedFrames) -> str:
137 return ' [... skipping similar frames: {}]\n'.format(
138 repeated_frames.description
139 )
141 def format_frame(self, frame: Union[FrameInfo, FrameType, TracebackType]) -> Iterable[str]:
142 if not isinstance(frame, FrameInfo):
143 frame = FrameInfo(frame, self.options)
145 yield self.format_frame_header(frame)
147 for line in frame.lines:
148 if isinstance(line, Line):
149 yield self.format_line(line)
150 elif isinstance(line, BlankLineRange):
151 yield self.format_blank_lines_linenumbers(line)
152 else:
153 assert_(line is LINE_GAP)
154 yield self.line_gap_string + "\n"
156 if self.show_variables:
157 try:
158 yield from self.format_variables(frame)
159 except Exception:
160 pass
162 def format_frame_header(self, frame_info: FrameInfo) -> str:
163 return ' File "{frame_info.filename}", line {frame_info.lineno}, in {name}\n'.format(
164 frame_info=frame_info,
165 name=(
166 frame_info.executing.code_qualname()
167 if self.use_code_qualname else
168 frame_info.code.co_name
169 ),
170 )
172 def format_line(self, line: Line) -> str:
173 result = ""
174 if self.current_line_indicator:
175 if line.is_current:
176 result = self.current_line_indicator
177 else:
178 result = " " * len(self.current_line_indicator)
179 result += " "
180 else:
181 result = " "
183 if self.show_linenos:
184 result += self.line_number_format_string.format(line.lineno)
186 prefix = result
188 result += line.render(
189 pygmented=self.pygmented,
190 escape_html=self.html,
191 strip_leading_indent=self.strip_leading_indent,
192 ) + "\n"
194 if self.show_executing_node and not self.pygmented:
195 for line_range in line.executing_node_ranges:
196 start = line_range.start - line.leading_indent
197 end = line_range.end - line.leading_indent
198 # if end <= start, we have an empty line inside a highlighted
199 # block of code. In this case, we need to avoid inserting
200 # an extra blank line with no markers present.
201 if end > start:
202 result += (
203 " " * (start + len(prefix))
204 + self.executing_node_underline * (end - start)
205 + "\n"
206 )
207 return result
210 def format_blank_lines_linenumbers(self, blank_line):
211 if self.current_line_indicator:
212 result = " " * len(self.current_line_indicator) + " "
213 else:
214 result = " "
215 if blank_line.begin_lineno == blank_line.end_lineno:
216 return result + self.line_number_format_string.format(blank_line.begin_lineno) + "\n"
217 return result + " {}\n".format(self.line_number_gap_string)
220 def format_variables(self, frame_info: FrameInfo) -> Iterable[str]:
221 for var in sorted(frame_info.variables, key=lambda v: v.name):
222 try:
223 yield self.format_variable(var) + "\n"
224 except Exception:
225 pass
227 def format_variable(self, var: Variable) -> str:
228 return "{} = {}".format(
229 var.name,
230 self.format_variable_value(var.value),
231 )
233 def format_variable_value(self, value) -> str:
234 return repr(value)