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