Mini Shell
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)
Zerion Mini Shell 1.0