shelltastic 0.8.2__tar.gz → 0.9.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.
- {shelltastic-0.8.2 → shelltastic-0.9.1}/PKG-INFO +1 -1
- shelltastic-0.9.1/pyproject.toml +59 -0
- shelltastic-0.8.2/pyproject.toml → shelltastic-0.9.1/pyproject.toml.orig +16 -12
- {shelltastic-0.8.2 → shelltastic-0.9.1}/src/shelltastic/backend/base.py +0 -2
- {shelltastic-0.8.2 → shelltastic-0.9.1}/src/shelltastic/backend/default/common.py +19 -15
- {shelltastic-0.8.2 → shelltastic-0.9.1}/src/shelltastic/enum.py +2 -0
- {shelltastic-0.8.2 → shelltastic-0.9.1}/src/shelltastic/frontend/shell.py +59 -13
- {shelltastic-0.8.2 → shelltastic-0.9.1}/LICENSE +0 -0
- {shelltastic-0.8.2 → shelltastic-0.9.1}/README.md +0 -0
- {shelltastic-0.8.2 → shelltastic-0.9.1}/src/shelltastic/__init__.py +5 -5
- {shelltastic-0.8.2 → shelltastic-0.9.1}/src/shelltastic/backend/__init__.py +3 -3
- {shelltastic-0.8.2 → shelltastic-0.9.1}/src/shelltastic/backend/default/__init__.py +0 -0
- {shelltastic-0.8.2 → shelltastic-0.9.1}/src/shelltastic/backend/default/local.py +0 -0
- {shelltastic-0.8.2 → shelltastic-0.9.1}/src/shelltastic/backend/default/remote.py +0 -0
- {shelltastic-0.8.2 → shelltastic-0.9.1}/src/shelltastic/display.py +0 -0
- {shelltastic-0.8.2 → shelltastic-0.9.1}/src/shelltastic/exception.py +0 -0
- {shelltastic-0.8.2 → shelltastic-0.9.1}/src/shelltastic/frontend/__init__.py +0 -0
- {shelltastic-0.8.2 → shelltastic-0.9.1}/src/shelltastic/frontend/common.py +0 -0
- {shelltastic-0.8.2 → shelltastic-0.9.1}/src/shelltastic/frontend/git.py +0 -0
- {shelltastic-0.8.2 → shelltastic-0.9.1}/src/shelltastic/frontend/scp.py +0 -0
- {shelltastic-0.8.2 → shelltastic-0.9.1}/src/shelltastic/host.py +0 -0
- {shelltastic-0.8.2 → shelltastic-0.9.1}/src/shelltastic/io.py +0 -0
- {shelltastic-0.8.2 → shelltastic-0.9.1}/src/shelltastic/result.py +0 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "shelltastic"
|
|
3
|
+
version = "0.9.1"
|
|
4
|
+
description = "A fantastic shell command runner for python"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MPL-2.0"
|
|
7
|
+
license-files = ["LICENSE"]
|
|
8
|
+
requires-python = ">=3.12"
|
|
9
|
+
dependencies = []
|
|
10
|
+
|
|
11
|
+
[[project.authors]]
|
|
12
|
+
name = "Bearmine"
|
|
13
|
+
|
|
14
|
+
[project.urls]
|
|
15
|
+
Repository = "https://forge.bearmine.com/bearmine/shelltastic"
|
|
16
|
+
|
|
17
|
+
[dependency-groups]
|
|
18
|
+
lint = ["ruff"]
|
|
19
|
+
test = [
|
|
20
|
+
"pytest",
|
|
21
|
+
"pytest-cov>=7.1.0",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
[[dependency-groups.dev]]
|
|
25
|
+
include-group = "lint"
|
|
26
|
+
|
|
27
|
+
[[dependency-groups.dev]]
|
|
28
|
+
include-group = "test"
|
|
29
|
+
|
|
30
|
+
[build-system]
|
|
31
|
+
requires = ["uv_build>=0.11.18,<0.12.0"]
|
|
32
|
+
build-backend = "uv_build"
|
|
33
|
+
|
|
34
|
+
[tool.ruff.lint]
|
|
35
|
+
extend-select = [
|
|
36
|
+
"F",
|
|
37
|
+
"W",
|
|
38
|
+
"E",
|
|
39
|
+
"I",
|
|
40
|
+
"UP",
|
|
41
|
+
"C4",
|
|
42
|
+
"FA",
|
|
43
|
+
"ISC",
|
|
44
|
+
"ICN",
|
|
45
|
+
"RET",
|
|
46
|
+
"SIM",
|
|
47
|
+
"TID",
|
|
48
|
+
"TC",
|
|
49
|
+
"PTH",
|
|
50
|
+
"TD",
|
|
51
|
+
"NPY",
|
|
52
|
+
"FURB",
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
[tool.ruff.lint.pycodestyle]
|
|
56
|
+
max-line-length = 100
|
|
57
|
+
|
|
58
|
+
[tool.pytest.ini_options]
|
|
59
|
+
addopts = "--cov=shelltastic --cov-report html"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "shelltastic"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.9.1"
|
|
4
4
|
description = "A fantastic shell command runner for python"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [
|
|
@@ -13,6 +13,19 @@ license-files = [
|
|
|
13
13
|
requires-python = ">=3.12"
|
|
14
14
|
dependencies = []
|
|
15
15
|
|
|
16
|
+
[dependency-groups]
|
|
17
|
+
dev = [
|
|
18
|
+
{include-group = "lint"},
|
|
19
|
+
{include-group = "test"},
|
|
20
|
+
]
|
|
21
|
+
lint = [
|
|
22
|
+
"ruff"
|
|
23
|
+
]
|
|
24
|
+
test = [
|
|
25
|
+
"pytest",
|
|
26
|
+
"pytest-cov>=7.1.0"
|
|
27
|
+
]
|
|
28
|
+
|
|
16
29
|
[project.urls]
|
|
17
30
|
Repository = "https://forge.bearmine.com/bearmine/shelltastic"
|
|
18
31
|
|
|
@@ -44,14 +57,5 @@ extend-select = [
|
|
|
44
57
|
[tool.ruff.lint.pycodestyle]
|
|
45
58
|
max-line-length = 100
|
|
46
59
|
|
|
47
|
-
[
|
|
48
|
-
|
|
49
|
-
{include-group = "lint"},
|
|
50
|
-
{include-group = "test"}
|
|
51
|
-
]
|
|
52
|
-
lint = [
|
|
53
|
-
"ruff"
|
|
54
|
-
]
|
|
55
|
-
test = [
|
|
56
|
-
"pytest"
|
|
57
|
-
]
|
|
60
|
+
[tool.pytest.ini_options]
|
|
61
|
+
addopts = "--cov=shelltastic --cov-report html"
|
|
@@ -92,12 +92,10 @@ _backend_shims: list[BackendShim] = []
|
|
|
92
92
|
|
|
93
93
|
def clear_shims():
|
|
94
94
|
"""Clear all shims"""
|
|
95
|
-
global _backend_shims
|
|
96
95
|
_backend_shims.clear()
|
|
97
96
|
|
|
98
97
|
def add_shim(shim: BackendShim):
|
|
99
98
|
"""Add a shim to manipulate `ShellBackend` params before any `ShellBackend` runs"""
|
|
100
|
-
global _backend_shims
|
|
101
99
|
_backend_shims.append(shim)
|
|
102
100
|
|
|
103
101
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import contextlib
|
|
1
2
|
import logging
|
|
2
3
|
import os
|
|
3
4
|
import shlex
|
|
@@ -233,32 +234,35 @@ class CommonShellAsyncResult(ShellResultAsync[RawIOBase | None, RawIOBase | None
|
|
|
233
234
|
return self._stderr
|
|
234
235
|
|
|
235
236
|
def __exit__(self, exc_type, exc, tb):
|
|
236
|
-
|
|
237
|
+
with contextlib.suppress(KeyboardInterrupt, subprocess.TimeoutExpired):
|
|
238
|
+
self.close()
|
|
237
239
|
|
|
238
|
-
def close(self) -> None:
|
|
240
|
+
def close(self, timeout: float | None = None) -> None:
|
|
239
241
|
if self._closed:
|
|
240
242
|
return
|
|
241
243
|
|
|
242
|
-
# Terminate popen
|
|
243
|
-
self._popen.terminate()
|
|
244
|
-
|
|
245
244
|
# Wait for command to finish before closing everything
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
self.
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
245
|
+
try:
|
|
246
|
+
self._popen.wait(timeout)
|
|
247
|
+
except (KeyboardInterrupt, subprocess.TimeoutExpired):
|
|
248
|
+
# Kill the process and then reraise
|
|
249
|
+
self._popen.kill()
|
|
250
|
+
LOGGER.error("pid %s killed", self._popen.pid)
|
|
251
|
+
raise
|
|
252
|
+
finally:
|
|
253
|
+
# Close stdout and stderr
|
|
254
|
+
if self.stdout is not None:
|
|
255
|
+
self.stdout.close()
|
|
256
|
+
if self.stderr is not None:
|
|
257
|
+
self.stderr.close()
|
|
258
|
+
|
|
259
|
+
self._closed = True
|
|
255
260
|
|
|
256
261
|
def terminate(self) -> None:
|
|
257
262
|
if self._closed:
|
|
258
263
|
raise ShellClosedException(f"{self} is closed")
|
|
259
264
|
|
|
260
265
|
self._popen.terminate()
|
|
261
|
-
self.close()
|
|
262
266
|
|
|
263
267
|
def result(self) -> ShellResult[RawIOBase | None, RawIOBase | None]:
|
|
264
268
|
if self._closed:
|
|
@@ -18,8 +18,11 @@ if TYPE_CHECKING:
|
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
class ShellFrontend:
|
|
21
|
+
__slots__ = ("_backend", "_cached_system_type")
|
|
22
|
+
|
|
21
23
|
def __init__(self, backend: ShellBackend) -> None:
|
|
22
24
|
self._backend = backend
|
|
25
|
+
self._cached_system_type = None
|
|
23
26
|
|
|
24
27
|
def which(self, command: str) -> Path | None:
|
|
25
28
|
"""Return the path to the command or None if the command is not on the path."""
|
|
@@ -34,12 +37,20 @@ class ShellFrontend:
|
|
|
34
37
|
|
|
35
38
|
def file_exists(self, path: str | Path) -> bool:
|
|
36
39
|
"""Returns true if the file at the given path exists and false if it doesn't."""
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
if self.system_type() == SystemType.WINDOWS:
|
|
41
|
+
result = self.run(
|
|
42
|
+
f'IF EXIST "{path}" (exit 0) ELSE (exit 1)',
|
|
43
|
+
check=False,
|
|
44
|
+
stdout=CaptureMode.DEVNULL,
|
|
45
|
+
stderr=CaptureMode.DEVNULL,
|
|
46
|
+
)
|
|
47
|
+
else:
|
|
48
|
+
result = self.run(
|
|
49
|
+
f'stat "{path}"',
|
|
50
|
+
check=False,
|
|
51
|
+
stdout=CaptureMode.DEVNULL,
|
|
52
|
+
stderr=CaptureMode.DEVNULL,
|
|
53
|
+
)
|
|
43
54
|
return result.returncode == 0
|
|
44
55
|
|
|
45
56
|
def home_dir(self) -> Path:
|
|
@@ -50,20 +61,55 @@ class ShellFrontend:
|
|
|
50
61
|
assert result.stdout is not None
|
|
51
62
|
return Path(result.stdout.decode().strip())
|
|
52
63
|
|
|
53
|
-
def
|
|
54
|
-
"""Return system type"""
|
|
64
|
+
def _system_type(self) -> SystemType:
|
|
55
65
|
if (
|
|
66
|
+
"Linux"
|
|
67
|
+
in self.run(
|
|
68
|
+
"uname -o",
|
|
69
|
+
check=False,
|
|
70
|
+
stdout=CaptureMode.PIPE,
|
|
71
|
+
stderr=CaptureMode.DEVNULL,
|
|
72
|
+
).stdout.decode("utf-8") # type: ignore
|
|
73
|
+
):
|
|
74
|
+
# Linux
|
|
75
|
+
if (
|
|
76
|
+
self.run(
|
|
77
|
+
"stat /etc/debian_version",
|
|
78
|
+
check=False,
|
|
79
|
+
stdout=CaptureMode.DEVNULL,
|
|
80
|
+
stderr=CaptureMode.DEVNULL,
|
|
81
|
+
).returncode
|
|
82
|
+
== 0
|
|
83
|
+
):
|
|
84
|
+
return SystemType.DEBIAN_BASED
|
|
85
|
+
|
|
86
|
+
if "cachyos" in self.run(
|
|
87
|
+
"uname -r",
|
|
88
|
+
check=False,
|
|
89
|
+
stdout=CaptureMode.PIPE,
|
|
90
|
+
stderr=CaptureMode.DEVNULL,
|
|
91
|
+
).stdout.decode("utf-8"): # type: ignore
|
|
92
|
+
return SystemType.CACHYOS
|
|
93
|
+
elif (
|
|
56
94
|
self.run(
|
|
57
|
-
"
|
|
95
|
+
"echo %OS%",
|
|
58
96
|
check=False,
|
|
59
|
-
stdout=CaptureMode.
|
|
97
|
+
stdout=CaptureMode.PIPE,
|
|
60
98
|
stderr=CaptureMode.DEVNULL,
|
|
61
|
-
)
|
|
62
|
-
|
|
99
|
+
)
|
|
100
|
+
.stdout.decode("utf-8") # type: ignore
|
|
101
|
+
.strip()
|
|
102
|
+
== "Windows_NT"
|
|
63
103
|
):
|
|
64
|
-
return SystemType.
|
|
104
|
+
return SystemType.WINDOWS
|
|
65
105
|
return SystemType.UNKNOWN
|
|
66
106
|
|
|
107
|
+
def system_type(self) -> SystemType:
|
|
108
|
+
"""Return system type"""
|
|
109
|
+
if not self._cached_system_type:
|
|
110
|
+
self._cached_system_type = self._system_type()
|
|
111
|
+
return self._cached_system_type
|
|
112
|
+
|
|
67
113
|
def run(
|
|
68
114
|
self,
|
|
69
115
|
cmd: str | list[str],
|
|
File without changes
|
|
File without changes
|
|
@@ -6,14 +6,14 @@ from shelltastic.host import Host
|
|
|
6
6
|
from shelltastic.result import ShellResult
|
|
7
7
|
|
|
8
8
|
__all__ = [
|
|
9
|
-
"shell",
|
|
10
|
-
"scp",
|
|
11
|
-
"git",
|
|
12
|
-
"DisplayMode",
|
|
13
9
|
"CaptureMode",
|
|
14
|
-
"
|
|
10
|
+
"DisplayMode",
|
|
15
11
|
"Host",
|
|
16
12
|
"ShellResult",
|
|
13
|
+
"SystemType",
|
|
14
|
+
"git",
|
|
15
|
+
"scp",
|
|
16
|
+
"shell",
|
|
17
17
|
]
|
|
18
18
|
|
|
19
19
|
shell = LocalShellFrontend()
|
|
@@ -11,16 +11,16 @@ from shelltastic.backend.default.remote import DefaultRemoteBackend
|
|
|
11
11
|
from shelltastic.host import Host
|
|
12
12
|
|
|
13
13
|
__all__ = [
|
|
14
|
+
"BackendShim",
|
|
14
15
|
"LocalShellBackend",
|
|
15
16
|
"RemoteShellBackend",
|
|
16
|
-
"BackendShim",
|
|
17
17
|
"ShellBackend",
|
|
18
|
-
"register_default_local_backend",
|
|
19
|
-
"register_default_remote_backend",
|
|
20
18
|
"add_shim",
|
|
21
19
|
"clear_shims",
|
|
22
20
|
"get_local_backend",
|
|
23
21
|
"get_remote_backend",
|
|
22
|
+
"register_default_local_backend",
|
|
23
|
+
"register_default_remote_backend",
|
|
24
24
|
]
|
|
25
25
|
|
|
26
26
|
_default_local_backend: type[LocalShellBackend] = DefaultLocalBackend
|
|
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
|