shelltastic 0.4.3__tar.gz → 0.4.5__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.3 → shelltastic-0.4.5}/PKG-INFO +1 -1
- {shelltastic-0.4.3 → shelltastic-0.4.5}/pyproject.toml +1 -1
- {shelltastic-0.4.3 → shelltastic-0.4.5}/src/shelltastic/backend/__init__.py +19 -1
- shelltastic-0.4.5/src/shelltastic/backend/base.py +131 -0
- {shelltastic-0.4.3 → shelltastic-0.4.5}/src/shelltastic/backend/default/remote.py +6 -0
- {shelltastic-0.4.3 → shelltastic-0.4.5}/src/shelltastic/display.py +14 -19
- shelltastic-0.4.3/src/shelltastic/backend/base.py +0 -50
- {shelltastic-0.4.3 → shelltastic-0.4.5}/LICENSE +0 -0
- {shelltastic-0.4.3 → shelltastic-0.4.5}/README.md +0 -0
- {shelltastic-0.4.3 → shelltastic-0.4.5}/src/shelltastic/__init__.py +0 -0
- {shelltastic-0.4.3 → shelltastic-0.4.5}/src/shelltastic/backend/default/__init__.py +0 -0
- {shelltastic-0.4.3 → shelltastic-0.4.5}/src/shelltastic/backend/default/common.py +0 -0
- {shelltastic-0.4.3 → shelltastic-0.4.5}/src/shelltastic/backend/default/local.py +0 -0
- {shelltastic-0.4.3 → shelltastic-0.4.5}/src/shelltastic/enum.py +0 -0
- {shelltastic-0.4.3 → shelltastic-0.4.5}/src/shelltastic/exception.py +0 -0
- {shelltastic-0.4.3 → shelltastic-0.4.5}/src/shelltastic/frontend/__init__.py +0 -0
- {shelltastic-0.4.3 → shelltastic-0.4.5}/src/shelltastic/frontend/common.py +0 -0
- {shelltastic-0.4.3 → shelltastic-0.4.5}/src/shelltastic/frontend/git.py +0 -0
- {shelltastic-0.4.3 → shelltastic-0.4.5}/src/shelltastic/frontend/scp.py +0 -0
- {shelltastic-0.4.3 → shelltastic-0.4.5}/src/shelltastic/frontend/shell.py +0 -0
- {shelltastic-0.4.3 → shelltastic-0.4.5}/src/shelltastic/host.py +0 -0
- {shelltastic-0.4.3 → shelltastic-0.4.5}/src/shelltastic/result.py +0 -0
|
@@ -1,8 +1,26 @@
|
|
|
1
|
-
from shelltastic.backend.base import
|
|
1
|
+
from shelltastic.backend.base import (
|
|
2
|
+
BackendShim,
|
|
3
|
+
LocalShellBackend,
|
|
4
|
+
RemoteShellBackend,
|
|
5
|
+
ShellBackend,
|
|
6
|
+
add_shim,
|
|
7
|
+
)
|
|
2
8
|
from shelltastic.backend.default.local import DefaultLocalBackend
|
|
3
9
|
from shelltastic.backend.default.remote import DefaultRemoteBackend
|
|
4
10
|
from shelltastic.host import Host
|
|
5
11
|
|
|
12
|
+
__all__ = [
|
|
13
|
+
"LocalShellBackend",
|
|
14
|
+
"RemoteShellBackend",
|
|
15
|
+
"BackendShim",
|
|
16
|
+
"ShellBackend",
|
|
17
|
+
"register_default_local_backend",
|
|
18
|
+
"register_default_remote_backend",
|
|
19
|
+
"add_shim",
|
|
20
|
+
"get_local_backend",
|
|
21
|
+
"get_remote_backend",
|
|
22
|
+
]
|
|
23
|
+
|
|
6
24
|
_default_local_backend: type[LocalShellBackend] = DefaultLocalBackend
|
|
7
25
|
_default_remote_backend: type[RemoteShellBackend] = DefaultRemoteBackend
|
|
8
26
|
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from abc import ABC, ABCMeta, abstractmethod
|
|
4
|
+
from functools import wraps
|
|
5
|
+
from typing import TYPE_CHECKING, Any
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from shelltastic.display import IODisplay
|
|
11
|
+
from shelltastic.enum import CaptureMode, DisplayMode
|
|
12
|
+
from shelltastic.host import Host
|
|
13
|
+
from shelltastic.result import ShellResult
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class BackendShimResult:
|
|
17
|
+
def __init__(self, *args, **kwargs) -> None:
|
|
18
|
+
self.args = args
|
|
19
|
+
self.kwargs = kwargs
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class BackendShim(ABC):
|
|
23
|
+
@abstractmethod
|
|
24
|
+
def run_shim(
|
|
25
|
+
self,
|
|
26
|
+
backend_type: type,
|
|
27
|
+
/,
|
|
28
|
+
*args,
|
|
29
|
+
**kwargs,
|
|
30
|
+
) -> BackendShimResult:
|
|
31
|
+
"""
|
|
32
|
+
Shim for run() method of `ShellBackend`
|
|
33
|
+
|
|
34
|
+
Use `self.run(...)` to create and return a `BackendShimResult`
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
return self.run(*args, **kwargs)
|
|
38
|
+
```
|
|
39
|
+
"""
|
|
40
|
+
raise NotImplementedError()
|
|
41
|
+
|
|
42
|
+
def run(
|
|
43
|
+
self,
|
|
44
|
+
cmd: str | list[str],
|
|
45
|
+
*,
|
|
46
|
+
check: bool = True,
|
|
47
|
+
stdout: CaptureMode | None = None,
|
|
48
|
+
stderr: CaptureMode | None = None,
|
|
49
|
+
stdout_display: DisplayMode | IODisplay | None = None,
|
|
50
|
+
stderr_display: DisplayMode | IODisplay | None = None,
|
|
51
|
+
cwd: str | Path | None = None,
|
|
52
|
+
echo_cmd: DisplayMode | IODisplay | bool | None = None,
|
|
53
|
+
**kwargs,
|
|
54
|
+
) -> BackendShimResult:
|
|
55
|
+
"""A helper function to make a BackendShimResult"""
|
|
56
|
+
return BackendShimResult(
|
|
57
|
+
cmd,
|
|
58
|
+
check=check,
|
|
59
|
+
stdout=stdout,
|
|
60
|
+
stderr=stderr,
|
|
61
|
+
stdout_display=stdout_display,
|
|
62
|
+
stderr_display=stderr_display,
|
|
63
|
+
cwd=cwd,
|
|
64
|
+
echo_cmd=echo_cmd,
|
|
65
|
+
**kwargs,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
_backend_shims: list[BackendShim] = []
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def add_shim(shim: BackendShim):
|
|
73
|
+
"""Add a shim to manipulate `ShellBackend` params before any `ShellBackend` runs"""
|
|
74
|
+
global _backend_shims
|
|
75
|
+
_backend_shims.append(shim)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _shim_run(f, backend_type: type):
|
|
79
|
+
@wraps(f)
|
|
80
|
+
def wrapper(*args, **kwargs):
|
|
81
|
+
result: BackendShimResult = BackendShimResult(*args, **kwargs)
|
|
82
|
+
for shim in _backend_shims:
|
|
83
|
+
result = shim.run_shim(backend_type, *result.args, **result.kwargs)
|
|
84
|
+
return f(*result.args, **result.kwargs)
|
|
85
|
+
|
|
86
|
+
return wrapper
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class _InstallShims(ABCMeta):
|
|
90
|
+
def __call__(self, *args: Any, **kwds: Any) -> Any:
|
|
91
|
+
instance = super().__call__(*args, **kwds)
|
|
92
|
+
instance.run = _shim_run(instance.run, instance.__class__)
|
|
93
|
+
return instance
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class ShellBackend(metaclass=_InstallShims):
|
|
97
|
+
__slots__ = ()
|
|
98
|
+
|
|
99
|
+
@abstractmethod
|
|
100
|
+
def run(
|
|
101
|
+
self,
|
|
102
|
+
cmd: str | list[str],
|
|
103
|
+
*,
|
|
104
|
+
check: bool = True,
|
|
105
|
+
stdout: CaptureMode | None = None,
|
|
106
|
+
stderr: CaptureMode | None = None,
|
|
107
|
+
stdout_display: DisplayMode | IODisplay | None = None,
|
|
108
|
+
stderr_display: DisplayMode | IODisplay | None = None,
|
|
109
|
+
cwd: str | Path | None = None,
|
|
110
|
+
echo_cmd: DisplayMode | IODisplay | bool | None = None,
|
|
111
|
+
**kwargs,
|
|
112
|
+
) -> ShellResult:
|
|
113
|
+
raise NotImplementedError()
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class LocalShellBackend(ShellBackend):
|
|
117
|
+
__slots__ = ()
|
|
118
|
+
|
|
119
|
+
@staticmethod
|
|
120
|
+
@abstractmethod
|
|
121
|
+
def local() -> LocalShellBackend:
|
|
122
|
+
raise NotImplementedError()
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class RemoteShellBackend(ShellBackend):
|
|
126
|
+
__slots__ = ()
|
|
127
|
+
|
|
128
|
+
@staticmethod
|
|
129
|
+
@abstractmethod
|
|
130
|
+
def for_host(host: Host) -> RemoteShellBackend:
|
|
131
|
+
raise NotImplementedError()
|
|
@@ -6,6 +6,7 @@ from typing import TYPE_CHECKING
|
|
|
6
6
|
|
|
7
7
|
from shelltastic.backend.base import RemoteShellBackend
|
|
8
8
|
from shelltastic.backend.default.common import CommonDefaultBackend
|
|
9
|
+
from shelltastic.enum import CaptureMode
|
|
9
10
|
from shelltastic.exception import ShellException, SSHConnectionError
|
|
10
11
|
|
|
11
12
|
if TYPE_CHECKING:
|
|
@@ -44,6 +45,11 @@ class DefaultRemoteBackend(CommonDefaultBackend, RemoteShellBackend):
|
|
|
44
45
|
if subshell:
|
|
45
46
|
cmd = shlex.join(["bash", "-c", cmd])
|
|
46
47
|
|
|
48
|
+
# If we're redirecting stderr to stdout,
|
|
49
|
+
# add the same redirect inside the ssh command.
|
|
50
|
+
if kwargs.get("stderr") == CaptureMode.STDOUT:
|
|
51
|
+
cmd = cmd + " 2>&1"
|
|
52
|
+
|
|
47
53
|
# cd into cwd first if cwd is set
|
|
48
54
|
if cwd:
|
|
49
55
|
cmd = shlex.join(["cd", str(cwd)]) + " && " + cmd
|
|
@@ -1,43 +1,41 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import sys
|
|
3
|
+
import threading
|
|
3
4
|
from abc import ABC, abstractmethod
|
|
4
5
|
|
|
5
6
|
from shelltastic.enum import DisplayMode
|
|
6
7
|
|
|
7
8
|
LOGGER = logging.getLogger(__name__)
|
|
8
9
|
|
|
10
|
+
_io_display_lock = threading.RLock()
|
|
11
|
+
|
|
9
12
|
|
|
10
13
|
class IODisplay(ABC):
|
|
11
|
-
@abstractmethod
|
|
12
14
|
def printbytes(self, line: bytes):
|
|
13
|
-
|
|
15
|
+
with _io_display_lock:
|
|
16
|
+
self._emit_line(line.decode().rstrip())
|
|
14
17
|
|
|
15
|
-
@abstractmethod
|
|
16
18
|
def printline(self, msg: object, *args: object):
|
|
19
|
+
with _io_display_lock:
|
|
20
|
+
self._emit_line(msg, *args)
|
|
21
|
+
|
|
22
|
+
@abstractmethod
|
|
23
|
+
def _emit_line(self, msg: object, *args: object):
|
|
17
24
|
raise NotImplementedError
|
|
18
25
|
|
|
19
26
|
|
|
20
27
|
class DevNullDisplay(IODisplay):
|
|
21
|
-
def
|
|
22
|
-
pass
|
|
23
|
-
|
|
24
|
-
def printline(self, msg, *args):
|
|
28
|
+
def _emit_line(self, msg: object, *args: object):
|
|
25
29
|
pass
|
|
26
30
|
|
|
27
31
|
|
|
28
32
|
class StdoutDisplay(IODisplay):
|
|
29
|
-
def
|
|
30
|
-
print(line.decode(), end="", flush=True)
|
|
31
|
-
|
|
32
|
-
def printline(self, msg, *args):
|
|
33
|
+
def _emit_line(self, msg: object, *args: object):
|
|
33
34
|
print(str(msg) % args, flush=True)
|
|
34
35
|
|
|
35
36
|
|
|
36
37
|
class StderrDisplay(IODisplay):
|
|
37
|
-
def
|
|
38
|
-
print(line.decode(), end="", file=sys.stderr, flush=True)
|
|
39
|
-
|
|
40
|
-
def printline(self, msg, *args):
|
|
38
|
+
def _emit_line(self, msg: object, *args: object):
|
|
41
39
|
print(str(msg) % args, file=sys.stderr, flush=True)
|
|
42
40
|
|
|
43
41
|
|
|
@@ -46,10 +44,7 @@ class LogDisplay(IODisplay):
|
|
|
46
44
|
super().__init__()
|
|
47
45
|
self.log_level = log_level
|
|
48
46
|
|
|
49
|
-
def
|
|
50
|
-
LOGGER.log(self.log_level, line.decode().rstrip(), stacklevel=2)
|
|
51
|
-
|
|
52
|
-
def printline(self, msg: object, *args: object):
|
|
47
|
+
def _emit_line(self, msg: object, *args: object):
|
|
53
48
|
LOGGER.log(self.log_level, msg, *args, stacklevel=2)
|
|
54
49
|
|
|
55
50
|
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from abc import ABC, abstractmethod
|
|
4
|
-
from typing import TYPE_CHECKING
|
|
5
|
-
|
|
6
|
-
if TYPE_CHECKING:
|
|
7
|
-
from pathlib import Path
|
|
8
|
-
|
|
9
|
-
from shelltastic.display import IODisplay
|
|
10
|
-
from shelltastic.enum import CaptureMode, DisplayMode
|
|
11
|
-
from shelltastic.host import Host
|
|
12
|
-
from shelltastic.result import ShellResult
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class ShellBackend(ABC):
|
|
16
|
-
__slots__ = ()
|
|
17
|
-
|
|
18
|
-
@abstractmethod
|
|
19
|
-
def run(
|
|
20
|
-
self,
|
|
21
|
-
cmd: str | list[str],
|
|
22
|
-
*,
|
|
23
|
-
check: bool = True,
|
|
24
|
-
stdout: CaptureMode | None = None,
|
|
25
|
-
stderr: CaptureMode | None = None,
|
|
26
|
-
stdout_display: DisplayMode | IODisplay | None = None,
|
|
27
|
-
stderr_display: DisplayMode | IODisplay | None = None,
|
|
28
|
-
cwd: str | Path | None = None,
|
|
29
|
-
echo_cmd: DisplayMode | IODisplay | bool | None = None,
|
|
30
|
-
**kwargs,
|
|
31
|
-
) -> ShellResult:
|
|
32
|
-
raise NotImplementedError()
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
class LocalShellBackend(ShellBackend):
|
|
36
|
-
__slots__ = ()
|
|
37
|
-
|
|
38
|
-
@staticmethod
|
|
39
|
-
@abstractmethod
|
|
40
|
-
def local() -> LocalShellBackend:
|
|
41
|
-
raise NotImplementedError()
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
class RemoteShellBackend(ShellBackend):
|
|
45
|
-
__slots__ = ()
|
|
46
|
-
|
|
47
|
-
@staticmethod
|
|
48
|
-
@abstractmethod
|
|
49
|
-
def for_host(host: Host) -> RemoteShellBackend:
|
|
50
|
-
raise NotImplementedError()
|
|
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
|