Skip to content
Snippets Groups Projects
Commit 7b04bc99 authored by Gordon Ball's avatar Gordon Ball
Browse files

New upstream version 0.5.0

parent a4d72d96
No related branches found
No related tags found
No related merge requests found
Showing
with 239 additions and 21 deletions
include LICENSE.txt
include README.md
include stack_data/py.typed
......@@ -31,3 +31,6 @@ tests = pytest; typeguard; pygments; littleutils; cython
[coverage:run]
relative_files = True
[options.package_data]
stack_data = py.typed
from .core import Source, FrameInfo, markers_from_ranges, Options, LINE_GAP, Line, Variable, RangeInLine, \
RepeatedFrames, MarkerInLine, style_with_executing_node
RepeatedFrames, MarkerInLine, style_with_executing_node, BlankLineRange, BlankLines
from .formatting import Formatter
from .serializing import Serializer
......
......@@ -3,6 +3,7 @@ import html
import os
import sys
from collections import defaultdict, Counter
from enum import Enum
from textwrap import dedent
from types import FrameType, CodeType, TracebackType
from typing import (
......@@ -46,6 +47,21 @@ Then use Line.render to insert the markers correctly.
"""
class BlankLines(Enum):
"""The values are intended to correspond to the following behaviour:
HIDDEN: blank lines are not shown in the output
VISIBLE: blank lines are visible in the output
SINGLE: any consecutive blank lines are shown as a single blank line
in the output. This option requires the line number to be shown.
For a single blank line, the corresponding line number is shown.
Two or more consecutive blank lines are shown as a single blank
line in the output with a custom string shown instead of a
specific line number.
"""
HIDDEN = 1
VISIBLE = 2
SINGLE=3
class Variable(
NamedTuple('_Variable',
[('name', str),
......@@ -171,13 +187,15 @@ class Options:
after: int = 1,
include_signature: bool = False,
max_lines_per_piece: int = 6,
pygments_formatter=None
pygments_formatter=None,
blank_lines = BlankLines.HIDDEN
):
self.before = before
self.after = after
self.include_signature = include_signature
self.max_lines_per_piece = max_lines_per_piece
self.pygments_formatter = pygments_formatter
self.blank_lines = blank_lines
def __repr__(self):
keys = sorted(self.__dict__)
......@@ -202,6 +220,16 @@ class LineGap(object):
LINE_GAP = LineGap()
class BlankLineRange:
"""
Records the line number range for blank lines gaps between pieces.
For a single blank line, begin_lineno == end_lineno.
"""
def __init__(self, begin_lineno: int, end_lineno: int):
self.begin_lineno = begin_lineno
self.end_lineno = end_lineno
class Line(object):
"""
A single line of source code for a particular stack frame.
......@@ -324,9 +352,7 @@ class Line(object):
except AttributeError:
range_start = node.col_offset
else:
range_start = 0
range_start = max(range_start, common_indent)
range_start = common_indent
if end == self.lineno:
try:
......@@ -338,6 +364,11 @@ class Line(object):
return None
else:
range_end = len(self.text)
if range_start == range_end == 0:
# This is an empty line. If it were included, it would result
# in a value of zero for the common indentation assigned to
# a block of code.
return None
return RangeInLine(range_start, range_end, data)
......@@ -524,6 +555,7 @@ class FrameInfo(object):
self.options = options or Options() # type: Options
self.source = self.executing.source # type: Source
def __repr__(self):
return "{self.__class__.__name__}({self.frame})".format(self=self)
......@@ -678,13 +710,17 @@ class FrameInfo(object):
indent = max(rang.start, begin_text)
indents.append(indent)
return min(indents) if indents else 0
if len(indents) <= 1:
return 0
return min(indents[1:])
@cached_property
def lines(self) -> List[Union[Line, LineGap]]:
def lines(self) -> List[Union[Line, LineGap, BlankLineRange]]:
"""
A list of lines to display, determined by options.
The objects yielded either have type Line or are the singleton LINE_GAP.
The objects yielded either have type Line, BlankLineRange
or are the singleton LINE_GAP.
Always check the type that you're dealing with when iterating.
LINE_GAP can be created in two ways:
......@@ -699,6 +735,8 @@ class FrameInfo(object):
if not pieces:
return []
add_empty_lines = self.options.blank_lines in (BlankLines.VISIBLE, BlankLines.SINGLE)
prev_piece = None
result = []
for i, piece in enumerate(pieces):
if (
......@@ -708,6 +746,12 @@ class FrameInfo(object):
and pieces[1] != self.scope_pieces[1]
):
result.append(LINE_GAP)
elif prev_piece and add_empty_lines and piece.start > prev_piece.stop:
if self.options.blank_lines == BlankLines.SINGLE:
result.append(BlankLineRange(prev_piece.stop, piece.start-1))
else: # BlankLines.VISIBLE
for lineno in range(prev_piece.stop, piece.start):
result.append(Line(self, lineno))
lines = [Line(self, i) for i in piece] # type: List[Line]
if piece != self.executing_piece:
......@@ -717,6 +761,7 @@ class FrameInfo(object):
middle=[LINE_GAP],
)
result.extend(lines)
prev_piece = piece
real_lines = [
line
......@@ -732,7 +777,6 @@ class FrameInfo(object):
leading_indent = len(real_lines[0].text) - len(dedented_lines[0])
for line in real_lines:
line.leading_indent = leading_indent
return result
@cached_property
......
......@@ -4,7 +4,8 @@ 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
from stack_data import (style_with_executing_node, Options, Line, FrameInfo, LINE_GAP,
Variable, RepeatedFrames, BlankLineRange, BlankLines)
from stack_data.utils import assert_
......@@ -21,6 +22,8 @@ class Formatter:
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,
......@@ -56,6 +59,8 @@ class Formatter:
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
......@@ -64,6 +69,10 @@ class Formatter:
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):
......@@ -138,6 +147,8 @@ class Formatter:
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"
......@@ -166,11 +177,11 @@ class Formatter:
else:
result = " " * len(self.current_line_indicator)
result += " "
else:
result = " "
if self.show_linenos:
result += "{:4} | ".format(line.lineno)
result = result or " "
result += self.line_number_format_string.format(line.lineno)
prefix = result
......@@ -184,14 +195,28 @@ class Formatter:
for line_range in line.executing_node_ranges:
start = line_range.start - line.leading_indent
end = line_range.end - line.leading_indent
result += (
" " * (start + len(prefix))
+ self.executing_node_underline * (end - start)
+ "\n"
)
# 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:
......
# Marker file for PEP 561. The ``stack_data`` package uses inline types.
......@@ -3,7 +3,7 @@ import os
import pyximport
from typeguard.importhook import install_import_hook
pyximport.install()
pyximport.install(language_level=3)
if not os.environ.get("STACK_DATA_SLOW_TESTS"):
install_import_hook(["stack_data"])
Traceback (most recent call last):
File "formatter_example.py", line 85, in blank_lines
def blank_lines():
a = [1, 2, 3]
length = len(a)
return a[length]
^^^^^^^^^
IndexError: list index out of range
Traceback (most recent call last):
File "formatter_example.py", line 85, in blank_lines
79 | def blank_lines():
80 | a = [1, 2, 3]
81 |
82 | length = len(a)
:
--> 85 | return a[length]
^^^^^^^^^
IndexError: list index out of range
Traceback (most recent call last):
File "formatter_example.py", line 85, in blank_lines
79 | def blank_lines():
80 | a = [1, 2, 3]
81 |
82 | length = len(a)
83 |
84 |
--> 85 | return a[length]
^^^^^^^^^
IndexError: list index out of range
Traceback (most recent call last):
File "formatter_example.py", line 85, in blank_lines
def blank_lines():
a = [1, 2, 3]
length = len(a)
return a[length]
^^^^^^^^^
IndexError: list index out of range
Traceback (most recent call last):
File "formatter_example.py", line 85, in blank_lines
79 | def blank_lines():
80 | a = [1, 2, 3]
81 |
82 | length = len(a)
83 |
84 |
85 | return a[length]
^^^^^^^^^
IndexError: list index out of range
Traceback (most recent call last):
File "formatter_example.py", line 72, in block_left
71 | def block_left():
--> 72 | nb_characters = len(letter
^^^^^^^^^^
73 | for letter
^^^^^^^^^^
74 |
75 | in
^^^^
76 | "words")
^^^^^^^^
TypeError: object of type 'generator' has no len()
Traceback (most recent call last):
File "formatter_example.py", line 76, in block_left
71 | def block_left():
72 | nb_characters = len(letter
^^^^^^^^^^
73 | for letter
^^^^^^^^^^
74 |
75 | in
^^^^
--> 76 | "words")
^^^^^^^^
TypeError: object of type 'generator' has no len()
Traceback (most recent call last):
File "formatter_example.py", line 65, in block_right
64 | def block_right():
--> 65 | nb = len(letter
^^^^^^^^^^
66 | for letter
^^^^^^^^^^
67 | in
^^^^
68 | "words")
^^^^^^^^
TypeError: object of type 'generator' has no len()
Traceback (most recent call last):
File "formatter_example.py", line 68, in block_right
64 | def block_right():
65 | nb = len(letter
^^^^^^^^^^
66 | for letter
^^^^^^^^^^
67 | in
^^^^
--> 68 | "words")
^^^^^^^^
TypeError: object of type 'generator' has no len()
......@@ -6,7 +6,7 @@ Traceback (most recent call last):
--> 57 | 1 /
^^^
58 | 0 + 4
^^^
^
59 | + 5
60 | )
61 | }"""
......
Traceback (most recent call last):
File "formatter_example.py", line 85, in blank_lines
79 | def blank_lines():
80 | a = [1, 2, 3]
82 | length = len(a)
85 | return a[length]
^^^^^^^^^
IndexError: list index out of range
Traceback (most recent call last):
File "formatter_example.py", line 85, in blank_lines
79 | def blank_lines():
80 | a = [1, 2, 3]
81 |
82 | length = len(a)
:
85 | return a[length]
^^^^^^^^^
IndexError: list index out of range
......@@ -61,6 +61,31 @@ def f_string():
}"""
def block_right():
nb = len(letter
for letter
in
"words")
def block_left():
nb_characters = len(letter
for letter
in
"words")
def blank_lines():
a = [1, 2, 3]
length = len(a)
return a[length]
if __name__ == '__main__':
try:
bar()
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment