aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/stack-data/stack_data/formatting.py
diff options
context:
space:
mode:
authornkozlovskiy <nmk@ydb.tech>2023-09-29 12:24:06 +0300
committernkozlovskiy <nmk@ydb.tech>2023-09-29 12:41:34 +0300
commite0e3e1717e3d33762ce61950504f9637a6e669ed (patch)
treebca3ff6939b10ed60c3d5c12439963a1146b9711 /contrib/python/stack-data/stack_data/formatting.py
parent38f2c5852db84c7b4d83adfcb009eb61541d1ccd (diff)
downloadydb-e0e3e1717e3d33762ce61950504f9637a6e669ed.tar.gz
add ydb deps
Diffstat (limited to 'contrib/python/stack-data/stack_data/formatting.py')
-rw-r--r--contrib/python/stack-data/stack_data/formatting.py234
1 files changed, 234 insertions, 0 deletions
diff --git a/contrib/python/stack-data/stack_data/formatting.py b/contrib/python/stack-data/stack_data/formatting.py
new file mode 100644
index 0000000000..e4c6f07fc8
--- /dev/null
+++ b/contrib/python/stack-data/stack_data/formatting.py
@@ -0,0 +1,234 @@
+import inspect
+import sys
+import traceback
+from types import FrameType, TracebackType
+from typing import Union, Iterable
+
+from stack_data import (style_with_executing_node, Options, Line, FrameInfo, LINE_GAP,
+ Variable, RepeatedFrames, BlankLineRange, BlankLines)
+from stack_data.utils import assert_
+
+
+class Formatter:
+ def __init__(
+ self, *,
+ options=None,
+ pygmented=False,
+ show_executing_node=True,
+ pygments_formatter_cls=None,
+ pygments_formatter_kwargs=None,
+ pygments_style="monokai",
+ executing_node_modifier="bg:#005080",
+ executing_node_underline="^",
+ current_line_indicator="-->",
+ line_gap_string="(...)",
+ line_number_gap_string=":",
+ line_number_format_string="{:4} | ",
+ show_variables=False,
+ use_code_qualname=True,
+ show_linenos=True,
+ strip_leading_indent=True,
+ html=False,
+ chain=True,
+ collapse_repeated_frames=True
+ ):
+ if options is None:
+ options = Options()
+
+ if pygmented and not options.pygments_formatter:
+ if show_executing_node:
+ pygments_style = style_with_executing_node(
+ pygments_style, executing_node_modifier
+ )
+
+ if pygments_formatter_cls is None:
+ from pygments.formatters.terminal256 import Terminal256Formatter \
+ as pygments_formatter_cls
+
+ options.pygments_formatter = pygments_formatter_cls(
+ style=pygments_style,
+ **pygments_formatter_kwargs or {},
+ )
+
+ self.pygmented = pygmented
+ self.show_executing_node = show_executing_node
+ assert_(
+ len(executing_node_underline) == 1,
+ ValueError("executing_node_underline must be a single character"),
+ )
+ self.executing_node_underline = executing_node_underline
+ self.current_line_indicator = current_line_indicator or ""
+ self.line_gap_string = line_gap_string
+ self.line_number_gap_string = line_number_gap_string
+ self.line_number_format_string = line_number_format_string
+ self.show_variables = show_variables
+ self.show_linenos = show_linenos
+ self.use_code_qualname = use_code_qualname
+ self.strip_leading_indent = strip_leading_indent
+ self.html = html
+ self.chain = chain
+ self.options = options
+ self.collapse_repeated_frames = collapse_repeated_frames
+ if not self.show_linenos and self.options.blank_lines == BlankLines.SINGLE:
+ raise ValueError(
+ "BlankLines.SINGLE option can only be used when show_linenos=True"
+ )
+
+ def set_hook(self):
+ def excepthook(_etype, evalue, _tb):
+ self.print_exception(evalue)
+
+ sys.excepthook = excepthook
+
+ def print_exception(self, e=None, *, file=None):
+ self.print_lines(self.format_exception(e), file=file)
+
+ def print_stack(self, frame_or_tb=None, *, file=None):
+ if frame_or_tb is None:
+ frame_or_tb = inspect.currentframe().f_back
+
+ self.print_lines(self.format_stack(frame_or_tb), file=file)
+
+ def print_lines(self, lines, *, file=None):
+ if file is None:
+ file = sys.stderr
+ for line in lines:
+ print(line, file=file, end="")
+
+ def format_exception(self, e=None) -> Iterable[str]:
+ if e is None:
+ e = sys.exc_info()[1]
+
+ if self.chain:
+ if e.__cause__ is not None:
+ yield from self.format_exception(e.__cause__)
+ yield traceback._cause_message
+ elif (e.__context__ is not None
+ and not e.__suppress_context__):
+ yield from self.format_exception(e.__context__)
+ yield traceback._context_message
+
+ yield 'Traceback (most recent call last):\n'
+ yield from self.format_stack(e.__traceback__)
+ yield from traceback.format_exception_only(type(e), e)
+
+ def format_stack(self, frame_or_tb=None) -> Iterable[str]:
+ if frame_or_tb is None:
+ frame_or_tb = inspect.currentframe().f_back
+
+ yield from self.format_stack_data(
+ FrameInfo.stack_data(
+ frame_or_tb,
+ self.options,
+ collapse_repeated_frames=self.collapse_repeated_frames,
+ )
+ )
+
+ def format_stack_data(
+ self, stack: Iterable[Union[FrameInfo, RepeatedFrames]]
+ ) -> Iterable[str]:
+ for item in stack:
+ if isinstance(item, FrameInfo):
+ yield from self.format_frame(item)
+ else:
+ yield self.format_repeated_frames(item)
+
+ def format_repeated_frames(self, repeated_frames: RepeatedFrames) -> str:
+ return ' [... skipping similar frames: {}]\n'.format(
+ repeated_frames.description
+ )
+
+ def format_frame(self, frame: Union[FrameInfo, FrameType, TracebackType]) -> Iterable[str]:
+ if not isinstance(frame, FrameInfo):
+ frame = FrameInfo(frame, self.options)
+
+ yield self.format_frame_header(frame)
+
+ for line in frame.lines:
+ if isinstance(line, Line):
+ yield self.format_line(line)
+ elif isinstance(line, BlankLineRange):
+ yield self.format_blank_lines_linenumbers(line)
+ else:
+ assert_(line is LINE_GAP)
+ yield self.line_gap_string + "\n"
+
+ if self.show_variables:
+ try:
+ yield from self.format_variables(frame)
+ except Exception:
+ pass
+
+ def format_frame_header(self, frame_info: FrameInfo) -> str:
+ return ' File "{frame_info.filename}", line {frame_info.lineno}, in {name}\n'.format(
+ frame_info=frame_info,
+ name=(
+ frame_info.executing.code_qualname()
+ if self.use_code_qualname else
+ frame_info.code.co_name
+ ),
+ )
+
+ def format_line(self, line: Line) -> str:
+ result = ""
+ if self.current_line_indicator:
+ if line.is_current:
+ result = self.current_line_indicator
+ else:
+ result = " " * len(self.current_line_indicator)
+ result += " "
+ else:
+ result = " "
+
+ if self.show_linenos:
+ result += self.line_number_format_string.format(line.lineno)
+
+ prefix = result
+
+ result += line.render(
+ pygmented=self.pygmented,
+ escape_html=self.html,
+ strip_leading_indent=self.strip_leading_indent,
+ ) + "\n"
+
+ if self.show_executing_node and not self.pygmented:
+ for line_range in line.executing_node_ranges:
+ start = line_range.start - line.leading_indent
+ end = line_range.end - line.leading_indent
+ # if end <= start, we have an empty line inside a highlighted
+ # block of code. In this case, we need to avoid inserting
+ # an extra blank line with no markers present.
+ if end > start:
+ result += (
+ " " * (start + len(prefix))
+ + self.executing_node_underline * (end - start)
+ + "\n"
+ )
+ return result
+
+
+ def format_blank_lines_linenumbers(self, blank_line):
+ if self.current_line_indicator:
+ result = " " * len(self.current_line_indicator) + " "
+ else:
+ result = " "
+ if blank_line.begin_lineno == blank_line.end_lineno:
+ return result + self.line_number_format_string.format(blank_line.begin_lineno) + "\n"
+ return result + " {}\n".format(self.line_number_gap_string)
+
+
+ def format_variables(self, frame_info: FrameInfo) -> Iterable[str]:
+ for var in sorted(frame_info.variables, key=lambda v: v.name):
+ try:
+ yield self.format_variable(var) + "\n"
+ except Exception:
+ pass
+
+ def format_variable(self, var: Variable) -> str:
+ return "{} = {}".format(
+ var.name,
+ self.format_variable_value(var.value),
+ )
+
+ def format_variable_value(self, value) -> str:
+ return repr(value)