shelltastic 0.4.0__tar.gz → 0.4.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.4.0 → shelltastic-0.4.1}/PKG-INFO +1 -1
- {shelltastic-0.4.0 → shelltastic-0.4.1}/pyproject.toml +1 -1
- {shelltastic-0.4.0 → shelltastic-0.4.1}/src/shelltastic/__init__.py +2 -0
- shelltastic-0.4.1/src/shelltastic/backend/__init__.py +35 -0
- {shelltastic-0.4.0 → shelltastic-0.4.1}/src/shelltastic/backend/base.py +10 -15
- shelltastic-0.4.0/src/shelltastic/backend/__init__.py → shelltastic-0.4.1/src/shelltastic/backend/default/common.py +3 -56
- shelltastic-0.4.1/src/shelltastic/backend/default/local.py +14 -0
- shelltastic-0.4.1/src/shelltastic/backend/default/remote.py +65 -0
- shelltastic-0.4.1/src/shelltastic/frontend/__init__.py +0 -0
- {shelltastic-0.4.0 → shelltastic-0.4.1}/src/shelltastic/frontend/git.py +6 -6
- {shelltastic-0.4.0 → shelltastic-0.4.1}/src/shelltastic/frontend/scp.py +3 -3
- {shelltastic-0.4.0 → shelltastic-0.4.1}/src/shelltastic/frontend/shell.py +5 -4
- shelltastic-0.4.1/src/shelltastic/result.py +16 -0
- {shelltastic-0.4.0 → shelltastic-0.4.1}/LICENSE +0 -0
- {shelltastic-0.4.0 → shelltastic-0.4.1}/README.md +0 -0
- {shelltastic-0.4.0/src/shelltastic/frontend → shelltastic-0.4.1/src/shelltastic/backend/default}/__init__.py +0 -0
- {shelltastic-0.4.0 → shelltastic-0.4.1}/src/shelltastic/display.py +0 -0
- {shelltastic-0.4.0 → shelltastic-0.4.1}/src/shelltastic/enum.py +0 -0
- {shelltastic-0.4.0 → shelltastic-0.4.1}/src/shelltastic/exception.py +0 -0
- {shelltastic-0.4.0 → shelltastic-0.4.1}/src/shelltastic/frontend/common.py +0 -0
- {shelltastic-0.4.0 → shelltastic-0.4.1}/src/shelltastic/host.py +0 -0
|
@@ -3,6 +3,7 @@ from shelltastic.frontend.git import LocalGitFrontend
|
|
|
3
3
|
from shelltastic.frontend.scp import SCP
|
|
4
4
|
from shelltastic.frontend.shell import LocalShellFrontend
|
|
5
5
|
from shelltastic.host import Host
|
|
6
|
+
from shelltastic.result import ShellResult
|
|
6
7
|
|
|
7
8
|
__all__ = [
|
|
8
9
|
"shell",
|
|
@@ -12,6 +13,7 @@ __all__ = [
|
|
|
12
13
|
"CaptureMode",
|
|
13
14
|
"SystemType",
|
|
14
15
|
"Host",
|
|
16
|
+
"ShellResult",
|
|
15
17
|
]
|
|
16
18
|
|
|
17
19
|
shell = LocalShellFrontend()
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from typing import Type
|
|
2
|
+
|
|
3
|
+
from shelltastic.backend.base import LocalShellBackend, RemoteShellBackend
|
|
4
|
+
from shelltastic.backend.default.local import DefaultLocalBackend
|
|
5
|
+
from shelltastic.backend.default.remote import DefaultRemoteBackend
|
|
6
|
+
from shelltastic.host import Host
|
|
7
|
+
|
|
8
|
+
_default_local_backend: Type[LocalShellBackend] = DefaultLocalBackend
|
|
9
|
+
_default_remote_backend: Type[RemoteShellBackend] = DefaultRemoteBackend
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def register_default_local_backend(backend: Type[LocalShellBackend]):
|
|
13
|
+
global _default_local_backend
|
|
14
|
+
_default_local_backend = backend
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def register_default_remote_backend(backend: Type[RemoteShellBackend]):
|
|
18
|
+
global _default_remote_backend
|
|
19
|
+
_default_remote_backend = backend
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def get_local_backend(
|
|
23
|
+
*, backend: Type[LocalShellBackend] | None = None
|
|
24
|
+
) -> LocalShellBackend:
|
|
25
|
+
if backend:
|
|
26
|
+
return backend.local()
|
|
27
|
+
return _default_local_backend.local()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def get_remote_backend(
|
|
31
|
+
host: Host, *, backend: Type[RemoteShellBackend] | None = None
|
|
32
|
+
) -> RemoteShellBackend:
|
|
33
|
+
if backend:
|
|
34
|
+
return backend.for_host(host)
|
|
35
|
+
return _default_remote_backend.for_host(host)
|
|
@@ -1,26 +1,12 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from abc import ABC, abstractmethod
|
|
4
|
-
from dataclasses import dataclass
|
|
5
4
|
from pathlib import Path
|
|
6
5
|
|
|
7
6
|
from shelltastic.display import IODisplay
|
|
8
7
|
from shelltastic.enum import CaptureMode, DisplayMode
|
|
9
|
-
from shelltastic.exception import ShellException
|
|
10
8
|
from shelltastic.host import Host
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
@dataclass
|
|
14
|
-
class ShellResult:
|
|
15
|
-
cmd: str | list[str]
|
|
16
|
-
returncode: int
|
|
17
|
-
stdout: bytes | None
|
|
18
|
-
stderr: bytes | None
|
|
19
|
-
|
|
20
|
-
def check_returncode(self):
|
|
21
|
-
"""If returncode is not 0, raise ShellException"""
|
|
22
|
-
if self.returncode != 0:
|
|
23
|
-
raise ShellException(self.cmd, self.returncode, self.stdout, self.stderr)
|
|
9
|
+
from shelltastic.result import ShellResult
|
|
24
10
|
|
|
25
11
|
|
|
26
12
|
class ShellBackend(ABC):
|
|
@@ -43,6 +29,15 @@ class ShellBackend(ABC):
|
|
|
43
29
|
raise NotImplementedError()
|
|
44
30
|
|
|
45
31
|
|
|
32
|
+
class LocalShellBackend(ShellBackend):
|
|
33
|
+
__slots__ = ()
|
|
34
|
+
|
|
35
|
+
@staticmethod
|
|
36
|
+
@abstractmethod
|
|
37
|
+
def local() -> LocalShellBackend:
|
|
38
|
+
raise NotImplementedError()
|
|
39
|
+
|
|
40
|
+
|
|
46
41
|
class RemoteShellBackend(ShellBackend):
|
|
47
42
|
__slots__ = ()
|
|
48
43
|
|
|
@@ -8,10 +8,9 @@ from multiprocessing.pool import ThreadPool
|
|
|
8
8
|
from typing import IO, Literal
|
|
9
9
|
|
|
10
10
|
from shelltastic import display
|
|
11
|
-
from shelltastic.backend.base import
|
|
11
|
+
from shelltastic.backend.base import ShellBackend
|
|
12
12
|
from shelltastic.enum import CaptureMode, DisplayMode
|
|
13
|
-
from shelltastic.
|
|
14
|
-
from shelltastic.host import Host
|
|
13
|
+
from shelltastic.result import ShellResult
|
|
15
14
|
|
|
16
15
|
LOGGER = logging.getLogger(__name__)
|
|
17
16
|
|
|
@@ -87,7 +86,7 @@ def _determine_subprocess_output_mode(
|
|
|
87
86
|
return subprocess.PIPE
|
|
88
87
|
|
|
89
88
|
|
|
90
|
-
class
|
|
89
|
+
class CommonDefaultBackend(ShellBackend):
|
|
91
90
|
__slots__ = ()
|
|
92
91
|
|
|
93
92
|
def run(
|
|
@@ -195,55 +194,3 @@ class LocalShellBackend(ShellBackend):
|
|
|
195
194
|
result.check_returncode()
|
|
196
195
|
|
|
197
196
|
return result
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
class SSHShellBackend(LocalShellBackend, RemoteShellBackend):
|
|
201
|
-
def __init__(self, host: Host) -> None:
|
|
202
|
-
super().__init__()
|
|
203
|
-
self.host: Host = host
|
|
204
|
-
|
|
205
|
-
@staticmethod
|
|
206
|
-
def for_host(host: Host) -> RemoteShellBackend:
|
|
207
|
-
return SSHShellBackend(host)
|
|
208
|
-
|
|
209
|
-
def run(
|
|
210
|
-
self,
|
|
211
|
-
cmd: str | list[str],
|
|
212
|
-
*,
|
|
213
|
-
subshell: bool = True,
|
|
214
|
-
cwd: str | pathlib.Path | None = None,
|
|
215
|
-
**kwargs,
|
|
216
|
-
) -> ShellResult:
|
|
217
|
-
"""
|
|
218
|
-
Execute a remote command using SSH.
|
|
219
|
-
"""
|
|
220
|
-
if isinstance(cmd, list):
|
|
221
|
-
cmd = shlex.join(cmd)
|
|
222
|
-
|
|
223
|
-
# Wrap user command in sh -c call
|
|
224
|
-
if subshell:
|
|
225
|
-
cmd = shlex.join(["bash", "-c", cmd])
|
|
226
|
-
|
|
227
|
-
# cd into cwd first if cwd is set
|
|
228
|
-
if cwd:
|
|
229
|
-
cmd = shlex.join(["cd", str(cwd)]) + " && " + cmd
|
|
230
|
-
|
|
231
|
-
port_flag = ["-p", str(self.host.port)] if self.host.port else []
|
|
232
|
-
|
|
233
|
-
try:
|
|
234
|
-
result = super().run(
|
|
235
|
-
["ssh", *port_flag, self.host.host_specifier(), cmd], **kwargs
|
|
236
|
-
)
|
|
237
|
-
if (
|
|
238
|
-
result.returncode == 255
|
|
239
|
-
): # 255 is what ssh returns if it has an error with ssh itself
|
|
240
|
-
raise SSHConnectionError(
|
|
241
|
-
self.host.hostname, result.returncode, result.stdout, result.stderr
|
|
242
|
-
)
|
|
243
|
-
return result
|
|
244
|
-
except subprocess.CalledProcessError as ex:
|
|
245
|
-
if ex.returncode == 255:
|
|
246
|
-
raise SSHConnectionError(
|
|
247
|
-
self.host.hostname, ex.returncode, ex.stdout, ex.stderr
|
|
248
|
-
) from ex
|
|
249
|
-
raise
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
from shelltastic.backend.base import LocalShellBackend
|
|
6
|
+
from shelltastic.backend.default.common import CommonDefaultBackend
|
|
7
|
+
|
|
8
|
+
LOGGER = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class DefaultLocalBackend(CommonDefaultBackend, LocalShellBackend):
|
|
12
|
+
@staticmethod
|
|
13
|
+
def local() -> LocalShellBackend:
|
|
14
|
+
return DefaultLocalBackend()
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import pathlib
|
|
5
|
+
import shlex
|
|
6
|
+
|
|
7
|
+
from shelltastic.backend.base import RemoteShellBackend
|
|
8
|
+
from shelltastic.backend.default.common import CommonDefaultBackend
|
|
9
|
+
from shelltastic.exception import ShellException, SSHConnectionError
|
|
10
|
+
from shelltastic.host import Host
|
|
11
|
+
from shelltastic.result import ShellResult
|
|
12
|
+
|
|
13
|
+
LOGGER = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class DefaultRemoteBackend(CommonDefaultBackend, RemoteShellBackend):
|
|
17
|
+
def __init__(self, host: Host) -> None:
|
|
18
|
+
super().__init__()
|
|
19
|
+
self.host: Host = host
|
|
20
|
+
|
|
21
|
+
@staticmethod
|
|
22
|
+
def for_host(host: Host) -> RemoteShellBackend:
|
|
23
|
+
return DefaultRemoteBackend(host)
|
|
24
|
+
|
|
25
|
+
def run(
|
|
26
|
+
self,
|
|
27
|
+
cmd: str | list[str],
|
|
28
|
+
*,
|
|
29
|
+
subshell: bool = True,
|
|
30
|
+
cwd: str | pathlib.Path | None = None,
|
|
31
|
+
**kwargs,
|
|
32
|
+
) -> ShellResult:
|
|
33
|
+
"""
|
|
34
|
+
Execute a remote command using SSH.
|
|
35
|
+
"""
|
|
36
|
+
if isinstance(cmd, list):
|
|
37
|
+
cmd = shlex.join(cmd)
|
|
38
|
+
|
|
39
|
+
# Wrap user command in sh -c call
|
|
40
|
+
if subshell:
|
|
41
|
+
cmd = shlex.join(["bash", "-c", cmd])
|
|
42
|
+
|
|
43
|
+
# cd into cwd first if cwd is set
|
|
44
|
+
if cwd:
|
|
45
|
+
cmd = shlex.join(["cd", str(cwd)]) + " && " + cmd
|
|
46
|
+
|
|
47
|
+
port_flag = ["-p", str(self.host.port)] if self.host.port else []
|
|
48
|
+
|
|
49
|
+
try:
|
|
50
|
+
result = super().run(
|
|
51
|
+
["ssh", *port_flag, self.host.host_specifier(), cmd], **kwargs
|
|
52
|
+
)
|
|
53
|
+
if (
|
|
54
|
+
result.returncode == 255
|
|
55
|
+
): # 255 is what ssh returns if it has an error with ssh itself
|
|
56
|
+
raise SSHConnectionError(
|
|
57
|
+
self.host.hostname, result.returncode, result.stdout, result.stderr
|
|
58
|
+
)
|
|
59
|
+
return result
|
|
60
|
+
except ShellException as ex:
|
|
61
|
+
if ex.returncode == 255:
|
|
62
|
+
raise SSHConnectionError(
|
|
63
|
+
self.host.hostname, ex.returncode, ex.stdout, ex.stderr
|
|
64
|
+
) from ex
|
|
65
|
+
raise
|
|
File without changes
|
|
@@ -2,7 +2,7 @@ import logging
|
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
from typing import overload
|
|
4
4
|
|
|
5
|
-
from shelltastic
|
|
5
|
+
from shelltastic import backend
|
|
6
6
|
from shelltastic.backend.base import ShellBackend
|
|
7
7
|
from shelltastic.enum import CaptureMode
|
|
8
8
|
from shelltastic.frontend.common import RemoteFrontend, RemoteFrontendFactory
|
|
@@ -29,7 +29,7 @@ class GitFrontend:
|
|
|
29
29
|
stdout=CaptureMode.PIPE,
|
|
30
30
|
cwd=repo_path,
|
|
31
31
|
)
|
|
32
|
-
assert
|
|
32
|
+
assert result.stdout is not None
|
|
33
33
|
return result.stdout.decode().strip()
|
|
34
34
|
|
|
35
35
|
def set_remote_url(
|
|
@@ -57,7 +57,7 @@ class GitFrontend:
|
|
|
57
57
|
stdout=CaptureMode.PIPE,
|
|
58
58
|
stderr=CaptureMode.PIPE,
|
|
59
59
|
)
|
|
60
|
-
assert
|
|
60
|
+
assert result.stdout is not None
|
|
61
61
|
configs = [i.strip() for i in result.stdout.decode().split("\n") if i.strip()]
|
|
62
62
|
return configs
|
|
63
63
|
|
|
@@ -90,7 +90,7 @@ class GitFrontend:
|
|
|
90
90
|
if response.returncode != 0:
|
|
91
91
|
return []
|
|
92
92
|
else:
|
|
93
|
-
assert
|
|
93
|
+
assert response.stdout is not None
|
|
94
94
|
gitcredentials = response.stdout.decode()
|
|
95
95
|
|
|
96
96
|
return gitcredentials.splitlines()
|
|
@@ -100,7 +100,7 @@ class RemoteGitFrontend(GitFrontend, RemoteFrontend):
|
|
|
100
100
|
__slots__ = ()
|
|
101
101
|
|
|
102
102
|
def __init__(self, host: Host) -> None:
|
|
103
|
-
super().__init__(
|
|
103
|
+
super().__init__(backend.get_remote_backend(host))
|
|
104
104
|
super(GitFrontend, self).__init__(host)
|
|
105
105
|
|
|
106
106
|
|
|
@@ -108,7 +108,7 @@ class LocalGitFrontend(GitFrontend, RemoteFrontendFactory[RemoteGitFrontend]):
|
|
|
108
108
|
__slots__ = ()
|
|
109
109
|
|
|
110
110
|
def __init__(self) -> None:
|
|
111
|
-
super().__init__(
|
|
111
|
+
super().__init__(backend.get_local_backend())
|
|
112
112
|
|
|
113
113
|
def _create_remote_frontend(self, host: Host) -> RemoteGitFrontend:
|
|
114
114
|
return RemoteGitFrontend(host)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
|
|
4
|
-
from shelltastic
|
|
4
|
+
from shelltastic import backend
|
|
5
5
|
from shelltastic.enum import CaptureMode
|
|
6
6
|
from shelltastic.frontend.common import RemoteFrontend, RemoteFrontendFactory
|
|
7
7
|
from shelltastic.host import Host
|
|
@@ -12,8 +12,8 @@ LOGGER = logging.getLogger(__name__)
|
|
|
12
12
|
class SCPFrontend(RemoteFrontend):
|
|
13
13
|
def __init__(self, host: Host) -> None:
|
|
14
14
|
super().__init__(host)
|
|
15
|
-
self._local_backend =
|
|
16
|
-
self._remote_backend =
|
|
15
|
+
self._local_backend = backend.get_local_backend()
|
|
16
|
+
self._remote_backend = backend.get_remote_backend(host)
|
|
17
17
|
|
|
18
18
|
def _scp_flags(self, recursive) -> list[str]:
|
|
19
19
|
# Set Port
|
|
@@ -3,12 +3,13 @@ from __future__ import annotations
|
|
|
3
3
|
import shutil
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
|
|
6
|
-
from shelltastic
|
|
7
|
-
from shelltastic.backend.base import ShellBackend
|
|
6
|
+
from shelltastic import backend
|
|
7
|
+
from shelltastic.backend.base import ShellBackend
|
|
8
8
|
from shelltastic.display import IODisplay
|
|
9
9
|
from shelltastic.enum import CaptureMode, DisplayMode, SystemType
|
|
10
10
|
from shelltastic.frontend.common import RemoteFrontend, RemoteFrontendFactory
|
|
11
11
|
from shelltastic.host import Host
|
|
12
|
+
from shelltastic.result import ShellResult
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
class ShellFrontend:
|
|
@@ -91,7 +92,7 @@ class RemoteShellFrontend(ShellFrontend, RemoteFrontend):
|
|
|
91
92
|
__slots__ = ()
|
|
92
93
|
|
|
93
94
|
def __init__(self, host: Host) -> None:
|
|
94
|
-
super().__init__(
|
|
95
|
+
super().__init__(backend.get_remote_backend(host))
|
|
95
96
|
super(ShellFrontend, self).__init__(host)
|
|
96
97
|
|
|
97
98
|
|
|
@@ -99,7 +100,7 @@ class LocalShellFrontend(ShellFrontend, RemoteFrontendFactory[RemoteShellFronten
|
|
|
99
100
|
__slots__ = ()
|
|
100
101
|
|
|
101
102
|
def __init__(self) -> None:
|
|
102
|
-
super().__init__(
|
|
103
|
+
super().__init__(backend.get_local_backend())
|
|
103
104
|
|
|
104
105
|
def _create_remote_frontend(self, host: Host) -> RemoteShellFrontend:
|
|
105
106
|
return RemoteShellFrontend(host)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
from shelltastic.exception import ShellException
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass
|
|
7
|
+
class ShellResult:
|
|
8
|
+
cmd: str | list[str]
|
|
9
|
+
returncode: int
|
|
10
|
+
stdout: bytes | None
|
|
11
|
+
stderr: bytes | None
|
|
12
|
+
|
|
13
|
+
def check_returncode(self):
|
|
14
|
+
"""If returncode is not 0, raise ShellException"""
|
|
15
|
+
if self.returncode != 0:
|
|
16
|
+
raise ShellException(self.cmd, self.returncode, self.stdout, self.stderr)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|