repl-session 0.1.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.1.0 → repl_session-0.2.1}/PKG-INFO +77 -23
- {repl_session-0.1.0 → repl_session-0.2.1}/README.md +75 -21
- {repl_session-0.1.0 → repl_session-0.2.1}/pyproject.toml +6 -2
- repl_session-0.2.1/src/repl_session/__init__.py +223 -0
- {repl_session-0.1.0 → repl_session-0.2.1}/src/repl_session.egg-info/PKG-INFO +77 -23
- {repl_session-0.1.0 → repl_session-0.2.1}/src/repl_session.egg-info/SOURCES.txt +2 -1
- {repl_session-0.1.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.1.0/src/repl_session/__init__.py +0 -159
- {repl_session-0.1.0 → repl_session-0.2.1}/LICENSE +0 -0
- {repl_session-0.1.0 → repl_session-0.2.1}/setup.cfg +0 -0
- {repl_session-0.1.0 → repl_session-0.2.1}/src/repl_session/py.typed +0 -0
- {repl_session-0.1.0 → repl_session-0.2.1}/src/repl_session.egg-info/dependency_links.txt +0 -0
- {repl_session-0.1.0 → repl_session-0.2.1}/src/repl_session.egg-info/entry_points.txt +0 -0
- {repl_session-0.1.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.1
|
|
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
|
|
@@ -60,22 +60,22 @@ I like to work with [Chez Scheme](https://cisco.github.io/ChezScheme/). Suppose
|
|
|
60
60
|
```yaml
|
|
61
61
|
#| file: test/scheme.yml
|
|
62
62
|
config:
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
63
|
+
command: "scheme --eedisable"
|
|
64
|
+
first_prompt: "> "
|
|
65
|
+
change_prompt: '(waiter-prompt-string "{key}>")'
|
|
66
|
+
prompt: "{key}> "
|
|
67
|
+
# continuation_prompt: ""
|
|
68
68
|
commands:
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
- command: (* 6 7)
|
|
70
|
+
- command: |
|
|
71
71
|
(define (fac n init)
|
|
72
72
|
(if (zero? n)
|
|
73
73
|
init
|
|
74
74
|
(fac (- n 1) (* init n)))))
|
|
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
|
|
|
@@ -94,15 +94,27 @@ This looks very similar to the previous example:
|
|
|
94
94
|
```yaml
|
|
95
95
|
#| file: test/lua.yml
|
|
96
96
|
config:
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
97
|
+
command: "lua"
|
|
98
|
+
first_prompt: "> "
|
|
99
|
+
change_prompt: '_PROMPT = "{key}> "; _PROMPT2 = "{key}+ "'
|
|
100
|
+
prompt: "{key}> "
|
|
101
|
+
continuation_prompt: "{key}\\+ "
|
|
102
|
+
strip_ansi: true
|
|
103
103
|
commands:
|
|
104
|
-
|
|
105
|
-
|
|
104
|
+
- command: 6 * 7
|
|
105
|
+
- command: '"Hello" .. ", " .. "World!"'
|
|
106
|
+
- command: |
|
|
107
|
+
function fac(n, m)
|
|
108
|
+
if m == nil then
|
|
109
|
+
return fac(n, 1)
|
|
110
|
+
end
|
|
111
|
+
if n == 0 then
|
|
112
|
+
return m
|
|
113
|
+
else
|
|
114
|
+
return fac(n-1, m*n)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
- command: fac(10)
|
|
106
118
|
```
|
|
107
119
|
|
|
108
120
|
The Lua REPL is not so nice. It sends ANSI escape codes and those need to be filtered out.
|
|
@@ -114,6 +126,45 @@ repl-session < test/lua.yml | jq '.commands.[].output'
|
|
|
114
126
|
```
|
|
115
127
|
"42"
|
|
116
128
|
"Hello, World!"
|
|
129
|
+
null
|
|
130
|
+
"3628800"
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Python
|
|
134
|
+
|
|
135
|
+
The Python REPL got a revision in version 3.13, with lots of colour and ANSI codes.
|
|
136
|
+
|
|
137
|
+
```yaml
|
|
138
|
+
#| file: test/python.yml
|
|
139
|
+
config:
|
|
140
|
+
command: python -q
|
|
141
|
+
first_prompt: ">>>"
|
|
142
|
+
change_prompt: 'import sys; sys.ps1 = "{key}>>> "; sys.ps2 = "{key}+++ "'
|
|
143
|
+
prompt: "{key}>>> "
|
|
144
|
+
continuation_prompt: "{key}\\+\\+\\+ "
|
|
145
|
+
environment:
|
|
146
|
+
NO_COLOR: "1"
|
|
147
|
+
PYTHON_BASIC_REPL: "1"
|
|
148
|
+
commands:
|
|
149
|
+
- command: print("Hello, World!")
|
|
150
|
+
- command: 6 * 7
|
|
151
|
+
- command: |
|
|
152
|
+
def fac(n):
|
|
153
|
+
for i in range(1, n):
|
|
154
|
+
n *= i
|
|
155
|
+
return n
|
|
156
|
+
- command: fac(10)
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
repl-session < test/python.yml | jq '.commands.[].output'
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
```
|
|
164
|
+
"Hello, World!"
|
|
165
|
+
"42"
|
|
166
|
+
null
|
|
167
|
+
"3628800"
|
|
117
168
|
```
|
|
118
169
|
|
|
119
170
|
## Input/Output structure
|
|
@@ -137,13 +188,14 @@ class ReplConfig(msgspec.Struct):
|
|
|
137
188
|
output; useful if the REPL echoes your input before answering.
|
|
138
189
|
timeout (float): Command timeout for this session in seconds.
|
|
139
190
|
"""
|
|
191
|
+
|
|
140
192
|
command: str
|
|
141
193
|
first_prompt: str
|
|
142
194
|
change_prompt: str
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
strip_command: bool = False
|
|
195
|
+
prompt: str
|
|
196
|
+
continuation_prompt: str | None = None
|
|
146
197
|
strip_ansi: bool = False
|
|
198
|
+
environment: dict[str, str] = msgspec.field(default_factory=dict)
|
|
147
199
|
timeout: float = 5.0
|
|
148
200
|
```
|
|
149
201
|
|
|
@@ -160,6 +212,7 @@ class ReplCommand(msgspec.Struct):
|
|
|
160
212
|
output (str | None): evaluated output.
|
|
161
213
|
expected (str | None): expected output.
|
|
162
214
|
"""
|
|
215
|
+
|
|
163
216
|
command: str
|
|
164
217
|
output_type: str = "text/plain"
|
|
165
218
|
output: str | None = None
|
|
@@ -173,6 +226,7 @@ class ReplSession(msgspec.Struct):
|
|
|
173
226
|
config (ReplConfig): Config for setting up a REPL session.
|
|
174
227
|
commands (list[ReplCommand]): List of commands in the session.
|
|
175
228
|
"""
|
|
229
|
+
|
|
176
230
|
config: ReplConfig
|
|
177
231
|
commands: list[ReplCommand]
|
|
178
232
|
```
|
|
@@ -42,22 +42,22 @@ I like to work with [Chez Scheme](https://cisco.github.io/ChezScheme/). Suppose
|
|
|
42
42
|
```yaml
|
|
43
43
|
#| file: test/scheme.yml
|
|
44
44
|
config:
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
45
|
+
command: "scheme --eedisable"
|
|
46
|
+
first_prompt: "> "
|
|
47
|
+
change_prompt: '(waiter-prompt-string "{key}>")'
|
|
48
|
+
prompt: "{key}> "
|
|
49
|
+
# continuation_prompt: ""
|
|
50
50
|
commands:
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
- command: (* 6 7)
|
|
52
|
+
- command: |
|
|
53
53
|
(define (fac n init)
|
|
54
54
|
(if (zero? n)
|
|
55
55
|
init
|
|
56
56
|
(fac (- n 1) (* init n)))))
|
|
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
|
|
|
@@ -76,15 +76,27 @@ This looks very similar to the previous example:
|
|
|
76
76
|
```yaml
|
|
77
77
|
#| file: test/lua.yml
|
|
78
78
|
config:
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
79
|
+
command: "lua"
|
|
80
|
+
first_prompt: "> "
|
|
81
|
+
change_prompt: '_PROMPT = "{key}> "; _PROMPT2 = "{key}+ "'
|
|
82
|
+
prompt: "{key}> "
|
|
83
|
+
continuation_prompt: "{key}\\+ "
|
|
84
|
+
strip_ansi: true
|
|
85
85
|
commands:
|
|
86
|
-
|
|
87
|
-
|
|
86
|
+
- command: 6 * 7
|
|
87
|
+
- command: '"Hello" .. ", " .. "World!"'
|
|
88
|
+
- command: |
|
|
89
|
+
function fac(n, m)
|
|
90
|
+
if m == nil then
|
|
91
|
+
return fac(n, 1)
|
|
92
|
+
end
|
|
93
|
+
if n == 0 then
|
|
94
|
+
return m
|
|
95
|
+
else
|
|
96
|
+
return fac(n-1, m*n)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
- command: fac(10)
|
|
88
100
|
```
|
|
89
101
|
|
|
90
102
|
The Lua REPL is not so nice. It sends ANSI escape codes and those need to be filtered out.
|
|
@@ -96,6 +108,45 @@ repl-session < test/lua.yml | jq '.commands.[].output'
|
|
|
96
108
|
```
|
|
97
109
|
"42"
|
|
98
110
|
"Hello, World!"
|
|
111
|
+
null
|
|
112
|
+
"3628800"
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Python
|
|
116
|
+
|
|
117
|
+
The Python REPL got a revision in version 3.13, with lots of colour and ANSI codes.
|
|
118
|
+
|
|
119
|
+
```yaml
|
|
120
|
+
#| file: test/python.yml
|
|
121
|
+
config:
|
|
122
|
+
command: python -q
|
|
123
|
+
first_prompt: ">>>"
|
|
124
|
+
change_prompt: 'import sys; sys.ps1 = "{key}>>> "; sys.ps2 = "{key}+++ "'
|
|
125
|
+
prompt: "{key}>>> "
|
|
126
|
+
continuation_prompt: "{key}\\+\\+\\+ "
|
|
127
|
+
environment:
|
|
128
|
+
NO_COLOR: "1"
|
|
129
|
+
PYTHON_BASIC_REPL: "1"
|
|
130
|
+
commands:
|
|
131
|
+
- command: print("Hello, World!")
|
|
132
|
+
- command: 6 * 7
|
|
133
|
+
- command: |
|
|
134
|
+
def fac(n):
|
|
135
|
+
for i in range(1, n):
|
|
136
|
+
n *= i
|
|
137
|
+
return n
|
|
138
|
+
- command: fac(10)
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
repl-session < test/python.yml | jq '.commands.[].output'
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
```
|
|
146
|
+
"Hello, World!"
|
|
147
|
+
"42"
|
|
148
|
+
null
|
|
149
|
+
"3628800"
|
|
99
150
|
```
|
|
100
151
|
|
|
101
152
|
## Input/Output structure
|
|
@@ -119,13 +170,14 @@ class ReplConfig(msgspec.Struct):
|
|
|
119
170
|
output; useful if the REPL echoes your input before answering.
|
|
120
171
|
timeout (float): Command timeout for this session in seconds.
|
|
121
172
|
"""
|
|
173
|
+
|
|
122
174
|
command: str
|
|
123
175
|
first_prompt: str
|
|
124
176
|
change_prompt: str
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
strip_command: bool = False
|
|
177
|
+
prompt: str
|
|
178
|
+
continuation_prompt: str | None = None
|
|
128
179
|
strip_ansi: bool = False
|
|
180
|
+
environment: dict[str, str] = msgspec.field(default_factory=dict)
|
|
129
181
|
timeout: float = 5.0
|
|
130
182
|
```
|
|
131
183
|
|
|
@@ -142,6 +194,7 @@ class ReplCommand(msgspec.Struct):
|
|
|
142
194
|
output (str | None): evaluated output.
|
|
143
195
|
expected (str | None): expected output.
|
|
144
196
|
"""
|
|
197
|
+
|
|
145
198
|
command: str
|
|
146
199
|
output_type: str = "text/plain"
|
|
147
200
|
output: str | None = None
|
|
@@ -155,6 +208,7 @@ class ReplSession(msgspec.Struct):
|
|
|
155
208
|
config (ReplConfig): Config for setting up a REPL session.
|
|
156
209
|
commands (list[ReplCommand]): List of commands in the session.
|
|
157
210
|
"""
|
|
211
|
+
|
|
158
212
|
config: ReplConfig
|
|
159
213
|
commands: list[ReplCommand]
|
|
160
214
|
```
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "repl-session"
|
|
3
|
-
version = "0.1
|
|
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,7 +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",
|
|
28
|
+
"python-lsp-server>=1.13.1",
|
|
29
|
+
"ruff>=0.13.2",
|
|
27
30
|
"ty>=0.0.1a21",
|
|
31
|
+
"types-pexpect>=4.9.0.20250916",
|
|
28
32
|
]
|
|
29
33
|
|
|
30
34
|
[project.scripts]
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
# ~/~ begin <<docs/index.md#src/repl_session/__init__.py>>[init]
|
|
2
|
+
"""
|
|
3
|
+
`repl-session` is a command-line tool to evaluate a given session
|
|
4
|
+
in any REPL, and store the results.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
# ~/~ begin <<docs/index.md#imports>>[init]
|
|
8
|
+
# from datetime import datetime, tzinfo
|
|
9
|
+
from typing import IO, cast
|
|
10
|
+
from collections.abc import Generator, Callable
|
|
11
|
+
|
|
12
|
+
# import re
|
|
13
|
+
from contextlib import contextmanager
|
|
14
|
+
import uuid
|
|
15
|
+
import sys
|
|
16
|
+
import re
|
|
17
|
+
import logging
|
|
18
|
+
import os
|
|
19
|
+
|
|
20
|
+
import pexpect
|
|
21
|
+
import msgspec
|
|
22
|
+
import click
|
|
23
|
+
import importlib.metadata
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
__version__ = importlib.metadata.version("repl-session")
|
|
27
|
+
# ~/~ end
|
|
28
|
+
# ~/~ begin <<README.md#input-data>>[init]
|
|
29
|
+
class ReplConfig(msgspec.Struct):
|
|
30
|
+
"""Configuration
|
|
31
|
+
|
|
32
|
+
Attributes:
|
|
33
|
+
command (str): Command to start the REPL
|
|
34
|
+
first_prompt (str): Regex to match the first prompt
|
|
35
|
+
change_prompt (str): Command to change prompt; should contain '{key}' as an
|
|
36
|
+
argument.
|
|
37
|
+
next_prompt (str): Regex to match the changed prompts; should contain '{key}'
|
|
38
|
+
as an argument.
|
|
39
|
+
append_newline (bool): Whether to append a newline to given commands.
|
|
40
|
+
strip_command (bool): Whether to strip the original command from the gotten
|
|
41
|
+
output; useful if the REPL echoes your input before answering.
|
|
42
|
+
timeout (float): Command timeout for this session in seconds.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
command: str
|
|
46
|
+
first_prompt: str
|
|
47
|
+
change_prompt: str
|
|
48
|
+
prompt: str
|
|
49
|
+
continuation_prompt: str | None = None
|
|
50
|
+
strip_ansi: bool = False
|
|
51
|
+
environment: dict[str, str] = msgspec.field(default_factory=dict)
|
|
52
|
+
timeout: float = 5.0
|
|
53
|
+
# ~/~ end
|
|
54
|
+
# ~/~ begin <<README.md#input-data>>[1]
|
|
55
|
+
class ReplCommand(msgspec.Struct):
|
|
56
|
+
"""A command to be sent to the REPL.
|
|
57
|
+
|
|
58
|
+
Attributes:
|
|
59
|
+
command (str): the command.
|
|
60
|
+
output_type (str): MIME type of expected output.
|
|
61
|
+
output (str | None): evaluated output.
|
|
62
|
+
expected (str | None): expected output.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
command: str
|
|
66
|
+
output_type: str = "text/plain"
|
|
67
|
+
output: str | None = None
|
|
68
|
+
expected: str | None = None
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class ReplSession(msgspec.Struct):
|
|
72
|
+
"""A REPL session.
|
|
73
|
+
|
|
74
|
+
Attributes:
|
|
75
|
+
config (ReplConfig): Config for setting up a REPL session.
|
|
76
|
+
commands (list[ReplCommand]): List of commands in the session.
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
config: ReplConfig
|
|
80
|
+
commands: list[ReplCommand]
|
|
81
|
+
# ~/~ end
|
|
82
|
+
# ~/~ begin <<docs/index.md#repl-contextmanager>>[init]
|
|
83
|
+
def spawn(config: ReplConfig):
|
|
84
|
+
env = dict(os.environ) | config.environment
|
|
85
|
+
child: pexpect.spawn[str] = pexpect.spawn(
|
|
86
|
+
config.command,
|
|
87
|
+
timeout=config.timeout,
|
|
88
|
+
echo=False,
|
|
89
|
+
encoding="utf-8",
|
|
90
|
+
env=dict(os.environ) | config.environment,
|
|
91
|
+
)
|
|
92
|
+
return child
|
|
93
|
+
|
|
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
|
+
|
|
108
|
+
@contextmanager
|
|
109
|
+
def repl(config: ReplConfig) -> Generator[Callable[[str], str | None]]:
|
|
110
|
+
key = str(uuid.uuid4())
|
|
111
|
+
change_prompt_cmd = config.change_prompt.format(key=key)
|
|
112
|
+
prompt = config.prompt.format(key=key)
|
|
113
|
+
continuation_prompt = (
|
|
114
|
+
config.continuation_prompt.format(key=key)
|
|
115
|
+
if config.continuation_prompt is not None
|
|
116
|
+
else None
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
child: pexpect.spawn[str]
|
|
120
|
+
with spawn(config) as child:
|
|
121
|
+
_ = child.expect(config.first_prompt)
|
|
122
|
+
_ = child.sendline(change_prompt_cmd)
|
|
123
|
+
# if config.strip_command:
|
|
124
|
+
# child.expect(key)
|
|
125
|
+
_ = child.expect(prompt)
|
|
126
|
+
|
|
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
|
|
134
|
+
|
|
135
|
+
still_waiting: bool = True
|
|
136
|
+
for line in lines:
|
|
137
|
+
logging.debug("sending: '%s'", line)
|
|
138
|
+
_ = child.sendline(line)
|
|
139
|
+
logging.debug("waiting for prompt or continuation")
|
|
140
|
+
_ = child.expect(
|
|
141
|
+
f"(?P<norm>{prompt})|(?P<cont>{continuation_prompt})"
|
|
142
|
+
)
|
|
143
|
+
if not isinstance(child.match, re.Match):
|
|
144
|
+
continue
|
|
145
|
+
if child.match.group("cont") is not None:
|
|
146
|
+
logging.debug("continuation: %s -- %s", child.before, child.after)
|
|
147
|
+
still_waiting = True
|
|
148
|
+
else:
|
|
149
|
+
logging.debug("done: %s", child.before)
|
|
150
|
+
if child.before is not None:
|
|
151
|
+
answer.append(child.before)
|
|
152
|
+
still_waiting = False
|
|
153
|
+
|
|
154
|
+
if still_waiting:
|
|
155
|
+
logging.debug(f"waiting for last prompt")
|
|
156
|
+
_ = child.sendline("")
|
|
157
|
+
_ = child.expect(prompt)
|
|
158
|
+
logging.debug(f"got: %s", child.before)
|
|
159
|
+
if child.before:
|
|
160
|
+
answer.append(child.before)
|
|
161
|
+
|
|
162
|
+
if not answer:
|
|
163
|
+
return None
|
|
164
|
+
|
|
165
|
+
return clean_output(config, answer[-1])
|
|
166
|
+
|
|
167
|
+
else:
|
|
168
|
+
def send(msg: str) -> str | None:
|
|
169
|
+
logging.debug("sending: %s", msg)
|
|
170
|
+
|
|
171
|
+
_ = child.sendline(msg)
|
|
172
|
+
_ = child.expect(prompt)
|
|
173
|
+
|
|
174
|
+
if not child.before:
|
|
175
|
+
return None
|
|
176
|
+
|
|
177
|
+
return clean_output(config, child.before)
|
|
178
|
+
|
|
179
|
+
yield send
|
|
180
|
+
# ~/~ end
|
|
181
|
+
# ~/~ begin <<docs/index.md#run-session>>[init]
|
|
182
|
+
def run_session(session: ReplSession):
|
|
183
|
+
with repl(session.config) as run:
|
|
184
|
+
for cmd in session.commands:
|
|
185
|
+
expected = cmd.expected or cmd.output
|
|
186
|
+
output = run(cmd.command)
|
|
187
|
+
cmd.output = output
|
|
188
|
+
cmd.expected = expected
|
|
189
|
+
|
|
190
|
+
return session
|
|
191
|
+
# ~/~ end
|
|
192
|
+
# ~/~ begin <<docs/index.md#io>>[init]
|
|
193
|
+
def read_session(port: IO[str] = sys.stdin) -> ReplSession:
|
|
194
|
+
data: str = port.read()
|
|
195
|
+
return msgspec.yaml.decode(data, type=ReplSession)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def write_session(session: ReplSession, port: IO[str] = sys.stdout):
|
|
199
|
+
data = msgspec.json.encode(session)
|
|
200
|
+
_ = port.write(data.decode())
|
|
201
|
+
# ~/~ end
|
|
202
|
+
|
|
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):
|
|
210
|
+
"""
|
|
211
|
+
repl-session runs a REPL session, reading JSON from standard input and
|
|
212
|
+
writing to standard output. Both the input and output follow the same
|
|
213
|
+
schema.
|
|
214
|
+
"""
|
|
215
|
+
if log_enable:
|
|
216
|
+
logging.basicConfig(level=logging.DEBUG)
|
|
217
|
+
|
|
218
|
+
write_session(run_session(read_session()))
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def main():
|
|
222
|
+
repl_session()
|
|
223
|
+
# ~/~ end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: repl-session
|
|
3
|
-
Version: 0.1
|
|
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
|
|
@@ -60,22 +60,22 @@ I like to work with [Chez Scheme](https://cisco.github.io/ChezScheme/). Suppose
|
|
|
60
60
|
```yaml
|
|
61
61
|
#| file: test/scheme.yml
|
|
62
62
|
config:
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
63
|
+
command: "scheme --eedisable"
|
|
64
|
+
first_prompt: "> "
|
|
65
|
+
change_prompt: '(waiter-prompt-string "{key}>")'
|
|
66
|
+
prompt: "{key}> "
|
|
67
|
+
# continuation_prompt: ""
|
|
68
68
|
commands:
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
- command: (* 6 7)
|
|
70
|
+
- command: |
|
|
71
71
|
(define (fac n init)
|
|
72
72
|
(if (zero? n)
|
|
73
73
|
init
|
|
74
74
|
(fac (- n 1) (* init n)))))
|
|
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
|
|
|
@@ -94,15 +94,27 @@ This looks very similar to the previous example:
|
|
|
94
94
|
```yaml
|
|
95
95
|
#| file: test/lua.yml
|
|
96
96
|
config:
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
97
|
+
command: "lua"
|
|
98
|
+
first_prompt: "> "
|
|
99
|
+
change_prompt: '_PROMPT = "{key}> "; _PROMPT2 = "{key}+ "'
|
|
100
|
+
prompt: "{key}> "
|
|
101
|
+
continuation_prompt: "{key}\\+ "
|
|
102
|
+
strip_ansi: true
|
|
103
103
|
commands:
|
|
104
|
-
|
|
105
|
-
|
|
104
|
+
- command: 6 * 7
|
|
105
|
+
- command: '"Hello" .. ", " .. "World!"'
|
|
106
|
+
- command: |
|
|
107
|
+
function fac(n, m)
|
|
108
|
+
if m == nil then
|
|
109
|
+
return fac(n, 1)
|
|
110
|
+
end
|
|
111
|
+
if n == 0 then
|
|
112
|
+
return m
|
|
113
|
+
else
|
|
114
|
+
return fac(n-1, m*n)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
- command: fac(10)
|
|
106
118
|
```
|
|
107
119
|
|
|
108
120
|
The Lua REPL is not so nice. It sends ANSI escape codes and those need to be filtered out.
|
|
@@ -114,6 +126,45 @@ repl-session < test/lua.yml | jq '.commands.[].output'
|
|
|
114
126
|
```
|
|
115
127
|
"42"
|
|
116
128
|
"Hello, World!"
|
|
129
|
+
null
|
|
130
|
+
"3628800"
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Python
|
|
134
|
+
|
|
135
|
+
The Python REPL got a revision in version 3.13, with lots of colour and ANSI codes.
|
|
136
|
+
|
|
137
|
+
```yaml
|
|
138
|
+
#| file: test/python.yml
|
|
139
|
+
config:
|
|
140
|
+
command: python -q
|
|
141
|
+
first_prompt: ">>>"
|
|
142
|
+
change_prompt: 'import sys; sys.ps1 = "{key}>>> "; sys.ps2 = "{key}+++ "'
|
|
143
|
+
prompt: "{key}>>> "
|
|
144
|
+
continuation_prompt: "{key}\\+\\+\\+ "
|
|
145
|
+
environment:
|
|
146
|
+
NO_COLOR: "1"
|
|
147
|
+
PYTHON_BASIC_REPL: "1"
|
|
148
|
+
commands:
|
|
149
|
+
- command: print("Hello, World!")
|
|
150
|
+
- command: 6 * 7
|
|
151
|
+
- command: |
|
|
152
|
+
def fac(n):
|
|
153
|
+
for i in range(1, n):
|
|
154
|
+
n *= i
|
|
155
|
+
return n
|
|
156
|
+
- command: fac(10)
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
repl-session < test/python.yml | jq '.commands.[].output'
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
```
|
|
164
|
+
"Hello, World!"
|
|
165
|
+
"42"
|
|
166
|
+
null
|
|
167
|
+
"3628800"
|
|
117
168
|
```
|
|
118
169
|
|
|
119
170
|
## Input/Output structure
|
|
@@ -137,13 +188,14 @@ class ReplConfig(msgspec.Struct):
|
|
|
137
188
|
output; useful if the REPL echoes your input before answering.
|
|
138
189
|
timeout (float): Command timeout for this session in seconds.
|
|
139
190
|
"""
|
|
191
|
+
|
|
140
192
|
command: str
|
|
141
193
|
first_prompt: str
|
|
142
194
|
change_prompt: str
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
strip_command: bool = False
|
|
195
|
+
prompt: str
|
|
196
|
+
continuation_prompt: str | None = None
|
|
146
197
|
strip_ansi: bool = False
|
|
198
|
+
environment: dict[str, str] = msgspec.field(default_factory=dict)
|
|
147
199
|
timeout: float = 5.0
|
|
148
200
|
```
|
|
149
201
|
|
|
@@ -160,6 +212,7 @@ class ReplCommand(msgspec.Struct):
|
|
|
160
212
|
output (str | None): evaluated output.
|
|
161
213
|
expected (str | None): expected output.
|
|
162
214
|
"""
|
|
215
|
+
|
|
163
216
|
command: str
|
|
164
217
|
output_type: str = "text/plain"
|
|
165
218
|
output: str | None = None
|
|
@@ -173,6 +226,7 @@ class ReplSession(msgspec.Struct):
|
|
|
173
226
|
config (ReplConfig): Config for setting up a REPL session.
|
|
174
227
|
commands (list[ReplCommand]): List of commands in the session.
|
|
175
228
|
"""
|
|
229
|
+
|
|
176
230
|
config: ReplConfig
|
|
177
231
|
commands: list[ReplCommand]
|
|
178
232
|
```
|
|
@@ -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]
|
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
# ~/~ begin <<docs/index.md#src/repl_session/__init__.py>>[init]
|
|
2
|
-
#| file: src/repl_session/__init__.py
|
|
3
|
-
"""
|
|
4
|
-
`repl-session` is a command-line tool to evaluate a given session
|
|
5
|
-
in any REPL, and store the results.
|
|
6
|
-
"""
|
|
7
|
-
# ~/~ begin <<docs/index.md#imports>>[init]
|
|
8
|
-
#| id: imports
|
|
9
|
-
# from datetime import datetime, tzinfo
|
|
10
|
-
from typing import IO
|
|
11
|
-
from collections.abc import Generator, Callable
|
|
12
|
-
# import re
|
|
13
|
-
from contextlib import contextmanager
|
|
14
|
-
import uuid
|
|
15
|
-
import sys
|
|
16
|
-
import re
|
|
17
|
-
|
|
18
|
-
import pexpect
|
|
19
|
-
import msgspec
|
|
20
|
-
import argh
|
|
21
|
-
import importlib.metadata
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
__version__ = importlib.metadata.version("repl-session")
|
|
25
|
-
# ~/~ end
|
|
26
|
-
|
|
27
|
-
# ~/~ begin <<README.md#input-data>>[init]
|
|
28
|
-
#| id: input-data
|
|
29
|
-
class ReplConfig(msgspec.Struct):
|
|
30
|
-
"""Configuration
|
|
31
|
-
|
|
32
|
-
Attributes:
|
|
33
|
-
command (str): Command to start the REPL
|
|
34
|
-
first_prompt (str): Regex to match the first prompt
|
|
35
|
-
change_prompt (str): Command to change prompt; should contain '{key}' as an
|
|
36
|
-
argument.
|
|
37
|
-
next_prompt (str): Regex to match the changed prompts; should contain '{key}'
|
|
38
|
-
as an argument.
|
|
39
|
-
append_newline (bool): Whether to append a newline to given commands.
|
|
40
|
-
strip_command (bool): Whether to strip the original command from the gotten
|
|
41
|
-
output; useful if the REPL echoes your input before answering.
|
|
42
|
-
timeout (float): Command timeout for this session in seconds.
|
|
43
|
-
"""
|
|
44
|
-
command: str
|
|
45
|
-
first_prompt: str
|
|
46
|
-
change_prompt: str
|
|
47
|
-
next_prompt: str
|
|
48
|
-
append_newline: bool = True
|
|
49
|
-
strip_command: bool = False
|
|
50
|
-
strip_ansi: bool = False
|
|
51
|
-
timeout: float = 5.0
|
|
52
|
-
# ~/~ end
|
|
53
|
-
# ~/~ begin <<README.md#input-data>>[1]
|
|
54
|
-
#| id: input-data
|
|
55
|
-
class ReplCommand(msgspec.Struct):
|
|
56
|
-
"""A command to be sent to the REPL.
|
|
57
|
-
|
|
58
|
-
Attributes:
|
|
59
|
-
command (str): the command.
|
|
60
|
-
output_type (str): MIME type of expected output.
|
|
61
|
-
output (str | None): evaluated output.
|
|
62
|
-
expected (str | None): expected output.
|
|
63
|
-
"""
|
|
64
|
-
command: str
|
|
65
|
-
output_type: str = "text/plain"
|
|
66
|
-
output: str | None = None
|
|
67
|
-
expected: str | None = None
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
class ReplSession(msgspec.Struct):
|
|
71
|
-
"""A REPL session.
|
|
72
|
-
|
|
73
|
-
Attributes:
|
|
74
|
-
config (ReplConfig): Config for setting up a REPL session.
|
|
75
|
-
commands (list[ReplCommand]): List of commands in the session.
|
|
76
|
-
"""
|
|
77
|
-
config: ReplConfig
|
|
78
|
-
commands: list[ReplCommand]
|
|
79
|
-
# ~/~ end
|
|
80
|
-
|
|
81
|
-
# ~/~ begin <<docs/index.md#repl-contextmanager>>[init]
|
|
82
|
-
#| id: repl-contextmanager
|
|
83
|
-
|
|
84
|
-
@contextmanager
|
|
85
|
-
def repl(config: ReplConfig) -> Generator[Callable[[str], str]]:
|
|
86
|
-
key = str(uuid.uuid4())
|
|
87
|
-
|
|
88
|
-
with pexpect.spawn(config.command, timeout=config.timeout) as child:
|
|
89
|
-
child.expect(config.first_prompt)
|
|
90
|
-
change_prompt_cmd = config.change_prompt.format(key=key)
|
|
91
|
-
if config.append_newline:
|
|
92
|
-
change_prompt_cmd = change_prompt_cmd + "\n"
|
|
93
|
-
child.send(change_prompt_cmd)
|
|
94
|
-
if config.strip_command:
|
|
95
|
-
child.expect(key)
|
|
96
|
-
prompt = config.next_prompt.format(key=key)
|
|
97
|
-
child.expect(prompt)
|
|
98
|
-
|
|
99
|
-
def send(msg: str) -> str:
|
|
100
|
-
if config.append_newline:
|
|
101
|
-
msg = msg + "\n"
|
|
102
|
-
child.send(msg)
|
|
103
|
-
child.expect("(.*)" + prompt)
|
|
104
|
-
|
|
105
|
-
answer = child.match[1].decode()
|
|
106
|
-
if config.strip_ansi:
|
|
107
|
-
ansi_escape = re.compile(r'(\u001b\[|\x1B\[)[0-?]*[ -\/]*[@-~]')
|
|
108
|
-
answer = ansi_escape.sub("", answer)
|
|
109
|
-
if config.strip_command:
|
|
110
|
-
answer = answer.strip().replace("\r", "")
|
|
111
|
-
return answer.removeprefix(msg)
|
|
112
|
-
else:
|
|
113
|
-
return child.match[1].decode()
|
|
114
|
-
|
|
115
|
-
yield send
|
|
116
|
-
# ~/~ end
|
|
117
|
-
# ~/~ begin <<docs/index.md#run-session>>[init]
|
|
118
|
-
#| id: run-session
|
|
119
|
-
def run_session(session: ReplSession):
|
|
120
|
-
with repl(session.config) as run:
|
|
121
|
-
for cmd in session.commands:
|
|
122
|
-
expected = cmd.expected or cmd.output
|
|
123
|
-
output = run(cmd.command)
|
|
124
|
-
cmd.output = output
|
|
125
|
-
cmd.expected = expected
|
|
126
|
-
|
|
127
|
-
return session
|
|
128
|
-
# ~/~ end
|
|
129
|
-
# ~/~ begin <<docs/index.md#io>>[init]
|
|
130
|
-
#| id: io
|
|
131
|
-
|
|
132
|
-
def read_session(port: IO[str] = sys.stdin) -> ReplSession:
|
|
133
|
-
data: str = port.read()
|
|
134
|
-
return msgspec.yaml.decode(data, type=ReplSession)
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
def write_session(session: ReplSession, port: IO[str] = sys.stdout):
|
|
138
|
-
data = msgspec.json.encode(session)
|
|
139
|
-
port.write(data.decode())
|
|
140
|
-
# ~/~ end
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
@argh.arg("-v", "--version", help="show version and exit")
|
|
144
|
-
def repl_session(version: bool = False):
|
|
145
|
-
"""
|
|
146
|
-
repl-session runs a REPL session, reading JSON from standard input and
|
|
147
|
-
writing to standard output. Both the input and output follow the same
|
|
148
|
-
schema.
|
|
149
|
-
"""
|
|
150
|
-
if version:
|
|
151
|
-
print(f"repl-session {__version__}")
|
|
152
|
-
sys.exit(0)
|
|
153
|
-
|
|
154
|
-
write_session(run_session(read_session()))
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
def main():
|
|
158
|
-
argh.dispatch_command(repl_session)
|
|
159
|
-
# ~/~ end
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|