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.
Files changed (23) hide show
  1. {shelltastic-0.8.2 → shelltastic-0.9.1}/PKG-INFO +1 -1
  2. shelltastic-0.9.1/pyproject.toml +59 -0
  3. shelltastic-0.8.2/pyproject.toml → shelltastic-0.9.1/pyproject.toml.orig +16 -12
  4. {shelltastic-0.8.2 → shelltastic-0.9.1}/src/shelltastic/backend/base.py +0 -2
  5. {shelltastic-0.8.2 → shelltastic-0.9.1}/src/shelltastic/backend/default/common.py +19 -15
  6. {shelltastic-0.8.2 → shelltastic-0.9.1}/src/shelltastic/enum.py +2 -0
  7. {shelltastic-0.8.2 → shelltastic-0.9.1}/src/shelltastic/frontend/shell.py +59 -13
  8. {shelltastic-0.8.2 → shelltastic-0.9.1}/LICENSE +0 -0
  9. {shelltastic-0.8.2 → shelltastic-0.9.1}/README.md +0 -0
  10. {shelltastic-0.8.2 → shelltastic-0.9.1}/src/shelltastic/__init__.py +5 -5
  11. {shelltastic-0.8.2 → shelltastic-0.9.1}/src/shelltastic/backend/__init__.py +3 -3
  12. {shelltastic-0.8.2 → shelltastic-0.9.1}/src/shelltastic/backend/default/__init__.py +0 -0
  13. {shelltastic-0.8.2 → shelltastic-0.9.1}/src/shelltastic/backend/default/local.py +0 -0
  14. {shelltastic-0.8.2 → shelltastic-0.9.1}/src/shelltastic/backend/default/remote.py +0 -0
  15. {shelltastic-0.8.2 → shelltastic-0.9.1}/src/shelltastic/display.py +0 -0
  16. {shelltastic-0.8.2 → shelltastic-0.9.1}/src/shelltastic/exception.py +0 -0
  17. {shelltastic-0.8.2 → shelltastic-0.9.1}/src/shelltastic/frontend/__init__.py +0 -0
  18. {shelltastic-0.8.2 → shelltastic-0.9.1}/src/shelltastic/frontend/common.py +0 -0
  19. {shelltastic-0.8.2 → shelltastic-0.9.1}/src/shelltastic/frontend/git.py +0 -0
  20. {shelltastic-0.8.2 → shelltastic-0.9.1}/src/shelltastic/frontend/scp.py +0 -0
  21. {shelltastic-0.8.2 → shelltastic-0.9.1}/src/shelltastic/host.py +0 -0
  22. {shelltastic-0.8.2 → shelltastic-0.9.1}/src/shelltastic/io.py +0 -0
  23. {shelltastic-0.8.2 → shelltastic-0.9.1}/src/shelltastic/result.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: shelltastic
3
- Version: 0.8.2
3
+ Version: 0.9.1
4
4
  Summary: A fantastic shell command runner for python
5
5
  Author: Bearmine
6
6
  License-Expression: MPL-2.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.8.2"
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
- [dependency-groups]
48
- dev = [
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
- self.close()
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
- self.wait()
247
-
248
- # Close stdout and stderr
249
- if self.stdout is not None:
250
- self.stdout.close()
251
- if self.stderr is not None:
252
- self.stderr.close()
253
-
254
- self._closed = True
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:
@@ -3,6 +3,8 @@ from enum import Enum, auto
3
3
 
4
4
  class SystemType(Enum):
5
5
  DEBIAN_BASED = auto()
6
+ CACHYOS = auto()
7
+ WINDOWS = auto()
6
8
  UNKNOWN = auto()
7
9
 
8
10
 
@@ -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
- result = self.run(
38
- f'stat "{path}"',
39
- check=False,
40
- stdout=CaptureMode.DEVNULL,
41
- stderr=CaptureMode.DEVNULL,
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 system_type(self) -> SystemType:
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
- "stat /etc/debian_version",
95
+ "echo %OS%",
58
96
  check=False,
59
- stdout=CaptureMode.DEVNULL,
97
+ stdout=CaptureMode.PIPE,
60
98
  stderr=CaptureMode.DEVNULL,
61
- ).returncode
62
- == 0
99
+ )
100
+ .stdout.decode("utf-8") # type: ignore
101
+ .strip()
102
+ == "Windows_NT"
63
103
  ):
64
- return SystemType.DEBIAN_BASED
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
- "SystemType",
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