repl-session 0.2.0__tar.gz → 0.2.1__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.
- {repl_session-0.2.0 → repl_session-0.2.1}/PKG-INFO +19 -9
- {repl_session-0.2.0 → repl_session-0.2.1}/README.md +17 -7
- {repl_session-0.2.0 → repl_session-0.2.1}/pyproject.toml +4 -2
- {repl_session-0.2.0 → repl_session-0.2.1}/src/repl_session/__init__.py +55 -58
- {repl_session-0.2.0 → repl_session-0.2.1}/src/repl_session.egg-info/PKG-INFO +19 -9
- {repl_session-0.2.0 → repl_session-0.2.1}/src/repl_session.egg-info/SOURCES.txt +2 -1
- {repl_session-0.2.0 → repl_session-0.2.1}/src/repl_session.egg-info/requires.txt +1 -1
- repl_session-0.2.1/test/test_examples.py +40 -0
- {repl_session-0.2.0 → repl_session-0.2.1}/LICENSE +0 -0
- {repl_session-0.2.0 → repl_session-0.2.1}/setup.cfg +0 -0
- {repl_session-0.2.0 → repl_session-0.2.1}/src/repl_session/py.typed +0 -0
- {repl_session-0.2.0 → repl_session-0.2.1}/src/repl_session.egg-info/dependency_links.txt +0 -0
- {repl_session-0.2.0 → repl_session-0.2.1}/src/repl_session.egg-info/entry_points.txt +0 -0
- {repl_session-0.2.0 → repl_session-0.2.1}/src/repl_session.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: repl-session
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: Runs a session with a Read-Eval-Print-Loop (REPL). Takes YAML as input gives JSON as output.
|
|
5
5
|
Classifier: Development Status :: 3 - Alpha
|
|
6
6
|
Classifier: Environment :: Console
|
|
@@ -11,7 +11,7 @@ Classifier: Topic :: Utilities
|
|
|
11
11
|
Requires-Python: >=3.11
|
|
12
12
|
Description-Content-Type: text/markdown
|
|
13
13
|
License-File: LICENSE
|
|
14
|
-
Requires-Dist:
|
|
14
|
+
Requires-Dist: click>=8.1
|
|
15
15
|
Requires-Dist: msgspec>=0.19.0
|
|
16
16
|
Requires-Dist: pexpect>=4.9.0
|
|
17
17
|
Dynamic: license-file
|
|
@@ -64,7 +64,7 @@ config:
|
|
|
64
64
|
first_prompt: "> "
|
|
65
65
|
change_prompt: '(waiter-prompt-string "{key}>")'
|
|
66
66
|
prompt: "{key}> "
|
|
67
|
-
continuation_prompt: ""
|
|
67
|
+
# continuation_prompt: ""
|
|
68
68
|
commands:
|
|
69
69
|
- command: (* 6 7)
|
|
70
70
|
- command: |
|
|
@@ -75,7 +75,7 @@ commands:
|
|
|
75
75
|
- command: (fac 10 1)
|
|
76
76
|
```
|
|
77
77
|
|
|
78
|
-
Passing this to `repl-session`, it will start the Chez Scheme interpreter, waiting for the `>` prompt to appear. It then changes the prompt to a generated `uuid4` code, for instance `27e87a8a-742c-4501-b05d-b05814f5a010> `. This will make sure that we can't accidentally match something else for an interactive prompt (imagine we're generating some XML!).
|
|
78
|
+
Passing this to `repl-session`, it will start the Chez Scheme interpreter, waiting for the `>` prompt to appear. It then changes the prompt to a generated `uuid4` code, for instance `27e87a8a-742c-4501-b05d-b05814f5a010> `. This will make sure that we can't accidentally match something else for an interactive prompt (imagine we're generating some XML!). Running this should give:
|
|
79
79
|
|
|
80
80
|
```bash
|
|
81
81
|
repl-session < test/scheme.yml | jq '.commands.[].output'
|
|
@@ -83,7 +83,7 @@ repl-session < test/scheme.yml | jq '.commands.[].output'
|
|
|
83
83
|
|
|
84
84
|
```
|
|
85
85
|
"42"
|
|
86
|
-
|
|
86
|
+
null
|
|
87
87
|
"3628800"
|
|
88
88
|
```
|
|
89
89
|
|
|
@@ -126,6 +126,8 @@ repl-session < test/lua.yml | jq '.commands.[].output'
|
|
|
126
126
|
```
|
|
127
127
|
"42"
|
|
128
128
|
"Hello, World!"
|
|
129
|
+
null
|
|
130
|
+
"3628800"
|
|
129
131
|
```
|
|
130
132
|
|
|
131
133
|
### Python
|
|
@@ -142,6 +144,7 @@ config:
|
|
|
142
144
|
continuation_prompt: "{key}\\+\\+\\+ "
|
|
143
145
|
environment:
|
|
144
146
|
NO_COLOR: "1"
|
|
147
|
+
PYTHON_BASIC_REPL: "1"
|
|
145
148
|
commands:
|
|
146
149
|
- command: print("Hello, World!")
|
|
147
150
|
- command: 6 * 7
|
|
@@ -153,6 +156,17 @@ commands:
|
|
|
153
156
|
- command: fac(10)
|
|
154
157
|
```
|
|
155
158
|
|
|
159
|
+
```bash
|
|
160
|
+
repl-session < test/python.yml | jq '.commands.[].output'
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
```
|
|
164
|
+
"Hello, World!"
|
|
165
|
+
"42"
|
|
166
|
+
null
|
|
167
|
+
"3628800"
|
|
168
|
+
```
|
|
169
|
+
|
|
156
170
|
## Input/Output structure
|
|
157
171
|
|
|
158
172
|
The user can configure how the REPL is called and interpreted.
|
|
@@ -183,8 +197,6 @@ class ReplConfig(msgspec.Struct):
|
|
|
183
197
|
strip_ansi: bool = False
|
|
184
198
|
environment: dict[str, str] = msgspec.field(default_factory=dict)
|
|
185
199
|
timeout: float = 5.0
|
|
186
|
-
|
|
187
|
-
|
|
188
200
|
```
|
|
189
201
|
|
|
190
202
|
Then, a session is a list of commands. Each command should be a UTF-8 string, and we allow to attach some meta-data like expected MIME type for the output. We can also pass an expected output in the case of a documentation test. If `output` was already given on the input, it is moved to `expected`. This way it becomes really easy to setup regression tests on your documentation. Just rerun on the generated output file.
|
|
@@ -217,8 +229,6 @@ class ReplSession(msgspec.Struct):
|
|
|
217
229
|
|
|
218
230
|
config: ReplConfig
|
|
219
231
|
commands: list[ReplCommand]
|
|
220
|
-
|
|
221
|
-
|
|
222
232
|
```
|
|
223
233
|
|
|
224
234
|
## License and contribution
|
|
@@ -46,7 +46,7 @@ config:
|
|
|
46
46
|
first_prompt: "> "
|
|
47
47
|
change_prompt: '(waiter-prompt-string "{key}>")'
|
|
48
48
|
prompt: "{key}> "
|
|
49
|
-
continuation_prompt: ""
|
|
49
|
+
# continuation_prompt: ""
|
|
50
50
|
commands:
|
|
51
51
|
- command: (* 6 7)
|
|
52
52
|
- command: |
|
|
@@ -57,7 +57,7 @@ commands:
|
|
|
57
57
|
- command: (fac 10 1)
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
-
Passing this to `repl-session`, it will start the Chez Scheme interpreter, waiting for the `>` prompt to appear. It then changes the prompt to a generated `uuid4` code, for instance `27e87a8a-742c-4501-b05d-b05814f5a010> `. This will make sure that we can't accidentally match something else for an interactive prompt (imagine we're generating some XML!).
|
|
60
|
+
Passing this to `repl-session`, it will start the Chez Scheme interpreter, waiting for the `>` prompt to appear. It then changes the prompt to a generated `uuid4` code, for instance `27e87a8a-742c-4501-b05d-b05814f5a010> `. This will make sure that we can't accidentally match something else for an interactive prompt (imagine we're generating some XML!). Running this should give:
|
|
61
61
|
|
|
62
62
|
```bash
|
|
63
63
|
repl-session < test/scheme.yml | jq '.commands.[].output'
|
|
@@ -65,7 +65,7 @@ repl-session < test/scheme.yml | jq '.commands.[].output'
|
|
|
65
65
|
|
|
66
66
|
```
|
|
67
67
|
"42"
|
|
68
|
-
|
|
68
|
+
null
|
|
69
69
|
"3628800"
|
|
70
70
|
```
|
|
71
71
|
|
|
@@ -108,6 +108,8 @@ repl-session < test/lua.yml | jq '.commands.[].output'
|
|
|
108
108
|
```
|
|
109
109
|
"42"
|
|
110
110
|
"Hello, World!"
|
|
111
|
+
null
|
|
112
|
+
"3628800"
|
|
111
113
|
```
|
|
112
114
|
|
|
113
115
|
### Python
|
|
@@ -124,6 +126,7 @@ config:
|
|
|
124
126
|
continuation_prompt: "{key}\\+\\+\\+ "
|
|
125
127
|
environment:
|
|
126
128
|
NO_COLOR: "1"
|
|
129
|
+
PYTHON_BASIC_REPL: "1"
|
|
127
130
|
commands:
|
|
128
131
|
- command: print("Hello, World!")
|
|
129
132
|
- command: 6 * 7
|
|
@@ -135,6 +138,17 @@ commands:
|
|
|
135
138
|
- command: fac(10)
|
|
136
139
|
```
|
|
137
140
|
|
|
141
|
+
```bash
|
|
142
|
+
repl-session < test/python.yml | jq '.commands.[].output'
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
```
|
|
146
|
+
"Hello, World!"
|
|
147
|
+
"42"
|
|
148
|
+
null
|
|
149
|
+
"3628800"
|
|
150
|
+
```
|
|
151
|
+
|
|
138
152
|
## Input/Output structure
|
|
139
153
|
|
|
140
154
|
The user can configure how the REPL is called and interpreted.
|
|
@@ -165,8 +179,6 @@ class ReplConfig(msgspec.Struct):
|
|
|
165
179
|
strip_ansi: bool = False
|
|
166
180
|
environment: dict[str, str] = msgspec.field(default_factory=dict)
|
|
167
181
|
timeout: float = 5.0
|
|
168
|
-
|
|
169
|
-
|
|
170
182
|
```
|
|
171
183
|
|
|
172
184
|
Then, a session is a list of commands. Each command should be a UTF-8 string, and we allow to attach some meta-data like expected MIME type for the output. We can also pass an expected output in the case of a documentation test. If `output` was already given on the input, it is moved to `expected`. This way it becomes really easy to setup regression tests on your documentation. Just rerun on the generated output file.
|
|
@@ -199,8 +211,6 @@ class ReplSession(msgspec.Struct):
|
|
|
199
211
|
|
|
200
212
|
config: ReplConfig
|
|
201
213
|
commands: list[ReplCommand]
|
|
202
|
-
|
|
203
|
-
|
|
204
214
|
```
|
|
205
215
|
|
|
206
216
|
## License and contribution
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "repl-session"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.1"
|
|
4
4
|
description = "Runs a session with a Read-Eval-Print-Loop (REPL). Takes YAML as input gives JSON as output."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
classifiers = [
|
|
@@ -13,7 +13,7 @@ classifiers = [
|
|
|
13
13
|
]
|
|
14
14
|
requires-python = ">=3.11"
|
|
15
15
|
dependencies = [
|
|
16
|
-
"
|
|
16
|
+
"click>=8.1",
|
|
17
17
|
"msgspec>=0.19.0",
|
|
18
18
|
"pexpect>=4.9.0",
|
|
19
19
|
]
|
|
@@ -24,9 +24,11 @@ dev = [
|
|
|
24
24
|
"entangled-cli>=2.1.13",
|
|
25
25
|
"mkdocs-macros-plugin>=1.3.9",
|
|
26
26
|
"mkdocs-material>=9.6.20",
|
|
27
|
+
"pytest>=9.2.0",
|
|
27
28
|
"python-lsp-server>=1.13.1",
|
|
28
29
|
"ruff>=0.13.2",
|
|
29
30
|
"ty>=0.0.1a21",
|
|
31
|
+
"types-pexpect>=4.9.0.20250916",
|
|
30
32
|
]
|
|
31
33
|
|
|
32
34
|
[project.scripts]
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
# ~/~ begin <<docs/index.md#src/repl_session/__init__.py>>[init]
|
|
2
|
-
#| file: src/repl_session/__init__.py
|
|
3
2
|
"""
|
|
4
3
|
`repl-session` is a command-line tool to evaluate a given session
|
|
5
4
|
in any REPL, and store the results.
|
|
6
5
|
"""
|
|
7
6
|
|
|
8
7
|
# ~/~ begin <<docs/index.md#imports>>[init]
|
|
9
|
-
#| id: imports
|
|
10
8
|
# from datetime import datetime, tzinfo
|
|
11
9
|
from typing import IO, cast
|
|
12
10
|
from collections.abc import Generator, Callable
|
|
@@ -17,19 +15,17 @@ import uuid
|
|
|
17
15
|
import sys
|
|
18
16
|
import re
|
|
19
17
|
import logging
|
|
18
|
+
import os
|
|
20
19
|
|
|
21
20
|
import pexpect
|
|
22
21
|
import msgspec
|
|
23
|
-
import
|
|
22
|
+
import click
|
|
24
23
|
import importlib.metadata
|
|
25
24
|
|
|
26
25
|
|
|
27
26
|
__version__ = importlib.metadata.version("repl-session")
|
|
28
27
|
# ~/~ end
|
|
29
|
-
|
|
30
|
-
|
|
31
28
|
# ~/~ begin <<README.md#input-data>>[init]
|
|
32
|
-
#| id: input-data
|
|
33
29
|
class ReplConfig(msgspec.Struct):
|
|
34
30
|
"""Configuration
|
|
35
31
|
|
|
@@ -54,11 +50,8 @@ class ReplConfig(msgspec.Struct):
|
|
|
54
50
|
strip_ansi: bool = False
|
|
55
51
|
environment: dict[str, str] = msgspec.field(default_factory=dict)
|
|
56
52
|
timeout: float = 5.0
|
|
57
|
-
|
|
58
|
-
|
|
59
53
|
# ~/~ end
|
|
60
54
|
# ~/~ begin <<README.md#input-data>>[1]
|
|
61
|
-
#| id: input-data
|
|
62
55
|
class ReplCommand(msgspec.Struct):
|
|
63
56
|
"""A command to be sent to the REPL.
|
|
64
57
|
|
|
@@ -85,24 +78,33 @@ class ReplSession(msgspec.Struct):
|
|
|
85
78
|
|
|
86
79
|
config: ReplConfig
|
|
87
80
|
commands: list[ReplCommand]
|
|
88
|
-
|
|
89
|
-
|
|
90
81
|
# ~/~ end
|
|
91
|
-
|
|
92
|
-
|
|
93
82
|
# ~/~ begin <<docs/index.md#repl-contextmanager>>[init]
|
|
94
|
-
#| id: repl-contextmanager
|
|
95
83
|
def spawn(config: ReplConfig):
|
|
84
|
+
env = dict(os.environ) | config.environment
|
|
96
85
|
child: pexpect.spawn[str] = pexpect.spawn(
|
|
97
86
|
config.command,
|
|
98
87
|
timeout=config.timeout,
|
|
99
88
|
echo=False,
|
|
100
89
|
encoding="utf-8",
|
|
101
|
-
env=config.environment,
|
|
90
|
+
env=dict(os.environ) | config.environment,
|
|
102
91
|
)
|
|
103
92
|
return child
|
|
104
93
|
|
|
105
94
|
|
|
95
|
+
ANSI_ESCAPE = re.compile(r"(\u001b|\x1B)(\[[0-?]*[ -\/]*[@-~]|[\>\=])")
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def clean_output(config: ReplConfig, output: str) -> str | None:
|
|
99
|
+
"""Remove ANSI escapes (if requested), normalize the CRLF line endings
|
|
100
|
+
that the pseudo-terminal produces, and trim surrounding whitespace.
|
|
101
|
+
Returns `None` if nothing is left."""
|
|
102
|
+
if config.strip_ansi:
|
|
103
|
+
output = ANSI_ESCAPE.sub("", output)
|
|
104
|
+
output = output.replace("\r\n", "\n").replace("\r", "").strip()
|
|
105
|
+
return output or None
|
|
106
|
+
|
|
107
|
+
|
|
106
108
|
@contextmanager
|
|
107
109
|
def repl(config: ReplConfig) -> Generator[Callable[[str], str | None]]:
|
|
108
110
|
key = str(uuid.uuid4())
|
|
@@ -122,58 +124,61 @@ def repl(config: ReplConfig) -> Generator[Callable[[str], str | None]]:
|
|
|
122
124
|
# child.expect(key)
|
|
123
125
|
_ = child.expect(prompt)
|
|
124
126
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
127
|
+
if continuation_prompt is not None:
|
|
128
|
+
def send(msg: str) -> str | None:
|
|
129
|
+
lines = msg.splitlines()
|
|
130
|
+
answer: list[str] = []
|
|
131
|
+
|
|
132
|
+
if not lines:
|
|
133
|
+
return None
|
|
129
134
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
if continuation_prompt is not None:
|
|
135
|
+
still_waiting: bool = True
|
|
136
|
+
for line in lines:
|
|
137
|
+
logging.debug("sending: '%s'", line)
|
|
138
|
+
_ = child.sendline(line)
|
|
135
139
|
logging.debug("waiting for prompt or continuation")
|
|
136
140
|
_ = child.expect(
|
|
137
|
-
f"(?P<
|
|
141
|
+
f"(?P<norm>{prompt})|(?P<cont>{continuation_prompt})"
|
|
138
142
|
)
|
|
139
143
|
if not isinstance(child.match, re.Match):
|
|
140
144
|
continue
|
|
141
145
|
if child.match.group("cont") is not None:
|
|
142
|
-
logging.debug("continuation")
|
|
146
|
+
logging.debug("continuation: %s -- %s", child.before, child.after)
|
|
143
147
|
still_waiting = True
|
|
144
148
|
else:
|
|
145
149
|
logging.debug("done: %s", child.before)
|
|
146
150
|
if child.before is not None:
|
|
147
151
|
answer.append(child.before)
|
|
148
152
|
still_waiting = False
|
|
149
|
-
|
|
150
|
-
|
|
153
|
+
|
|
154
|
+
if still_waiting:
|
|
155
|
+
logging.debug(f"waiting for last prompt")
|
|
156
|
+
_ = child.sendline("")
|
|
151
157
|
_ = child.expect(prompt)
|
|
158
|
+
logging.debug(f"got: %s", child.before)
|
|
152
159
|
if child.before:
|
|
153
160
|
answer.append(child.before)
|
|
154
|
-
still_waiting = False
|
|
155
161
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
_ = child.expect(prompt)
|
|
159
|
-
if child.before:
|
|
160
|
-
answer.append(child.before)
|
|
162
|
+
if not answer:
|
|
163
|
+
return None
|
|
161
164
|
|
|
162
|
-
|
|
163
|
-
return None
|
|
165
|
+
return clean_output(config, answer[-1])
|
|
164
166
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
167
|
+
else:
|
|
168
|
+
def send(msg: str) -> str | None:
|
|
169
|
+
logging.debug("sending: %s", msg)
|
|
168
170
|
|
|
169
|
-
|
|
171
|
+
_ = child.sendline(msg)
|
|
172
|
+
_ = child.expect(prompt)
|
|
170
173
|
|
|
171
|
-
|
|
174
|
+
if not child.before:
|
|
175
|
+
return None
|
|
172
176
|
|
|
177
|
+
return clean_output(config, child.before)
|
|
173
178
|
|
|
179
|
+
yield send
|
|
174
180
|
# ~/~ end
|
|
175
181
|
# ~/~ begin <<docs/index.md#run-session>>[init]
|
|
176
|
-
#| id: run-session
|
|
177
182
|
def run_session(session: ReplSession):
|
|
178
183
|
with repl(session.config) as run:
|
|
179
184
|
for cmd in session.commands:
|
|
@@ -183,11 +188,8 @@ def run_session(session: ReplSession):
|
|
|
183
188
|
cmd.expected = expected
|
|
184
189
|
|
|
185
190
|
return session
|
|
186
|
-
|
|
187
|
-
|
|
188
191
|
# ~/~ end
|
|
189
192
|
# ~/~ begin <<docs/index.md#io>>[init]
|
|
190
|
-
#| id: io
|
|
191
193
|
def read_session(port: IO[str] = sys.stdin) -> ReplSession:
|
|
192
194
|
data: str = port.read()
|
|
193
195
|
return msgspec.yaml.decode(data, type=ReplSession)
|
|
@@ -196,23 +198,20 @@ def read_session(port: IO[str] = sys.stdin) -> ReplSession:
|
|
|
196
198
|
def write_session(session: ReplSession, port: IO[str] = sys.stdout):
|
|
197
199
|
data = msgspec.json.encode(session)
|
|
198
200
|
_ = port.write(data.decode())
|
|
199
|
-
|
|
200
|
-
|
|
201
201
|
# ~/~ end
|
|
202
202
|
|
|
203
|
-
|
|
204
|
-
@
|
|
205
|
-
|
|
206
|
-
|
|
203
|
+
@click.command()
|
|
204
|
+
@click.version_option(
|
|
205
|
+
__version__, "-v", "--version",
|
|
206
|
+
prog_name="repl-session", message="%(prog)s %(version)s",
|
|
207
|
+
help="show version and exit")
|
|
208
|
+
@click.option("-l", "--log-enable", is_flag=True, help="show debugging output")
|
|
209
|
+
def repl_session(log_enable: bool = False):
|
|
207
210
|
"""
|
|
208
211
|
repl-session runs a REPL session, reading JSON from standard input and
|
|
209
212
|
writing to standard output. Both the input and output follow the same
|
|
210
213
|
schema.
|
|
211
214
|
"""
|
|
212
|
-
if version:
|
|
213
|
-
print(f"repl-session {__version__}")
|
|
214
|
-
sys.exit(0)
|
|
215
|
-
|
|
216
215
|
if log_enable:
|
|
217
216
|
logging.basicConfig(level=logging.DEBUG)
|
|
218
217
|
|
|
@@ -220,7 +219,5 @@ def repl_session(version: bool = False, log_enable: bool = False):
|
|
|
220
219
|
|
|
221
220
|
|
|
222
221
|
def main():
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
222
|
+
repl_session()
|
|
226
223
|
# ~/~ end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: repl-session
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: Runs a session with a Read-Eval-Print-Loop (REPL). Takes YAML as input gives JSON as output.
|
|
5
5
|
Classifier: Development Status :: 3 - Alpha
|
|
6
6
|
Classifier: Environment :: Console
|
|
@@ -11,7 +11,7 @@ Classifier: Topic :: Utilities
|
|
|
11
11
|
Requires-Python: >=3.11
|
|
12
12
|
Description-Content-Type: text/markdown
|
|
13
13
|
License-File: LICENSE
|
|
14
|
-
Requires-Dist:
|
|
14
|
+
Requires-Dist: click>=8.1
|
|
15
15
|
Requires-Dist: msgspec>=0.19.0
|
|
16
16
|
Requires-Dist: pexpect>=4.9.0
|
|
17
17
|
Dynamic: license-file
|
|
@@ -64,7 +64,7 @@ config:
|
|
|
64
64
|
first_prompt: "> "
|
|
65
65
|
change_prompt: '(waiter-prompt-string "{key}>")'
|
|
66
66
|
prompt: "{key}> "
|
|
67
|
-
continuation_prompt: ""
|
|
67
|
+
# continuation_prompt: ""
|
|
68
68
|
commands:
|
|
69
69
|
- command: (* 6 7)
|
|
70
70
|
- command: |
|
|
@@ -75,7 +75,7 @@ commands:
|
|
|
75
75
|
- command: (fac 10 1)
|
|
76
76
|
```
|
|
77
77
|
|
|
78
|
-
Passing this to `repl-session`, it will start the Chez Scheme interpreter, waiting for the `>` prompt to appear. It then changes the prompt to a generated `uuid4` code, for instance `27e87a8a-742c-4501-b05d-b05814f5a010> `. This will make sure that we can't accidentally match something else for an interactive prompt (imagine we're generating some XML!).
|
|
78
|
+
Passing this to `repl-session`, it will start the Chez Scheme interpreter, waiting for the `>` prompt to appear. It then changes the prompt to a generated `uuid4` code, for instance `27e87a8a-742c-4501-b05d-b05814f5a010> `. This will make sure that we can't accidentally match something else for an interactive prompt (imagine we're generating some XML!). Running this should give:
|
|
79
79
|
|
|
80
80
|
```bash
|
|
81
81
|
repl-session < test/scheme.yml | jq '.commands.[].output'
|
|
@@ -83,7 +83,7 @@ repl-session < test/scheme.yml | jq '.commands.[].output'
|
|
|
83
83
|
|
|
84
84
|
```
|
|
85
85
|
"42"
|
|
86
|
-
|
|
86
|
+
null
|
|
87
87
|
"3628800"
|
|
88
88
|
```
|
|
89
89
|
|
|
@@ -126,6 +126,8 @@ repl-session < test/lua.yml | jq '.commands.[].output'
|
|
|
126
126
|
```
|
|
127
127
|
"42"
|
|
128
128
|
"Hello, World!"
|
|
129
|
+
null
|
|
130
|
+
"3628800"
|
|
129
131
|
```
|
|
130
132
|
|
|
131
133
|
### Python
|
|
@@ -142,6 +144,7 @@ config:
|
|
|
142
144
|
continuation_prompt: "{key}\\+\\+\\+ "
|
|
143
145
|
environment:
|
|
144
146
|
NO_COLOR: "1"
|
|
147
|
+
PYTHON_BASIC_REPL: "1"
|
|
145
148
|
commands:
|
|
146
149
|
- command: print("Hello, World!")
|
|
147
150
|
- command: 6 * 7
|
|
@@ -153,6 +156,17 @@ commands:
|
|
|
153
156
|
- command: fac(10)
|
|
154
157
|
```
|
|
155
158
|
|
|
159
|
+
```bash
|
|
160
|
+
repl-session < test/python.yml | jq '.commands.[].output'
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
```
|
|
164
|
+
"Hello, World!"
|
|
165
|
+
"42"
|
|
166
|
+
null
|
|
167
|
+
"3628800"
|
|
168
|
+
```
|
|
169
|
+
|
|
156
170
|
## Input/Output structure
|
|
157
171
|
|
|
158
172
|
The user can configure how the REPL is called and interpreted.
|
|
@@ -183,8 +197,6 @@ class ReplConfig(msgspec.Struct):
|
|
|
183
197
|
strip_ansi: bool = False
|
|
184
198
|
environment: dict[str, str] = msgspec.field(default_factory=dict)
|
|
185
199
|
timeout: float = 5.0
|
|
186
|
-
|
|
187
|
-
|
|
188
200
|
```
|
|
189
201
|
|
|
190
202
|
Then, a session is a list of commands. Each command should be a UTF-8 string, and we allow to attach some meta-data like expected MIME type for the output. We can also pass an expected output in the case of a documentation test. If `output` was already given on the input, it is moved to `expected`. This way it becomes really easy to setup regression tests on your documentation. Just rerun on the generated output file.
|
|
@@ -217,8 +229,6 @@ class ReplSession(msgspec.Struct):
|
|
|
217
229
|
|
|
218
230
|
config: ReplConfig
|
|
219
231
|
commands: list[ReplCommand]
|
|
220
|
-
|
|
221
|
-
|
|
222
232
|
```
|
|
223
233
|
|
|
224
234
|
## License and contribution
|
|
@@ -8,4 +8,5 @@ src/repl_session.egg-info/SOURCES.txt
|
|
|
8
8
|
src/repl_session.egg-info/dependency_links.txt
|
|
9
9
|
src/repl_session.egg-info/entry_points.txt
|
|
10
10
|
src/repl_session.egg-info/requires.txt
|
|
11
|
-
src/repl_session.egg-info/top_level.txt
|
|
11
|
+
src/repl_session.egg-info/top_level.txt
|
|
12
|
+
test/test_examples.py
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Run the example sessions from the README and check their outputs."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import shlex
|
|
5
|
+
import shutil
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import msgspec
|
|
9
|
+
import pytest
|
|
10
|
+
|
|
11
|
+
from repl_session import ReplSession, run_session
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
TEST_DIR = Path(__file__).parent
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
EXPECTED = {
|
|
18
|
+
"scheme": ["42", None, "3628800"],
|
|
19
|
+
"lua": ["42", "Hello, World!", None, "3628800"],
|
|
20
|
+
"python": ["Hello, World!", "42", None, "3628800"],
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def load_session(name: str) -> ReplSession:
|
|
25
|
+
data = (TEST_DIR / f"{name}.yml").read_bytes()
|
|
26
|
+
return msgspec.yaml.decode(data, type=ReplSession)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@pytest.mark.parametrize("name", EXPECTED.keys())
|
|
30
|
+
def test_example(name: str):
|
|
31
|
+
session = load_session(name)
|
|
32
|
+
executable = shlex.split(session.config.command)[0]
|
|
33
|
+
if shutil.which(executable) is None:
|
|
34
|
+
# In CI every interpreter must be available; locally we allow skipping.
|
|
35
|
+
if os.environ.get("CI"):
|
|
36
|
+
pytest.fail(f"`{executable}` not found")
|
|
37
|
+
pytest.skip(f"`{executable}` not found")
|
|
38
|
+
|
|
39
|
+
result = run_session(session)
|
|
40
|
+
assert [cmd.output for cmd in result.commands] == EXPECTED[name]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|