robotcode-repl 0.99.0__tar.gz → 0.100.0__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: robotcode-repl
3
- Version: 0.99.0
3
+ Version: 0.100.0
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
@@ -24,8 +24,7 @@ Classifier: Programming Language :: Python :: Implementation :: PyPy
24
24
  Classifier: Topic :: Utilities
25
25
  Classifier: Typing :: Typed
26
26
  Requires-Python: >=3.8
27
- Requires-Dist: robotcode-jsonrpc2==0.99.0
28
- Requires-Dist: robotcode-runner==0.99.0
27
+ Requires-Dist: robotcode-runner==0.100.0
29
28
  Description-Content-Type: text/markdown
30
29
 
31
30
  # robotcode-repl
@@ -38,7 +37,8 @@ Description-Content-Type: text/markdown
38
37
 
39
38
  ## Introduction
40
39
 
41
- Some classes for [RobotCode](https://robotcode.io) plugin management
40
+ Provides a REPL for [RobotCode](https://robotcode.io).
41
+
42
42
 
43
43
  ## Installation
44
44
 
@@ -8,7 +8,8 @@
8
8
 
9
9
  ## Introduction
10
10
 
11
- Some classes for [RobotCode](https://robotcode.io) plugin management
11
+ Provides a REPL for [RobotCode](https://robotcode.io).
12
+
12
13
 
13
14
  ## Installation
14
15
 
@@ -27,8 +27,7 @@ classifiers = [
27
27
  ]
28
28
  dynamic = ["version"]
29
29
  dependencies = [
30
- "robotcode-jsonrpc2==0.99.0",
31
- "robotcode-runner==0.99.0"
30
+ "robotcode-runner==0.100.0"
32
31
  ]
33
32
 
34
33
  [project.entry-points.robotcode]
@@ -0,0 +1 @@
1
+ __version__ = "0.100.0"
@@ -1,8 +1,8 @@
1
- import sys
1
+ import abc
2
+ from datetime import datetime
2
3
  from pathlib import Path
3
4
  from typing import TYPE_CHECKING, Any, Iterator, List, Optional, Tuple, Union, cast
4
5
 
5
- import click
6
6
  from robot.api import get_model
7
7
  from robot.errors import ExecutionStatus
8
8
  from robot.output import LOGGER
@@ -11,7 +11,7 @@ from robot.running import Keyword, TestCase, TestSuite
11
11
  from robot.running.context import EXECUTION_CONTEXTS
12
12
  from robot.running.signalhandler import _StopSignalMonitor
13
13
 
14
- from robotcode.plugin import Application
14
+ from robotcode.core.utils.path import normalized_path
15
15
  from robotcode.robot.utils import get_robot_version
16
16
  from robotcode.robot.utils.ast import iter_nodes
17
17
 
@@ -46,26 +46,21 @@ else:
46
46
  return kw.run(context)
47
47
 
48
48
 
49
- TRUE_STRINGS = {"TRUE", "YES", "ON", "1"}
50
-
51
-
52
- def is_true(value: Union[str, bool]) -> bool:
53
- if isinstance(value, str):
54
- return value.upper() in TRUE_STRINGS
55
- return bool(value)
56
-
57
-
58
49
  if get_robot_version() < (7, 0):
59
50
 
60
51
  class InterpreterLogger:
61
- def __init__(self, interpreter: "Interpreter") -> None:
52
+ def __init__(self, interpreter: "BaseInterpreter") -> None:
62
53
  self.interpreter = interpreter
63
54
  self.enabled = False
64
55
 
65
56
  def log_message(self, message: OutputMessage) -> None:
57
+ if not self.enabled:
58
+ return
66
59
  self.interpreter.log_message(message.message, message.level, message.html, message.timestamp)
67
60
 
68
61
  def message(self, message: OutputMessage) -> None:
62
+ if not self.enabled:
63
+ return
69
64
  self.interpreter.message(message.message, message.level, message.html, message.timestamp)
70
65
 
71
66
  def start_keyword(self, args: Any) -> None:
@@ -82,14 +77,18 @@ else:
82
77
  import robot.output.loggerapi # pyright: ignore[reportMissingImports]
83
78
 
84
79
  class InterpreterLogger(robot.output.loggerapi.LoggerApi): # type: ignore[no-redef]
85
- def __init__(self, interpreter: "Interpreter") -> None:
80
+ def __init__(self, interpreter: "BaseInterpreter") -> None:
86
81
  self.interpreter = interpreter
87
82
  self.enabled = False
88
83
 
89
84
  def log_message(self, message: OutputMessage) -> None:
85
+ if not self.enabled:
86
+ return
90
87
  self.interpreter.log_message(message.message, message.level, message.html, message.timestamp)
91
88
 
92
89
  def message(self, message: OutputMessage) -> None:
90
+ if not self.enabled:
91
+ return
93
92
  self.interpreter.message(message.message, message.level, message.html, message.timestamp)
94
93
 
95
94
  def start_keyword(self, data: "running.Keyword", result: "result.Keyword") -> None:
@@ -102,28 +101,26 @@ else:
102
101
  return
103
102
  self.interpreter.end_keyword(data, result)
104
103
 
104
+ def start_body_item(self, data: "running.Keyword", result: "result.Keyword") -> None:
105
+ if not self.enabled:
106
+ return
107
+ self.interpreter.start_keyword(data, result)
105
108
 
106
- class Interpreter:
107
- def __init__(
108
- self,
109
- app: Optional[Application],
110
- files: Optional[List[Path]] = None,
111
- show_keywords: bool = False,
112
- inspect: Optional[bool] = False,
113
- ) -> None:
114
- _patch()
109
+ def end_body_item(self, data: "running.Keyword", result: "result.Keyword") -> None:
110
+ if not self.enabled:
111
+ return
112
+ self.interpreter.end_keyword(data, result)
115
113
 
116
- self.app = app
117
- self.files = files
118
- self.show_keywords = show_keywords
119
- self.inspect = inspect
120
114
 
121
- self.executed_files: List[Path] = []
115
+ class BaseInterpreter(abc.ABC):
116
+ def __init__(self) -> None:
117
+ _patch()
122
118
 
123
119
  self._logger = InterpreterLogger(self)
124
120
  LOGGER.register_logger(self._logger)
125
-
121
+ self.last_result: Any = None
126
122
  self.indent = 0
123
+ self.source: Optional[Path] = None
127
124
 
128
125
  def check_for_errors(self, node: Any) -> List[str]:
129
126
  if hasattr(node, "tokens"):
@@ -145,7 +142,9 @@ class Interpreter:
145
142
  + ("\n ".join(command.split("\n")) if "\n" in command else command)
146
143
  ) + "\n"
147
144
 
148
- model = get_model(suite_str)
145
+ curdir = normalized_path(self.source).parent if self.source is not None else Path.cwd()
146
+
147
+ model = get_model(suite_str, curdir=str(curdir).replace("\\", "\\\\"))
149
148
  suite: TestSuite = TestSuite.from_model(model)
150
149
 
151
150
  errors: List[str] = []
@@ -155,80 +154,39 @@ class Interpreter:
155
154
 
156
155
  return cast(TestCase, suite.tests[0]), errors
157
156
 
158
- def get_input(self) -> Iterator[Optional[Keyword]]:
159
- if self.executed_files and not self.files and not self.inspect:
160
- raise EOFError
161
-
162
- if self.files:
163
- file = self.files.pop(0)
164
-
165
- self.executed_files.append(file)
166
-
167
- text = file.read_text(encoding="utf-8")
168
-
169
- test, errors = self.get_test_body_from_string(text)
170
- if errors:
171
- return
172
-
173
- for kw in test.body:
174
- yield kw
175
- else:
176
-
177
- lines: List[str] = []
178
- last_one = False
179
- while True:
180
-
181
- prompt = ""
182
- if sys.stdin.isatty():
183
- prompt = ">>> " if not lines else "... "
184
-
185
- try:
186
- text = input(prompt)
187
- if len(lines) == 0 and text == "":
188
- break
189
- except KeyboardInterrupt:
190
- if len(lines) > 0:
191
- lines = []
192
- last_one = False
193
- continue
194
- raise
195
-
196
- lines.append(text)
197
-
198
- test, errors = self.get_test_body_from_string("\n".join(lines))
199
-
200
- if len(lines) > 1 and lines[-1] == "" and text == "":
201
- last_one = True
202
-
203
- if errors:
204
- if not last_one:
205
- continue
206
-
207
- for kw in test.body:
208
- yield kw
209
-
210
- break
157
+ @abc.abstractmethod
158
+ def get_input(self) -> Iterator[Optional[Keyword]]: ...
211
159
 
212
160
  def run_keyword(self, kw: Keyword) -> Any:
213
161
  self.indent = 0
214
162
  context = EXECUTION_CONTEXTS.current
215
- return _run_keyword(kw, context)
163
+ try:
164
+ return _run_keyword(kw, context)
165
+ except (SystemExit, KeyboardInterrupt):
166
+ raise
167
+ except ExecutionStatus:
168
+ raise
169
+ except BaseException as e:
170
+ self.log_message(str(e), "ERROR", timestamp=datetime.now()) # noqa: DTZ005
216
171
 
217
172
  def run(self) -> Any:
218
173
  self._logger.enabled = True
219
174
 
220
175
  has_input = True
221
- while has_input:
222
- try:
223
- self.run_input()
224
- except EOFError:
225
- break
226
- except (SystemExit, KeyboardInterrupt):
227
- break
228
- except ExecutionStatus:
229
- pass
230
- except BaseException as e:
231
- self.log_message(str(e), "ERROR")
176
+ try:
177
+ while has_input:
178
+ try:
179
+ self.run_input()
180
+ except EOFError:
181
+ break
182
+ except (SystemExit, KeyboardInterrupt):
183
+ break
184
+ except ExecutionStatus:
185
+ pass
186
+ except BaseException as e:
187
+ self.log_message(str(e), "ERROR", timestamp=datetime.now()) # noqa: DTZ005
188
+ finally:
189
+ self._logger.enabled = False
232
190
 
233
191
  def run_input(self) -> None:
234
192
  for kw in self.get_input():
@@ -237,65 +195,29 @@ class Interpreter:
237
195
  self.set_last_result(self.run_keyword(kw))
238
196
 
239
197
  def set_last_result(self, result: Any) -> None:
240
- if result is None:
241
- return
242
- if self.app is not None:
243
- self.app.echo(f"{result}")
198
+ self.last_result = result
244
199
 
200
+ @abc.abstractmethod
245
201
  def log_message(
246
- self, message: str, level: str, html: Union[str, bool] = False, timestamp: Optional[str] = None
247
- ) -> None:
248
- if self.app is None:
249
- return
250
-
251
- if not self.app.config.verbose and level in ["DEBUG", "TRACE"]:
252
- return
253
-
254
- std_err = level in ["ERROR", "FAIL"]
255
-
256
- if level == "INFO":
257
- level = click.style("INFO", fg="green")
258
- elif level == "WARN":
259
- level = click.style("WARN", fg="yellow")
260
- elif level == "ERROR":
261
- level = click.style("ERROR", fg="red")
262
- elif level == "FAIL":
263
- level = click.style("FAIL", fg="red", bold=True)
264
- elif level == "SKIP":
265
- level = click.style("SKIP", dim=True)
266
- elif level == "DEBUG":
267
- level = click.style("DEBUG", fg="bright_black")
268
- elif level == "TRACE":
269
- level = click.style("TRACE", fg="bright_black", dim=True)
270
-
271
- if is_true(html):
272
- message = f"*HTML*{message}"
273
-
274
- self.app.echo(f"{' '*self.indent}[ {level} ] {message}", file=sys.__stdout__ if std_err else sys.__stderr__)
202
+ self, message: str, level: str, html: Union[str, bool] = False, timestamp: Union[datetime, str, None] = None
203
+ ) -> None: ...
275
204
 
205
+ @abc.abstractmethod
276
206
  def message(
277
- self, message: str, level: str, html: Union[str, bool] = False, timestamp: Optional[str] = None
278
- ) -> None:
279
- if self.app is not None and self.app.config.verbose:
280
- self.log_message(message, level, html, timestamp)
207
+ self, message: str, level: str, html: Union[str, bool] = False, timestamp: Union[datetime, str, None] = None
208
+ ) -> None: ...
281
209
 
282
210
  def start_keyword(self, data: "running.Keyword", result: "result.Keyword") -> None:
283
- if not self.show_keywords:
284
- return
211
+ pass
285
212
 
286
- if self.app is None:
287
- return
213
+ def end_keyword(self, data: "running.Keyword", result: "result.Keyword") -> None:
214
+ pass
288
215
 
289
- self.app.echo(
290
- f"{' '*self.indent}KEYWORD {result.libname}.{result.kwname} {' '.join(result.args)}", file=sys.__stdout__
291
- )
292
- self.indent += 1
293
216
 
294
- def end_keyword(self, data: "running.Keyword", result: "result.Keyword") -> None:
295
- if not self.show_keywords:
296
- return
217
+ TRUE_STRINGS = {"TRUE", "YES", "ON", "1"}
297
218
 
298
- if self.app is None:
299
- return
300
219
 
301
- self.indent -= 1
220
+ def is_true(value: Union[str, bool]) -> bool:
221
+ if isinstance(value, str):
222
+ return value.upper() in TRUE_STRINGS
223
+ return bool(value)
@@ -0,0 +1,141 @@
1
+ from pathlib import Path
2
+ from typing import Optional, Tuple
3
+
4
+ import click
5
+
6
+ from robotcode.plugin import Application, pass_application
7
+
8
+ from .__version__ import __version__
9
+ from .console_interpreter import ConsoleInterpreter
10
+ from .run import run_repl
11
+
12
+
13
+ @click.command(add_help_option=True)
14
+ @click.option(
15
+ "-v",
16
+ "--variable",
17
+ metavar="name:value",
18
+ type=str,
19
+ multiple=True,
20
+ help="Set variables in the test data. see `robot --variable` option.",
21
+ )
22
+ @click.option(
23
+ "-V",
24
+ "--variablefile",
25
+ metavar="PATH",
26
+ type=str,
27
+ multiple=True,
28
+ help="Python or YAML file file to read variables from. see `robot --variablefile` option.",
29
+ )
30
+ @click.option(
31
+ "-P",
32
+ "--pythonpath",
33
+ metavar="PATH",
34
+ type=str,
35
+ multiple=True,
36
+ help="Additional locations where to search test libraries"
37
+ " and other extensions when they are imported. see `robot --pythonpath` option.",
38
+ )
39
+ @click.option(
40
+ "-k",
41
+ "--show-keywords",
42
+ is_flag=True,
43
+ default=False,
44
+ help="Executed keywords will be shown in the output.",
45
+ )
46
+ @click.option(
47
+ "-i",
48
+ "--inspect",
49
+ is_flag=True,
50
+ default=False,
51
+ help="Activate inspection mode. This forces a prompt to appear after the REPL script is executed.",
52
+ )
53
+ @click.option(
54
+ "-d",
55
+ "--outputdir",
56
+ metavar="DIR",
57
+ type=str,
58
+ help="Where to create output files. see `robot --outputdir` option.",
59
+ )
60
+ @click.option(
61
+ "-o",
62
+ "--output",
63
+ metavar="FILE",
64
+ type=str,
65
+ help="XML output file. see `robot --output` option.",
66
+ )
67
+ @click.option(
68
+ "-r",
69
+ "--report",
70
+ metavar="FILE",
71
+ type=str,
72
+ help="HTML output file. see `robot --report` option.",
73
+ )
74
+ @click.option(
75
+ "-l",
76
+ "--log",
77
+ metavar="FILE",
78
+ type=str,
79
+ help="HTML log file. see `robot --log` option.",
80
+ )
81
+ @click.option(
82
+ "-x",
83
+ "--xunit",
84
+ metavar="FILE",
85
+ type=str,
86
+ help="xUnit output file. see `robot --xunit` option.",
87
+ )
88
+ @click.option(
89
+ "-s",
90
+ "--source",
91
+ type=click.Path(path_type=Path),
92
+ metavar="FILE",
93
+ help="Specifies the path to a source file. This file must not exist and will neither be read nor written. "
94
+ "It is used solely to set the current working directory for the REPL script "
95
+ "and to assign a name to the internal suite.",
96
+ )
97
+ @click.version_option(version=__version__, prog_name="RobotCode REPL")
98
+ @click.argument(
99
+ "files",
100
+ type=click.Path(exists=True, dir_okay=False, path_type=Path),
101
+ nargs=-1,
102
+ required=False,
103
+ )
104
+ @pass_application
105
+ def repl(
106
+ app: Application,
107
+ variable: Tuple[str, ...],
108
+ variablefile: Tuple[str, ...],
109
+ pythonpath: Tuple[str, ...],
110
+ show_keywords: bool,
111
+ inspect: bool,
112
+ outputdir: Optional[str],
113
+ output: Optional[str],
114
+ report: Optional[str],
115
+ log: Optional[str],
116
+ xunit: Optional[str],
117
+ source: Optional[Path],
118
+ files: Tuple[Path, ...],
119
+ ) -> None:
120
+ """\
121
+ Run Robot Framework interactively.
122
+ """
123
+ if files:
124
+ files = tuple(f.absolute() for f in files)
125
+
126
+ interpreter = ConsoleInterpreter(app, files=list(files), show_keywords=show_keywords, inspect=inspect)
127
+
128
+ run_repl(
129
+ interpreter=interpreter,
130
+ app=app,
131
+ variablefile=variable,
132
+ variable=variablefile,
133
+ pythonpath=pythonpath,
134
+ outputdir=outputdir,
135
+ output=output,
136
+ report=report,
137
+ log=log,
138
+ xunit=xunit,
139
+ source=source,
140
+ files=files,
141
+ )
@@ -0,0 +1,140 @@
1
+ import sys
2
+ from datetime import datetime
3
+ from pathlib import Path
4
+ from typing import Iterator, List, Optional, Union
5
+
6
+ import click
7
+ from robot import result, running
8
+ from robot.running import Keyword
9
+
10
+ from robotcode.plugin import Application
11
+
12
+ from .base_interpreter import BaseInterpreter, is_true
13
+
14
+
15
+ class ConsoleInterpreter(BaseInterpreter):
16
+ def __init__(
17
+ self,
18
+ app: Optional[Application],
19
+ files: Optional[List[Path]] = None,
20
+ show_keywords: bool = False,
21
+ inspect: Optional[bool] = False,
22
+ ) -> None:
23
+ super().__init__()
24
+
25
+ self.app = app
26
+ self.files = files
27
+ self.show_keywords = show_keywords
28
+ self.inspect = inspect
29
+
30
+ self.executed_files: List[Path] = []
31
+
32
+ def get_input(self) -> Iterator[Optional[Keyword]]:
33
+ if self.executed_files and not self.files and not self.inspect:
34
+ raise EOFError
35
+
36
+ if self.files:
37
+ file = self.files.pop(0)
38
+
39
+ self.executed_files.append(file)
40
+
41
+ text = file.read_text(encoding="utf-8")
42
+
43
+ test, errors = self.get_test_body_from_string(text)
44
+ if errors:
45
+ return
46
+
47
+ for kw in test.body:
48
+ yield kw
49
+ else:
50
+ lines: List[str] = []
51
+ last_one = False
52
+ while True:
53
+ prompt = ""
54
+ if sys.stdin.isatty():
55
+ prompt = ">>> " if not lines else "... "
56
+
57
+ try:
58
+ text = input(prompt)
59
+ if len(lines) == 0 and text == "":
60
+ break
61
+ except KeyboardInterrupt:
62
+ if len(lines) > 0:
63
+ lines = []
64
+ last_one = False
65
+ continue
66
+ raise
67
+
68
+ lines.append(text)
69
+
70
+ test, errors = self.get_test_body_from_string("\n".join(lines))
71
+
72
+ if len(lines) > 1 and lines[-1] == "" and text == "":
73
+ last_one = True
74
+
75
+ if errors:
76
+ if not last_one:
77
+ continue
78
+
79
+ for kw in test.body:
80
+ yield kw
81
+
82
+ break
83
+
84
+ def log_message(
85
+ self, message: str, level: str, html: Union[str, bool] = False, timestamp: Union[datetime, str, None] = None
86
+ ) -> None:
87
+ if self.app is None:
88
+ return
89
+
90
+ if not self.app.config.verbose and level in ["DEBUG", "TRACE"]:
91
+ return
92
+
93
+ std_err = level in ["ERROR", "FAIL"]
94
+
95
+ if level == "INFO":
96
+ level = click.style("INFO", fg="green")
97
+ elif level == "WARN":
98
+ level = click.style("WARN", fg="yellow")
99
+ elif level == "ERROR":
100
+ level = click.style("ERROR", fg="red")
101
+ elif level == "FAIL":
102
+ level = click.style("FAIL", fg="red", bold=True)
103
+ elif level == "SKIP":
104
+ level = click.style("SKIP", dim=True)
105
+ elif level == "DEBUG":
106
+ level = click.style("DEBUG", fg="bright_black")
107
+ elif level == "TRACE":
108
+ level = click.style("TRACE", fg="bright_black", dim=True)
109
+
110
+ if is_true(html):
111
+ message = f"*HTML*{message}"
112
+
113
+ self.app.echo(f"{' '*self.indent}[ {level} ] {message}", file=sys.__stdout__ if std_err else sys.__stderr__)
114
+
115
+ def message(
116
+ self, message: str, level: str, html: Union[str, bool] = False, timestamp: Union[datetime, str, None] = None
117
+ ) -> None:
118
+ if self.app is not None and self.app.config.verbose:
119
+ self.log_message(message, level, html, timestamp)
120
+
121
+ def start_keyword(self, data: "running.Keyword", result: "result.Keyword") -> None:
122
+ if not self.show_keywords:
123
+ return
124
+
125
+ if self.app is None:
126
+ return
127
+
128
+ self.app.echo(
129
+ f"{' '*self.indent}KEYWORD {result.libname}.{result.kwname} {' '.join(result.args)}", file=sys.__stdout__
130
+ )
131
+ self.indent += 1
132
+
133
+ def end_keyword(self, data: "running.Keyword", result: "result.Keyword") -> None:
134
+ if not self.show_keywords:
135
+ return
136
+
137
+ if self.app is None:
138
+ return
139
+
140
+ self.indent -= 1
@@ -0,0 +1,133 @@
1
+ import io
2
+ import sys
3
+ from pathlib import Path
4
+ from typing import Any, ClassVar, Dict, Optional, Tuple
5
+
6
+ from robot.api import TestSuite, get_model
7
+ from robot.conf import RobotSettings
8
+ from robot.errors import DATA_ERROR, INFO_PRINTED, DataError, Information
9
+ from robot.output import LOGGER
10
+ from robot.reporting import ResultWriter
11
+
12
+ from robotcode.core.utils.path import normalized_path
13
+ from robotcode.plugin import (
14
+ Application,
15
+ )
16
+ from robotcode.robot.utils import get_robot_version
17
+ from robotcode.runner.cli.robot import RobotFrameworkEx, handle_robot_options
18
+
19
+ from .base_interpreter import BaseInterpreter
20
+
21
+ REPL_SUITE = """\
22
+ *** Settings ***
23
+ Library robotcode.repl.Repl
24
+ *** Test Cases ***
25
+ RobotCode REPL
26
+ repl
27
+ """
28
+
29
+
30
+ class ReplListener:
31
+ ROBOT_LISTENER_API_VERSION = 2
32
+ instance: ClassVar["ReplListener"]
33
+
34
+ def __init__(self, app: Application, interpreter: BaseInterpreter) -> None:
35
+ ReplListener.instance = self
36
+ self.app = app
37
+ self.interpreter = interpreter
38
+
39
+ def start_keyword(
40
+ self,
41
+ name: str,
42
+ attributes: Dict[str, Any],
43
+ ) -> None:
44
+ if name != "robotcode.repl.Repl.Repl":
45
+ return
46
+
47
+ self.interpreter.run()
48
+
49
+
50
+ def run_repl(
51
+ interpreter: BaseInterpreter,
52
+ app: Application,
53
+ variable: Tuple[str, ...] = (),
54
+ variablefile: Tuple[str, ...] = (),
55
+ pythonpath: Tuple[str, ...] = (),
56
+ outputdir: Optional[str] = None,
57
+ output: Optional[str] = None,
58
+ report: Optional[str] = None,
59
+ log: Optional[str] = None,
60
+ xunit: Optional[str] = None,
61
+ source: Optional[Path] = None,
62
+ files: Tuple[Path, ...] = (),
63
+ ) -> None:
64
+ robot_options_and_args: Tuple[str, ...] = ()
65
+
66
+ if files:
67
+ files = tuple(f.absolute() for f in files)
68
+
69
+ for var in variable:
70
+ robot_options_and_args += ("--variable", var)
71
+ for varfile in variablefile:
72
+ robot_options_and_args += ("--variablefile", varfile)
73
+ for pypath in pythonpath:
74
+ robot_options_and_args += ("--pythonpath", pypath)
75
+ if outputdir:
76
+ robot_options_and_args += ("--outputdir", outputdir)
77
+
78
+ root_folder, _profile, cmd_options = handle_robot_options(app, (*robot_options_and_args, *(str(f) for f in files)))
79
+
80
+ with app.chdir(root_folder) as orig_folder:
81
+ try:
82
+ curdir = normalized_path(source).parent if source is not None else Path.cwd()
83
+
84
+ options, _ = RobotFrameworkEx(
85
+ app,
86
+ ["."],
87
+ app.config.dry,
88
+ root_folder=root_folder,
89
+ orig_folder=orig_folder,
90
+ ).parse_arguments((*cmd_options, *robot_options_and_args))
91
+
92
+ interpreter.source = source
93
+
94
+ settings = RobotSettings(
95
+ options,
96
+ outputdir=str(curdir),
97
+ console="NONE",
98
+ output=output,
99
+ log=log,
100
+ report=report,
101
+ xunit=xunit,
102
+ quiet=True,
103
+ listener=[
104
+ ReplListener(app, interpreter),
105
+ ],
106
+ )
107
+
108
+ if app is not None and app.show_diagnostics:
109
+ LOGGER.register_console_logger(**settings.console_output_config)
110
+ else:
111
+ LOGGER.unregister_console_logger()
112
+
113
+ if get_robot_version() >= (5, 0):
114
+ if settings.pythonpath:
115
+ sys.path = settings.pythonpath + sys.path
116
+
117
+ with io.StringIO(REPL_SUITE) as suite_io:
118
+ model = get_model(suite_io, curdir=str(curdir).replace("\\", "\\\\"))
119
+
120
+ suite = TestSuite.from_model(model)
121
+ suite.configure(**settings.suite_config)
122
+ result = suite.run(settings)
123
+
124
+ if settings.log or settings.report or settings.xunit:
125
+ writer = ResultWriter(settings.output if settings.log else result)
126
+ writer.write_results(settings.get_rebot_settings())
127
+
128
+ except Information as err:
129
+ app.echo(str(err))
130
+ app.exit(INFO_PRINTED)
131
+ except DataError as err:
132
+ app.error(str(err))
133
+ app.exit(DATA_ERROR)
@@ -1 +0,0 @@
1
- __version__ = "0.99.0"
@@ -1,3 +0,0 @@
1
- from .repl import repl
2
-
3
- __all__ = ["repl"]
@@ -1,232 +0,0 @@
1
- import io
2
- import sys
3
- from pathlib import Path
4
- from typing import Optional, Tuple
5
-
6
- import click
7
- from robot.api import TestSuite, get_model
8
- from robot.conf import RobotSettings
9
- from robot.errors import DATA_ERROR, INFO_PRINTED, DataError, Information
10
- from robot.output import LOGGER
11
- from robot.reporting import ResultWriter
12
-
13
- from robotcode.plugin import (
14
- Application,
15
- pass_application,
16
- )
17
- from robotcode.robot.utils import get_robot_version
18
- from robotcode.runner.cli.robot import RobotFrameworkEx, handle_robot_options
19
-
20
- from ..interpreter import Interpreter
21
- from ..repl_listener import ReplListener
22
-
23
- REPL_SUITE = """\
24
- *** Settings ***
25
- Library robotcode.repl.Repl
26
- *** Test Cases ***
27
- RobotCode REPL
28
- repl
29
- """
30
-
31
-
32
- def run_repl(
33
- app: Application,
34
- inspect: bool = False,
35
- variable: Tuple[str, ...] = (),
36
- variablefile: Tuple[str, ...] = (),
37
- pythonpath: Tuple[str, ...] = (),
38
- outputdir: Optional[str] = None,
39
- output: Optional[str] = None,
40
- report: Optional[str] = None,
41
- log: Optional[str] = None,
42
- xunit: Optional[str] = None,
43
- files: Tuple[Path, ...] = (),
44
- show_keywords: bool = False,
45
- interpreter: Optional[Interpreter] = None,
46
- ) -> None:
47
- robot_options_and_args: Tuple[str, ...] = ()
48
-
49
- if files:
50
- files = tuple(f.absolute() for f in files)
51
-
52
- for var in variable:
53
- robot_options_and_args += ("--variable", var)
54
- for varfile in variablefile:
55
- robot_options_and_args += ("--variablefile", varfile)
56
- for pypath in pythonpath:
57
- robot_options_and_args += ("--pythonpath", pypath)
58
- if outputdir:
59
- robot_options_and_args += ("--outputdir", outputdir)
60
-
61
- root_folder, _profile, cmd_options = handle_robot_options(app, (*robot_options_and_args, *(str(f) for f in files)))
62
-
63
- try:
64
-
65
- options, _ = RobotFrameworkEx(
66
- app,
67
- ["."],
68
- app.config.dry,
69
- root_folder,
70
- ).parse_arguments((*cmd_options, *robot_options_and_args))
71
-
72
- if interpreter is None:
73
- interpreter = Interpreter(app, files=list(files), show_keywords=show_keywords, inspect=inspect)
74
-
75
- settings = RobotSettings(
76
- options,
77
- console="NONE",
78
- output=output,
79
- log=log,
80
- report=report,
81
- xunit=xunit,
82
- quiet=True,
83
- listener=[
84
- ReplListener(app, interpreter),
85
- ],
86
- )
87
-
88
- if app is not None and app.show_diagnostics:
89
- LOGGER.register_console_logger(**settings.console_output_config)
90
- else:
91
- LOGGER.unregister_console_logger()
92
-
93
- if get_robot_version() >= (5, 0):
94
- if settings.pythonpath:
95
- sys.path = settings.pythonpath + sys.path
96
-
97
- with io.StringIO(REPL_SUITE) as suite_io:
98
- model = get_model(suite_io)
99
-
100
- suite = TestSuite.from_model(model)
101
- suite.configure(**settings.suite_config)
102
- result = suite.run(settings)
103
-
104
- if settings.log or settings.report or settings.xunit:
105
- writer = ResultWriter(settings.output if settings.log else result)
106
- writer.write_results(settings.get_rebot_settings())
107
-
108
- except Information as err:
109
- app.echo(str(err))
110
- app.exit(INFO_PRINTED)
111
- except DataError as err:
112
- app.error(str(err))
113
- app.exit(DATA_ERROR)
114
-
115
-
116
- @click.command(
117
- context_settings={"allow_extra_args": True, "ignore_unknown_options": True},
118
- add_help_option=True,
119
- )
120
- @click.option(
121
- "-v",
122
- "--variable",
123
- metavar="name:value",
124
- type=str,
125
- multiple=True,
126
- help="Set variables in the test data. see `robot --variable` option.",
127
- )
128
- @click.option(
129
- "-V",
130
- "--variablefile",
131
- metavar="PATH",
132
- type=str,
133
- multiple=True,
134
- help="Python or YAML file file to read variables from. see `robot --variablefile` option.",
135
- )
136
- @click.option(
137
- "-P",
138
- "--pythonpath",
139
- metavar="PATH",
140
- type=str,
141
- multiple=True,
142
- help="Additional locations where to search test libraries"
143
- " and other extensions when they are imported. see `robot --pythonpath` option.",
144
- )
145
- @click.option(
146
- "-k",
147
- "--show-keywords",
148
- is_flag=True,
149
- default=False,
150
- help="Executed keywords will be shown in the output.",
151
- )
152
- @click.option(
153
- "-i",
154
- "--inspect",
155
- is_flag=True,
156
- default=False,
157
- help="Activate inspection mode. This forces a prompt to appear after the REPL script is executed.",
158
- )
159
- @click.option(
160
- "-d",
161
- "--outputdir",
162
- metavar="DIR",
163
- type=str,
164
- help="Where to create output files. see `robot --outputdir` option.",
165
- )
166
- @click.option(
167
- "-o",
168
- "--output",
169
- metavar="FILE",
170
- type=str,
171
- help="XML output file. see `robot --output` option.",
172
- )
173
- @click.option(
174
- "-r",
175
- "--report",
176
- metavar="FILE",
177
- type=str,
178
- help="HTML output file. see `robot --report` option.",
179
- )
180
- @click.option(
181
- "-l",
182
- "--log",
183
- metavar="FILE",
184
- type=str,
185
- help="HTML log file. see `robot --log` option.",
186
- )
187
- @click.option(
188
- "-x",
189
- "--xunit",
190
- metavar="FILE",
191
- type=str,
192
- help="xUnit output file. see `robot --xunit` option.",
193
- )
194
- @click.argument(
195
- "files",
196
- type=click.Path(exists=True, dir_okay=False, path_type=Path),
197
- nargs=-1,
198
- required=False,
199
- )
200
- @pass_application
201
- def repl(
202
- app: Application,
203
- variable: Tuple[str, ...],
204
- variablefile: Tuple[str, ...],
205
- pythonpath: Tuple[str, ...],
206
- show_keywords: bool,
207
- inspect: bool,
208
- outputdir: Optional[str],
209
- output: Optional[str],
210
- report: Optional[str],
211
- log: Optional[str],
212
- xunit: Optional[str],
213
- files: Tuple[Path, ...],
214
- ) -> None:
215
- """\
216
- Run Robot Framework interactively.
217
- """
218
-
219
- run_repl(
220
- app=app,
221
- inspect=inspect,
222
- variablefile=variable,
223
- variable=variablefile,
224
- pythonpath=pythonpath,
225
- outputdir=outputdir,
226
- output=output,
227
- report=report,
228
- log=log,
229
- xunit=xunit,
230
- files=files,
231
- show_keywords=show_keywords,
232
- )
@@ -1,25 +0,0 @@
1
- from typing import Any, ClassVar, Dict, Optional
2
-
3
- from robotcode.plugin import Application
4
-
5
- from .interpreter import Interpreter
6
-
7
-
8
- class ReplListener:
9
- ROBOT_LISTENER_API_VERSION = 2
10
- instance: ClassVar["ReplListener"]
11
-
12
- def __init__(self, app: Application, interpreter: Optional[Interpreter] = None) -> None:
13
- ReplListener.instance = self
14
- self.app = app
15
- self.interpreter = interpreter or Interpreter(app)
16
-
17
- def start_keyword(
18
- self,
19
- name: str,
20
- attributes: Dict[str, Any],
21
- ) -> None:
22
- if name != "robotcode.repl.Repl.Repl":
23
- return
24
-
25
- self.interpreter.run()