robotcode-repl 2.9.0.dev51__py3-none-any.whl → 2.9.0.dev74__py3-none-any.whl
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/__version__.py +1 -1
- robotcode/repl/_debug/controller.py +17 -0
- robotcode/repl/_session.py +1119 -0
- robotcode/repl/base_interpreter.py +7 -1
- robotcode/repl/cli.py +142 -31
- robotcode/repl/console_interpreter.py +43 -12
- robotcode/repl/hooks.py +2 -1
- robotcode/repl/prompt_toolkit_interpreter.py +2 -0
- robotcode/repl/session_cli.py +530 -0
- {robotcode_repl-2.9.0.dev51.dist-info → robotcode_repl-2.9.0.dev74.dist-info}/METADATA +2 -2
- {robotcode_repl-2.9.0.dev51.dist-info → robotcode_repl-2.9.0.dev74.dist-info}/RECORD +13 -11
- {robotcode_repl-2.9.0.dev51.dist-info → robotcode_repl-2.9.0.dev74.dist-info}/WHEEL +0 -0
- {robotcode_repl-2.9.0.dev51.dist-info → robotcode_repl-2.9.0.dev74.dist-info}/entry_points.txt +0 -0
|
@@ -597,13 +597,19 @@ class BaseInterpreter(abc.ABC):
|
|
|
597
597
|
# Checked after marking the statement: a SIGTERM in between can't be missed.
|
|
598
598
|
if kw is None or self._termination_requested:
|
|
599
599
|
break
|
|
600
|
-
self.
|
|
600
|
+
result = self.run_keyword(kw)
|
|
601
|
+
self.set_last_result(result)
|
|
602
|
+
self.show_result(kw, result)
|
|
601
603
|
finally:
|
|
602
604
|
self._running_statement = False
|
|
603
605
|
|
|
604
606
|
def set_last_result(self, result: Any) -> None:
|
|
605
607
|
self.last_result = result
|
|
606
608
|
|
|
609
|
+
def show_result(self, statement: Keyword, result: Any) -> None:
|
|
610
|
+
"""Show the result of a statement of the input after it has run. Each interpreter knows how;
|
|
611
|
+
the REPL server returns results through its protocol."""
|
|
612
|
+
|
|
607
613
|
@abc.abstractmethod
|
|
608
614
|
def log_message(
|
|
609
615
|
self, message: str, level: str, html: Union[str, bool] = False, timestamp: Union[datetime, str, None] = None
|
robotcode/repl/cli.py
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
import contextlib
|
|
1
2
|
import os
|
|
2
3
|
import sys
|
|
3
4
|
from pathlib import Path
|
|
4
|
-
from typing import Dict, List, Optional, Tuple
|
|
5
|
+
from typing import ContextManager, Dict, List, Optional, Tuple
|
|
5
6
|
|
|
6
7
|
import click
|
|
7
8
|
|
|
9
|
+
from robotcode.core.utils.cli import show_hidden_arguments
|
|
8
10
|
from robotcode.plugin import Application, pass_application
|
|
9
11
|
from robotcode.plugin._agent_detection import is_running_in_ai_agent
|
|
10
12
|
from robotcode.plugin.click_helper.aliases import AliasedCommand
|
|
@@ -15,6 +17,7 @@ from robotcode.runner.cli.robot import ROBOT_OPTIONS, ROBOT_VERSION_OPTIONS, loa
|
|
|
15
17
|
from .__version__ import __version__
|
|
16
18
|
from ._backends import BACKEND_CHOICES
|
|
17
19
|
from ._debug import DebugController, DebugTerminated
|
|
20
|
+
from ._session import SessionError, SessionInterpreter, SessionKind, SessionServer, project_folder
|
|
18
21
|
from .console_interpreter import _LINE_BREAK_RE, ConsoleInterpreter
|
|
19
22
|
from .prompt_toolkit_interpreter import PromptToolkitConsoleInterpreter
|
|
20
23
|
from .run import ReplResult, run_repl
|
|
@@ -28,6 +31,7 @@ def _pick_interpreter(
|
|
|
28
31
|
inspect: bool,
|
|
29
32
|
no_history: bool,
|
|
30
33
|
backend: str,
|
|
34
|
+
show_prompt: bool,
|
|
31
35
|
) -> ConsoleInterpreter:
|
|
32
36
|
"""Construct the interpreter that matches ``backend``.
|
|
33
37
|
|
|
@@ -43,6 +47,7 @@ def _pick_interpreter(
|
|
|
43
47
|
show_keywords=show_keywords,
|
|
44
48
|
inspect=inspect,
|
|
45
49
|
no_history=no_history,
|
|
50
|
+
show_prompt=show_prompt,
|
|
46
51
|
)
|
|
47
52
|
if backend not in ("auto", "prompt-toolkit"):
|
|
48
53
|
raise ValueError(f"Unknown backend: {backend!r}. Choose from {BACKEND_CHOICES}.")
|
|
@@ -52,6 +57,7 @@ def _pick_interpreter(
|
|
|
52
57
|
show_keywords=show_keywords,
|
|
53
58
|
inspect=inspect,
|
|
54
59
|
no_history=no_history,
|
|
60
|
+
show_prompt=show_prompt,
|
|
55
61
|
)
|
|
56
62
|
|
|
57
63
|
|
|
@@ -153,6 +159,20 @@ def _resolve_backend(plain: bool, backend: str) -> str:
|
|
|
153
159
|
return backend
|
|
154
160
|
|
|
155
161
|
|
|
162
|
+
def _resolve_show_prompt(show_prompt: Optional[bool]) -> bool:
|
|
163
|
+
"""Whether `repl` shows its `>>> ` / `... ` prompt.
|
|
164
|
+
|
|
165
|
+
Always on an interactive terminal. Otherwise `--show-prompt` / `--no-show-prompt`
|
|
166
|
+
(or `ROBOTCODE_REPL_SHOW_PROMPT`) decides, and without either a recognised AI
|
|
167
|
+
agent gets the prompt: it marks where the output of a statement ends.
|
|
168
|
+
"""
|
|
169
|
+
if _is_interactive_stdin():
|
|
170
|
+
return True
|
|
171
|
+
if show_prompt is not None:
|
|
172
|
+
return show_prompt
|
|
173
|
+
return is_running_in_ai_agent()
|
|
174
|
+
|
|
175
|
+
|
|
156
176
|
def _resolve_attached(
|
|
157
177
|
debugger_attached: Optional[bool],
|
|
158
178
|
*,
|
|
@@ -302,6 +322,15 @@ SHELL_OPTIONS = [
|
|
|
302
322
|
default=False,
|
|
303
323
|
help="Activate inspection mode. This forces a prompt to appear after the REPL script is executed.",
|
|
304
324
|
),
|
|
325
|
+
click.option(
|
|
326
|
+
"--show-prompt/--no-show-prompt",
|
|
327
|
+
default=None,
|
|
328
|
+
envvar="ROBOTCODE_REPL_SHOW_PROMPT",
|
|
329
|
+
help="Show (`--show-prompt`) or hide (`--no-show-prompt`) the `>>>` prompt when the input is not a "
|
|
330
|
+
"terminal, such as piped input. Without either option it shows there only in the session of a "
|
|
331
|
+
"recognised AI agent, where it marks the end of each statement's output. On a terminal the prompt "
|
|
332
|
+
"always shows.",
|
|
333
|
+
),
|
|
305
334
|
click.option(
|
|
306
335
|
"-d",
|
|
307
336
|
"--outputdir",
|
|
@@ -365,6 +394,52 @@ SHELL_OPTIONS = [
|
|
|
365
394
|
),
|
|
366
395
|
]
|
|
367
396
|
|
|
397
|
+
# The session that `robotcode session start` runs `repl` or `robot-debug` as.
|
|
398
|
+
SESSION_OPTIONS = [
|
|
399
|
+
click.option(
|
|
400
|
+
"--serve-session",
|
|
401
|
+
metavar="ID",
|
|
402
|
+
hidden=show_hidden_arguments(),
|
|
403
|
+
help="Serve the session ID that `robotcode session start` started. This is an internal option.",
|
|
404
|
+
),
|
|
405
|
+
click.option(
|
|
406
|
+
"--serve-output",
|
|
407
|
+
type=int,
|
|
408
|
+
metavar="FD",
|
|
409
|
+
hidden=show_hidden_arguments(),
|
|
410
|
+
help="Read the output of the session from the pipe FD, a handle on Windows, into memory. "
|
|
411
|
+
"This is an internal option.",
|
|
412
|
+
),
|
|
413
|
+
click.option(
|
|
414
|
+
"--serve-idle-timeout",
|
|
415
|
+
type=float,
|
|
416
|
+
default=15,
|
|
417
|
+
metavar="MINUTES",
|
|
418
|
+
hidden=show_hidden_arguments(),
|
|
419
|
+
help="End the session after it has waited at its prompt for MINUTES without a request. "
|
|
420
|
+
"This is an internal option.",
|
|
421
|
+
),
|
|
422
|
+
]
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
def _session_server(
|
|
426
|
+
app: Application,
|
|
427
|
+
serve_session: Optional[str],
|
|
428
|
+
serve_output: Optional[int],
|
|
429
|
+
kind: SessionKind,
|
|
430
|
+
idle_timeout: float,
|
|
431
|
+
) -> Optional[SessionServer]:
|
|
432
|
+
if serve_session is None:
|
|
433
|
+
return None
|
|
434
|
+
if serve_output is None:
|
|
435
|
+
raise click.UsageError("--serve-session needs --serve-output.")
|
|
436
|
+
try:
|
|
437
|
+
# The folder `session start` ran in decides the project, also for a debug run of a suite elsewhere.
|
|
438
|
+
return SessionServer(serve_session, kind, project_folder(app), idle_timeout * 60, serve_output)
|
|
439
|
+
except SessionError as e:
|
|
440
|
+
raise click.ClickException(str(e)) from e
|
|
441
|
+
|
|
442
|
+
|
|
368
443
|
# Debugger options — shared by `repl` and `robot-debug`. `--debugger-attached`
|
|
369
444
|
# is the master switch (does anything pause at all?); the `--break*` flags are
|
|
370
445
|
# the individual pause triggers. (`robot-debug` adds `--stop-on-entry`, which
|
|
@@ -421,6 +496,7 @@ DEBUG_OPTIONS = [
|
|
|
421
496
|
@add_options(*REPL_BASE_OPTIONS)
|
|
422
497
|
@add_options(*SHELL_OPTIONS)
|
|
423
498
|
@add_options(*DEBUG_OPTIONS)
|
|
499
|
+
@add_options(*SESSION_OPTIONS)
|
|
424
500
|
@click.version_option(version=__version__, prog_name="RobotCode REPL")
|
|
425
501
|
@pass_application
|
|
426
502
|
def repl(
|
|
@@ -430,6 +506,7 @@ def repl(
|
|
|
430
506
|
pythonpath: Tuple[str, ...],
|
|
431
507
|
show_keywords: bool,
|
|
432
508
|
inspect: bool,
|
|
509
|
+
show_prompt: Optional[bool],
|
|
433
510
|
no_history: bool,
|
|
434
511
|
backend: str,
|
|
435
512
|
plain: bool,
|
|
@@ -447,6 +524,9 @@ def repl(
|
|
|
447
524
|
break_on_all_exceptions: bool,
|
|
448
525
|
break_on_failed_test: bool,
|
|
449
526
|
break_on_failed_suite: bool,
|
|
527
|
+
serve_session: Optional[str],
|
|
528
|
+
serve_output: Optional[int],
|
|
529
|
+
serve_idle_timeout: float,
|
|
450
530
|
) -> None:
|
|
451
531
|
"""Run Robot Framework interactively (alias `shell`).
|
|
452
532
|
|
|
@@ -473,13 +553,19 @@ def repl(
|
|
|
473
553
|
|
|
474
554
|
backend = _resolve_backend(plain, backend)
|
|
475
555
|
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
files=list(files),
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
556
|
+
server = _session_server(app, serve_session, serve_output, SessionKind.REPL, serve_idle_timeout)
|
|
557
|
+
interpreter = (
|
|
558
|
+
SessionInterpreter(server, app=app, files=list(files), show_keywords=show_keywords, inspect=inspect)
|
|
559
|
+
if server is not None
|
|
560
|
+
else _pick_interpreter(
|
|
561
|
+
app=app,
|
|
562
|
+
files=list(files),
|
|
563
|
+
show_keywords=show_keywords,
|
|
564
|
+
inspect=inspect,
|
|
565
|
+
no_history=no_history,
|
|
566
|
+
backend=backend,
|
|
567
|
+
show_prompt=_resolve_show_prompt(show_prompt),
|
|
568
|
+
)
|
|
483
569
|
)
|
|
484
570
|
# Only `repl` fails its session test on unhandled failures — not `robot-debug`,
|
|
485
571
|
# which builds its interpreter the same way.
|
|
@@ -487,7 +573,7 @@ def repl(
|
|
|
487
573
|
# The debugger stays wired for the whole interactive session and is torn down
|
|
488
574
|
# with the interpreter when the session ends — so, unlike `robot`, there is no
|
|
489
575
|
# per-run unregister here. `attached` decides whether it actually pauses.
|
|
490
|
-
_attach_debugger(
|
|
576
|
+
controller = _attach_debugger(
|
|
491
577
|
interpreter,
|
|
492
578
|
break_at=break_at,
|
|
493
579
|
attached=attached,
|
|
@@ -496,26 +582,31 @@ def repl(
|
|
|
496
582
|
break_on_failed_test=break_on_failed_test,
|
|
497
583
|
break_on_failed_suite=break_on_failed_suite,
|
|
498
584
|
)
|
|
585
|
+
if server is not None:
|
|
586
|
+
server.keep_breakpoints(controller)
|
|
587
|
+
|
|
588
|
+
# A session records the exit code of the command when the REPL exits, and stays open.
|
|
589
|
+
session: ContextManager[object] = server if server is not None else contextlib.nullcontext()
|
|
590
|
+
with session:
|
|
591
|
+
result = run_repl(
|
|
592
|
+
interpreter=interpreter,
|
|
593
|
+
app=app,
|
|
594
|
+
variable=variable,
|
|
595
|
+
variablefile=variablefile,
|
|
596
|
+
pythonpath=pythonpath,
|
|
597
|
+
outputdir=outputdir,
|
|
598
|
+
output=output,
|
|
599
|
+
report=report,
|
|
600
|
+
log=log,
|
|
601
|
+
xunit=xunit,
|
|
602
|
+
source=source,
|
|
603
|
+
files=files,
|
|
604
|
+
statusrc=statusrc,
|
|
605
|
+
)
|
|
499
606
|
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
app=
|
|
503
|
-
variable=variable,
|
|
504
|
-
variablefile=variablefile,
|
|
505
|
-
pythonpath=pythonpath,
|
|
506
|
-
outputdir=outputdir,
|
|
507
|
-
output=output,
|
|
508
|
-
report=report,
|
|
509
|
-
log=log,
|
|
510
|
-
xunit=xunit,
|
|
511
|
-
source=source,
|
|
512
|
-
files=files,
|
|
513
|
-
statusrc=statusrc,
|
|
514
|
-
)
|
|
515
|
-
|
|
516
|
-
# Script files without `--inspect` end on their own, like piped input.
|
|
517
|
-
interactive = _is_interactive_stdin() and (not files or inspect)
|
|
518
|
-
app.exit(_session_exit_code(result, statusrc=statusrc, interactive=interactive))
|
|
607
|
+
# Script files without `--inspect` end on their own, like piped input.
|
|
608
|
+
interactive = _is_interactive_stdin() and (not files or inspect)
|
|
609
|
+
app.exit(_session_exit_code(result, statusrc=statusrc, interactive=interactive))
|
|
519
610
|
|
|
520
611
|
|
|
521
612
|
@wrappable
|
|
@@ -529,6 +620,7 @@ def repl(
|
|
|
529
620
|
@add_options(*REPL_BASE_OPTIONS)
|
|
530
621
|
@add_options(*(option for option in ROBOT_OPTIONS if option not in ROBOT_VERSION_OPTIONS))
|
|
531
622
|
@add_options(*DEBUG_OPTIONS)
|
|
623
|
+
@add_options(*SESSION_OPTIONS)
|
|
532
624
|
@click.option(
|
|
533
625
|
"--stop-on-entry",
|
|
534
626
|
is_flag=True,
|
|
@@ -556,6 +648,9 @@ def robot_debug(
|
|
|
556
648
|
break_on_all_exceptions: bool,
|
|
557
649
|
break_on_failed_test: bool,
|
|
558
650
|
break_on_failed_suite: bool,
|
|
651
|
+
serve_session: Optional[str],
|
|
652
|
+
serve_output: Optional[int],
|
|
653
|
+
serve_idle_timeout: float,
|
|
559
654
|
) -> None:
|
|
560
655
|
"""Run a real Robot Framework suite with the debugger attached (alias `run-debug`).
|
|
561
656
|
|
|
@@ -572,8 +667,20 @@ def robot_debug(
|
|
|
572
667
|
|
|
573
668
|
backend = _resolve_backend(plain, backend)
|
|
574
669
|
|
|
575
|
-
|
|
576
|
-
|
|
670
|
+
server = _session_server(app, serve_session, serve_output, SessionKind.DEBUG, serve_idle_timeout)
|
|
671
|
+
# A run stops at `(rdb) `, which always shows; `robot-debug` has no option for `>>> `.
|
|
672
|
+
interpreter = (
|
|
673
|
+
SessionInterpreter(server, app=app, files=[], show_keywords=False, inspect=False)
|
|
674
|
+
if server is not None
|
|
675
|
+
else _pick_interpreter(
|
|
676
|
+
app=app,
|
|
677
|
+
files=[],
|
|
678
|
+
show_keywords=False,
|
|
679
|
+
inspect=False,
|
|
680
|
+
no_history=no_history,
|
|
681
|
+
backend=backend,
|
|
682
|
+
show_prompt=_is_interactive_stdin(),
|
|
683
|
+
)
|
|
577
684
|
)
|
|
578
685
|
controller = _attach_debugger(
|
|
579
686
|
interpreter,
|
|
@@ -585,14 +692,18 @@ def robot_debug(
|
|
|
585
692
|
break_on_failed_test=break_on_failed_test,
|
|
586
693
|
break_on_failed_suite=break_on_failed_suite,
|
|
587
694
|
)
|
|
695
|
+
if server is not None:
|
|
696
|
+
server.keep_breakpoints(controller)
|
|
588
697
|
|
|
589
698
|
# `robot-debug` produces the same console output as `robotcode robot` — the run
|
|
590
699
|
# is fully visible. At a stop the debug prompt is interleaved with Robot's live
|
|
591
700
|
# console; `_render_stop` breaks off any in-progress status line, and the run's
|
|
592
701
|
# output resumes on continue/step/detach. The console mode is left to Robot's
|
|
593
702
|
# default (or the user's `--console`), not forced.
|
|
703
|
+
# A session records the exit code of the run when the run ends, and stays open.
|
|
704
|
+
session: ContextManager[object] = server if server is not None else contextlib.nullcontext()
|
|
594
705
|
try:
|
|
595
|
-
with interpreter.forward_events(echo_messages=False):
|
|
706
|
+
with session, interpreter.forward_events(echo_messages=False):
|
|
596
707
|
_invoke_runner(
|
|
597
708
|
ctx,
|
|
598
709
|
by_longname,
|
|
@@ -28,7 +28,7 @@ from typing import TYPE_CHECKING, Any, Callable, Dict, Iterator, List, Optional,
|
|
|
28
28
|
|
|
29
29
|
import click
|
|
30
30
|
from robot import result, running
|
|
31
|
-
from robot.errors import ExecutionFailed, ExecutionPassed
|
|
31
|
+
from robot.errors import ExecutionFailed, ExecutionPassed, ExecutionStatus
|
|
32
32
|
from robot.running import Keyword
|
|
33
33
|
from robot.running.context import EXECUTION_CONTEXTS
|
|
34
34
|
from robot.utils import normalize
|
|
@@ -334,6 +334,7 @@ class ConsoleInterpreter(BaseInterpreter):
|
|
|
334
334
|
"""
|
|
335
335
|
|
|
336
336
|
# The prompt shown while paused at a debug stop (vs the `>>> ` REPL prompt).
|
|
337
|
+
PROMPT = ">>> "
|
|
337
338
|
DEBUG_PROMPT = "(rdb) "
|
|
338
339
|
|
|
339
340
|
# Whether `show_doc` opens a scrollable fullscreen viewer (prompt_toolkit) vs
|
|
@@ -348,6 +349,7 @@ class ConsoleInterpreter(BaseInterpreter):
|
|
|
348
349
|
show_keywords: bool = False,
|
|
349
350
|
inspect: Optional[bool] = False,
|
|
350
351
|
no_history: bool = False,
|
|
352
|
+
show_prompt: bool = False,
|
|
351
353
|
) -> None:
|
|
352
354
|
del no_history # plain mode has no history file to honour the flag against
|
|
353
355
|
super().__init__()
|
|
@@ -356,6 +358,8 @@ class ConsoleInterpreter(BaseInterpreter):
|
|
|
356
358
|
self.files = files
|
|
357
359
|
self.show_keywords = show_keywords
|
|
358
360
|
self.inspect = inspect
|
|
361
|
+
# Whether `>>> ` and `... ` show; `robotcode repl` decides it (`_resolve_show_prompt`).
|
|
362
|
+
self.show_prompt = show_prompt
|
|
359
363
|
|
|
360
364
|
self.executed_files: List[Path] = []
|
|
361
365
|
# REPL inputs that parsed cleanly — `.save` exports them as a
|
|
@@ -368,6 +372,8 @@ class ConsoleInterpreter(BaseInterpreter):
|
|
|
368
372
|
# runs, then the session ends without reading again — on a terminal, a
|
|
369
373
|
# read after Ctrl-D waits for more input instead of ending.
|
|
370
374
|
self._input_ended = False
|
|
375
|
+
# Set while the statements of a script file run: like the body of a test, they show no results.
|
|
376
|
+
self._input_from_file = False
|
|
371
377
|
# Set while a prompt waits for input: SIGTERM then ends the wait (`_end_waiting`).
|
|
372
378
|
self._reading = False
|
|
373
379
|
|
|
@@ -575,7 +581,8 @@ class ConsoleInterpreter(BaseInterpreter):
|
|
|
575
581
|
pager ignores them.
|
|
576
582
|
"""
|
|
577
583
|
del scroll_to, outline # only the scrollable viewer (prompt_toolkit) uses them
|
|
578
|
-
|
|
584
|
+
# Documentation can end with empty lines of its markdown; the output ends with the text.
|
|
585
|
+
body = f"{title}\n{'=' * len(title)}\n\n{markdown}".rstrip()
|
|
579
586
|
if self.app is not None:
|
|
580
587
|
self.app.echo_via_pager(body, color=False)
|
|
581
588
|
|
|
@@ -686,6 +693,10 @@ class ConsoleInterpreter(BaseInterpreter):
|
|
|
686
693
|
self._dispatch_dot_command(line)
|
|
687
694
|
else:
|
|
688
695
|
self._evaluate_at_stop(line)
|
|
696
|
+
except ExecutionStatus:
|
|
697
|
+
# Robot Framework has reported the failure of the keyword, or its `Pass Execution`, as a message
|
|
698
|
+
# already; the REPL prompt adds nothing to it either.
|
|
699
|
+
pass
|
|
689
700
|
except Exception as e: # a throwing command must not eject the debug prompt
|
|
690
701
|
# `.abort` (SystemExit), Ctrl-C, and DebugTerminated are
|
|
691
702
|
# BaseException, not Exception, so they still propagate.
|
|
@@ -761,7 +772,7 @@ class ConsoleInterpreter(BaseInterpreter):
|
|
|
761
772
|
return f"{path}:{frame.line}" if frame.line is not None else path
|
|
762
773
|
|
|
763
774
|
def _render_stop(self, stop: StopEvent) -> None:
|
|
764
|
-
self.
|
|
775
|
+
self._end_console_line()
|
|
765
776
|
parts = [f"* {stop.reason.value}", stop.frame.name]
|
|
766
777
|
loc = self._location(stop.frame)
|
|
767
778
|
if loc:
|
|
@@ -772,6 +783,11 @@ class ConsoleInterpreter(BaseInterpreter):
|
|
|
772
783
|
self._echo(line)
|
|
773
784
|
self._show_displays()
|
|
774
785
|
|
|
786
|
+
def _end_console_line(self) -> None:
|
|
787
|
+
"""Break off the line of the run's console that a stop comes in, such as the name of a test that Robot
|
|
788
|
+
Framework has written before its status."""
|
|
789
|
+
self._echo("")
|
|
790
|
+
|
|
775
791
|
def _show_displays(self) -> None:
|
|
776
792
|
"""Auto-evaluate the `.display` expressions in the selected frame at a stop."""
|
|
777
793
|
if not self._display_exprs or self._controller is None:
|
|
@@ -837,8 +853,8 @@ class ConsoleInterpreter(BaseInterpreter):
|
|
|
837
853
|
width = max(len(f.name) for f in stack)
|
|
838
854
|
self._echo(self._frame_line(self._frame_no, self._selected_frame(), width))
|
|
839
855
|
|
|
840
|
-
def _evaluate_at_stop(self, line: str) ->
|
|
841
|
-
"""Run `line` as Robot keyword(s) in the paused context.
|
|
856
|
+
def _evaluate_at_stop(self, line: str) -> bool:
|
|
857
|
+
"""Run `line` as Robot keyword(s) in the paused context; False when it can't run.
|
|
842
858
|
|
|
843
859
|
Wrapped in `suppress_pausing()` so the evaluated keyword's own events
|
|
844
860
|
don't re-trigger a stop, and in `forward_events(echo_messages=True)` so
|
|
@@ -848,18 +864,18 @@ class ConsoleInterpreter(BaseInterpreter):
|
|
|
848
864
|
# output.xml when we stop; a keyword written after it makes the file unreadable.
|
|
849
865
|
if self._stop is not None and self._stop.reason == StopReason.EXCEPTION and self._stop.frame.type == "SUITE":
|
|
850
866
|
self._echo("! Keywords can't run at the end of a suite: its output is already closed.")
|
|
851
|
-
return
|
|
867
|
+
return False
|
|
852
868
|
try:
|
|
853
869
|
test, errors = self.get_test_body_from_string(line)
|
|
854
870
|
except Exception as e: # surface any parse failure to the prompt
|
|
855
871
|
self._echo(f"! {e}")
|
|
856
|
-
return
|
|
872
|
+
return False
|
|
857
873
|
if errors:
|
|
858
874
|
for err in errors:
|
|
859
875
|
self._echo(f"! {err}")
|
|
860
|
-
return
|
|
876
|
+
return False
|
|
861
877
|
if not test.body:
|
|
862
|
-
return
|
|
878
|
+
return True
|
|
863
879
|
# Keywords evaluated at a stop don't count for the session status.
|
|
864
880
|
record_failures, self.record_failures = self.record_failures, False
|
|
865
881
|
try:
|
|
@@ -868,7 +884,8 @@ class ConsoleInterpreter(BaseInterpreter):
|
|
|
868
884
|
self.set_last_result(self.run_keyword(kw))
|
|
869
885
|
finally:
|
|
870
886
|
self.record_failures = record_failures
|
|
871
|
-
self.
|
|
887
|
+
self._echo_result(test.body[-1], self.last_result)
|
|
888
|
+
return True
|
|
872
889
|
|
|
873
890
|
# ------------------------------------------------------------------
|
|
874
891
|
# Robot integration
|
|
@@ -892,6 +909,18 @@ class ConsoleInterpreter(BaseInterpreter):
|
|
|
892
909
|
# than crash the REPL.
|
|
893
910
|
pass
|
|
894
911
|
|
|
912
|
+
def show_result(self, statement: Keyword, result: Any) -> None:
|
|
913
|
+
if not self._input_from_file:
|
|
914
|
+
self._echo_result(statement, result)
|
|
915
|
+
|
|
916
|
+
def _echo_result(self, statement: Any, result: Any) -> None:
|
|
917
|
+
"""Print the result of `statement` as `=> <value>`, at the `>>>` and the `(rdb)` prompt alike.
|
|
918
|
+
`None` shows nothing, and neither does an assignment: the message that sets the variable
|
|
919
|
+
names the value."""
|
|
920
|
+
if result is None or getattr(statement, "assign", None):
|
|
921
|
+
return
|
|
922
|
+
self._echo(f"=> {_repr.repr(result)}")
|
|
923
|
+
|
|
895
924
|
def _alias_setting_imports(self, test: "running.TestCase") -> None:
|
|
896
925
|
"""REPL-only: rewrite top-level ``Library`` / ``Resource`` / ``Variables``
|
|
897
926
|
keyword calls into the BuiltIn ``Import …`` keywords.
|
|
@@ -929,8 +958,10 @@ class ConsoleInterpreter(BaseInterpreter):
|
|
|
929
958
|
text = file.read_text(encoding="utf-8")
|
|
930
959
|
|
|
931
960
|
# Run like a test body: an invalid statement fails when execution reaches it.
|
|
961
|
+
self._input_from_file = True
|
|
932
962
|
yield from self._body_to_run(self.parse_input(text))
|
|
933
963
|
else:
|
|
964
|
+
self._input_from_file = False
|
|
934
965
|
if self._input_ended:
|
|
935
966
|
raise EOFError
|
|
936
967
|
|
|
@@ -938,8 +969,8 @@ class ConsoleInterpreter(BaseInterpreter):
|
|
|
938
969
|
last_one = False
|
|
939
970
|
while True:
|
|
940
971
|
prompt = ""
|
|
941
|
-
if
|
|
942
|
-
prompt =
|
|
972
|
+
if self.show_prompt:
|
|
973
|
+
prompt = self.PROMPT if not lines else "... "
|
|
943
974
|
|
|
944
975
|
prefill = compute_indent(lines) if lines else ""
|
|
945
976
|
try:
|
robotcode/repl/hooks.py
CHANGED
|
@@ -6,8 +6,9 @@ from robotcode.plugin import hookimpl
|
|
|
6
6
|
|
|
7
7
|
from .cli import repl, robot_debug
|
|
8
8
|
from .doc_cli import doc
|
|
9
|
+
from .session_cli import session
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
@hookimpl
|
|
12
13
|
def register_cli_commands() -> List[click.Command]:
|
|
13
|
-
return [repl, robot_debug, doc]
|
|
14
|
+
return [repl, robot_debug, doc, session]
|
|
@@ -67,6 +67,7 @@ class PromptToolkitConsoleInterpreter(ConsoleInterpreter):
|
|
|
67
67
|
show_keywords: bool = False,
|
|
68
68
|
inspect: Optional[bool] = False,
|
|
69
69
|
no_history: bool = False,
|
|
70
|
+
show_prompt: bool = False,
|
|
70
71
|
) -> None:
|
|
71
72
|
super().__init__(
|
|
72
73
|
app=app,
|
|
@@ -74,6 +75,7 @@ class PromptToolkitConsoleInterpreter(ConsoleInterpreter):
|
|
|
74
75
|
show_keywords=show_keywords,
|
|
75
76
|
inspect=inspect,
|
|
76
77
|
no_history=no_history,
|
|
78
|
+
show_prompt=show_prompt,
|
|
77
79
|
)
|
|
78
80
|
|
|
79
81
|
self._history_store: History
|