robotcode-repl 2.9.0.dev87__tar.gz → 2.9.0.dev115__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {robotcode_repl-2.9.0.dev87 → robotcode_repl-2.9.0.dev115}/PKG-INFO +2 -2
- {robotcode_repl-2.9.0.dev87 → robotcode_repl-2.9.0.dev115}/pyproject.toml +1 -1
- robotcode_repl-2.9.0.dev115/src/robotcode/repl/__version__.py +1 -0
- {robotcode_repl-2.9.0.dev87 → robotcode_repl-2.9.0.dev115}/src/robotcode/repl/_debug/controller.py +50 -17
- {robotcode_repl-2.9.0.dev87 → robotcode_repl-2.9.0.dev115}/src/robotcode/repl/_debug/types.py +9 -1
- {robotcode_repl-2.9.0.dev87 → robotcode_repl-2.9.0.dev115}/src/robotcode/repl/_pt/doc_viewer.py +7 -2
- {robotcode_repl-2.9.0.dev87 → robotcode_repl-2.9.0.dev115}/src/robotcode/repl/_session.py +5 -1
- {robotcode_repl-2.9.0.dev87 → robotcode_repl-2.9.0.dev115}/src/robotcode/repl/base_interpreter.py +2 -0
- {robotcode_repl-2.9.0.dev87 → robotcode_repl-2.9.0.dev115}/src/robotcode/repl/cli.py +17 -10
- {robotcode_repl-2.9.0.dev87 → robotcode_repl-2.9.0.dev115}/src/robotcode/repl/console_interpreter.py +60 -48
- robotcode_repl-2.9.0.dev87/src/robotcode/repl/__version__.py +0 -1
- {robotcode_repl-2.9.0.dev87 → robotcode_repl-2.9.0.dev115}/.gitignore +0 -0
- {robotcode_repl-2.9.0.dev87 → robotcode_repl-2.9.0.dev115}/README.md +0 -0
- {robotcode_repl-2.9.0.dev87 → robotcode_repl-2.9.0.dev115}/src/robotcode/repl/Repl/__init__.py +0 -0
- {robotcode_repl-2.9.0.dev87 → robotcode_repl-2.9.0.dev115}/src/robotcode/repl/Repl/repl.py +0 -0
- {robotcode_repl-2.9.0.dev87 → robotcode_repl-2.9.0.dev115}/src/robotcode/repl/__init__.py +0 -0
- {robotcode_repl-2.9.0.dev87 → robotcode_repl-2.9.0.dev115}/src/robotcode/repl/_backends.py +0 -0
- {robotcode_repl-2.9.0.dev87 → robotcode_repl-2.9.0.dev115}/src/robotcode/repl/_debug/__init__.py +0 -0
- {robotcode_repl-2.9.0.dev87 → robotcode_repl-2.9.0.dev115}/src/robotcode/repl/_indent.py +0 -0
- {robotcode_repl-2.9.0.dev87 → robotcode_repl-2.9.0.dev115}/src/robotcode/repl/_keyword_lookup.py +0 -0
- {robotcode_repl-2.9.0.dev87 → robotcode_repl-2.9.0.dev115}/src/robotcode/repl/_progress.py +0 -0
- {robotcode_repl-2.9.0.dev87 → robotcode_repl-2.9.0.dev115}/src/robotcode/repl/_pt/__init__.py +0 -0
- {robotcode_repl-2.9.0.dev87 → robotcode_repl-2.9.0.dev115}/src/robotcode/repl/_pt/completion.py +0 -0
- {robotcode_repl-2.9.0.dev87 → robotcode_repl-2.9.0.dev115}/src/robotcode/repl/_pt/components.py +0 -0
- {robotcode_repl-2.9.0.dev87 → robotcode_repl-2.9.0.dev115}/src/robotcode/repl/_pt/history.py +0 -0
- {robotcode_repl-2.9.0.dev87 → robotcode_repl-2.9.0.dev115}/src/robotcode/repl/_pt/lexer.py +0 -0
- {robotcode_repl-2.9.0.dev87 → robotcode_repl-2.9.0.dev115}/src/robotcode/repl/_session_export.py +0 -0
- {robotcode_repl-2.9.0.dev87 → robotcode_repl-2.9.0.dev115}/src/robotcode/repl/doc_cli.py +0 -0
- {robotcode_repl-2.9.0.dev87 → robotcode_repl-2.9.0.dev115}/src/robotcode/repl/hooks.py +0 -0
- {robotcode_repl-2.9.0.dev87 → robotcode_repl-2.9.0.dev115}/src/robotcode/repl/prompt_toolkit_interpreter.py +0 -0
- {robotcode_repl-2.9.0.dev87 → robotcode_repl-2.9.0.dev115}/src/robotcode/repl/py.typed +0 -0
- {robotcode_repl-2.9.0.dev87 → robotcode_repl-2.9.0.dev115}/src/robotcode/repl/run.py +0 -0
- {robotcode_repl-2.9.0.dev87 → robotcode_repl-2.9.0.dev115}/src/robotcode/repl/session_cli.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: robotcode-repl
|
|
3
|
-
Version: 2.9.0-dev.
|
|
3
|
+
Version: 2.9.0-dev.115
|
|
4
4
|
Summary: RobotCode REPL for Robot Framework
|
|
5
5
|
Project-URL: Homepage, https://robotcode.io
|
|
6
6
|
Project-URL: Donate, https://opencollective.com/robotcode
|
|
@@ -27,7 +27,7 @@ Classifier: Topic :: Utilities
|
|
|
27
27
|
Classifier: Typing :: Typed
|
|
28
28
|
Requires-Python: >=3.10
|
|
29
29
|
Requires-Dist: prompt-toolkit!=3.0.52
|
|
30
|
-
Requires-Dist: robotcode-runner==2.9.0-dev.
|
|
30
|
+
Requires-Dist: robotcode-runner==2.9.0-dev.115
|
|
31
31
|
Description-Content-Type: text/markdown
|
|
32
32
|
|
|
33
33
|
# robotcode-repl
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2.9.0-dev.115"
|
{robotcode_repl-2.9.0.dev87 → robotcode_repl-2.9.0.dev115}/src/robotcode/repl/_debug/controller.py
RENAMED
|
@@ -121,6 +121,22 @@ def _normalize_source(source: str) -> str:
|
|
|
121
121
|
return str(normalized_path(Path(source)))
|
|
122
122
|
|
|
123
123
|
|
|
124
|
+
def _breakpoint_source(source: str) -> str:
|
|
125
|
+
"""What a line breakpoint matches by: a path with a root or a drive, or one that leaves the folder with `..`,
|
|
126
|
+
as an absolute path; any other as it is, see `_source_matches`."""
|
|
127
|
+
path = Path(source)
|
|
128
|
+
return _normalize_source(source) if path.anchor or ".." in path.parts else str(path)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _source_matches(source: str, frame_source: str) -> bool:
|
|
132
|
+
"""Whether a line breakpoint means the file of a frame: an absolute path exactly, any other by the
|
|
133
|
+
end of the path, as in gdb, so that `login.robot` finds `tests/login.robot`."""
|
|
134
|
+
given, actual = Path(source), Path(frame_source)
|
|
135
|
+
if given.is_absolute():
|
|
136
|
+
return given == actual
|
|
137
|
+
return Path(*actual.parts[-len(given.parts) :]) == given
|
|
138
|
+
|
|
139
|
+
|
|
124
140
|
class DebugController:
|
|
125
141
|
def __init__(self, frontend: Optional[Frontend] = None) -> None:
|
|
126
142
|
self._stack: List[StackFrame] = []
|
|
@@ -159,6 +175,9 @@ class DebugController:
|
|
|
159
175
|
# breakpoints/filters are kept so re-attaching resumes the same setup.
|
|
160
176
|
self._detached = False
|
|
161
177
|
|
|
178
|
+
# set for a run that goes through a suite: at its end, say which breakpoints no keyword reached.
|
|
179
|
+
self._warn_about_unreached = False
|
|
180
|
+
|
|
162
181
|
# --- configuration ------------------------------------------------------
|
|
163
182
|
|
|
164
183
|
def set_frontend(self, frontend: Optional[Frontend]) -> None:
|
|
@@ -181,7 +200,8 @@ class DebugController:
|
|
|
181
200
|
) -> Breakpoint:
|
|
182
201
|
return self._add_breakpoint(
|
|
183
202
|
kind="line",
|
|
184
|
-
source=
|
|
203
|
+
source=_breakpoint_source(source),
|
|
204
|
+
location=source,
|
|
185
205
|
line=line,
|
|
186
206
|
condition=condition,
|
|
187
207
|
log_message=log_message,
|
|
@@ -202,7 +222,7 @@ class DebugController:
|
|
|
202
222
|
|
|
203
223
|
def set_line_breakpoints(self, source: str, lines: List[int]) -> None:
|
|
204
224
|
"""Replace the line breakpoints for `source` (DAP-style per-source set)."""
|
|
205
|
-
norm =
|
|
225
|
+
norm = _breakpoint_source(source)
|
|
206
226
|
self._breakpoints = [bp for bp in self._breakpoints if not (bp.kind == "line" and bp.source == norm)]
|
|
207
227
|
for line in lines:
|
|
208
228
|
self.add_line_breakpoint(source, line)
|
|
@@ -213,6 +233,11 @@ class DebugController:
|
|
|
213
233
|
for name in names:
|
|
214
234
|
self.add_keyword_breakpoint(name)
|
|
215
235
|
|
|
236
|
+
def warn_about_unreached_breakpoints(self) -> None:
|
|
237
|
+
"""Warn at the end of the run about the breakpoints no keyword reached: from the outside, a mistyped
|
|
238
|
+
path looks like a run that simply went through."""
|
|
239
|
+
self._warn_about_unreached = True
|
|
240
|
+
|
|
216
241
|
def get_breakpoint(self, bp_id: int) -> Optional[Breakpoint]:
|
|
217
242
|
return next((bp for bp in self._breakpoints if bp.id == bp_id), None)
|
|
218
243
|
|
|
@@ -230,7 +255,7 @@ class DebugController:
|
|
|
230
255
|
def restore_breakpoints(self, state: BreakpointState) -> None:
|
|
231
256
|
"""Take over the breakpoints of `breakpoint_state`, in place of those set so far. Their hit counts start at 0,
|
|
232
257
|
as `.ignore` counts the hits of a run."""
|
|
233
|
-
self._breakpoints = [dataclasses.replace(bp, hits=0) for bp in state.breakpoints]
|
|
258
|
+
self._breakpoints = [dataclasses.replace(bp, hits=0, reached=False) for bp in state.breakpoints]
|
|
234
259
|
self._next_bp_id = state.next_id
|
|
235
260
|
self._exception_filters = set(state.exception_filters)
|
|
236
261
|
|
|
@@ -288,7 +313,7 @@ class DebugController:
|
|
|
288
313
|
|
|
289
314
|
@property
|
|
290
315
|
def line_breakpoints(self) -> Dict[str, Set[int]]:
|
|
291
|
-
"""Active line breakpoints, keyed by
|
|
316
|
+
"""Active line breakpoints, keyed by the path they match by (derived)."""
|
|
292
317
|
out: Dict[str, Set[int]] = {}
|
|
293
318
|
for bp in self._breakpoints:
|
|
294
319
|
if bp.kind == "line" and bp.source is not None and bp.line is not None:
|
|
@@ -551,6 +576,10 @@ class DebugController:
|
|
|
551
576
|
|
|
552
577
|
def end_suite(self, data: "running.TestSuite", result: "result.TestSuite") -> None:
|
|
553
578
|
self._end_exec(result, "failed_suite")
|
|
579
|
+
if self._warn_about_unreached and not self._stack and not self._detached and self._frontend is not None:
|
|
580
|
+
for bp in self._breakpoints:
|
|
581
|
+
if bp.enabled and not bp.reached:
|
|
582
|
+
self._frontend.on_output(f"[ WARN ] Breakpoint {bp.where} was never reached.")
|
|
554
583
|
|
|
555
584
|
def _end_exec(self, result: Any, filter_id: str) -> None:
|
|
556
585
|
# The SUITE/TEST frame pushed at the matching start is on top of the stack.
|
|
@@ -685,20 +714,24 @@ class DebugController:
|
|
|
685
714
|
return True
|
|
686
715
|
|
|
687
716
|
def _match_breakpoint(self, frame: StackFrame) -> Optional[Breakpoint]:
|
|
717
|
+
matched: Optional[Breakpoint] = None
|
|
688
718
|
for bp in self._breakpoints:
|
|
689
|
-
if
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
719
|
+
if bp.enabled and self._is_at_breakpoint(bp, frame):
|
|
720
|
+
# Whether its condition holds or not: the warning is about the place.
|
|
721
|
+
bp.reached = True
|
|
722
|
+
matched = matched or bp
|
|
723
|
+
return matched
|
|
724
|
+
|
|
725
|
+
def _is_at_breakpoint(self, bp: Breakpoint, frame: StackFrame) -> bool:
|
|
726
|
+
if bp.kind == "keyword":
|
|
727
|
+
return bp.name is not None and self._keyword_matches(bp.name, frame)
|
|
728
|
+
return (
|
|
729
|
+
frame.source is not None
|
|
730
|
+
and frame.line is not None
|
|
731
|
+
and bp.source is not None
|
|
732
|
+
and bp.line == frame.line
|
|
733
|
+
and _source_matches(bp.source, _normalize_source(frame.source))
|
|
734
|
+
)
|
|
702
735
|
|
|
703
736
|
@staticmethod
|
|
704
737
|
def _keyword_matches(name: str, frame: StackFrame) -> bool:
|
{robotcode_repl-2.9.0.dev87 → robotcode_repl-2.9.0.dev115}/src/robotcode/repl/_debug/types.py
RENAMED
|
@@ -67,7 +67,8 @@ class StackFrame:
|
|
|
67
67
|
class Breakpoint:
|
|
68
68
|
"""A registered breakpoint with optional attributes (the shared CLI + DAP model).
|
|
69
69
|
|
|
70
|
-
`kind` is ``"line"`` (`source`+`line`
|
|
70
|
+
`kind` is ``"line"`` (`source`+`line`, with `location` as it was given) or
|
|
71
|
+
``"keyword"`` (`name`). The optional
|
|
71
72
|
attributes drive the hit-time decision: `condition` (stop only if the
|
|
72
73
|
expression is truthy — a *failing* condition stops too, past `ignore_count`
|
|
73
74
|
and `temporary`, so the breakage is noticed), `ignore_count` (skip the next N triggering hits), `log_message`
|
|
@@ -88,6 +89,13 @@ class Breakpoint:
|
|
|
88
89
|
enabled: bool = True
|
|
89
90
|
commands: List[str] = field(default_factory=list)
|
|
90
91
|
hits: int = 0
|
|
92
|
+
location: Optional[str] = None
|
|
93
|
+
reached: bool = False
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def where(self) -> str:
|
|
97
|
+
"""Where the breakpoint was set, as it was given."""
|
|
98
|
+
return f"{self.name}" if self.kind == "keyword" else f"{self.location}:{self.line}"
|
|
91
99
|
|
|
92
100
|
|
|
93
101
|
@dataclass
|
{robotcode_repl-2.9.0.dev87 → robotcode_repl-2.9.0.dev115}/src/robotcode/repl/_pt/doc_viewer.py
RENAMED
|
@@ -59,7 +59,7 @@ from rich.console import Console
|
|
|
59
59
|
from rich.markdown import Markdown
|
|
60
60
|
from robot.utils import MultiMatcher
|
|
61
61
|
|
|
62
|
-
from robotcode.robot.utils.markdown_docs import heading_anchors
|
|
62
|
+
from robotcode.robot.utils.markdown_docs import code_lines_before_headings, heading_anchors
|
|
63
63
|
|
|
64
64
|
|
|
65
65
|
def render_markdown_to_ansi(text: str, *, width: int = 80) -> str:
|
|
@@ -267,10 +267,15 @@ def _build_anchor_to_line_map(md_source: str, rendered_plain: str) -> Dict[str,
|
|
|
267
267
|
cursor = 0
|
|
268
268
|
# `heading_anchors` skips `#` lines of fenced code blocks, which are no headings,
|
|
269
269
|
# and numbers repeated anchors as the links of the page do
|
|
270
|
-
for _, raw_title, anchor in heading_anchors(md_source):
|
|
270
|
+
for (_, raw_title, anchor), code_lines in zip(heading_anchors(md_source), code_lines_before_headings(md_source)):
|
|
271
271
|
display = _strip_md_emphasis(raw_title)
|
|
272
|
+
# a line of a code block can read like the heading after it
|
|
273
|
+
skip = code_lines.count(display)
|
|
272
274
|
for j in range(cursor, len(rendered_lines)):
|
|
273
275
|
if rendered_lines[j].strip() == display:
|
|
276
|
+
if skip:
|
|
277
|
+
skip -= 1
|
|
278
|
+
continue
|
|
274
279
|
result[anchor] = j
|
|
275
280
|
cursor = j + 1
|
|
276
281
|
break
|
|
@@ -1178,7 +1178,7 @@ class SessionInterpreter(ConsoleInterpreter):
|
|
|
1178
1178
|
@property
|
|
1179
1179
|
def termination_requested(self) -> bool:
|
|
1180
1180
|
"""Whether a signal has asked the REPL or the run to end, as SIGTERM does."""
|
|
1181
|
-
return self._termination_requested
|
|
1181
|
+
return self._termination_requested and not self._aborted
|
|
1182
1182
|
|
|
1183
1183
|
def read_line(
|
|
1184
1184
|
self, prompt: str, *, multiline_continuation: bool = False, prefill: str = "", completer: Any = None
|
|
@@ -1221,6 +1221,10 @@ class SessionInterpreter(ConsoleInterpreter):
|
|
|
1221
1221
|
if not self._server.at_line_start():
|
|
1222
1222
|
super()._end_console_line()
|
|
1223
1223
|
|
|
1224
|
+
def _dot_command_failed(self, text: str) -> None:
|
|
1225
|
+
self._server.line_failed()
|
|
1226
|
+
super()._dot_command_failed(text)
|
|
1227
|
+
|
|
1224
1228
|
def _evaluate_at_stop(self, line: str) -> bool:
|
|
1225
1229
|
# A keyword at a stop fails the input as at the REPL prompt.
|
|
1226
1230
|
try:
|
{robotcode_repl-2.9.0.dev87 → robotcode_repl-2.9.0.dev115}/src/robotcode/repl/base_interpreter.py
RENAMED
|
@@ -436,6 +436,8 @@ class BaseInterpreter(abc.ABC):
|
|
|
436
436
|
self.exit_code: Optional[int] = None
|
|
437
437
|
# Set by a signal that ends the session or the run.
|
|
438
438
|
self._termination_requested = False
|
|
439
|
+
# Set by `.abort`, which ends the run by the same way as a signal but isn't one from outside.
|
|
440
|
+
self._aborted = False
|
|
439
441
|
# Set while a statement of the input runs: SIGTERM then stops it at its next keyword.
|
|
440
442
|
self._running_statement = False
|
|
441
443
|
|
|
@@ -460,8 +460,8 @@ DEBUG_OPTIONS = [
|
|
|
460
460
|
"break_at",
|
|
461
461
|
metavar="LOCATION",
|
|
462
462
|
multiple=True,
|
|
463
|
-
help="Break at LOCATION — a `file:line`, or a keyword name
|
|
464
|
-
"matched like Robot Framework. Repeatable.",
|
|
463
|
+
help="Break at LOCATION — a `file:line`, where the end of the path is enough, or a keyword name "
|
|
464
|
+
"or glob pattern matched like Robot Framework. Repeatable.",
|
|
465
465
|
),
|
|
466
466
|
click.option(
|
|
467
467
|
"--break-on-exception/--no-break-on-exception",
|
|
@@ -694,6 +694,7 @@ def robot_debug(
|
|
|
694
694
|
)
|
|
695
695
|
if server is not None:
|
|
696
696
|
server.keep_breakpoints(controller)
|
|
697
|
+
controller.warn_about_unreached_breakpoints()
|
|
697
698
|
|
|
698
699
|
# `robot-debug` produces the same console output as `robotcode robot` — the run
|
|
699
700
|
# is fully visible. At a stop the debug prompt is interleaved with Robot's live
|
|
@@ -704,14 +705,20 @@ def robot_debug(
|
|
|
704
705
|
session: ContextManager[object] = server if server is not None else contextlib.nullcontext()
|
|
705
706
|
try:
|
|
706
707
|
with session, interpreter.forward_events(echo_messages=False):
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
708
|
+
try:
|
|
709
|
+
_invoke_runner(
|
|
710
|
+
ctx,
|
|
711
|
+
by_longname,
|
|
712
|
+
exclude_by_longname,
|
|
713
|
+
by_test_metadata,
|
|
714
|
+
exclude_by_test_metadata,
|
|
715
|
+
robot_options_and_args,
|
|
716
|
+
)
|
|
717
|
+
except SystemExit:
|
|
718
|
+
# After `.abort` the code says that, not how many tests failed.
|
|
719
|
+
if interpreter.exit_code is None:
|
|
720
|
+
raise
|
|
721
|
+
raise SystemExit(interpreter.exit_code) from None
|
|
715
722
|
except DebugTerminated:
|
|
716
723
|
app.echo("Debugger: run terminated.")
|
|
717
724
|
finally:
|
{robotcode_repl-2.9.0.dev87 → robotcode_repl-2.9.0.dev115}/src/robotcode/repl/console_interpreter.py
RENAMED
|
@@ -21,6 +21,7 @@ import pprint
|
|
|
21
21
|
import re
|
|
22
22
|
import reprlib
|
|
23
23
|
import shlex
|
|
24
|
+
import signal
|
|
24
25
|
import sys
|
|
25
26
|
from datetime import datetime
|
|
26
27
|
from pathlib import Path
|
|
@@ -31,6 +32,7 @@ from robot import result, running
|
|
|
31
32
|
from robot.errors import ExecutionFailed, ExecutionPassed, ExecutionStatus
|
|
32
33
|
from robot.running import Keyword
|
|
33
34
|
from robot.running.context import EXECUTION_CONTEXTS
|
|
35
|
+
from robot.running.signalhandler import STOP_SIGNAL_MONITOR
|
|
34
36
|
from robot.utils import normalize
|
|
35
37
|
|
|
36
38
|
from robotcode.plugin import Application
|
|
@@ -471,8 +473,7 @@ class ConsoleInterpreter(BaseInterpreter):
|
|
|
471
473
|
arg = (m.group(2) or "").strip()
|
|
472
474
|
attr_name, error = self._resolve_dot_command(name)
|
|
473
475
|
if attr_name is None:
|
|
474
|
-
|
|
475
|
-
self.app.echo(error or f"Unknown dot-command: .{name}. Try .help.")
|
|
476
|
+
self._dot_command_failed(error or f"Unknown dot-command: .{name}. Try .help.")
|
|
476
477
|
return True
|
|
477
478
|
getattr(self, attr_name)(arg)
|
|
478
479
|
return True
|
|
@@ -667,9 +668,9 @@ class ConsoleInterpreter(BaseInterpreter):
|
|
|
667
668
|
silent = bool(bp is not None and bp.commands and bp.commands[0] == "silent" and not stop.description)
|
|
668
669
|
if not silent:
|
|
669
670
|
self._render_stop(stop)
|
|
670
|
-
# try/finally so the stop state is always cleared, even if
|
|
671
|
-
#
|
|
672
|
-
#
|
|
671
|
+
# try/finally so the stop state is always cleared, even if a Ctrl-C
|
|
672
|
+
# during an at-stop evaluation unwinds through the loop — otherwise a
|
|
673
|
+
# stale `_stop` would leave the interactive shell
|
|
673
674
|
# prompt wedged in a phantom debug state after the run returns.
|
|
674
675
|
try:
|
|
675
676
|
if bp is not None and bp.commands:
|
|
@@ -698,9 +699,9 @@ class ConsoleInterpreter(BaseInterpreter):
|
|
|
698
699
|
# already; the REPL prompt adds nothing to it either.
|
|
699
700
|
pass
|
|
700
701
|
except Exception as e: # a throwing command must not eject the debug prompt
|
|
701
|
-
#
|
|
702
|
-
#
|
|
703
|
-
self.
|
|
702
|
+
# Ctrl-C and DebugTerminated are BaseException, not
|
|
703
|
+
# Exception, so they still propagate.
|
|
704
|
+
self._dot_command_failed(f"! {e}")
|
|
704
705
|
return self._pending_action
|
|
705
706
|
finally:
|
|
706
707
|
self._stop = None
|
|
@@ -736,10 +737,14 @@ class ConsoleInterpreter(BaseInterpreter):
|
|
|
736
737
|
if self.app is not None:
|
|
737
738
|
self.app.echo(text)
|
|
738
739
|
|
|
740
|
+
def _dot_command_failed(self, text: str) -> None:
|
|
741
|
+
"""Say that a dot-command couldn't do what was asked."""
|
|
742
|
+
self._echo(text)
|
|
743
|
+
|
|
739
744
|
def _require_stop(self) -> bool:
|
|
740
745
|
"""Guard for commands that only mean something while paused."""
|
|
741
746
|
if self._stop is None:
|
|
742
|
-
self.
|
|
747
|
+
self._dot_command_failed("not at a breakpoint")
|
|
743
748
|
return False
|
|
744
749
|
return True
|
|
745
750
|
|
|
@@ -747,7 +752,7 @@ class ConsoleInterpreter(BaseInterpreter):
|
|
|
747
752
|
"""Guard for commands that need a wired debug controller. (Distinct from
|
|
748
753
|
the attach/detach *state* — a detached controller is still wired.)"""
|
|
749
754
|
if self._controller is None:
|
|
750
|
-
self.
|
|
755
|
+
self._dot_command_failed("no debugger available")
|
|
751
756
|
return False
|
|
752
757
|
return True
|
|
753
758
|
|
|
@@ -1377,12 +1382,12 @@ class ConsoleInterpreter(BaseInterpreter):
|
|
|
1377
1382
|
if self.app is None:
|
|
1378
1383
|
return
|
|
1379
1384
|
if not arg:
|
|
1380
|
-
self.
|
|
1385
|
+
self._dot_command_failed("Usage: .doc <library-or-resource-name>")
|
|
1381
1386
|
return
|
|
1382
1387
|
|
|
1383
1388
|
lib_doc, error = self._resolve_doc_target(arg)
|
|
1384
1389
|
if lib_doc is None:
|
|
1385
|
-
self.
|
|
1390
|
+
self._dot_command_failed(error or f"Could not load {arg!r}.")
|
|
1386
1391
|
return
|
|
1387
1392
|
|
|
1388
1393
|
self.show_doc(
|
|
@@ -1445,7 +1450,7 @@ class ConsoleInterpreter(BaseInterpreter):
|
|
|
1445
1450
|
try:
|
|
1446
1451
|
self.app.echo(str(Path.cwd()))
|
|
1447
1452
|
except OSError as e:
|
|
1448
|
-
self.
|
|
1453
|
+
self._dot_command_failed(f"(working directory unavailable: {e})")
|
|
1449
1454
|
|
|
1450
1455
|
@dot_command("exit", "quit")
|
|
1451
1456
|
def _exit(self, arg: str) -> None:
|
|
@@ -1465,13 +1470,13 @@ class ConsoleInterpreter(BaseInterpreter):
|
|
|
1465
1470
|
.exit 3
|
|
1466
1471
|
"""
|
|
1467
1472
|
if self._stop is not None:
|
|
1468
|
-
self.
|
|
1473
|
+
self._dot_command_failed("at a debug stop — use .continue or .detach to resume, or .abort to quit the run")
|
|
1469
1474
|
return
|
|
1470
1475
|
if arg:
|
|
1471
1476
|
try:
|
|
1472
1477
|
self.exit_code = int(arg)
|
|
1473
1478
|
except ValueError:
|
|
1474
|
-
self.
|
|
1479
|
+
self._dot_command_failed("Usage: .exit [CODE]")
|
|
1475
1480
|
return
|
|
1476
1481
|
raise EOFError
|
|
1477
1482
|
|
|
@@ -1515,7 +1520,7 @@ class ConsoleInterpreter(BaseInterpreter):
|
|
|
1515
1520
|
try:
|
|
1516
1521
|
opts = parser.parse_args(_split_command_args(arg))
|
|
1517
1522
|
except (argparse.ArgumentError, SystemExit, ValueError):
|
|
1518
|
-
self.
|
|
1523
|
+
self._dot_command_failed("Usage: .save [-a] [-t NAME] [--keep-failed] FILENAME")
|
|
1519
1524
|
return
|
|
1520
1525
|
|
|
1521
1526
|
recorded: List[str] = list(self._session_lines or [])
|
|
@@ -1541,7 +1546,7 @@ class ConsoleInterpreter(BaseInterpreter):
|
|
|
1541
1546
|
else:
|
|
1542
1547
|
path.write_text(content, encoding="utf-8")
|
|
1543
1548
|
except OSError as e:
|
|
1544
|
-
self.
|
|
1549
|
+
self._dot_command_failed(f"Could not write {opts.filename}: {e}")
|
|
1545
1550
|
return
|
|
1546
1551
|
|
|
1547
1552
|
summary = f"{len(lines)} {'entry' if len(lines) == 1 else 'entries'}"
|
|
@@ -1656,7 +1661,7 @@ class ConsoleInterpreter(BaseInterpreter):
|
|
|
1656
1661
|
(default 10) — `.source Open Browser 25`.
|
|
1657
1662
|
"""
|
|
1658
1663
|
if not arg:
|
|
1659
|
-
self.
|
|
1664
|
+
self._dot_command_failed("usage: .source <keyword-name> [lines]")
|
|
1660
1665
|
return
|
|
1661
1666
|
# Split off a trailing line-count (the plain-backend window size). Keyword
|
|
1662
1667
|
# names contain spaces, so only a trailing integer token counts.
|
|
@@ -1666,7 +1671,7 @@ class ConsoleInterpreter(BaseInterpreter):
|
|
|
1666
1671
|
name, count = head[0], max(1, int(head[1]))
|
|
1667
1672
|
found = lookup_keyword_owner(name)
|
|
1668
1673
|
if found is None:
|
|
1669
|
-
self.
|
|
1674
|
+
self._dot_command_failed(f"keyword {name!r} not found")
|
|
1670
1675
|
return
|
|
1671
1676
|
owner, runtime_kw, is_resource = found
|
|
1672
1677
|
kw_name = getattr(runtime_kw, "name", name)
|
|
@@ -1676,7 +1681,7 @@ class ConsoleInterpreter(BaseInterpreter):
|
|
|
1676
1681
|
if source is None:
|
|
1677
1682
|
source = getattr(runtime_kw, "source", None)
|
|
1678
1683
|
if source is None or line is None:
|
|
1679
|
-
self.
|
|
1684
|
+
self._dot_command_failed(f"(no source available for {kw_name})")
|
|
1680
1685
|
return
|
|
1681
1686
|
source, line = str(source), int(line)
|
|
1682
1687
|
header = f"{kw_name} ({os.path.basename(source)}:{line})"
|
|
@@ -1707,7 +1712,7 @@ class ConsoleInterpreter(BaseInterpreter):
|
|
|
1707
1712
|
target = int(arg)
|
|
1708
1713
|
except ValueError:
|
|
1709
1714
|
top = len(self._stop.stack) - 1 if self._stop else 0
|
|
1710
|
-
self.
|
|
1715
|
+
self._dot_command_failed(f"usage: .frame <n> (0..{top})")
|
|
1711
1716
|
return
|
|
1712
1717
|
self._move_frame(target)
|
|
1713
1718
|
|
|
@@ -1717,12 +1722,12 @@ class ConsoleInterpreter(BaseInterpreter):
|
|
|
1717
1722
|
if not self._require_stop():
|
|
1718
1723
|
return
|
|
1719
1724
|
if not arg:
|
|
1720
|
-
self.
|
|
1725
|
+
self._dot_command_failed("usage: .print <variable-or-expression>")
|
|
1721
1726
|
return
|
|
1722
1727
|
try:
|
|
1723
1728
|
value = self._controller.evaluate_expression(self._selected_frame(), arg) # type: ignore[union-attr]
|
|
1724
1729
|
except Exception as e: # surface lookup/eval failures to the prompt
|
|
1725
|
-
self.
|
|
1730
|
+
self._dot_command_failed(f"! {e}")
|
|
1726
1731
|
return
|
|
1727
1732
|
self._echo(f"{arg} = {_repr.repr(value)}")
|
|
1728
1733
|
|
|
@@ -1732,12 +1737,12 @@ class ConsoleInterpreter(BaseInterpreter):
|
|
|
1732
1737
|
if not self._require_stop():
|
|
1733
1738
|
return
|
|
1734
1739
|
if not arg:
|
|
1735
|
-
self.
|
|
1740
|
+
self._dot_command_failed("usage: .pprint <variable-or-expression>")
|
|
1736
1741
|
return
|
|
1737
1742
|
try:
|
|
1738
1743
|
value = self._controller.evaluate_expression(self._selected_frame(), arg) # type: ignore[union-attr]
|
|
1739
1744
|
except Exception as e: # surface lookup/eval failures to the prompt
|
|
1740
|
-
self.
|
|
1745
|
+
self._dot_command_failed(f"! {e}")
|
|
1741
1746
|
return
|
|
1742
1747
|
self._echo(pprint.pformat(value))
|
|
1743
1748
|
|
|
@@ -1747,12 +1752,12 @@ class ConsoleInterpreter(BaseInterpreter):
|
|
|
1747
1752
|
if not self._require_stop():
|
|
1748
1753
|
return
|
|
1749
1754
|
if not arg:
|
|
1750
|
-
self.
|
|
1755
|
+
self._dot_command_failed("usage: .whatis <variable-or-expression>")
|
|
1751
1756
|
return
|
|
1752
1757
|
try:
|
|
1753
1758
|
value = self._controller.evaluate_expression(self._selected_frame(), arg) # type: ignore[union-attr]
|
|
1754
1759
|
except Exception as e: # surface lookup/eval failures to the prompt
|
|
1755
|
-
self.
|
|
1760
|
+
self._dot_command_failed(f"! {e}")
|
|
1756
1761
|
return
|
|
1757
1762
|
self._echo(f"{arg}: {type(value).__name__}")
|
|
1758
1763
|
|
|
@@ -1784,7 +1789,7 @@ class ConsoleInterpreter(BaseInterpreter):
|
|
|
1784
1789
|
self._display_exprs.remove(arg)
|
|
1785
1790
|
self._echo(f"no longer displaying {arg}")
|
|
1786
1791
|
else:
|
|
1787
|
-
self.
|
|
1792
|
+
self._dot_command_failed(f"not displaying: {arg}")
|
|
1788
1793
|
|
|
1789
1794
|
@dot_command("set", group="Debugger")
|
|
1790
1795
|
def _set(self, arg: str) -> None:
|
|
@@ -1803,18 +1808,18 @@ class ConsoleInterpreter(BaseInterpreter):
|
|
|
1803
1808
|
return
|
|
1804
1809
|
parts = arg.split(None, 1)
|
|
1805
1810
|
if len(parts) < 2:
|
|
1806
|
-
self.
|
|
1811
|
+
self._dot_command_failed("usage: .set ${name} <value>")
|
|
1807
1812
|
return
|
|
1808
1813
|
name, value = parts[0], parts[1]
|
|
1809
1814
|
if not (name.startswith("${") and name.endswith("}")) or "[" in name:
|
|
1810
|
-
self.
|
|
1815
|
+
self._dot_command_failed(f"! .set only supports whole scalar variables (${{...}}), not {name}")
|
|
1811
1816
|
return
|
|
1812
1817
|
try:
|
|
1813
1818
|
new_repr = self._controller.set_variable( # type: ignore[union-attr]
|
|
1814
1819
|
self._selected_frame(), name, value, evaluate=False
|
|
1815
1820
|
)
|
|
1816
1821
|
except Exception as e: # surface NameError / unknown-variable to the prompt
|
|
1817
|
-
self.
|
|
1822
|
+
self._dot_command_failed(f"! {e}")
|
|
1818
1823
|
return
|
|
1819
1824
|
self._echo(f"{name} = {new_repr}")
|
|
1820
1825
|
|
|
@@ -1822,7 +1827,7 @@ class ConsoleInterpreter(BaseInterpreter):
|
|
|
1822
1827
|
if not self._require_controller():
|
|
1823
1828
|
return
|
|
1824
1829
|
if not arg:
|
|
1825
|
-
self.
|
|
1830
|
+
self._dot_command_failed("usage: .break <file:line | keyword name> [, <condition>]")
|
|
1826
1831
|
return
|
|
1827
1832
|
spec, condition = arg, None
|
|
1828
1833
|
if "," in arg: # `.break <loc>, <condition>`
|
|
@@ -1863,7 +1868,6 @@ class ConsoleInterpreter(BaseInterpreter):
|
|
|
1863
1868
|
assert controller is not None # narrowed by _require_controller
|
|
1864
1869
|
bps = controller.breakpoints
|
|
1865
1870
|
for bp in bps:
|
|
1866
|
-
loc = bp.name if bp.kind == "keyword" else f"{os.path.basename(bp.source or '?')}:{bp.line}"
|
|
1867
1871
|
flags: List[str] = []
|
|
1868
1872
|
if not bp.enabled:
|
|
1869
1873
|
flags.append("disabled")
|
|
@@ -1878,7 +1882,7 @@ class ConsoleInterpreter(BaseInterpreter):
|
|
|
1878
1882
|
if bp.commands:
|
|
1879
1883
|
flags.append(f"{len(bp.commands)} {'cmd' if len(bp.commands) == 1 else 'cmds'}")
|
|
1880
1884
|
suffix = (" " + ", ".join(flags)) if flags else ""
|
|
1881
|
-
self._echo(f" #{bp.id} {bp.kind:<7} {
|
|
1885
|
+
self._echo(f" #{bp.id} {bp.kind:<7} {bp.where}{suffix}")
|
|
1882
1886
|
filters = controller.exception_filters
|
|
1883
1887
|
if filters:
|
|
1884
1888
|
self._echo(f" catch {', '.join(sorted(filters))}")
|
|
@@ -1892,11 +1896,11 @@ class ConsoleInterpreter(BaseInterpreter):
|
|
|
1892
1896
|
try:
|
|
1893
1897
|
bp_id = int(token)
|
|
1894
1898
|
except (TypeError, ValueError):
|
|
1895
|
-
self.
|
|
1899
|
+
self._dot_command_failed(f"not a breakpoint number: {token}")
|
|
1896
1900
|
return None
|
|
1897
1901
|
bp = controller.get_breakpoint(bp_id)
|
|
1898
1902
|
if bp is None:
|
|
1899
|
-
self.
|
|
1903
|
+
self._dot_command_failed(f"no breakpoint {bp_id}")
|
|
1900
1904
|
return bp
|
|
1901
1905
|
|
|
1902
1906
|
@dot_command("condition", group="Debugger")
|
|
@@ -1906,7 +1910,7 @@ class ConsoleInterpreter(BaseInterpreter):
|
|
|
1906
1910
|
return
|
|
1907
1911
|
parts = arg.split(None, 1)
|
|
1908
1912
|
if not parts:
|
|
1909
|
-
self.
|
|
1913
|
+
self._dot_command_failed("usage: .condition <breakpoint-number> [<expression>]")
|
|
1910
1914
|
return
|
|
1911
1915
|
bp = self._bp_by_arg(parts[0])
|
|
1912
1916
|
if bp is None:
|
|
@@ -1924,7 +1928,7 @@ class ConsoleInterpreter(BaseInterpreter):
|
|
|
1924
1928
|
return
|
|
1925
1929
|
parts = arg.split()
|
|
1926
1930
|
if len(parts) != 2:
|
|
1927
|
-
self.
|
|
1931
|
+
self._dot_command_failed("usage: .ignore <breakpoint-number> <count>")
|
|
1928
1932
|
return
|
|
1929
1933
|
bp = self._bp_by_arg(parts[0])
|
|
1930
1934
|
if bp is None:
|
|
@@ -1932,7 +1936,7 @@ class ConsoleInterpreter(BaseInterpreter):
|
|
|
1932
1936
|
try:
|
|
1933
1937
|
count = int(parts[1])
|
|
1934
1938
|
except ValueError:
|
|
1935
|
-
self.
|
|
1939
|
+
self._dot_command_failed(f"not a count: {parts[1]}")
|
|
1936
1940
|
return
|
|
1937
1941
|
bp.ignore_count = max(0, count)
|
|
1938
1942
|
bp.hits = 0 # the next `count` triggering hits are skipped from now
|
|
@@ -1997,7 +2001,7 @@ class ConsoleInterpreter(BaseInterpreter):
|
|
|
1997
2001
|
if not self._require_controller():
|
|
1998
2002
|
return
|
|
1999
2003
|
if not arg.strip():
|
|
2000
|
-
self.
|
|
2004
|
+
self._dot_command_failed("usage: .commands <breakpoint-number>")
|
|
2001
2005
|
return
|
|
2002
2006
|
bp = self._bp_by_arg(arg.strip())
|
|
2003
2007
|
if bp is None:
|
|
@@ -2045,7 +2049,7 @@ class ConsoleInterpreter(BaseInterpreter):
|
|
|
2045
2049
|
for token in tokens:
|
|
2046
2050
|
target = _CATCH_SHORTHANDS.get(token.lower())
|
|
2047
2051
|
if target is None:
|
|
2048
|
-
self.
|
|
2052
|
+
self._dot_command_failed(f"unknown catch filter: {token}; try uncaught, all, test, suite, off")
|
|
2049
2053
|
return
|
|
2050
2054
|
mapped.add(target)
|
|
2051
2055
|
controller.set_exception_breakpoints(mapped)
|
|
@@ -2077,7 +2081,7 @@ class ConsoleInterpreter(BaseInterpreter):
|
|
|
2077
2081
|
controller.set_attached(False)
|
|
2078
2082
|
self._echo("debugger: detached")
|
|
2079
2083
|
else:
|
|
2080
|
-
self.
|
|
2084
|
+
self._dot_command_failed("usage: .debug on|off")
|
|
2081
2085
|
|
|
2082
2086
|
@dot_command("detach", group="Debugger")
|
|
2083
2087
|
def _detach(self, arg: str) -> None:
|
|
@@ -2099,13 +2103,21 @@ class ConsoleInterpreter(BaseInterpreter):
|
|
|
2099
2103
|
|
|
2100
2104
|
@dot_command("abort", group="Debugger")
|
|
2101
2105
|
def _abort(self, arg: str) -> None:
|
|
2102
|
-
"""
|
|
2106
|
+
"""End the run: the current test fails, the rest don't run, teardowns run, the output is written.
|
|
2107
|
+
|
|
2108
|
+
Use `.detach` to let the suite finish instead.
|
|
2109
|
+
"""
|
|
2103
2110
|
del arg
|
|
2104
2111
|
if self._stop is None:
|
|
2105
|
-
self.
|
|
2112
|
+
self._dot_command_failed("no run to abort")
|
|
2106
2113
|
return
|
|
2107
|
-
# Robot can't be cleanly stopped mid-keyword from a logger callback — a
|
|
2108
|
-
# raised exception is swallowed by suite.run() — so exit via SystemExit,
|
|
2109
|
-
# the one exception Robot does propagate.
|
|
2110
2114
|
self._echo("aborting the run")
|
|
2111
|
-
|
|
2115
|
+
self._pending_action = ResumeAction.CONTINUE
|
|
2116
|
+
self.exit_code = 253
|
|
2117
|
+
self._aborted = True
|
|
2118
|
+
try:
|
|
2119
|
+
# As SIGTERM does, so that Robot Framework writes the output files to their end.
|
|
2120
|
+
STOP_SIGNAL_MONITOR(signal.SIGTERM, None)
|
|
2121
|
+
except ExecutionStatus:
|
|
2122
|
+
# For a keyword that runs: from here it would get lost, the next keyword that starts stops the run.
|
|
2123
|
+
pass
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "2.9.0-dev.87"
|
|
File without changes
|
|
File without changes
|
{robotcode_repl-2.9.0.dev87 → robotcode_repl-2.9.0.dev115}/src/robotcode/repl/Repl/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{robotcode_repl-2.9.0.dev87 → robotcode_repl-2.9.0.dev115}/src/robotcode/repl/_debug/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{robotcode_repl-2.9.0.dev87 → robotcode_repl-2.9.0.dev115}/src/robotcode/repl/_keyword_lookup.py
RENAMED
|
File without changes
|
|
File without changes
|
{robotcode_repl-2.9.0.dev87 → robotcode_repl-2.9.0.dev115}/src/robotcode/repl/_pt/__init__.py
RENAMED
|
File without changes
|
{robotcode_repl-2.9.0.dev87 → robotcode_repl-2.9.0.dev115}/src/robotcode/repl/_pt/completion.py
RENAMED
|
File without changes
|
{robotcode_repl-2.9.0.dev87 → robotcode_repl-2.9.0.dev115}/src/robotcode/repl/_pt/components.py
RENAMED
|
File without changes
|
{robotcode_repl-2.9.0.dev87 → robotcode_repl-2.9.0.dev115}/src/robotcode/repl/_pt/history.py
RENAMED
|
File without changes
|
|
File without changes
|
{robotcode_repl-2.9.0.dev87 → robotcode_repl-2.9.0.dev115}/src/robotcode/repl/_session_export.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{robotcode_repl-2.9.0.dev87 → robotcode_repl-2.9.0.dev115}/src/robotcode/repl/session_cli.py
RENAMED
|
File without changes
|