qemu-compose 0.1.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.
- qemu_compose-0.1.0/.gitignore +2 -0
- qemu_compose-0.1.0/LICENSE.txt +37 -0
- qemu_compose-0.1.0/PKG-INFO +36 -0
- qemu_compose-0.1.0/README.md +27 -0
- qemu_compose-0.1.0/pyproject.toml +34 -0
- qemu_compose-0.1.0/qemu_compose/__init__.py +0 -0
- qemu_compose-0.1.0/qemu_compose/jsonlisp.py +254 -0
- qemu_compose-0.1.0/qemu_compose/log_tool.py +64 -0
- qemu_compose-0.1.0/qemu_compose/main.py +270 -0
- qemu_compose-0.1.0/qemu_compose/qemu/__init__.py +3 -0
- qemu_compose-0.1.0/qemu_compose/qemu/machine/__init__.py +36 -0
- qemu_compose-0.1.0/qemu_compose/qemu/machine/console_socket.py +142 -0
- qemu_compose-0.1.0/qemu_compose/qemu/machine/machine.py +958 -0
- qemu_compose-0.1.0/qemu_compose/qemu/machine/py.typed +0 -0
- qemu_compose-0.1.0/qemu_compose/qemu/machine/qtest.py +191 -0
- qemu_compose-0.1.0/qemu_compose/qemu/qmp/__init__.py +59 -0
- qemu_compose-0.1.0/qemu_compose/qemu/qmp/error.py +50 -0
- qemu_compose-0.1.0/qemu_compose/qemu/qmp/events.py +717 -0
- qemu_compose-0.1.0/qemu_compose/qemu/qmp/legacy.py +319 -0
- qemu_compose-0.1.0/qemu_compose/qemu/qmp/message.py +209 -0
- qemu_compose-0.1.0/qemu_compose/qemu/qmp/models.py +146 -0
- qemu_compose-0.1.0/qemu_compose/qemu/qmp/protocol.py +1057 -0
- qemu_compose-0.1.0/qemu_compose/qemu/qmp/py.typed +0 -0
- qemu_compose-0.1.0/qemu_compose/qemu/qmp/qmp_client.py +655 -0
- qemu_compose-0.1.0/qemu_compose/qemu/qmp/qmp_shell.py +618 -0
- qemu_compose-0.1.0/qemu_compose/qemu/qmp/qmp_tui.py +655 -0
- qemu_compose-0.1.0/qemu_compose/qemu/qmp/util.py +219 -0
- qemu_compose-0.1.0/qemu_compose/qemu/utils/__init__.py +162 -0
- qemu_compose-0.1.0/qemu_compose/qemu/utils/accel.py +84 -0
- qemu_compose-0.1.0/qemu_compose/qemu/utils/py.typed +0 -0
- qemu_compose-0.1.0/qemu_compose/qemu/utils/qemu_ga_client.py +323 -0
- qemu_compose-0.1.0/qemu_compose/qemu/utils/qom.py +273 -0
- qemu_compose-0.1.0/qemu_compose/qemu/utils/qom_common.py +175 -0
- qemu_compose-0.1.0/qemu_compose/qemu/utils/qom_fuse.py +207 -0
- qemu_compose-0.1.0/qemu_compose/zio.py +2165 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
The Star And Thank Author License (SATA)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 zTrix(i@ztrix.me)
|
|
4
|
+
|
|
5
|
+
Project Url: https://github.com/zTrix/qemu-compose
|
|
6
|
+
|
|
7
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
in the Software without restriction, including without limitation the rights
|
|
10
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
furnished to do so, subject to the following conditions:
|
|
13
|
+
|
|
14
|
+
The above copyright notice and this permission notice shall be included in
|
|
15
|
+
all copies or substantial portions of the Software.
|
|
16
|
+
|
|
17
|
+
And wait, the most important, you shall star/+1/like the project(s) in project url
|
|
18
|
+
section above first, and then thank the author(s) in Copyright section.
|
|
19
|
+
|
|
20
|
+
Here are some suggested ways:
|
|
21
|
+
|
|
22
|
+
- Email the authors a thank-you letter, and make friends with him/her/them.
|
|
23
|
+
- Report bugs or issues.
|
|
24
|
+
- Tell friends what a wonderful project this is.
|
|
25
|
+
- And, sure, you can just express thanks in your mind without telling the world.
|
|
26
|
+
|
|
27
|
+
Contributors of this project by forking have the option to add his/her name and
|
|
28
|
+
forked project url at copyright and project url sections, but shall not delete
|
|
29
|
+
or modify anything else in these two sections.
|
|
30
|
+
|
|
31
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
32
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
33
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
34
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
35
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
36
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
37
|
+
THE SOFTWARE.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: qemu-compose
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: docker-compose style composer for qemu
|
|
5
|
+
License-File: LICENSE.txt
|
|
6
|
+
Requires-Python: >=3.11
|
|
7
|
+
Requires-Dist: pyyaml>=6.0.2
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# qemu-compose
|
|
12
|
+
|
|
13
|
+
qemu-compose aims to provide a docker-compose style composer for qemu command, with advanced scripting feature as well as http support for cloud init or general purpose provisioning.
|
|
14
|
+
|
|
15
|
+
bring up a qemu VM by providing a qemu-compose.yml and run `qemu-compose up`
|
|
16
|
+
|
|
17
|
+
## Advantages
|
|
18
|
+
|
|
19
|
+
- Simple and robust
|
|
20
|
+
- No libvirt stuff, no complex
|
|
21
|
+
- support `before_script` and `after_script` for setup and cleanup
|
|
22
|
+
- support `boot_commands` for vm provisioning (implemented using jsonlisp for expressive power which apparently is turing-complete)
|
|
23
|
+
- support `http_serve` for cloudinit
|
|
24
|
+
- env interpolation for advanced configuration
|
|
25
|
+
|
|
26
|
+
## Examples and Screenshot
|
|
27
|
+
|
|
28
|
+
bring up ubuntu cloudimg qemu vm and run a interactive shell
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
$ cd ./script/ubuntu-cloudimg__amd64/
|
|
33
|
+
$ qemu-compose up
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+

|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
|
|
2
|
+
# qemu-compose
|
|
3
|
+
|
|
4
|
+
qemu-compose aims to provide a docker-compose style composer for qemu command, with advanced scripting feature as well as http support for cloud init or general purpose provisioning.
|
|
5
|
+
|
|
6
|
+
bring up a qemu VM by providing a qemu-compose.yml and run `qemu-compose up`
|
|
7
|
+
|
|
8
|
+
## Advantages
|
|
9
|
+
|
|
10
|
+
- Simple and robust
|
|
11
|
+
- No libvirt stuff, no complex
|
|
12
|
+
- support `before_script` and `after_script` for setup and cleanup
|
|
13
|
+
- support `boot_commands` for vm provisioning (implemented using jsonlisp for expressive power which apparently is turing-complete)
|
|
14
|
+
- support `http_serve` for cloudinit
|
|
15
|
+
- env interpolation for advanced configuration
|
|
16
|
+
|
|
17
|
+
## Examples and Screenshot
|
|
18
|
+
|
|
19
|
+
bring up ubuntu cloudimg qemu vm and run a interactive shell
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
$ cd ./script/ubuntu-cloudimg__amd64/
|
|
24
|
+
$ qemu-compose up
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+

|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "qemu-compose"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
readme = "README.md"
|
|
5
|
+
description = "docker-compose style composer for qemu"
|
|
6
|
+
requires-python = ">=3.11"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"pyyaml>=6.0.2",
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
[project.scripts]
|
|
12
|
+
qemu-compose = "qemu_compose.main:cli"
|
|
13
|
+
|
|
14
|
+
[tool.uv]
|
|
15
|
+
environments = [
|
|
16
|
+
"sys_platform == 'darwin'",
|
|
17
|
+
"sys_platform == 'linux'",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[build-system]
|
|
21
|
+
requires = ["hatchling"]
|
|
22
|
+
build-backend = "hatchling.build"
|
|
23
|
+
|
|
24
|
+
[tool.hatch.build.targets.wheel]
|
|
25
|
+
packages = ["qemu_compose/"]
|
|
26
|
+
exclude = [
|
|
27
|
+
"*.rst",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[tool.hatch.build.targets.sdist]
|
|
31
|
+
packages = ["qemu_compose/"]
|
|
32
|
+
exclude = [
|
|
33
|
+
"*.rst",
|
|
34
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
|
|
2
|
+
# NOTE: the following code comes from https://github.com/Leden/jsonlisp with small modification for this project
|
|
3
|
+
# but I have no idea why the original repo is 404 now, so I just leave a comment here to express thanks to Leden
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import logging
|
|
7
|
+
import operator
|
|
8
|
+
from collections import ChainMap
|
|
9
|
+
import ast
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
Symbol = str
|
|
14
|
+
List = list
|
|
15
|
+
Number = (int, float)
|
|
16
|
+
Dict = dict
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Env(ChainMap):
|
|
20
|
+
def __json__(self):
|
|
21
|
+
return self.maps
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class Proc:
|
|
25
|
+
def __init__(self, params, body, env):
|
|
26
|
+
self.params = params
|
|
27
|
+
self.body = body
|
|
28
|
+
self.env = env
|
|
29
|
+
|
|
30
|
+
def __call__(self, *args):
|
|
31
|
+
env = Env(dict(zip(self.params, args)), self.env)
|
|
32
|
+
return interp(self.body, env)
|
|
33
|
+
|
|
34
|
+
def __json__(self):
|
|
35
|
+
return ["lambda", self.params, self.body]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class Macro:
|
|
39
|
+
def __init__(self, params, body, env):
|
|
40
|
+
self.params = params
|
|
41
|
+
self.body = body
|
|
42
|
+
self.env = env
|
|
43
|
+
|
|
44
|
+
def expand(self, *args, env):
|
|
45
|
+
static_env = Env(dict(zip(self.params, args)), self.env)
|
|
46
|
+
exp = interp(self.body, static_env)
|
|
47
|
+
return interp(exp, env)
|
|
48
|
+
|
|
49
|
+
def __json__(self):
|
|
50
|
+
return ["macro", self.params, self.body]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
builtins = {
|
|
54
|
+
# Operators
|
|
55
|
+
"*": operator.mul,
|
|
56
|
+
"+": operator.add,
|
|
57
|
+
"-": operator.sub,
|
|
58
|
+
"/": operator.truediv,
|
|
59
|
+
"<": operator.lt,
|
|
60
|
+
"<=": operator.le,
|
|
61
|
+
"=": operator.eq,
|
|
62
|
+
">": operator.gt,
|
|
63
|
+
">=": operator.ge,
|
|
64
|
+
"^": operator.xor,
|
|
65
|
+
"and": operator.and_,
|
|
66
|
+
"contains": operator.contains,
|
|
67
|
+
"in": lambda x, y: x in y,
|
|
68
|
+
"is": operator.is_,
|
|
69
|
+
"is-not": operator.is_not,
|
|
70
|
+
"not": operator.not_,
|
|
71
|
+
"or": operator.or_,
|
|
72
|
+
"xor": lambda x, y: bool(x) ^ bool(y), # logical XOR
|
|
73
|
+
# Typechecks
|
|
74
|
+
"dict?": lambda x: isinstance(x, Dict),
|
|
75
|
+
"list?": lambda x: isinstance(x, List),
|
|
76
|
+
"macro?": lambda x: isinstance(x, Macro),
|
|
77
|
+
"null?": lambda x: x is None,
|
|
78
|
+
"number?": lambda x: isinstance(x, Number),
|
|
79
|
+
"proc?": callable,
|
|
80
|
+
"symbol?": lambda x: isinstance(x, Symbol),
|
|
81
|
+
# List functions
|
|
82
|
+
"begin": lambda *x: x[-1],
|
|
83
|
+
"cons": lambda x, y: [x, *y],
|
|
84
|
+
"head": lambda x: x[0],
|
|
85
|
+
"len": len,
|
|
86
|
+
"list": lambda *x: list(x),
|
|
87
|
+
"map": lambda *args: list(map(*args)),
|
|
88
|
+
"range": lambda *x: list(range(*x)),
|
|
89
|
+
"tail": lambda x: x[1:],
|
|
90
|
+
# Dict functions
|
|
91
|
+
"dict": lambda x: dict(x),
|
|
92
|
+
"dict-del": lambda d, k: {_k: d[_k] for _k in d if k != _k},
|
|
93
|
+
"dict-get": lambda d, k: d.get(k),
|
|
94
|
+
"dict-items": lambda x: list(x.items()),
|
|
95
|
+
"dict-set": lambda d, k, v: {**d, k: v},
|
|
96
|
+
# Misc functions
|
|
97
|
+
"apply": lambda proc, args: proc(*args),
|
|
98
|
+
"print": print,
|
|
99
|
+
"literal": ast.literal_eval,
|
|
100
|
+
"str": str,
|
|
101
|
+
"format": lambda x, *args: x % tuple(args),
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
std_lib = [
|
|
106
|
+
"list",
|
|
107
|
+
[
|
|
108
|
+
"def",
|
|
109
|
+
"defmacro",
|
|
110
|
+
[
|
|
111
|
+
"macro",
|
|
112
|
+
["name", "params", "body"],
|
|
113
|
+
[
|
|
114
|
+
"list",
|
|
115
|
+
["quote", "def"],
|
|
116
|
+
"name",
|
|
117
|
+
["list", ["quote", "macro"], "params", "body"],
|
|
118
|
+
],
|
|
119
|
+
],
|
|
120
|
+
],
|
|
121
|
+
[
|
|
122
|
+
"defmacro",
|
|
123
|
+
"defproc",
|
|
124
|
+
["name", "params", "body"],
|
|
125
|
+
[
|
|
126
|
+
"list",
|
|
127
|
+
["quote", "def"],
|
|
128
|
+
"name",
|
|
129
|
+
["list", ["quote", "lambda"], "params", "body"],
|
|
130
|
+
],
|
|
131
|
+
],
|
|
132
|
+
]
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def default_env():
|
|
136
|
+
env = Env()
|
|
137
|
+
env.update(builtins)
|
|
138
|
+
env['key_up'] = "\x1b[A"
|
|
139
|
+
env['key_down'] = "\x1b[B"
|
|
140
|
+
env['key_right'] = "\x1b[C"
|
|
141
|
+
env['key_left'] = "\x1b[D"
|
|
142
|
+
env['key_home'] = "\x1b[H"
|
|
143
|
+
env['key_end'] = "\x1b[F"
|
|
144
|
+
env['key_ctrl_space'] = '\x00'
|
|
145
|
+
env['key_escape'] = '\x1b'
|
|
146
|
+
env['key_tab'] = '\t'
|
|
147
|
+
env['key_enter'] = '\n'
|
|
148
|
+
env['key_backspace'] = '\x7f'
|
|
149
|
+
interp(std_lib, env)
|
|
150
|
+
return env
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def parse(line):
|
|
154
|
+
return json.loads(line)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def parse_file(path):
|
|
158
|
+
return json.load(path)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def unparse(expr):
|
|
162
|
+
return json.dumps(expr, default=to_json)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def to_json(obj):
|
|
166
|
+
if hasattr(obj, "__json__"):
|
|
167
|
+
return obj.__json__()
|
|
168
|
+
return repr(obj)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def interp(x, env):
|
|
172
|
+
# a shortcut syntax for human readability
|
|
173
|
+
if isinstance(x, Dict) and len(x) == 1:
|
|
174
|
+
for k in x:
|
|
175
|
+
v = x[k]
|
|
176
|
+
if isinstance(v, List) or isinstance(v, Dict):
|
|
177
|
+
v = interp(v, env)
|
|
178
|
+
|
|
179
|
+
if not isinstance(v, List):
|
|
180
|
+
v = [v]
|
|
181
|
+
|
|
182
|
+
f = env[k]
|
|
183
|
+
logger.info('CALL_STEP: %s %s' % (f.__name__, v))
|
|
184
|
+
return f(*v)
|
|
185
|
+
|
|
186
|
+
if isinstance(x, Symbol):
|
|
187
|
+
if x.startswith('key_') and len(x) == 5:
|
|
188
|
+
return x[4]
|
|
189
|
+
return env[x]
|
|
190
|
+
|
|
191
|
+
if not isinstance(x, List):
|
|
192
|
+
return x
|
|
193
|
+
|
|
194
|
+
if x == []:
|
|
195
|
+
return x
|
|
196
|
+
|
|
197
|
+
if x[0] == "quote" or x[0] == "'":
|
|
198
|
+
_, exp = x
|
|
199
|
+
return exp
|
|
200
|
+
|
|
201
|
+
if x[0] == "flat_quote" or x[0] == "_'":
|
|
202
|
+
_, *exp = x
|
|
203
|
+
return exp
|
|
204
|
+
|
|
205
|
+
if x[0] == "if":
|
|
206
|
+
_, test, conseq, alt = x
|
|
207
|
+
exp = conseq if interp(test, env) else alt
|
|
208
|
+
return interp(exp, env)
|
|
209
|
+
|
|
210
|
+
if x[0] == "def":
|
|
211
|
+
_, var, exp = x
|
|
212
|
+
val = env[var] = interp(exp, env)
|
|
213
|
+
return val
|
|
214
|
+
|
|
215
|
+
if x[0] == "lambda":
|
|
216
|
+
_, params, body = x
|
|
217
|
+
return Proc(params, body, env)
|
|
218
|
+
|
|
219
|
+
if x[0] == "macro":
|
|
220
|
+
_, params, body = x
|
|
221
|
+
return Macro(params, body, env)
|
|
222
|
+
|
|
223
|
+
proc_or_macro_exp, *args = x
|
|
224
|
+
proc = interp(proc_or_macro_exp, env)
|
|
225
|
+
if isinstance(proc, Macro):
|
|
226
|
+
return proc.expand(*args, env=env)
|
|
227
|
+
else:
|
|
228
|
+
args = [interp(exp, env) for exp in args]
|
|
229
|
+
if callable(proc):
|
|
230
|
+
if proc_or_macro_exp != "begin":
|
|
231
|
+
logger.info('CALL_STEP: %s(%s) %s' % (proc.__name__, proc_or_macro_exp, args))
|
|
232
|
+
else:
|
|
233
|
+
logger.error('CALL_STEP:ERROR: function not callable: %s(%s) %s' % (proc, proc_or_macro_exp, args))
|
|
234
|
+
return proc(*args)
|
|
235
|
+
|
|
236
|
+
def repl(prompt=r"{λ}> "):
|
|
237
|
+
env = default_env()
|
|
238
|
+
|
|
239
|
+
while True:
|
|
240
|
+
try:
|
|
241
|
+
line = input(prompt)
|
|
242
|
+
print(unparse(interp(parse(line), env)))
|
|
243
|
+
except EOFError:
|
|
244
|
+
print("Bye")
|
|
245
|
+
break
|
|
246
|
+
except Exception as err:
|
|
247
|
+
print(repr(err))
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def run_file(path):
|
|
251
|
+
env = default_env()
|
|
252
|
+
|
|
253
|
+
with open(path) as f:
|
|
254
|
+
print(unparse(interp(parse_file(f), env)))
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
import sys
|
|
5
|
+
import socket
|
|
6
|
+
from zio import zio
|
|
7
|
+
|
|
8
|
+
def parse(log_path:str):
|
|
9
|
+
buf = [bytearray(), bytearray()]
|
|
10
|
+
ret = []
|
|
11
|
+
|
|
12
|
+
for line in open(log_path):
|
|
13
|
+
if 'IO.recv(' in line:
|
|
14
|
+
if buf[1]:
|
|
15
|
+
ret.append((1, bytes(buf[1])))
|
|
16
|
+
buf[1] = bytearray()
|
|
17
|
+
i = line.index(' -> ')
|
|
18
|
+
content = line[i+4:-1].strip()
|
|
19
|
+
assert (content.startswith('b"') and content.endswith('"')) or (content.startswith("b'") and content.endswith("'"))
|
|
20
|
+
buf[0].extend(ast.literal_eval(content))
|
|
21
|
+
elif 'IO.send(' in line:
|
|
22
|
+
if buf[0]:
|
|
23
|
+
ret.append((0, bytes(buf[0])))
|
|
24
|
+
buf[0] = bytearray()
|
|
25
|
+
i = line.index(' -> ')
|
|
26
|
+
content = line[i+4:-1].strip()
|
|
27
|
+
assert (content.startswith('b"') and content.endswith('"')) or (content.startswith("b'") and content.endswith("'"))
|
|
28
|
+
buf[1].extend(ast.literal_eval(content))
|
|
29
|
+
else:
|
|
30
|
+
continue
|
|
31
|
+
|
|
32
|
+
if buf[0]:
|
|
33
|
+
ret.append((0, bytes(buf[0])))
|
|
34
|
+
if buf[1]:
|
|
35
|
+
ret.append((1, bytes(buf[1])))
|
|
36
|
+
return ret
|
|
37
|
+
|
|
38
|
+
def serve(logs, addr=None):
|
|
39
|
+
server = socket.socket()
|
|
40
|
+
if addr is None:
|
|
41
|
+
addr = ('0.0.0.0', 1111)
|
|
42
|
+
server.bind(addr)
|
|
43
|
+
server.listen(1)
|
|
44
|
+
s, _ = server.accept()
|
|
45
|
+
io = zio(s, print_read=False, print_write=False, timeout=3600)
|
|
46
|
+
for d, c in logs:
|
|
47
|
+
if d == 1:
|
|
48
|
+
print('EXPECT', c)
|
|
49
|
+
io.read_until(c)
|
|
50
|
+
else:
|
|
51
|
+
print('SEND', c)
|
|
52
|
+
io.write(c)
|
|
53
|
+
io.close()
|
|
54
|
+
|
|
55
|
+
def main(name, log_path):
|
|
56
|
+
logs = parse(log_path)
|
|
57
|
+
if name == 'parse':
|
|
58
|
+
for direction, content in logs:
|
|
59
|
+
print('<<<<----' if direction else '---->>>>', content)
|
|
60
|
+
elif name == 'serve':
|
|
61
|
+
serve(logs)
|
|
62
|
+
|
|
63
|
+
if __name__ == '__main__':
|
|
64
|
+
main(sys.argv[1], sys.argv[2])
|