You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			235 lines
		
	
	
		
			8.3 KiB
		
	
	
	
		
			Python
		
	
			
		
		
	
	
			235 lines
		
	
	
		
			8.3 KiB
		
	
	
	
		
			Python
		
	
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)
 |