-
-
Notifications
You must be signed in to change notification settings - Fork 91
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
13 changed files
with
361 additions
and
175 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Removed `crashtest` dependency and vendored part of it into `cleo` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
from __future__ import annotations | ||
|
||
import operator | ||
|
||
from functools import reduce | ||
from pathlib import Path | ||
from typing import TYPE_CHECKING | ||
from typing import ClassVar | ||
|
||
|
||
if TYPE_CHECKING: | ||
import inspect | ||
|
||
from types import FrameType | ||
|
||
|
||
class Frame: | ||
_content_cache: ClassVar[dict[str, str]] = {} | ||
|
||
def __init__(self, frame_info: inspect.FrameInfo) -> None: | ||
self._frame = frame_info.frame | ||
self._frame_info = frame_info | ||
self._lineno = frame_info.lineno | ||
self._filename = frame_info.filename | ||
self._function = frame_info.function | ||
self._lines = None | ||
self._file_content: str | None = None | ||
|
||
@property | ||
def frame(self) -> FrameType: | ||
return self._frame | ||
|
||
@property | ||
def lineno(self) -> int: | ||
return self._lineno | ||
|
||
@property | ||
def filename(self) -> str: | ||
return self._filename | ||
|
||
@property | ||
def function(self) -> str: | ||
return self._function | ||
|
||
@property | ||
def line(self) -> str: | ||
if not self._frame_info.code_context: | ||
return "" | ||
|
||
return self._frame_info.code_context[0] | ||
|
||
@property | ||
def _key(self) -> tuple[str, str, int]: | ||
return self._filename, self._function, self._lineno | ||
|
||
@property | ||
def file_content(self) -> str: | ||
if self._file_content is not None: | ||
return self._file_content | ||
if not self._filename: | ||
self._file_content = "" | ||
return "" | ||
if self._filename not in type(self)._content_cache: | ||
try: | ||
file_content = Path(self._filename).read_text() | ||
except OSError: | ||
file_content = "" | ||
type(self)._content_cache[self._filename] = file_content | ||
self._file_content = type(self)._content_cache[self._filename] | ||
return self._file_content | ||
|
||
def __hash__(self) -> int: | ||
return reduce(operator.xor, map(hash, self._key)) | ||
|
||
def __eq__(self, other: object) -> bool: | ||
if not isinstance(other, Frame): | ||
return NotImplemented | ||
return self._key == other._key | ||
|
||
def __repr__(self) -> str: | ||
return f"<Frame {self._filename}, {self._function}, {self._lineno}>" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
from __future__ import annotations | ||
|
||
from typing import List | ||
|
||
from cleo.ui.exception_trace.frame import Frame | ||
|
||
|
||
class FrameCollection(List[Frame]): | ||
def __init__(self, frames: list[Frame] | None = None, count: int = 0) -> None: | ||
if frames is None: | ||
frames = [] | ||
|
||
super().__init__(frames) | ||
|
||
self._count = count | ||
|
||
@property | ||
def repetitions(self) -> int: | ||
return self._count - 1 | ||
|
||
@property | ||
def is_repeated(self) -> bool: | ||
return self._count > 1 | ||
|
||
def increment_count(self, increment: int = 1) -> FrameCollection: | ||
self._count += increment | ||
|
||
return self | ||
|
||
def compact(self) -> list[FrameCollection]: | ||
""" | ||
Compacts the frames to deduplicate recursive calls. | ||
""" | ||
collections = [] | ||
current_collection = FrameCollection() | ||
|
||
i = 0 | ||
while i < len(self) - 1: | ||
frame = self[i] | ||
if frame in self[i + 1 :]: | ||
duplicate_indices = [] | ||
for sub_index, sub_frame in enumerate(self[i + 1 :]): | ||
if frame == sub_frame: | ||
duplicate_indices.append(sub_index + i + 1) | ||
|
||
found_duplicate = False | ||
for duplicate_index in duplicate_indices: | ||
collection = FrameCollection(self[i:duplicate_index]) | ||
if collection == current_collection: | ||
current_collection.increment_count() | ||
i = duplicate_index | ||
found_duplicate = True | ||
break | ||
|
||
if found_duplicate: | ||
continue | ||
|
||
collections.append(current_collection) | ||
current_collection = FrameCollection(self[i : duplicate_indices[0]]) | ||
|
||
i = duplicate_indices[0] | ||
|
||
continue | ||
|
||
if current_collection.is_repeated: | ||
collections.append(current_collection) | ||
current_collection = FrameCollection() | ||
|
||
current_collection.append(frame) | ||
i += 1 | ||
|
||
collections.append(current_collection) | ||
|
||
return collections |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
from __future__ import annotations | ||
|
||
import inspect | ||
|
||
from cleo.ui.exception_trace.frame import Frame | ||
from cleo.ui.exception_trace.frame_collection import FrameCollection | ||
|
||
|
||
class Inspector: | ||
def __init__(self, exception: BaseException) -> None: | ||
self._exception = exception | ||
self._frames: FrameCollection | None = None | ||
self._outer_frames = None | ||
self._inner_frames = None | ||
self._previous_exception = exception.__context__ | ||
|
||
@property | ||
def exception(self) -> BaseException: | ||
return self._exception | ||
|
||
@property | ||
def exception_name(self) -> str: | ||
return type(self._exception).__name__ | ||
|
||
@property | ||
def exception_message(self) -> str: | ||
return str(self._exception) | ||
|
||
@property | ||
def frames(self) -> FrameCollection: | ||
if self._frames is not None: | ||
return self._frames | ||
|
||
self._frames = FrameCollection() | ||
|
||
tb = self._exception.__traceback__ | ||
|
||
while tb: | ||
frame_info = inspect.getframeinfo(tb) | ||
self._frames.append(Frame(inspect.FrameInfo(tb.tb_frame, *frame_info))) | ||
tb = tb.tb_next | ||
|
||
return self._frames | ||
|
||
@property | ||
def previous_exception(self) -> BaseException | None: | ||
return self._previous_exception | ||
|
||
def has_previous_exception(self) -> bool: | ||
return self._previous_exception is not None |
Oops, something went wrong.