ssh-handler 1.4.0__tar.gz → 1.4.2__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.
- {ssh_handler-1.4.0/ssh_handler.egg-info → ssh_handler-1.4.2}/PKG-INFO +1 -1
- {ssh_handler-1.4.0 → ssh_handler-1.4.2}/pyproject.toml +1 -1
- {ssh_handler-1.4.0 → ssh_handler-1.4.2}/ssh_handler/__init__.py +1 -1
- {ssh_handler-1.4.0 → ssh_handler-1.4.2}/ssh_handler/core.py +19 -7
- {ssh_handler-1.4.0 → ssh_handler-1.4.2}/ssh_handler/results.py +17 -0
- {ssh_handler-1.4.0 → ssh_handler-1.4.2}/ssh_handler/serial_handler.py +8 -2
- {ssh_handler-1.4.0 → ssh_handler-1.4.2/ssh_handler.egg-info}/PKG-INFO +1 -1
- {ssh_handler-1.4.0 → ssh_handler-1.4.2}/LICENSE +0 -0
- {ssh_handler-1.4.0 → ssh_handler-1.4.2}/README.md +0 -0
- {ssh_handler-1.4.0 → ssh_handler-1.4.2}/setup.cfg +0 -0
- {ssh_handler-1.4.0 → ssh_handler-1.4.2}/ssh_handler/__main__.py +0 -0
- {ssh_handler-1.4.0 → ssh_handler-1.4.2}/ssh_handler/cli.py +0 -0
- {ssh_handler-1.4.0 → ssh_handler-1.4.2}/ssh_handler/config.py +0 -0
- {ssh_handler-1.4.0 → ssh_handler-1.4.2}/ssh_handler/credentials.py +0 -0
- {ssh_handler-1.4.0 → ssh_handler-1.4.2}/ssh_handler/exceptions.py +0 -0
- {ssh_handler-1.4.0 → ssh_handler-1.4.2}/ssh_handler/ftp.py +0 -0
- {ssh_handler-1.4.0 → ssh_handler-1.4.2}/ssh_handler/openssh/OpenSSH-ARM64.zip +0 -0
- {ssh_handler-1.4.0 → ssh_handler-1.4.2}/ssh_handler/openssh/OpenSSH-Win32.zip +0 -0
- {ssh_handler-1.4.0 → ssh_handler-1.4.2}/ssh_handler/openssh/OpenSSH-Win64.zip +0 -0
- {ssh_handler-1.4.0 → ssh_handler-1.4.2}/ssh_handler/pool.py +0 -0
- {ssh_handler-1.4.0 → ssh_handler-1.4.2}/ssh_handler/pyqt_worker.py +0 -0
- {ssh_handler-1.4.0 → ssh_handler-1.4.2}/ssh_handler/setup_openssh_server.ps1 +0 -0
- {ssh_handler-1.4.0 → ssh_handler-1.4.2}/ssh_handler/winrm_bootstrap.py +0 -0
- {ssh_handler-1.4.0 → ssh_handler-1.4.2}/ssh_handler.egg-info/SOURCES.txt +0 -0
- {ssh_handler-1.4.0 → ssh_handler-1.4.2}/ssh_handler.egg-info/dependency_links.txt +0 -0
- {ssh_handler-1.4.0 → ssh_handler-1.4.2}/ssh_handler.egg-info/entry_points.txt +0 -0
- {ssh_handler-1.4.0 → ssh_handler-1.4.2}/ssh_handler.egg-info/requires.txt +0 -0
- {ssh_handler-1.4.0 → ssh_handler-1.4.2}/ssh_handler.egg-info/top_level.txt +0 -0
- {ssh_handler-1.4.0 → ssh_handler-1.4.2}/tests/test_offline.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ssh-handler"
|
|
7
|
-
version = "1.4.
|
|
7
|
+
version = "1.4.2"
|
|
8
8
|
description = "Extensive SSH/SFTP/SCP/FTP handler built on Paramiko, for test automation, CLIs and PyQt5 tools."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.8"
|
|
@@ -655,7 +655,8 @@ class SSHHandler:
|
|
|
655
655
|
|
|
656
656
|
def stream(self, command: str, *, on_line=None, on_match=None, match=None,
|
|
657
657
|
stop_on_match: bool = False, save_to: Optional[str] = None,
|
|
658
|
-
append: bool = True,
|
|
658
|
+
append: bool = True, clean: bool = True,
|
|
659
|
+
timeout: Optional[float] = None,
|
|
659
660
|
stop_event=None, encoding: str = "utf-8", safe: Optional[bool] = None):
|
|
660
661
|
"""
|
|
661
662
|
Consume a streaming command with built-in matching and file logging.
|
|
@@ -669,6 +670,7 @@ class SSHHandler:
|
|
|
669
670
|
:returns: dict with 'lines' (count) and 'matches' (list).
|
|
670
671
|
"""
|
|
671
672
|
import re
|
|
673
|
+
from .results import strip_ansi
|
|
672
674
|
|
|
673
675
|
def _do():
|
|
674
676
|
pat = re.compile(match) if isinstance(match, str) else match
|
|
@@ -678,6 +680,8 @@ class SSHHandler:
|
|
|
678
680
|
try:
|
|
679
681
|
for line in self.iter_lines(command, timeout=timeout,
|
|
680
682
|
stop_event=stop_event, encoding=encoding):
|
|
683
|
+
if clean:
|
|
684
|
+
line = strip_ansi(line)
|
|
681
685
|
count += 1
|
|
682
686
|
if fh:
|
|
683
687
|
fh.write(line + "\n")
|
|
@@ -708,8 +712,9 @@ class SSHHandler:
|
|
|
708
712
|
def serial_stream(self, device: str = "/dev/ttyUSB0", *, baudrate: int = 115200,
|
|
709
713
|
mode: str = "auto", on_line=None, on_match=None, match=None,
|
|
710
714
|
stop_on_match: bool = False, save_to: Optional[str] = None,
|
|
711
|
-
timeout: Optional[float] = None,
|
|
712
|
-
configure: bool = True,
|
|
715
|
+
clean: bool = True, timeout: Optional[float] = None,
|
|
716
|
+
stop_event=None, configure: bool = True,
|
|
717
|
+
safe: Optional[bool] = None):
|
|
713
718
|
"""
|
|
714
719
|
Stream a serial port attached to the **remote** host, over SSH — so it
|
|
715
720
|
works through a jump host (laptop -> RDP machine -> target). Same live
|
|
@@ -729,13 +734,20 @@ class SSHHandler:
|
|
|
729
734
|
is_windows = (mode == "windows" or
|
|
730
735
|
(mode == "auto" and device.upper().startswith("COM")))
|
|
731
736
|
if is_windows:
|
|
737
|
+
# Force UTF-8 console output so our UTF-8 reader doesn't get mojibake,
|
|
738
|
+
# and read whatever bytes are available (robust to any line ending)
|
|
739
|
+
# rather than ReadLine (which hangs if the device's EOL differs).
|
|
732
740
|
ps = (
|
|
733
741
|
"$ErrorActionPreference='SilentlyContinue';"
|
|
742
|
+
"[Console]::OutputEncoding=[System.Text.Encoding]::UTF8;"
|
|
734
743
|
f"$p=New-Object System.IO.Ports.SerialPort '{device}',{int(baudrate)},"
|
|
735
744
|
"'None',8,'One';"
|
|
736
|
-
"$p.
|
|
737
|
-
"
|
|
738
|
-
"
|
|
745
|
+
"$p.Encoding=[System.Text.Encoding]::UTF8;"
|
|
746
|
+
"$p.Open();"
|
|
747
|
+
"while($true){"
|
|
748
|
+
"if($p.BytesToRead -gt 0){"
|
|
749
|
+
"$s=$p.ReadExisting();[Console]::Out.Write($s);[Console]::Out.Flush()}"
|
|
750
|
+
"else{Start-Sleep -Milliseconds 30}}"
|
|
739
751
|
)
|
|
740
752
|
cmd = f"powershell -NoProfile -EncodedCommand {self._ps_encode(ps)}"
|
|
741
753
|
else:
|
|
@@ -746,7 +758,7 @@ class SSHHandler:
|
|
|
746
758
|
else:
|
|
747
759
|
cmd = f"cat {dev}"
|
|
748
760
|
return self.stream(cmd, on_line=on_line, on_match=on_match, match=match,
|
|
749
|
-
stop_on_match=stop_on_match, save_to=save_to,
|
|
761
|
+
stop_on_match=stop_on_match, save_to=save_to, clean=clean,
|
|
750
762
|
timeout=timeout, stop_event=stop_event, safe=safe)
|
|
751
763
|
|
|
752
764
|
def serial_write(self, device: str, data: str, *, baudrate: int = 115200,
|
|
@@ -2,11 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import re
|
|
5
6
|
import time
|
|
6
7
|
from dataclasses import dataclass, field, asdict
|
|
7
8
|
from typing import Optional
|
|
8
9
|
|
|
9
10
|
|
|
11
|
+
# ANSI/VT escape sequences (CSI like ESC[23;80H, OSC like ESC]0;title BEL, etc.)
|
|
12
|
+
_ANSI_RE = re.compile(r"\x1b(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~]|\][^\x07\x1b]*(?:\x07|\x1b\\))")
|
|
13
|
+
# control chars except tab(09), newline(0a), carriage-return(0d handled separately)
|
|
14
|
+
_CTRL_RE = re.compile(r"[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def strip_ansi(text: str) -> str:
|
|
18
|
+
"""Remove ANSI/VT escape codes, carriage returns, and other control chars,
|
|
19
|
+
so streamed console output is clean to save, match, and read."""
|
|
20
|
+
if not text:
|
|
21
|
+
return text
|
|
22
|
+
text = _ANSI_RE.sub("", text)
|
|
23
|
+
text = text.replace("\r", "")
|
|
24
|
+
return _CTRL_RE.sub("", text)
|
|
25
|
+
|
|
26
|
+
|
|
10
27
|
def _human_size(num: float) -> str:
|
|
11
28
|
for unit in ("B", "KB", "MB", "GB", "TB"):
|
|
12
29
|
if abs(num) < 1024.0:
|
|
@@ -153,12 +153,16 @@ class SerialHandler:
|
|
|
153
153
|
|
|
154
154
|
def stream(self, *, on_line=None, on_match=None, match=None,
|
|
155
155
|
stop_on_match: bool = False, save_to: Optional[str] = None,
|
|
156
|
-
append: bool = True,
|
|
156
|
+
append: bool = True, clean: bool = True,
|
|
157
|
+
timeout: Optional[float] = None, stop_event=None,
|
|
157
158
|
encoding: str = "utf-8", safe=None):
|
|
158
159
|
"""
|
|
159
160
|
Read the serial console continuously with built-in matching + file
|
|
160
|
-
logging. Same signature/semantics as SSHHandler.stream.
|
|
161
|
+
logging. Same signature/semantics as SSHHandler.stream. ``clean=True``
|
|
162
|
+
strips ANSI escape codes and control chars from each line.
|
|
161
163
|
"""
|
|
164
|
+
from .results import strip_ansi
|
|
165
|
+
|
|
162
166
|
def _do():
|
|
163
167
|
pat = re.compile(match) if isinstance(match, str) else match
|
|
164
168
|
matches, count = [], 0
|
|
@@ -167,6 +171,8 @@ class SerialHandler:
|
|
|
167
171
|
try:
|
|
168
172
|
for line in self.iter_lines(stop_event=stop_event, timeout=timeout,
|
|
169
173
|
encoding=encoding):
|
|
174
|
+
if clean:
|
|
175
|
+
line = strip_ansi(line)
|
|
170
176
|
count += 1
|
|
171
177
|
if fh:
|
|
172
178
|
fh.write(line + "\n")
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|