shelltastic 0.6.0__tar.gz → 0.7.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.
- {shelltastic-0.6.0 → shelltastic-0.7.0}/PKG-INFO +1 -1
- {shelltastic-0.6.0 → shelltastic-0.7.0}/pyproject.toml +1 -1
- {shelltastic-0.6.0 → shelltastic-0.7.0}/src/shelltastic/backend/default/common.py +132 -169
- {shelltastic-0.6.0 → shelltastic-0.7.0}/src/shelltastic/backend/default/local.py +0 -2
- {shelltastic-0.6.0 → shelltastic-0.7.0}/src/shelltastic/backend/default/remote.py +3 -9
- shelltastic-0.7.0/src/shelltastic/result.py +101 -0
- shelltastic-0.6.0/src/shelltastic/result.py +0 -47
- {shelltastic-0.6.0 → shelltastic-0.7.0}/LICENSE +0 -0
- {shelltastic-0.6.0 → shelltastic-0.7.0}/README.md +0 -0
- {shelltastic-0.6.0 → shelltastic-0.7.0}/src/shelltastic/__init__.py +0 -0
- {shelltastic-0.6.0 → shelltastic-0.7.0}/src/shelltastic/backend/__init__.py +0 -0
- {shelltastic-0.6.0 → shelltastic-0.7.0}/src/shelltastic/backend/base.py +0 -0
- {shelltastic-0.6.0 → shelltastic-0.7.0}/src/shelltastic/backend/default/__init__.py +0 -0
- {shelltastic-0.6.0 → shelltastic-0.7.0}/src/shelltastic/display.py +0 -0
- {shelltastic-0.6.0 → shelltastic-0.7.0}/src/shelltastic/enum.py +0 -0
- {shelltastic-0.6.0 → shelltastic-0.7.0}/src/shelltastic/exception.py +0 -0
- {shelltastic-0.6.0 → shelltastic-0.7.0}/src/shelltastic/frontend/__init__.py +0 -0
- {shelltastic-0.6.0 → shelltastic-0.7.0}/src/shelltastic/frontend/common.py +0 -0
- {shelltastic-0.6.0 → shelltastic-0.7.0}/src/shelltastic/frontend/git.py +0 -0
- {shelltastic-0.6.0 → shelltastic-0.7.0}/src/shelltastic/frontend/scp.py +0 -0
- {shelltastic-0.6.0 → shelltastic-0.7.0}/src/shelltastic/frontend/shell.py +0 -0
- {shelltastic-0.6.0 → shelltastic-0.7.0}/src/shelltastic/host.py +0 -0
- {shelltastic-0.6.0 → shelltastic-0.7.0}/src/shelltastic/io.py +0 -0
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
1
|
import logging
|
|
4
2
|
import os
|
|
5
3
|
import shlex
|
|
@@ -7,8 +5,10 @@ import subprocess
|
|
|
7
5
|
import time
|
|
8
6
|
from io import RawIOBase
|
|
9
7
|
from multiprocessing import Pipe
|
|
8
|
+
from multiprocessing.connection import Connection
|
|
10
9
|
from multiprocessing.pool import ThreadPool
|
|
11
|
-
from
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import IO, Literal
|
|
12
12
|
|
|
13
13
|
from shelltastic import display
|
|
14
14
|
from shelltastic.backend.base import ShellBackend
|
|
@@ -16,10 +16,6 @@ from shelltastic.enum import CaptureMode, DisplayMode
|
|
|
16
16
|
from shelltastic.io import PipeReader
|
|
17
17
|
from shelltastic.result import ShellResult, ShellResultAsync
|
|
18
18
|
|
|
19
|
-
if TYPE_CHECKING:
|
|
20
|
-
from multiprocessing.connection import Connection
|
|
21
|
-
from pathlib import Path
|
|
22
|
-
|
|
23
19
|
LOGGER = logging.getLogger(__name__)
|
|
24
20
|
|
|
25
21
|
|
|
@@ -91,45 +87,27 @@ class IOTimeoutStream:
|
|
|
91
87
|
return return_val
|
|
92
88
|
|
|
93
89
|
|
|
94
|
-
def _output_and_collect(
|
|
95
|
-
io: IO[bytes] | None, display: display.IODisplay | None, collect: bool = True
|
|
96
|
-
) -> bytes | None:
|
|
97
|
-
if io is None:
|
|
98
|
-
return None
|
|
99
|
-
|
|
100
|
-
if collect:
|
|
101
|
-
if display:
|
|
102
|
-
return_bytes: bytes = b""
|
|
103
|
-
for line in IOTimeoutStream(io):
|
|
104
|
-
return_bytes += line
|
|
105
|
-
display.printbytes(line)
|
|
106
|
-
return return_bytes
|
|
107
|
-
return io.read()
|
|
108
|
-
|
|
109
|
-
if display:
|
|
110
|
-
for line in IOTimeoutStream(io):
|
|
111
|
-
display.printbytes(line)
|
|
112
|
-
|
|
113
|
-
return None
|
|
114
|
-
|
|
115
|
-
|
|
116
90
|
def _output_and_pipe(
|
|
117
91
|
io: IO[bytes] | None, display: display.IODisplay | None, pipe: Connection | None
|
|
118
92
|
) -> None:
|
|
119
|
-
|
|
120
|
-
|
|
93
|
+
try:
|
|
94
|
+
if io is None:
|
|
95
|
+
return
|
|
96
|
+
|
|
97
|
+
if pipe:
|
|
98
|
+
if display:
|
|
99
|
+
for line in IOTimeoutStream(io):
|
|
100
|
+
pipe.send_bytes(line)
|
|
101
|
+
display.printbytes(line)
|
|
102
|
+
for line in IOTimeoutStream(io):
|
|
103
|
+
pipe.send_bytes(line)
|
|
121
104
|
|
|
122
|
-
if pipe:
|
|
123
105
|
if display:
|
|
124
106
|
for line in IOTimeoutStream(io):
|
|
125
|
-
pipe.send_bytes(line)
|
|
126
107
|
display.printbytes(line)
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
if display:
|
|
131
|
-
for line in IOTimeoutStream(io):
|
|
132
|
-
display.printbytes(line)
|
|
108
|
+
finally:
|
|
109
|
+
if pipe:
|
|
110
|
+
pipe.close()
|
|
133
111
|
|
|
134
112
|
|
|
135
113
|
def _determine_subprocess_output_mode(
|
|
@@ -184,50 +162,120 @@ class CommonShellAcyncResult(ShellResultAsync[RawIOBase | None, RawIOBase | None
|
|
|
184
162
|
self,
|
|
185
163
|
cmd: str | list[str],
|
|
186
164
|
popen: subprocess.Popen,
|
|
165
|
+
stdout_capture: CaptureMode | None,
|
|
166
|
+
stderr_capture: CaptureMode | None,
|
|
187
167
|
stdout_display: display.IODisplay | None,
|
|
188
168
|
stderr_display: display.IODisplay | None,
|
|
169
|
+
check: bool,
|
|
189
170
|
) -> None:
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
171
|
+
_display_pool = None
|
|
172
|
+
_stdout_async_result = None
|
|
173
|
+
_stderr_async_result = None
|
|
174
|
+
|
|
175
|
+
if popen.stdout:
|
|
176
|
+
# If stdout is PIPE, then setup reader
|
|
177
|
+
if stdout_capture == CaptureMode.PIPE:
|
|
178
|
+
stdout_rcv, stdout_snd = Pipe(False)
|
|
179
|
+
stdout = PipeReader(stdout_rcv)
|
|
180
|
+
else:
|
|
181
|
+
stdout_snd = None
|
|
182
|
+
stdout = None
|
|
183
|
+
|
|
184
|
+
if stdout_snd or stdout_display:
|
|
185
|
+
if _display_pool is None:
|
|
186
|
+
_display_pool = ThreadPool(2)
|
|
187
|
+
|
|
188
|
+
_stdout_async_result=_display_pool.apply_async(
|
|
189
|
+
_output_and_pipe,
|
|
190
|
+
args=[
|
|
191
|
+
popen.stdout,
|
|
192
|
+
stdout_display,
|
|
193
|
+
stdout_snd,
|
|
194
|
+
],
|
|
195
|
+
)
|
|
196
|
+
else:
|
|
197
|
+
stdout = None
|
|
205
198
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
199
|
+
if popen.stderr:
|
|
200
|
+
if stderr_capture == CaptureMode.PIPE:
|
|
201
|
+
stderr_rcv, stderr_snd = Pipe(False)
|
|
202
|
+
stderr = PipeReader(stderr_rcv)
|
|
203
|
+
else:
|
|
204
|
+
stderr_snd = None
|
|
205
|
+
stderr = None
|
|
206
|
+
|
|
207
|
+
if stderr_snd or stderr_display:
|
|
208
|
+
if _display_pool is None:
|
|
209
|
+
_display_pool = ThreadPool(2)
|
|
210
|
+
|
|
211
|
+
_stderr_async_result = _display_pool.apply_async(
|
|
212
|
+
_output_and_pipe,
|
|
213
|
+
args=[
|
|
214
|
+
popen.stderr,
|
|
215
|
+
stderr_display,
|
|
216
|
+
stderr_snd,
|
|
217
|
+
],
|
|
218
|
+
)
|
|
219
|
+
else:
|
|
220
|
+
stderr = None
|
|
221
|
+
|
|
222
|
+
super().__init__(cmd, stdout, stderr)
|
|
223
|
+
|
|
224
|
+
self._display_pool = _display_pool
|
|
225
|
+
self._stdout_async_result = _stdout_async_result
|
|
226
|
+
self._stderr_async_result = _stderr_async_result
|
|
227
|
+
self._popen = popen
|
|
228
|
+
self._check = check
|
|
215
229
|
|
|
216
230
|
def __exit__(self, exc_type, exc, tb):
|
|
231
|
+
# Wait for command to finish before closing everything
|
|
232
|
+
self.wait()
|
|
233
|
+
|
|
234
|
+
print("closing popen")
|
|
235
|
+
# Close popen
|
|
217
236
|
self._popen.__exit__(exc_type, exc, tb)
|
|
218
|
-
|
|
237
|
+
|
|
238
|
+
if self._display_pool:
|
|
239
|
+
print("closing _display_pool")
|
|
240
|
+
self._display_pool.__exit__(exc_type, exc, tb)
|
|
241
|
+
|
|
242
|
+
# Close stdout and stderr
|
|
243
|
+
if self.stdout:
|
|
244
|
+
print("closing stdout")
|
|
245
|
+
self.stdout.close()
|
|
246
|
+
if self.stderr:
|
|
247
|
+
print("closing stderr")
|
|
248
|
+
self.stderr.close()
|
|
219
249
|
|
|
220
250
|
def close(self) -> None:
|
|
221
251
|
self.__exit__(None, None, None)
|
|
222
252
|
|
|
253
|
+
def terminate(self) -> None:
|
|
254
|
+
self._popen.terminate()
|
|
255
|
+
self.close()
|
|
256
|
+
|
|
223
257
|
def result(self) -> ShellResult[RawIOBase | None, RawIOBase | None]:
|
|
224
|
-
|
|
258
|
+
# Get result
|
|
259
|
+
result = ShellResult(
|
|
225
260
|
cmd=self.cmd,
|
|
226
261
|
returncode=self._popen.wait(),
|
|
227
262
|
stdout=self.stdout,
|
|
228
263
|
stderr=self.stderr,
|
|
229
264
|
)
|
|
230
265
|
|
|
266
|
+
# Wait for our async reader to be complete
|
|
267
|
+
if self._stdout_async_result:
|
|
268
|
+
self._stdout_async_result.wait()
|
|
269
|
+
|
|
270
|
+
if self._stderr_async_result:
|
|
271
|
+
self._stderr_async_result.wait()
|
|
272
|
+
|
|
273
|
+
# Check if set
|
|
274
|
+
if self._check:
|
|
275
|
+
result.check_returncode()
|
|
276
|
+
|
|
277
|
+
return result
|
|
278
|
+
|
|
231
279
|
|
|
232
280
|
class CommonDefaultBackend(ShellBackend):
|
|
233
281
|
__slots__ = ()
|
|
@@ -245,106 +293,18 @@ class CommonDefaultBackend(ShellBackend):
|
|
|
245
293
|
echo_cmd: DisplayMode | display.IODisplay | bool | None = None,
|
|
246
294
|
**kwargs,
|
|
247
295
|
) -> ShellResult[bytes | None, bytes | None]:
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
if echo_cmd is None:
|
|
261
|
-
echo_cmd = DisplayMode.DEBUG_LOG
|
|
262
|
-
|
|
263
|
-
if isinstance(echo_cmd, display.IODisplay):
|
|
264
|
-
echo_display = echo_cmd
|
|
265
|
-
else:
|
|
266
|
-
echo_display = display.from_mode(echo_cmd)
|
|
267
|
-
|
|
268
|
-
if isinstance(cmd, str):
|
|
269
|
-
echo_display.printline("%s> %s", cwd or "", cmd)
|
|
270
|
-
else:
|
|
271
|
-
echo_display.printline("%s> %s", cwd or "", shlex.join(cmd))
|
|
272
|
-
|
|
273
|
-
# Determine PIPE mode for stdout
|
|
274
|
-
stdout_sub_mode = _determine_subprocess_output_mode(
|
|
275
|
-
stdout, stdout_display, "stdout"
|
|
276
|
-
)
|
|
277
|
-
|
|
278
|
-
# Determine PIPE mode for stderr
|
|
279
|
-
stderr_sub_mode = _determine_subprocess_output_mode(
|
|
280
|
-
stderr, stderr_display, "stderr"
|
|
281
|
-
)
|
|
282
|
-
|
|
283
|
-
if stdout_display is None:
|
|
284
|
-
stdout_display = (
|
|
285
|
-
DisplayMode.STDOUT if stdout is None else DisplayMode.DEVNULL
|
|
286
|
-
)
|
|
287
|
-
|
|
288
|
-
if stderr_display is None:
|
|
289
|
-
stderr_display = (
|
|
290
|
-
DisplayMode.STDERR if stderr is None else DisplayMode.DEVNULL
|
|
291
|
-
)
|
|
292
|
-
|
|
293
|
-
# Run subprocess command
|
|
294
|
-
with (
|
|
295
|
-
subprocess.Popen(
|
|
296
|
-
cmd,
|
|
297
|
-
shell=shell,
|
|
298
|
-
stdout=stdout_sub_mode,
|
|
299
|
-
stderr=stderr_sub_mode,
|
|
300
|
-
cwd=cwd,
|
|
301
|
-
) as popen,
|
|
302
|
-
ThreadPool(2) as display_pool,
|
|
303
|
-
):
|
|
304
|
-
# Print out and capture stdout
|
|
305
|
-
stdout_result = display_pool.apply_async(
|
|
306
|
-
_output_and_collect,
|
|
307
|
-
args=[
|
|
308
|
-
popen.stdout,
|
|
309
|
-
(
|
|
310
|
-
stdout_display
|
|
311
|
-
if isinstance(stdout_display, display.IODisplay)
|
|
312
|
-
else display.from_mode(stdout_display)
|
|
313
|
-
),
|
|
314
|
-
stdout is not None and stdout != CaptureMode.DEVNULL,
|
|
315
|
-
],
|
|
316
|
-
)
|
|
317
|
-
|
|
318
|
-
# Print out and capture stderr
|
|
319
|
-
stderr_result = display_pool.apply_async(
|
|
320
|
-
_output_and_collect,
|
|
321
|
-
args=[
|
|
322
|
-
popen.stderr,
|
|
323
|
-
(
|
|
324
|
-
stderr_display
|
|
325
|
-
if isinstance(stderr_display, display.IODisplay)
|
|
326
|
-
else display.from_mode(stderr_display)
|
|
327
|
-
),
|
|
328
|
-
stderr is not None and stderr != CaptureMode.DEVNULL,
|
|
329
|
-
],
|
|
330
|
-
)
|
|
331
|
-
|
|
332
|
-
# Wait on command completion
|
|
333
|
-
returncode = popen.wait()
|
|
334
|
-
|
|
335
|
-
# Build our result
|
|
336
|
-
result = ShellResult(
|
|
337
|
-
cmd=cmd,
|
|
338
|
-
stdout=stdout_result.get(),
|
|
339
|
-
stderr=stderr_result.get(),
|
|
340
|
-
returncode=returncode,
|
|
341
|
-
)
|
|
342
|
-
|
|
343
|
-
# If check is True, raise exception if returncode is not 0
|
|
344
|
-
if check:
|
|
345
|
-
result.check_returncode()
|
|
346
|
-
|
|
347
|
-
return result
|
|
296
|
+
with self.run_async(
|
|
297
|
+
cmd,
|
|
298
|
+
check=check,
|
|
299
|
+
stdout=stdout,
|
|
300
|
+
stderr=stderr,
|
|
301
|
+
stdout_display=stdout_display,
|
|
302
|
+
stderr_display=stderr_display,
|
|
303
|
+
cwd=cwd,
|
|
304
|
+
echo_cmd=echo_cmd,
|
|
305
|
+
**kwargs,
|
|
306
|
+
) as running_cmd:
|
|
307
|
+
return running_cmd.result_bytes()
|
|
348
308
|
|
|
349
309
|
def run_async(
|
|
350
310
|
self,
|
|
@@ -414,16 +374,19 @@ class CommonDefaultBackend(ShellBackend):
|
|
|
414
374
|
)
|
|
415
375
|
|
|
416
376
|
return CommonShellAcyncResult(
|
|
417
|
-
cmd,
|
|
418
|
-
popen,
|
|
419
|
-
|
|
377
|
+
cmd=cmd,
|
|
378
|
+
popen=popen,
|
|
379
|
+
stdout_capture=stdout,
|
|
380
|
+
stderr_capture=stderr,
|
|
381
|
+
stdout_display=(
|
|
420
382
|
stdout_display
|
|
421
383
|
if isinstance(stdout_display, display.IODisplay)
|
|
422
384
|
else display.from_mode(stdout_display)
|
|
423
385
|
),
|
|
424
|
-
(
|
|
386
|
+
stderr_display=(
|
|
425
387
|
stderr_display
|
|
426
388
|
if isinstance(stderr_display, display.IODisplay)
|
|
427
389
|
else display.from_mode(stderr_display)
|
|
428
390
|
),
|
|
391
|
+
check=check,
|
|
429
392
|
)
|
|
@@ -1,19 +1,13 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
1
|
import logging
|
|
4
2
|
import shlex
|
|
5
|
-
from
|
|
3
|
+
from pathlib import Path
|
|
6
4
|
|
|
7
5
|
from shelltastic.backend.base import RemoteShellBackend
|
|
8
6
|
from shelltastic.backend.default.common import CommonDefaultBackend
|
|
9
7
|
from shelltastic.enum import CaptureMode
|
|
10
8
|
from shelltastic.exception import ShellException, SSHConnectionError
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
from pathlib import Path
|
|
14
|
-
|
|
15
|
-
from shelltastic.host import Host
|
|
16
|
-
from shelltastic.result import ShellResult
|
|
9
|
+
from shelltastic.host import Host
|
|
10
|
+
from shelltastic.result import ShellResult
|
|
17
11
|
|
|
18
12
|
LOGGER = logging.getLogger(__name__)
|
|
19
13
|
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from io import RawIOBase
|
|
4
|
+
|
|
5
|
+
from shelltastic.exception import ShellException
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class ShellResult[SO: RawIOBase | bytes | None, SE: RawIOBase | bytes | None]:
|
|
10
|
+
cmd: str | list[str]
|
|
11
|
+
returncode: int
|
|
12
|
+
stdout: SO
|
|
13
|
+
stderr: SE
|
|
14
|
+
|
|
15
|
+
def check_returncode(self):
|
|
16
|
+
"""If returncode is not 0, raise ShellException"""
|
|
17
|
+
if self.returncode != 0:
|
|
18
|
+
raise ShellException(self.cmd, self.returncode, self.stdout, self.stderr)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ShellResultAsync[SO: RawIOBase | None, SE: RawIOBase | None](ABC):
|
|
22
|
+
"""
|
|
23
|
+
An asynchronsly running shell command.
|
|
24
|
+
|
|
25
|
+
cmd: String for the running shell command.
|
|
26
|
+
|
|
27
|
+
stdout: Bytes stream for stdout
|
|
28
|
+
|
|
29
|
+
stderr: Bytes stream for stderr
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(self, cmd: str | list[str], stdout: SO, stderr: SE) -> None:
|
|
33
|
+
self.cmd = cmd
|
|
34
|
+
"""The running command"""
|
|
35
|
+
|
|
36
|
+
self.stdout = stdout
|
|
37
|
+
"""Byte based IO stream for stdout"""
|
|
38
|
+
|
|
39
|
+
self.stderr = stderr
|
|
40
|
+
"""Byte based IO stream for stderr"""
|
|
41
|
+
|
|
42
|
+
def __enter__(self):
|
|
43
|
+
return self
|
|
44
|
+
|
|
45
|
+
def __exit__(self, exc_type, exc, tb):
|
|
46
|
+
self.close()
|
|
47
|
+
|
|
48
|
+
def close(self) -> None:
|
|
49
|
+
"""Close the running command."""
|
|
50
|
+
return
|
|
51
|
+
|
|
52
|
+
def terminate(self) -> None:
|
|
53
|
+
"""Terminate the running command"""
|
|
54
|
+
raise NotImplementedError()
|
|
55
|
+
|
|
56
|
+
@abstractmethod
|
|
57
|
+
def result(self) -> ShellResult[SO, SE]:
|
|
58
|
+
"""
|
|
59
|
+
Blocking
|
|
60
|
+
|
|
61
|
+
Wait for the command to finish and return a ShellResult with the outcome.
|
|
62
|
+
"""
|
|
63
|
+
raise NotImplementedError()
|
|
64
|
+
|
|
65
|
+
def wait(self) -> None:
|
|
66
|
+
"""
|
|
67
|
+
Blocking
|
|
68
|
+
|
|
69
|
+
Wait for the command to finish.
|
|
70
|
+
"""
|
|
71
|
+
self.result()
|
|
72
|
+
|
|
73
|
+
def returncode(self) -> int:
|
|
74
|
+
"""
|
|
75
|
+
Blocking
|
|
76
|
+
|
|
77
|
+
Get the returncode of the command.
|
|
78
|
+
"""
|
|
79
|
+
return self.result().returncode
|
|
80
|
+
|
|
81
|
+
def check_returncode(self):
|
|
82
|
+
"""
|
|
83
|
+
Blocking
|
|
84
|
+
|
|
85
|
+
Raise a ShellException if the returncode is not zero.
|
|
86
|
+
"""
|
|
87
|
+
self.result().check_returncode()
|
|
88
|
+
|
|
89
|
+
def result_bytes(self) -> ShellResult[bytes | None, bytes | None]:
|
|
90
|
+
result = self.result()
|
|
91
|
+
stdout = result.stdout
|
|
92
|
+
stdout_bytes = stdout.readall() if stdout is not None else None
|
|
93
|
+
stderr = result.stderr
|
|
94
|
+
stderr_bytes = stderr.readall() if stderr is not None else None
|
|
95
|
+
|
|
96
|
+
return ShellResult(
|
|
97
|
+
cmd=result.cmd,
|
|
98
|
+
returncode=result.returncode,
|
|
99
|
+
stdout=stdout_bytes,
|
|
100
|
+
stderr=stderr_bytes,
|
|
101
|
+
)
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
from abc import ABC, abstractmethod
|
|
2
|
-
from dataclasses import dataclass
|
|
3
|
-
from io import RawIOBase
|
|
4
|
-
|
|
5
|
-
from shelltastic.exception import ShellException
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
@dataclass
|
|
9
|
-
class ShellResult[SO: RawIOBase | bytes | None, SE: RawIOBase | bytes | None]:
|
|
10
|
-
cmd: str | list[str]
|
|
11
|
-
returncode: int
|
|
12
|
-
stdout: SO
|
|
13
|
-
stderr: SE
|
|
14
|
-
|
|
15
|
-
def check_returncode(self):
|
|
16
|
-
"""If returncode is not 0, raise ShellException"""
|
|
17
|
-
if self.returncode != 0:
|
|
18
|
-
raise ShellException(self.cmd, self.returncode, self.stdout, self.stderr)
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
class ShellResultAsync[SO: RawIOBase | None, SE: RawIOBase | None](ABC):
|
|
22
|
-
def __init__(self, cmd: str | list[str], stdout: SO, stderr: SE) -> None:
|
|
23
|
-
self.cmd = cmd
|
|
24
|
-
self.stdout = stdout
|
|
25
|
-
self.stderr = stderr
|
|
26
|
-
|
|
27
|
-
def __enter__(self):
|
|
28
|
-
return self
|
|
29
|
-
|
|
30
|
-
def __exit__(self, exc_type, exc, tb):
|
|
31
|
-
self.close()
|
|
32
|
-
|
|
33
|
-
def close(self) -> None:
|
|
34
|
-
return
|
|
35
|
-
|
|
36
|
-
@abstractmethod
|
|
37
|
-
def result(self) -> ShellResult[SO, SE]:
|
|
38
|
-
raise NotImplementedError()
|
|
39
|
-
|
|
40
|
-
def wait(self) -> None:
|
|
41
|
-
self.result()
|
|
42
|
-
|
|
43
|
-
def returncode(self) -> int:
|
|
44
|
-
return self.result().returncode
|
|
45
|
-
|
|
46
|
-
def check_returncode(self):
|
|
47
|
-
self.result().check_returncode()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|