main_window.py concentrait la fenetre principale, l'arbre des sessions, les 4 classes d'onglets, la detection de commandes ssh et le multi-send : 983 lignes et une seule classe qui pilotait toute l'application. - app/services/ssh_target.py : detection d'une cible ssh dans un terminal (pur, sans dependance Qt) - app/ui/tabs.py : BaseTerminalTab / SessionTabWidget / SFTPTabWidget / LocalTerminalTabWidget (deplaces tels quels) - app/controllers/session_controller.py : panneau lateral (arbre sessions + terminaux locaux) et CRUD des sessions - app/controllers/connection_controller.py : cycle de vie des onglets (ouverture, connexion rapide, renommage, fermeture) - app/controllers/broadcast_manager.py : bouton et logique du mode Multi-Send main_window.py ne fait plus que construire ces composants et les relier par signaux (983 -> ~200 lignes). Comportement inchange ; MainWindow.tabs et detect_ssh_target restent importables depuis app.main_window pour la compatibilite avec les tests existants. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
56 lines
1.9 KiB
Python
56 lines
1.9 KiB
Python
"""Detection d'une cible ssh (user/host/port) tapee a la main dans un terminal.
|
|
|
|
Sert a proposer le SFTP sur l'hote d'une commande `ssh ...` sans avoir a creer
|
|
un favori au prealable.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
|
|
# Repere une commande ssh tapee a la main dans un terminal.
|
|
SSH_COMMAND_RE = re.compile(r"\bssh\s+(?P<args>[^\r\n|;&]+)")
|
|
# options ssh qui consomment la valeur suivante (a ne pas confondre avec l'hote)
|
|
SSH_VALUE_OPTIONS = {
|
|
"-p", "-i", "-l", "-o", "-F", "-J", "-b", "-c", "-D", "-L", "-R", "-W", "-e", "-m", "-Q",
|
|
}
|
|
HOST_RE = re.compile(r"^[A-Za-z0-9][\w.\-]*$")
|
|
|
|
|
|
def _parse_ssh_args(args: str) -> dict | None:
|
|
tokens = args.split()
|
|
user, host, port = "", "", 22
|
|
index = 0
|
|
while index < len(tokens):
|
|
token = tokens[index]
|
|
if token.startswith("-"):
|
|
if token in SSH_VALUE_OPTIONS:
|
|
value = tokens[index + 1] if index + 1 < len(tokens) else ""
|
|
if token == "-p" and value.isdigit():
|
|
port = int(value)
|
|
if token == "-l" and value:
|
|
user = value
|
|
index += 2
|
|
continue
|
|
if token.startswith("-p") and token[2:].isdigit(): # forme collee : -p2222
|
|
port = int(token[2:])
|
|
index += 1
|
|
continue
|
|
if not host:
|
|
candidate = token
|
|
if "@" in candidate:
|
|
user, _, candidate = candidate.partition("@")
|
|
if HOST_RE.match(candidate):
|
|
host = candidate
|
|
index += 1
|
|
return {"user": user, "host": host, "port": port} if host else None
|
|
|
|
|
|
def detect_ssh_target(text: str) -> dict | None:
|
|
"""Renvoie la derniere cible ssh trouvee dans le texte d'un terminal."""
|
|
result = None
|
|
for match in SSH_COMMAND_RE.finditer(text):
|
|
parsed = _parse_ssh_args(match.group("args"))
|
|
if parsed:
|
|
result = parsed
|
|
return result
|