robotcode-repl 2.9.0.dev68__py3-none-any.whl → 2.9.0.dev79__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.
@@ -0,0 +1,547 @@
1
+ import dataclasses
2
+ import multiprocessing
3
+ import os
4
+ import sys
5
+ import time
6
+ from typing import Any, Callable, List, Optional, Tuple, Type, TypeVar
7
+
8
+ import click
9
+ from click.core import ParameterSource
10
+ from robot.errors import DATA_ERROR
11
+
12
+ from robotcode.core.utils.dataclasses import as_dict
13
+ from robotcode.core.utils.path import same_file
14
+ from robotcode.plugin import Application, OutputFormat, pass_application
15
+ from robotcode.plugin.click_helper.exit_codes import robot_exit_codes
16
+ from robotcode.plugin.click_helper.types import add_options
17
+
18
+ from ._session import (
19
+ STARTUP_LIMIT,
20
+ AnswerStatus,
21
+ ChannelAnswer,
22
+ MovedAnswer,
23
+ OutputAnswer,
24
+ RefusedAnswer,
25
+ Request,
26
+ RestartedAnswer,
27
+ RestartFailedAnswer,
28
+ ResultAnswer,
29
+ SessionAnswer,
30
+ SessionEntry,
31
+ SessionKind,
32
+ SessionRequest,
33
+ StateAnswer,
34
+ StoppedAnswer,
35
+ connect,
36
+ create_output_pipe,
37
+ create_sessions_dir,
38
+ new_session_id,
39
+ open_sessions,
40
+ project_folder,
41
+ read_entry,
42
+ read_to_end,
43
+ remove_session_files,
44
+ sessions_dir,
45
+ start_session_process,
46
+ )
47
+
48
+ if sys.platform == "win32":
49
+ import msvcrt
50
+
51
+ F = TypeVar("F", bound=Callable[..., Any])
52
+ A = TypeVar("A", bound=SessionAnswer)
53
+
54
+ # How long `session start` waits for a session to take commands, when `--timeout` isn't longer.
55
+ _STARTUP_LIMIT = STARTUP_LIMIT
56
+
57
+ # 124 for a statement that still runs, as `timeout(1)` exits.
58
+ _EXIT_CODES = {AnswerStatus.PASSED: 0, AnswerStatus.FAILED: 1, AnswerStatus.RUNNING: 124}
59
+
60
+
61
+ def _timeout_option(what: str) -> Callable[[F], F]:
62
+ return click.option(
63
+ "--timeout",
64
+ type=click.FloatRange(min=0),
65
+ default=60,
66
+ show_default=True,
67
+ metavar="SECONDS",
68
+ help=f"Wait at most SECONDS for {what}. After that, print the output so far and exit with code 124, while "
69
+ "the session goes on; `session wait` prints the rest.",
70
+ )
71
+
72
+
73
+ @click.group(invoke_without_command=False)
74
+ def session() -> None:
75
+ """\
76
+ Run a REPL or a debug run in the background and give it one input after the other.
77
+
78
+ `session start` starts the session and prints its ID, which the other commands
79
+ take. The session keeps running when the command has returned, until it is
80
+ stopped or has waited for the idle timeout without a request. When the REPL
81
+ exits or the debug run ends, the session stays open: `session status` tells
82
+ it, `session result` prints the exit code, and `session restart` starts it
83
+ again.
84
+
85
+ \b
86
+ Examples:
87
+ ```
88
+ robotcode session start repl
89
+ robotcode session send repl-3f9a2c1e "Evaluate 1 + 1"
90
+ robotcode session start debug --break tests/orders.robot:19 tests
91
+ robotcode session send debug-7b2d9e04 ".vars --user"
92
+ robotcode session stop repl-3f9a2c1e
93
+ ```
94
+ """
95
+
96
+
97
+ # Errors of robotcode itself exit with 252, as other commands that run Robot Framework do, and not with the 1 of an
98
+ # input that failed.
99
+ robot_exit_codes(session)
100
+
101
+
102
+ @session.group(invoke_without_command=False)
103
+ def start() -> None:
104
+ """\
105
+ Start a session in the background and print its ID and its output up to its first prompt or its end.
106
+
107
+ `start` returns once the session takes commands. A session that isn't
108
+ ready within 60 seconds, or within `--timeout` when that is longer, is
109
+ ended.
110
+ """
111
+
112
+
113
+ _START_OPTIONS = [
114
+ click.option(
115
+ "--idle-timeout",
116
+ type=click.FloatRange(min=0, min_open=True),
117
+ default=15,
118
+ show_default=True,
119
+ metavar="MINUTES",
120
+ help="End the session when it has waited at its prompt for MINUTES without a request. A statement that "
121
+ "runs doesn't count as waiting.",
122
+ ),
123
+ _timeout_option("the first prompt"),
124
+ click.argument("args", nargs=-1, type=click.UNPROCESSED),
125
+ ]
126
+
127
+
128
+ @start.command(
129
+ name="repl",
130
+ context_settings={"ignore_unknown_options": True},
131
+ epilog="Takes the options and arguments of `robotcode repl`.",
132
+ )
133
+ @add_options(*_START_OPTIONS)
134
+ @pass_application
135
+ @click.pass_context
136
+ def start_repl(
137
+ ctx: click.Context, app: Application, idle_timeout: float, timeout: float, args: Tuple[str, ...]
138
+ ) -> None:
139
+ """Start `robotcode repl` in the background; the session is ready at its first prompt."""
140
+ _start(ctx, app, SessionKind.REPL, idle_timeout, timeout, args)
141
+
142
+
143
+ @start.command(
144
+ name="debug",
145
+ context_settings={"ignore_unknown_options": True},
146
+ epilog="Takes the options and arguments of `robotcode robot-debug`.",
147
+ )
148
+ @add_options(*_START_OPTIONS)
149
+ @pass_application
150
+ @click.pass_context
151
+ def start_debug(
152
+ ctx: click.Context, app: Application, idle_timeout: float, timeout: float, args: Tuple[str, ...]
153
+ ) -> None:
154
+ """Start `robotcode robot-debug` in the background; the session is ready at its first stop or at the end of the
155
+ run."""
156
+ _start(ctx, app, SessionKind.DEBUG, idle_timeout, timeout, args)
157
+
158
+
159
+ def _robotcode_options(ctx: click.Context) -> List[str]:
160
+ """The options given to `robotcode` itself on the command line, which the session process gets too; options from
161
+ the environment reach it with the environment."""
162
+ root = ctx.find_root()
163
+ args: List[str] = []
164
+ for param in root.command.params:
165
+ if not isinstance(param, click.Option) or param.name is None or param.name not in root.params:
166
+ continue
167
+ if root.get_parameter_source(param.name) is not ParameterSource.COMMANDLINE:
168
+ continue
169
+ value = root.params[param.name]
170
+ if param.is_flag:
171
+ if value:
172
+ args.append(param.opts[0])
173
+ elif param.secondary_opts:
174
+ args.append(param.secondary_opts[0])
175
+ elif param.multiple:
176
+ for item in value:
177
+ args += [param.opts[0], str(item)]
178
+ elif value is not None:
179
+ args += [param.opts[0], str(value)]
180
+ return args
181
+
182
+
183
+ def _start(
184
+ ctx: click.Context, app: Application, kind: SessionKind, idle_timeout: float, timeout: float, args: Tuple[str, ...]
185
+ ) -> None:
186
+ session_id = new_session_id(kind)
187
+ try:
188
+ create_sessions_dir()
189
+ except OSError as e:
190
+ # A sandbox, for example, can keep a command from writing there.
191
+ raise click.ClickException(
192
+ f"Can't create the files of the session in {sessions_dir()}: {e.strerror or e}."
193
+ ) from e
194
+ # The process writes its standard output and standard error to the pipe and reads it at the other end.
195
+ read_end, write_end = create_output_pipe()
196
+ if sys.platform == "win32":
197
+ output = msvcrt.get_osfhandle(read_end)
198
+ else:
199
+ output = read_end
200
+ command = [
201
+ sys.executable,
202
+ "-m",
203
+ "robotcode.cli",
204
+ *_robotcode_options(ctx),
205
+ "--no-color",
206
+ "--no-pager",
207
+ kind.command,
208
+ "--serve-session",
209
+ session_id,
210
+ "--serve-output",
211
+ str(output),
212
+ "--serve-idle-timeout",
213
+ str(idle_timeout),
214
+ *args,
215
+ ]
216
+ # Unbuffered, so that an answer has all the output written before it, and in UTF-8, which the session decodes.
217
+ env = {**os.environ, "PYTHONUNBUFFERED": "1", "PYTHONIOENCODING": "utf-8"}
218
+ try:
219
+ process = start_session_process(command, output, write_end, env)
220
+ finally:
221
+ os.close(write_end)
222
+
223
+ try:
224
+ # `--timeout` is for the first prompt. Until the session takes commands at all, `start` waits longer, so that
225
+ # the other commands always reach a session whose ID it has printed.
226
+ deadline = time.monotonic() + timeout
227
+ limit = max(timeout, _STARTUP_LIMIT)
228
+ ready_by = time.monotonic() + limit
229
+ entry = read_entry(session_id)
230
+ while entry is None:
231
+ if process.poll() is not None:
232
+ _ended_before_ready(
233
+ app,
234
+ session_id,
235
+ read_to_end(read_end),
236
+ f"The session ended before it was ready, with exit code {process.returncode}.",
237
+ process.returncode,
238
+ )
239
+ if time.monotonic() >= ready_by:
240
+ process.kill()
241
+ process.wait()
242
+ _ended_before_ready(
243
+ app,
244
+ session_id,
245
+ read_to_end(read_end),
246
+ f"The session wasn't ready after {limit:g} seconds and was ended.",
247
+ DATA_ERROR,
248
+ )
249
+ time.sleep(0.05)
250
+ entry = read_entry(session_id)
251
+ finally:
252
+ # Once the session is ready, it reads the pipe itself.
253
+ os.close(read_end)
254
+
255
+ answer = _expect(OutputAnswer, entry, SessionRequest(Request.WAIT), max(0.0, deadline - time.monotonic()))
256
+ if _structured(app):
257
+ app.print_data({"id": session_id, **as_dict(answer, remove_defaults=True)})
258
+ else:
259
+ app.echo(f"Started session {session_id}.")
260
+ _echo_answer(app, entry, answer)
261
+ app.exit(_exit_code(answer))
262
+
263
+
264
+ def _ended_before_ready(app: Application, session_id: str, output: str, message: str, exit_code: int) -> None:
265
+ remove_session_files(session_id)
266
+ if output:
267
+ app.echo(output, nl=False)
268
+ app.error(message)
269
+ app.exit(exit_code)
270
+
271
+
272
+ @session.command()
273
+ @_timeout_option("the answer")
274
+ @click.argument("session_id", metavar="ID")
275
+ @click.argument("text", metavar="INPUT")
276
+ @pass_application
277
+ def send(app: Application, timeout: float, session_id: str, text: str) -> None:
278
+ """\
279
+ Give INPUT to the prompt of the session ID and print its output up to the next prompt.
280
+
281
+ INPUT is a whole input: a statement, a text of several lines such as a FOR
282
+ loop with its END, or a dot-command such as `.vars`, which works as at the
283
+ prompt, each in a send of its own. A block that INPUT leaves unfinished
284
+ fails. The exit code is 0 when the statement passed, 1 when it failed, and
285
+ 124 when it still runs after `--timeout`. When the REPL or the run ends,
286
+ `send` says so with its exit code; a `send` after that fails, and
287
+ `session restart` starts it again.
288
+ """
289
+ entry = _entry(session_id)
290
+ answer = _expect(OutputAnswer, entry, SessionRequest(Request.SEND, text), timeout)
291
+ _echo_answer(app, entry, answer)
292
+ app.exit(_exit_code(answer))
293
+
294
+
295
+ @session.command()
296
+ @_timeout_option("the next prompt")
297
+ @click.argument("session_id", metavar="ID")
298
+ @pass_application
299
+ def wait(app: Application, timeout: float, session_id: str) -> None:
300
+ """\
301
+ Wait until the session ID waits at its prompt or its REPL or run has ended, and print its output since the last
302
+ command.
303
+
304
+ Takes over from a `send` that returned after its `--timeout`, with the exit
305
+ code that `send` would have had.
306
+ """
307
+ entry = _entry(session_id)
308
+ answer = _expect(OutputAnswer, entry, SessionRequest(Request.WAIT), timeout)
309
+ _echo_answer(app, entry, answer)
310
+ app.exit(_exit_code(answer))
311
+
312
+
313
+ @session.command(name="status")
314
+ @click.argument("session_id", metavar="ID")
315
+ @pass_application
316
+ def session_status(app: Application, session_id: str) -> None:
317
+ """\
318
+ Print whether the session ID runs, waits at its prompt or its REPL or run has ended: `runs`, `waits` or `ended`.
319
+
320
+ It takes none of the output of the session, which `session wait` prints.
321
+ """
322
+ answer = _expect(StateAnswer, _entry(session_id), SessionRequest(Request.STATE))
323
+ if _structured(app):
324
+ app.print_data(answer)
325
+ else:
326
+ app.echo(str(answer.state))
327
+
328
+
329
+ @session.command(name="result")
330
+ @click.argument("session_id", metavar="ID")
331
+ @pass_application
332
+ def session_result(app: Application, session_id: str) -> None:
333
+ """\
334
+ Print the exit code of the REPL or the run of the session ID once it has ended.
335
+
336
+ `result` itself exits with 0, as Robot Framework gives exit codes a meaning
337
+ of its own; before the end, it says so and exits with 252.
338
+ """
339
+ answer = _expect(ResultAnswer, _entry(session_id), SessionRequest(Request.RESULT))
340
+ if _structured(app):
341
+ app.print_data(answer)
342
+ else:
343
+ app.echo(str(answer.exit_code))
344
+
345
+
346
+ @session.command()
347
+ @_timeout_option("the first prompt")
348
+ @click.argument("session_id", metavar="ID")
349
+ @pass_application
350
+ def restart(app: Application, timeout: float, session_id: str) -> None:
351
+ """\
352
+ Start the REPL or the run of the session ID again in a new process, and print its output up to its first prompt
353
+ or its end.
354
+
355
+ A REPL or a run that hasn't ended is ended as with `session stop`, and its
356
+ output since the last command comes first. The session keeps its ID, and
357
+ the new process starts with the options of `session start` and the
358
+ breakpoints as they are.
359
+ """
360
+ entry = _entry(session_id)
361
+ # The old process ends the REPL or the run as `stop` does, then waits for the new process to take commands.
362
+ restarted = _request(entry, SessionRequest(Request.RESTART), 2 * _STARTUP_LIMIT)
363
+ if not isinstance(restarted, RestartedAnswer):
364
+ # After its limit, `restart` gets the output so far, while the restart goes on.
365
+ if isinstance(restarted, (OutputAnswer, RestartFailedAnswer)) and restarted.output:
366
+ app.echo(restarted.output, nl=False)
367
+ if isinstance(restarted, RestartFailedAnswer):
368
+ raise click.ClickException(restarted.reason)
369
+ raise click.ClickException(f"Session '{session_id}' hasn't restarted yet; `session status` tells when it has.")
370
+ before = restarted.output
371
+ # The file names the new process now.
372
+ entry = _entry(session_id)
373
+ answer = _expect(OutputAnswer, entry, SessionRequest(Request.WAIT), timeout)
374
+ if _structured(app):
375
+ whole = dataclasses.replace(answer, output=before + answer.output)
376
+ app.print_data({"id": session_id, **as_dict(whole, remove_defaults=True)})
377
+ else:
378
+ if before:
379
+ app.echo(before, nl=not before.endswith("\n"))
380
+ app.echo(f"Restarted session {session_id}.")
381
+ _echo_answer(app, entry, answer)
382
+ app.exit(_exit_code(answer))
383
+
384
+
385
+ @session.command()
386
+ @click.argument("session_id", metavar="ID")
387
+ def interrupt(session_id: str) -> None:
388
+ """\
389
+ Interrupt the session ID as Ctrl-C does at its terminal.
390
+
391
+ In a REPL, the statement that runs fails; with the debugger attached, the run
392
+ pauses at the next keyword. `session wait` prints what follows.
393
+ """
394
+ _request(_entry(session_id), SessionRequest(Request.INTERRUPT))
395
+
396
+
397
+ @session.command()
398
+ @click.option("--project", "of_project", is_flag=True, help="End the sessions of the project of the current folder.")
399
+ @click.option("--all", "all_sessions", is_flag=True, help="End all your sessions.")
400
+ @click.argument("session_id", metavar="[ID]", required=False)
401
+ @pass_application
402
+ def stop(app: Application, of_project: bool, all_sessions: bool, session_id: Optional[str]) -> None:
403
+ """\
404
+ End the session ID and print its output since the last command, or end several sessions.
405
+
406
+ `--project` ends the sessions of the project the current folder belongs to,
407
+ the folder RobotCode finds for its `robot.toml`; a session belongs to the
408
+ project of the folder `session start` ran in. `--all` ends all your
409
+ sessions. A debug run that has not ended is aborted as with `.abort`.
410
+ """
411
+ if (session_id is not None) + of_project + all_sessions != 1:
412
+ raise click.UsageError("Give the ID of a session, --project or --all.")
413
+ if session_id is not None:
414
+ answer = _stop(_entry(session_id))
415
+ if answer.output:
416
+ app.echo(answer.output, nl=False)
417
+ return
418
+
419
+ entries = open_sessions()
420
+ if of_project:
421
+ project = project_folder(app)
422
+ entries = [entry for entry in entries if same_file(entry.project, project)]
423
+ if not entries:
424
+ app.echo("No session is open.")
425
+ for entry in entries:
426
+ # It can have ended since it was listed.
427
+ if read_entry(entry.id) is not None:
428
+ _stop(entry)
429
+ app.echo(f"Stopped session {entry.id}.")
430
+
431
+
432
+ def _stop(entry: SessionEntry) -> StoppedAnswer:
433
+ answer = _request(entry, SessionRequest(Request.STOP), 60)
434
+ if not isinstance(answer, StoppedAnswer):
435
+ raise click.ClickException(f"Session '{entry.id}' hasn't ended yet; a second `session stop` ends it at once.")
436
+ return answer
437
+
438
+
439
+ @session.command(name="list")
440
+ @pass_application
441
+ def list_sessions(app: Application) -> None:
442
+ """List the open sessions with their project folder, whether they run, wait or their REPL or run has ended, and
443
+ the time left until they end for being idle."""
444
+ entries = open_sessions()
445
+ if _structured(app):
446
+ app.print_data(
447
+ [
448
+ {
449
+ "id": entry.id,
450
+ "project": entry.project,
451
+ "state": entry.state,
452
+ "time_left": None if (left := entry.time_left()) is None else round(left),
453
+ }
454
+ for entry in entries
455
+ ]
456
+ )
457
+ return
458
+ if not entries:
459
+ app.echo("No session is open.")
460
+ return
461
+ rows = [("ID", "PROJECT", "STATE", "IDLE TIME LEFT")] + [
462
+ (entry.id, entry.project, entry.state, _minutes(entry.time_left())) for entry in entries
463
+ ]
464
+ widths = [max(len(row[column]) for row in rows) for column in range(3)]
465
+ for row in rows:
466
+ app.echo(" ".join([*(cell.ljust(width) for cell, width in zip(row, widths)), row[3]]))
467
+
468
+
469
+ def _minutes(seconds: Optional[float]) -> str:
470
+ if seconds is None:
471
+ return "-"
472
+ minutes, seconds = divmod(round(seconds), 60)
473
+ return f"{minutes}:{seconds:02d}"
474
+
475
+
476
+ def _entry(session_id: str) -> SessionEntry:
477
+ entry = read_entry(session_id)
478
+ if entry is None:
479
+ raise click.ClickException(f"No session '{session_id}' is open.")
480
+ return entry
481
+
482
+
483
+ def _request(entry: SessionEntry, request: SessionRequest, timeout: Optional[float] = None) -> SessionAnswer:
484
+ """The answer of the session `entry` to `request`; after `timeout` seconds, the output so far."""
485
+ answer = _ask(entry, request, timeout)
486
+ while isinstance(answer, MovedAnswer):
487
+ # The session has just restarted in a new process, which its file names now.
488
+ moved_to = read_entry(entry.id)
489
+ if moved_to is None or moved_to.address == entry.address:
490
+ raise click.ClickException(f"No session '{entry.id}' is open.")
491
+ entry = moved_to
492
+ answer = _ask(entry, request, timeout)
493
+ if isinstance(answer, RefusedAnswer):
494
+ raise click.ClickException(answer.message)
495
+ return answer
496
+
497
+
498
+ def _expect(answer_type: Type[A], entry: SessionEntry, request: SessionRequest, timeout: Optional[float] = None) -> A:
499
+ """The answer of the session `entry` to a `request` that only an `answer_type` answers."""
500
+ answer = _request(entry, request, timeout)
501
+ if not isinstance(answer, answer_type):
502
+ raise click.ClickException(f"Session '{entry.id}' gave an unexpected answer.")
503
+ return answer
504
+
505
+
506
+ def _ask(entry: SessionEntry, request: SessionRequest, timeout: Optional[float]) -> ChannelAnswer:
507
+ """Send `request` to the process that `entry` names and return its answer; after `timeout` seconds, the output so
508
+ far."""
509
+ try:
510
+ with connect(entry) as connection:
511
+ connection.send(request)
512
+ if timeout is not None and not connection.poll(timeout):
513
+ try:
514
+ connection.send(SessionRequest(Request.STATUS))
515
+ except OSError:
516
+ # The answer came just now, and the session has closed the connection.
517
+ pass
518
+ answer: ChannelAnswer = connection.recv()
519
+ except (EOFError, ConnectionError, FileNotFoundError):
520
+ # The process has ended before it answered, as the old process of a restart does; its file tells what follows.
521
+ return MovedAnswer()
522
+ except (OSError, multiprocessing.AuthenticationError) as e:
523
+ raise click.ClickException(f"Session '{entry.id}' doesn't answer: {e}") from e
524
+ return answer
525
+
526
+
527
+ def _structured(app: Application) -> bool:
528
+ return app.config.output_format not in (None, OutputFormat.TEXT)
529
+
530
+
531
+ def _echo_answer(app: Application, entry: SessionEntry, answer: OutputAnswer) -> None:
532
+ if _structured(app):
533
+ app.print_data(answer)
534
+ return
535
+ if answer.output:
536
+ app.echo(answer.output, nl=False)
537
+ if not answer.output.endswith("\n"):
538
+ app.echo("")
539
+ if answer.exit_code is not None:
540
+ app.echo(f"{entry.kind.what} has ended with exit code {answer.exit_code}.", err=True)
541
+ elif answer.status is AnswerStatus.RUNNING:
542
+ app.echo(f"It still runs; `robotcode session wait {entry.id}` prints the rest.", err=True)
543
+
544
+
545
+ def _exit_code(answer: OutputAnswer) -> int:
546
+ """How the input went; the exit code of the REPL or the run is in the output."""
547
+ return _EXIT_CODES[answer.status]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: robotcode-repl
3
- Version: 2.9.0-dev.68
3
+ Version: 2.9.0-dev.79
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.68
30
+ Requires-Dist: robotcode-runner==2.9.0-dev.79
31
31
  Description-Content-Type: text/markdown
32
32
 
33
33
  # robotcode-repl
@@ -1,30 +1,32 @@
1
1
  robotcode/repl/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- robotcode/repl/__version__.py,sha256=VKDQl3EnWjlaGGS16iOqZ2LLnLORneHG-g4VFPliDAk,29
2
+ robotcode/repl/__version__.py,sha256=v9ZsHq3uFixxjVmkGl_oIz0HlfBtYBNCWuHr9KV-HMo,29
3
3
  robotcode/repl/_backends.py,sha256=s7PV-HBhnAvwjn2le0cbOJ5hwLb4MjahUo6KWtRC3lc,110
4
4
  robotcode/repl/_indent.py,sha256=jwFKaHNvTWYpUwIWHu6JHoaCz7ljOxneR5sBrKcAjpI,3303
5
5
  robotcode/repl/_keyword_lookup.py,sha256=c8InMUDGKsw4wYmiqJzBr9GGxPC-59sRZHI0C-GWjFA,7748
6
6
  robotcode/repl/_progress.py,sha256=BaSbDGOy5UOXvD855Kd-lDrYbqPrYIWRVs507vJznK4,2586
7
+ robotcode/repl/_session.py,sha256=XEdqkrvuMr9L3xUXlTM3SMtB-bP8-oWQSiq-xd6IoCc,50348
7
8
  robotcode/repl/_session_export.py,sha256=sTSkLEMoSxNQOsII3xfDY8h-nV-mrem-4h6Iug7VQv4,2841
8
9
  robotcode/repl/base_interpreter.py,sha256=ep1xqH5QCtkn2U2P77IJQCk_Q5TTBsYOcRjGC5hV5cY,30176
9
- robotcode/repl/cli.py,sha256=1o8_p0I8bCPsnhI_QoHXWr9U6t9Mj11PrI1E3XG2e1c,23706
10
- robotcode/repl/console_interpreter.py,sha256=K8GP7nR_L0o7LpV456OZsRFHym7xH5CyK3NkeD9UvHc,88757
10
+ robotcode/repl/cli.py,sha256=RcjFT1yvcDy8HMzVO6r0cuqWCKqFofrs7XdkJPUpp1E,26878
11
+ robotcode/repl/console_interpreter.py,sha256=VNCYLHRrW4LPtj37q27_dq75OHPlayCWPurw4EO9k0o,89411
11
12
  robotcode/repl/doc_cli.py,sha256=lJ1KrS-oxqW32XyRSFiyhSrtKVvzA7TuMclMuZRYIrE,23221
12
- robotcode/repl/hooks.py,sha256=huqeT18sHWxG2dQNCGmgQUkb11CzVQkOoDgrDAy9hqg,238
13
+ robotcode/repl/hooks.py,sha256=JD0A6vqQ0pcJx45l5w0wq-bWqnNkBV_6tPugtMZN4yo,280
13
14
  robotcode/repl/prompt_toolkit_interpreter.py,sha256=fq0uRsWucGhjKwGekUShz4r4Vt0nvbeAa-phLvI6WNw,13416
14
15
  robotcode/repl/py.typed,sha256=bWew9mHgMy8LqMu7RuqQXFXLBxh2CRx0dUbSx-3wE48,27
15
16
  robotcode/repl/run.py,sha256=IWeiGZm56vkLn-hykP-_mWLrdpDciNyMk33_dT-6EJA,5327
17
+ robotcode/repl/session_cli.py,sha256=piaognxJ3MoXW1Ul0s_iupfsPKsigGvLNQIEO0RKQhY,20691
16
18
  robotcode/repl/Repl/__init__.py,sha256=-j2WxY3MzdLfsMvpa2wMs6WVF_eeJygdshtMN50r67U,43
17
19
  robotcode/repl/Repl/repl.py,sha256=k3vCeg9z_4cb2dgZ4YKHl4TD599gumVkQMImAzGl20Y,1628
18
- robotcode/repl/_debug/__init__.py,sha256=uQNyC3pdlpJZI3FRKzcp9BYEz1QtfBR--gsgKNomxrQ,908
19
- robotcode/repl/_debug/controller.py,sha256=D5lxDqrVmjRG4bAF9_5561TIQ9MzlvOphIBVW8DzhOU,33221
20
- robotcode/repl/_debug/types.py,sha256=3Vbecx44pruY0fXptEowKr_tYhljXSb6Sbr81YS22Mk,4597
20
+ robotcode/repl/_debug/__init__.py,sha256=70LQ5T2ELbckKR1T8fmIiWtubtg8gsrqkTy_va5sFro,952
21
+ robotcode/repl/_debug/controller.py,sha256=QOnHSxKcYHd8zyXlGFaQkF244Z1hYvJdkB0mDNGsX2U,33970
22
+ robotcode/repl/_debug/types.py,sha256=L1sLrsSyvv5J0rQORff9-piGztgeJo-CpnNFXGLJiFc,4838
21
23
  robotcode/repl/_pt/__init__.py,sha256=k1bJgYbKwp5ScYO8fSx0BVy8XW9Aw_8o0jTOWQTQdX8,411
22
24
  robotcode/repl/_pt/completion.py,sha256=BJD6sASKu1EfW0QPNKIDRc9O-8ttErXvxvnFb10lAxk,23318
23
25
  robotcode/repl/_pt/components.py,sha256=kuYVXJc4bFJq4b3-JtAtBYymvS7ssSwlKDldWYFGMuA,20152
24
26
  robotcode/repl/_pt/doc_viewer.py,sha256=iazNOQEvRygPSUyqKH2qOYShzdYqd7vufZcxPziJx8Q,71648
25
27
  robotcode/repl/_pt/history.py,sha256=Rvbdu4LtbYk9dPgYJY7D1JpfyT1SETUdNmtevxo21H4,1591
26
28
  robotcode/repl/_pt/lexer.py,sha256=YXtDMWQXPIocwceMXFlvULWyGXCDmqeAtTer_Ecuvwk,13925
27
- robotcode_repl-2.9.0.dev68.dist-info/METADATA,sha256=LsjjHtQEfaFbqU5N4pG91dDZiUm2BqaGyEJ1mYFrJ24,2833
28
- robotcode_repl-2.9.0.dev68.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
29
- robotcode_repl-2.9.0.dev68.dist-info/entry_points.txt,sha256=lI4idVOEmSWxmmioejg-_QvHXTLySK6ZxJ13R_v8ykU,40
30
- robotcode_repl-2.9.0.dev68.dist-info/RECORD,,
29
+ robotcode_repl-2.9.0.dev79.dist-info/METADATA,sha256=_-oj0uaPOkOd9Na5K78HvySd1dpfcngD5NC1aGwJXjY,2833
30
+ robotcode_repl-2.9.0.dev79.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
31
+ robotcode_repl-2.9.0.dev79.dist-info/entry_points.txt,sha256=lI4idVOEmSWxmmioejg-_QvHXTLySK6ZxJ13R_v8ykU,40
32
+ robotcode_repl-2.9.0.dev79.dist-info/RECORD,,