Skip to content

Commit

Permalink
Remove crashtest
Browse files Browse the repository at this point in the history
  • Loading branch information
Secrus committed Nov 5, 2023
1 parent 5209e8a commit bef5ac1
Show file tree
Hide file tree
Showing 15 changed files with 361 additions and 187 deletions.
1 change: 1 addition & 0 deletions news/382.deps.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Removed `crashtest` dependency and vendored part of it into `cleo`
13 changes: 1 addition & 12 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ classifiers = [

[tool.poetry.dependencies]
python = "^3.7"
crashtest = "^0.4.1"
rapidfuzz = "^3.0.0"

[tool.poetry.group.dev.dependencies]
Expand Down
17 changes: 2 additions & 15 deletions src/cleo/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,6 @@


if TYPE_CHECKING:
from crashtest.solution_providers.solution_provider_repository import (
SolutionProviderRepository,
)

from cleo.commands.command import Command
from cleo.events.event_dispatcher import EventDispatcher
from cleo.io.inputs.input import Input
Expand Down Expand Up @@ -78,8 +74,6 @@ def __init__(self, name: str = "console", version: str = "") -> None:

self._command_loader: CommandLoader | None = None

self._solution_provider_repository: SolutionProviderRepository | None = None

@property
def name(self) -> str:
return self._name
Expand Down Expand Up @@ -170,11 +164,6 @@ def catch_exceptions(self, catch_exceptions: bool = True) -> None:
def is_single_command(self) -> bool:
return self._single_command

def set_solution_provider_repository(
self, solution_provider_repository: SolutionProviderRepository
) -> None:
self._solution_provider_repository = solution_provider_repository

def add(self, command: Command) -> Command | None:
self._init()

Expand Down Expand Up @@ -493,11 +482,9 @@ def create_io(
return IO(input, output, error_output)

def render_error(self, error: Exception, io: IO) -> None:
from cleo.ui.exception_trace import ExceptionTrace
from cleo.ui.exception_trace.component import ExceptionTrace

trace = ExceptionTrace(
error, solution_provider_repository=self._solution_provider_repository
)
trace = ExceptionTrace(error)
simple = not io.is_verbose() or isinstance(error, CleoUserError)
trace.render(io.error_output, simple)

Expand Down
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,14 @@
from typing import TYPE_CHECKING
from typing import ClassVar

from crashtest.frame_collection import FrameCollection

from cleo.formatters.formatter import Formatter
from cleo.ui.exception_trace.frame_collection import FrameCollection


if TYPE_CHECKING:
from crashtest.frame import Frame
from crashtest.solution_providers.solution_provider_repository import (
SolutionProviderRepository,
)

from cleo.io.io import IO
from cleo.io.outputs.output import Output
from cleo.ui.exception_trace.frame import Frame


class Highlighter:
Expand Down Expand Up @@ -231,10 +226,8 @@ class ExceptionTrace:
def __init__(
self,
exception: Exception,
solution_provider_repository: SolutionProviderRepository | None = None,
) -> None:
self._exception = exception
self._solution_provider_repository = solution_provider_repository
self._exc_info = sys.exc_info()
self._ignore: str | None = None

Expand All @@ -252,10 +245,8 @@ def render(self, io: IO | Output, simple: bool = False) -> None:
else:
self._render_exception(io, self._exception)

self._render_solution(io, self._exception)

def _render_exception(self, io: IO | Output, exception: BaseException) -> None:
from crashtest.inspector import Inspector
from cleo.ui.exception_trace.inspector import Inspector

inspector = Inspector(exception)
if not inspector.frames:
Expand Down Expand Up @@ -297,31 +288,6 @@ def _render_snippet(self, io: IO | Output, frame: Frame) -> None:
for code_line in code_lines:
self._render_line(io, code_line, indent=4)

def _render_solution(self, io: IO | Output, exception: Exception) -> None:
if self._solution_provider_repository is None:
return

solutions = self._solution_provider_repository.get_solutions_for_exception(
exception
)
symbol = "•" if io.supports_utf8() else "*"

for solution in solutions:
title = solution.solution_title
description = solution.solution_description
links = solution.documentation_links

description = description.replace("\n", "\n ").strip(" ")

joined_links = ",".join(f"\n <fg=blue>{link}</>" for link in links)
self._render_line(
io,
f"<fg=blue;options=bold>{symbol} </>"
f"<fg=default;options=bold>{title.rstrip('.')}</>:"
f" {description}{joined_links}",
True,
)

def _render_trace(self, io: IO | Output, frames: FrameCollection) -> None:
stack_frames = FrameCollection()
for frame in frames:
Expand All @@ -341,7 +307,7 @@ def _render_trace(self, io: IO | Output, frames: FrameCollection) -> None:
frame_collections = stack_frames.compact()
i = remaining_frames_length
for collection in frame_collections:
if collection.is_repeated():
if collection.is_repeated:
if len(collection) > 1:
frames_message = f"<fg=yellow>{len(collection)}</> frames"
else:
Expand Down Expand Up @@ -411,22 +377,21 @@ def _render_trace(self, io: IO | Output, frames: FrameCollection) -> None:

i -= 1

@staticmethod
def _render_line(
self, io: IO | Output, line: str, new_line: bool = False, indent: int = 2
io: IO | Output, line: str, new_line: bool = False, indent: int = 2
) -> None:
if new_line:
io.write_line("")

io.write_line(f"{indent * ' '}{line}")

def _get_relative_file_path(self, filepath: str) -> str:
cwd = os.getcwd()

if cwd:
filepath = filepath.replace(cwd + os.path.sep, "")
@staticmethod
def _get_relative_file_path(filepath: str) -> str:
if cwd := os.getcwd():
filepath = filepath.replace(f"{cwd}{os.path.sep}", "")

home = os.path.expanduser("~")
if home:
filepath = filepath.replace(home + os.path.sep, "~" + os.path.sep)
if home := os.path.expanduser("~"):
filepath = filepath.replace(f"{home}{os.path.sep}", f"~{os.path.sep}")

return filepath
82 changes: 82 additions & 0 deletions src/cleo/ui/exception_trace/frame.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
from __future__ import annotations

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 file_content(self) -> str:
if self._file_content is None:
if not self._filename:
file_content = ""
else:
if self._filename not in self.__class__._content_cache:
try:
with open(self._filename, encoding="utf-8") as f:
file_content = f.read()
except OSError:
file_content = ""

self.__class__._content_cache[self._filename] = file_content

file_content = self.__class__._content_cache[self._filename]

self._file_content = file_content

return self._file_content

def __hash__(self) -> int:
return hash(self._filename) ^ hash(self._function) ^ hash(self._lineno)

def __eq__(self, other: object) -> bool:
if not isinstance(other, Frame):
raise NotImplementedError
return (
self._filename == other.filename
and self._function == other.function
and self._lineno == other.lineno
)

def __repr__(self) -> str:
return f"<Frame {self._filename}, {self._function}, {self._lineno}>"
74 changes: 74 additions & 0 deletions src/cleo/ui/exception_trace/frame_collection.py
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
Loading

0 comments on commit bef5ac1

Please sign in to comment.