From 26e01bdad349349f826f18c7c99a2b724841b2b7 Mon Sep 17 00:00:00 2001 From: Kanishk Pachauri Date: Sat, 31 May 2025 15:52:03 +0530 Subject: [PATCH 01/16] feat: convert to function based --- src/bitssh/prompt.py | 5 +-- src/bitssh/ui.py | 4 +- src/bitssh/utils.py | 104 ++++++++++++++++++++----------------------- 3 files changed, 52 insertions(+), 61 deletions(-) diff --git a/src/bitssh/prompt.py b/src/bitssh/prompt.py index b4b4a37..05607f7 100644 --- a/src/bitssh/prompt.py +++ b/src/bitssh/prompt.py @@ -4,11 +4,10 @@ from InquirerPy import inquirer from .ui import console -from .utils import ConfigPathUtility - +from .utils import get_config_file_host_data def ask_host_prompt(): - HOST: List[str] = ConfigPathUtility.get_config_file_host_data() + HOST: List[str] = get_config_file_host_data() questions: List[List] = inquirer.fuzzy( message="Select the Host Given in the Above List: ", choices=HOST, diff --git a/src/bitssh/ui.py b/src/bitssh/ui.py index 3bc63e5..ba21311 100644 --- a/src/bitssh/ui.py +++ b/src/bitssh/ui.py @@ -1,6 +1,6 @@ from rich.console import Console from rich.table import Table -from .utils import ConfigPathUtility +from .utils import get_config_file_row_data console = Console() @@ -11,7 +11,7 @@ def draw_table(): table.add_column("Port", justify="right", style="green") table.add_column("User", justify="right", style="yellow") - for i in ConfigPathUtility.get_config_file_row_data(): + for i in get_config_file_row_data(): table.add_row(i[0], i[1], i[2], i[3]) console.print(table) diff --git a/src/bitssh/utils.py b/src/bitssh/utils.py index d243732..50e4f7b 100644 --- a/src/bitssh/utils.py +++ b/src/bitssh/utils.py @@ -1,62 +1,54 @@ import os import re -from typing import Dict, List, Match, Optional, Pattern, Tuple +from typing import Dict, List, Tuple -from path import Path +CONFIG_FILE_PATH: str = os.path.expanduser("~/.ssh/config") - -class ConfigPathUtility: - config_file_path: str = os.path.expanduser("~/.ssh/config") - - @classmethod - def _validate_config_file(cls) -> None: - if not os.path.exists(cls.config_file_path): - raise FileNotFoundError( - f"Config file not found at {cls.config_file_path}. Please see the documentation for bitssh." - ) - - @classmethod - def get_config_content(cls) -> Dict[str, Dict[str, str]]: - cls._validate_config_file() - - with open(cls.config_file_path, "r") as file: - lines = file.read() - - host_pattern: Pattern[str] = re.compile(r"Host\s+(\w+)", re.MULTILINE) - hostname_pattern: Pattern[str] = re.compile( - r"(?:HostName|Hostname)\s+(\S+)", re.MULTILINE +def _validate_config_file() -> None: + if not os.path.exists(CONFIG_FILE_PATH): + raise FileNotFoundError( + f"Config file not found at {CONFIG_FILE_PATH}. " + "Please see the documentation for bitssh." ) - user_pattern: Pattern[str] = re.compile(r"User\s+(\S+)", re.MULTILINE) - - host_dict: Dict[str, Dict[str, str]] = {} - for match in host_pattern.finditer(lines): - host: str = match.group(1) - hostname_match: Optional[Match[str]] = hostname_pattern.search( - lines, match.end() - ) - hostname: str = hostname_match.group(1) if hostname_match else host - - user_match: Optional[Match[str]] = user_pattern.search(lines, match.end()) - user: Optional[str] = user_match.group(1) if user_match else None - - host_dict[host]: Dict[str, str] = { - "Hostname": hostname, - "User": user, - } - - return host_dict - - @classmethod - def get_config_file_row_data(cls) -> List[Tuple[str, str, str, str]]: - config_content = cls.get_config_content() - - rows: List[Tuple[str, str, str, str]] = [ - (attributes["Hostname"], host, "22", attributes["User"]) - for host, attributes in config_content.items() - ] - - return rows - @classmethod - def get_config_file_host_data(cls) -> List[str]: - return [f"🖥️ -> {host[1]}" for host in cls.get_config_file_row_data()] +def get_config_content() -> Dict[str, Dict[str, str]]: + _validate_config_file() + + with open(CONFIG_FILE_PATH, "r") as file: + lines = file.read() + + host_pattern = re.compile(r"Host\s+(\w+)", re.MULTILINE) + hostname_pattern = re.compile( + r"(?:HostName|Hostname)\s+(\S+)", re.MULTILINE + ) + user_pattern = re.compile(r"User\s+(\S+)", re.MULTILINE) + + host_dict: Dict[str, Dict[str, str]] = {} + for match in host_pattern.finditer(lines): + host = match.group(1) + host_end = match.end() + + hostname_match = hostname_pattern.search(lines, host_end) + hostname = hostname_match.group(1) if hostname_match else host + + user_match = user_pattern.search(lines, host_end) + user = user_match.group(1) if user_match else None + + host_dict[host] = { + "Hostname": hostname, + "User": user, + } + + return host_dict + +def get_config_file_row_data() -> List[Tuple[str, str, str, str]]: + config_content = get_config_content() + rows = [] + for host, attributes in config_content.items(): + hostname = attributes["Hostname"] + user = attributes["User"] + rows.append((hostname, host, "22", user)) + return rows + +def get_config_file_host_data() -> List[str]: + return [f"🖥️ -> {row[1]}" for row in get_config_file_row_data()] \ No newline at end of file From 19308ce76147f802dfc66c62af2c94d269af98ed Mon Sep 17 00:00:00 2001 From: Kanishk Pachauri Date: Sat, 31 May 2025 15:53:03 +0530 Subject: [PATCH 02/16] chore: pre-commit --- .github/workflows/test.yml | 6 +++--- Dockerfile | 2 +- README.md | 2 +- src/bitssh/cli.py | 2 -- src/bitssh/prompt.py | 1 + src/bitssh/ui.py | 1 + src/bitssh/utils.py | 12 +++++++----- 7 files changed, 14 insertions(+), 12 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7ec5092..e5a3765 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,12 +7,12 @@ on: jobs: build: - + runs-on: ubuntu-latest strategy: matrix: python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] - + steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} @@ -24,4 +24,4 @@ jobs: - name: Install dependencies run: pip3 install . && pip3 install pytest - name: Run Test - run: pytest \ No newline at end of file + run: pytest diff --git a/Dockerfile b/Dockerfile index 15cf26f..914f40e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,4 +13,4 @@ RUN python -m pip install --upgrade pip RUN pip install bitssh # Command to run when container starts -CMD ["bitssh"] \ No newline at end of file +CMD ["bitssh"] diff --git a/README.md b/README.md index f0ab7d8..e06340a 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Install bitssh with docker ```bash - docker run --rm -it -v ~/.ssh:/root/.ssh mrsunglasses/bitssh + docker run --rm -it -v ~/.ssh:/root/.ssh mrsunglasses/bitssh ``` diff --git a/src/bitssh/cli.py b/src/bitssh/cli.py index dbfb5c0..20d12bb 100644 --- a/src/bitssh/cli.py +++ b/src/bitssh/cli.py @@ -1,5 +1,3 @@ -import os - from bitssh import __version__ from .argument_parser import Config diff --git a/src/bitssh/prompt.py b/src/bitssh/prompt.py index 05607f7..9ead9a3 100644 --- a/src/bitssh/prompt.py +++ b/src/bitssh/prompt.py @@ -6,6 +6,7 @@ from .ui import console from .utils import get_config_file_host_data + def ask_host_prompt(): HOST: List[str] = get_config_file_host_data() questions: List[List] = inquirer.fuzzy( diff --git a/src/bitssh/ui.py b/src/bitssh/ui.py index ba21311..3f22e7f 100644 --- a/src/bitssh/ui.py +++ b/src/bitssh/ui.py @@ -4,6 +4,7 @@ console = Console() + def draw_table(): table: Table = Table(title="SSH Servers in Config File 📁") table.add_column("Hostname", justify="left", style="cyan", no_wrap=True) diff --git a/src/bitssh/utils.py b/src/bitssh/utils.py index 50e4f7b..0dbb284 100644 --- a/src/bitssh/utils.py +++ b/src/bitssh/utils.py @@ -4,6 +4,7 @@ CONFIG_FILE_PATH: str = os.path.expanduser("~/.ssh/config") + def _validate_config_file() -> None: if not os.path.exists(CONFIG_FILE_PATH): raise FileNotFoundError( @@ -11,6 +12,7 @@ def _validate_config_file() -> None: "Please see the documentation for bitssh." ) + def get_config_content() -> Dict[str, Dict[str, str]]: _validate_config_file() @@ -18,16 +20,14 @@ def get_config_content() -> Dict[str, Dict[str, str]]: lines = file.read() host_pattern = re.compile(r"Host\s+(\w+)", re.MULTILINE) - hostname_pattern = re.compile( - r"(?:HostName|Hostname)\s+(\S+)", re.MULTILINE - ) + hostname_pattern = re.compile(r"(?:HostName|Hostname)\s+(\S+)", re.MULTILINE) user_pattern = re.compile(r"User\s+(\S+)", re.MULTILINE) host_dict: Dict[str, Dict[str, str]] = {} for match in host_pattern.finditer(lines): host = match.group(1) host_end = match.end() - + hostname_match = hostname_pattern.search(lines, host_end) hostname = hostname_match.group(1) if hostname_match else host @@ -41,6 +41,7 @@ def get_config_content() -> Dict[str, Dict[str, str]]: return host_dict + def get_config_file_row_data() -> List[Tuple[str, str, str, str]]: config_content = get_config_content() rows = [] @@ -50,5 +51,6 @@ def get_config_file_row_data() -> List[Tuple[str, str, str, str]]: rows.append((hostname, host, "22", user)) return rows + def get_config_file_host_data() -> List[str]: - return [f"🖥️ -> {row[1]}" for row in get_config_file_row_data()] \ No newline at end of file + return [f"🖥️ -> {row[1]}" for row in get_config_file_row_data()] From 0ffd339259cce321183c588063c9eefaa62cebc2 Mon Sep 17 00:00:00 2001 From: Kanishk Pachauri Date: Sat, 31 May 2025 19:42:06 +0530 Subject: [PATCH 03/16] Improved tests and removed setup.py --- setup.py | 3 -- src/bitssh/prompt.py | 4 +-- src/bitssh/ui.py | 3 +- src/bitssh/utils.py | 18 +++++++--- tests/test_utils.py | 82 ++++++++++++++++++++------------------------ 5 files changed, 55 insertions(+), 55 deletions(-) delete mode 100644 setup.py diff --git a/setup.py b/setup.py deleted file mode 100644 index 6068493..0000000 --- a/setup.py +++ /dev/null @@ -1,3 +0,0 @@ -from setuptools import setup - -setup() diff --git a/src/bitssh/prompt.py b/src/bitssh/prompt.py index 9ead9a3..966502b 100644 --- a/src/bitssh/prompt.py +++ b/src/bitssh/prompt.py @@ -9,12 +9,12 @@ def ask_host_prompt(): HOST: List[str] = get_config_file_host_data() - questions: List[List] = inquirer.fuzzy( + questions = inquirer.fuzzy( message="Select the Host Given in the Above List: ", choices=HOST, ) try: - answers: Optional[Dict[str, str]] = questions.execute() + answers = questions.execute() if answers is None: return diff --git a/src/bitssh/ui.py b/src/bitssh/ui.py index 3f22e7f..9ab19ba 100644 --- a/src/bitssh/ui.py +++ b/src/bitssh/ui.py @@ -1,5 +1,6 @@ from rich.console import Console from rich.table import Table + from .utils import get_config_file_row_data console = Console() @@ -13,6 +14,6 @@ def draw_table(): table.add_column("User", justify="right", style="yellow") for i in get_config_file_row_data(): - table.add_row(i[0], i[1], i[2], i[3]) + table.add_row(i[0], i[1], "22" if i[2] is None else i[2], i[3]) console.print(table) diff --git a/src/bitssh/utils.py b/src/bitssh/utils.py index 0dbb284..56beba5 100644 --- a/src/bitssh/utils.py +++ b/src/bitssh/utils.py @@ -1,5 +1,6 @@ import os import re +from pprint import pprint from typing import Dict, List, Tuple CONFIG_FILE_PATH: str = os.path.expanduser("~/.ssh/config") @@ -13,7 +14,7 @@ def _validate_config_file() -> None: ) -def get_config_content() -> Dict[str, Dict[str, str]]: +def get_config_content(): _validate_config_file() with open(CONFIG_FILE_PATH, "r") as file: @@ -22,8 +23,9 @@ def get_config_content() -> Dict[str, Dict[str, str]]: host_pattern = re.compile(r"Host\s+(\w+)", re.MULTILINE) hostname_pattern = re.compile(r"(?:HostName|Hostname)\s+(\S+)", re.MULTILINE) user_pattern = re.compile(r"User\s+(\S+)", re.MULTILINE) + port_pattern = re.compile(r"port\s+(\d+)", re.MULTILINE) - host_dict: Dict[str, Dict[str, str]] = {} + host_dict = {} for match in host_pattern.finditer(lines): host = match.group(1) host_end = match.end() @@ -34,23 +36,31 @@ def get_config_content() -> Dict[str, Dict[str, str]]: user_match = user_pattern.search(lines, host_end) user = user_match.group(1) if user_match else None + port_match = port_pattern.search(lines, host_end) + port = port_match.group(1) if port_match else None + host_dict[host] = { "Hostname": hostname, "User": user, + "Port": port, } return host_dict -def get_config_file_row_data() -> List[Tuple[str, str, str, str]]: +def get_config_file_row_data(): config_content = get_config_content() rows = [] for host, attributes in config_content.items(): hostname = attributes["Hostname"] user = attributes["User"] - rows.append((hostname, host, "22", user)) + port = attributes["Port"] + rows.append((hostname, host, port, user)) return rows def get_config_file_host_data() -> List[str]: return [f"🖥️ -> {row[1]}" for row in get_config_file_row_data()] + + +pprint(get_config_content()) diff --git a/tests/test_utils.py b/tests/test_utils.py index 5d539bb..68e129a 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,9 +1,11 @@ import os import unittest -from unittest.mock import patch -from bitssh.utils import ConfigPathUtility - from typing import Dict, List, Tuple +from unittest.mock import mock_open, patch + +# Import the standalone functions instead of the class +from bitssh.utils import (get_config_content, get_config_file_host_data, + get_config_file_row_data) class TestConfigPathUtility(unittest.TestCase): @@ -13,53 +15,43 @@ def setUp(self) -> None: HostName test.hostname1.com User testUser1 Host testHost2 - HostName test.hostname2.com + Hostname test.hostname2.com User testUser2 """ - def tearDown(self) -> None: - if os.path.exists("config"): - os.remove("config") - @patch("os.path.exists", return_value=True) - @patch("builtins.open") - def test_get_config_content_success(self, mock_open, mock_exists) -> None: - mock_open.return_value.__enter__.return_value.read.return_value = ( - self.mock_config_data - ) - expected: Dict[str, Dict[str, str]] = { - "testHost1": {"Hostname": "test.hostname1.com", "User": "testUser1"}, - "testHost2": {"Hostname": "test.hostname2.com", "User": "testUser2"}, - } - result: ConfigPathUtility = ConfigPathUtility.get_config_content() - self.assertEqual(result, expected) + def test_get_config_content_success(self, mock_exists) -> None: + with patch("builtins.open", mock_open(read_data=self.mock_config_data)): + expected: Dict[str, Dict[str, str]] = { + "testHost1": {"Hostname": "test.hostname1.com", "User": "testUser1"}, + "testHost2": {"Hostname": "test.hostname2.com", "User": "testUser2"}, + } + result = get_config_content() + self.assertEqual(result, expected) @patch("os.path.exists", return_value=True) - @patch("builtins.open") - def test_get_config_file_row_data(self, mock_open, mock_exists) -> None: - mock_open.return_value.__enter__.return_value.read.return_value = ( - self.mock_config_data - ) - expected_rows: List[Tuple[str, str, str, str]] = [ - ("test.hostname1.com", "testHost1", "22", "testUser1"), - ("test.hostname2.com", "testHost2", "22", "testUser2"), - ] - rows: ConfigPathUtility = ConfigPathUtility.get_config_file_row_data() - self.assertEqual(rows, expected_rows) + def test_get_config_file_row_data(self, mock_exists) -> None: + with patch("builtins.open", mock_open(read_data=self.mock_config_data)): + expected_rows: List[Tuple[str, str, str, str]] = [ + ("test.hostname1.com", "testHost1", "22", "testUser1"), + ("test.hostname2.com", "testHost2", "22", "testUser2"), + ] + rows = get_config_file_row_data() + self.assertEqual(rows, expected_rows) @patch("os.path.exists", return_value=True) - @patch("builtins.open") - def test_get_config_file_host_data(self, mock_open, mock_exists) -> None: - mock_open.return_value.__enter__.return_value.read.return_value = ( - self.mock_config_data - ) - expected_hosts: List = [ - "🖥️ -> testHost1", - "🖥️ -> testHost2", - ] - hosts: ConfigPathUtility = ConfigPathUtility.get_config_file_host_data() - self.assertEqual(hosts, expected_hosts) - - -if __name__ == "__main__": - unittest.main() + def test_get_config_file_host_data(self, mock_exists) -> None: + with patch("builtins.open", mock_open(read_data=self.mock_config_data)): + expected_hosts: List[str] = [ + "🖥️ -> testHost1", + "🖥️ -> testHost2", + ] + hosts: List[str] = get_config_file_host_data() + self.assertEqual(hosts, expected_hosts) + + def test_file_not_found_error(self) -> None: + with ( + patch("os.path.exists", return_value=False), + self.assertRaises(FileNotFoundError), + ): + get_config_content() From b841d9736674c8c909b0673387aeefa997c94b9c Mon Sep 17 00:00:00 2001 From: Kanishk Pachauri Date: Sun, 1 Jun 2025 00:31:41 +0530 Subject: [PATCH 04/16] fix: editable install on local setup --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e06340a..6100143 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Install bitssh with docker ```bash - docker run --rm -it -v ~/.ssh:/root/.ssh mrsunglasses/bitssh + docker run --rm -it -v ~/.ssh:/root/.ssh mrsunglasses/bitssh ``` @@ -43,7 +43,7 @@ Install from source cd bitssh - python3 -m pip3 install . + python3 -m pip3 install -e . bitssh ``` From 69f0e5b6d78ed6a3cfddf218c6abeaf08c9bb08b Mon Sep 17 00:00:00 2001 From: Kanishk Pachauri Date: Sun, 1 Jun 2025 00:32:08 +0530 Subject: [PATCH 05/16] feat: removed unsed write of .ssh file --- .github/workflows/test.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e5a3765..40f08a3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -19,9 +19,7 @@ jobs: uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - - name: Create config mock - run: mkdir ~/.ssh && touch ~/.ssh/config - name: Install dependencies - run: pip3 install . && pip3 install pytest + run: pip3 install -e . && pip3 install pytest - name: Run Test run: pytest From 41dad331dfc95c6ede507ca05d0240227bf53928 Mon Sep 17 00:00:00 2001 From: Kanishk Pachauri Date: Sun, 1 Jun 2025 00:43:39 +0530 Subject: [PATCH 06/16] revert: config mock --- .github/workflows/test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 40f08a3..d51c84e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -19,6 +19,8 @@ jobs: uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} + - name: Create config mock + run: mkdir ~/.ssh && touch ~/.ssh/config - name: Install dependencies run: pip3 install -e . && pip3 install pytest - name: Run Test From e0e5c9f70485692ad31cb8c8b28e06cce82d95a1 Mon Sep 17 00:00:00 2001 From: Kanishk Pachauri Date: Sun, 1 Jun 2025 00:44:17 +0530 Subject: [PATCH 07/16] fix: port default logic on the function side --- src/bitssh/ui.py | 2 +- src/bitssh/utils.py | 2 +- tests/test_utils.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/bitssh/ui.py b/src/bitssh/ui.py index 9ab19ba..be42a7d 100644 --- a/src/bitssh/ui.py +++ b/src/bitssh/ui.py @@ -14,6 +14,6 @@ def draw_table(): table.add_column("User", justify="right", style="yellow") for i in get_config_file_row_data(): - table.add_row(i[0], i[1], "22" if i[2] is None else i[2], i[3]) + table.add_row(i[0], i[1], i[2], i[3]) console.print(table) diff --git a/src/bitssh/utils.py b/src/bitssh/utils.py index 56beba5..52b9795 100644 --- a/src/bitssh/utils.py +++ b/src/bitssh/utils.py @@ -37,7 +37,7 @@ def get_config_content(): user = user_match.group(1) if user_match else None port_match = port_pattern.search(lines, host_end) - port = port_match.group(1) if port_match else None + port = port_match.group(1) if port_match else "22" host_dict[host] = { "Hostname": hostname, diff --git a/tests/test_utils.py b/tests/test_utils.py index 68e129a..73613a6 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -23,8 +23,8 @@ def setUp(self) -> None: def test_get_config_content_success(self, mock_exists) -> None: with patch("builtins.open", mock_open(read_data=self.mock_config_data)): expected: Dict[str, Dict[str, str]] = { - "testHost1": {"Hostname": "test.hostname1.com", "User": "testUser1"}, - "testHost2": {"Hostname": "test.hostname2.com", "User": "testUser2"}, + "testHost1": {"Hostname": "test.hostname1.com", "User": "testUser1", "Port": "22"}, + "testHost2": {"Hostname": "test.hostname2.com", "User": "testUser2", "Port": "22"}, } result = get_config_content() self.assertEqual(result, expected) From e238f0e630f5b73196c27cf26c2dd990d9c307ee Mon Sep 17 00:00:00 2001 From: Kanishk Pachauri Date: Sun, 1 Jun 2025 01:10:16 +0530 Subject: [PATCH 08/16] fix: removed unsafe os.system --- src/bitssh/prompt.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/bitssh/prompt.py b/src/bitssh/prompt.py index 966502b..99474c6 100644 --- a/src/bitssh/prompt.py +++ b/src/bitssh/prompt.py @@ -1,4 +1,4 @@ -import os +import subprocess from typing import Dict, List, Optional from InquirerPy import inquirer @@ -19,13 +19,14 @@ def ask_host_prompt(): return cmd: str = answers - cmd = cmd[7::] - cmd = f"ssh {cmd}" - os.system("cls" if os.name == "nt" else "clear") + _cmd_exec_data = cmd[7::] # clean the data from answers + subprocess.run(["clear"], check=True) console.print( "Please Wait While Your System is Connecting to the Remote Server 🖥️", style="green", ) - os.system(cmd) + subprocess.run(["ssh", _cmd_exec_data], check=True) + except subprocess.CalledProcessError as e: + print(f"Error: {e.stderr}") except Exception as Error: print(f"\nInterrupted by {Error}") From 62d92ad57d5bd774f7d21fbc9382de47997ff0c8 Mon Sep 17 00:00:00 2001 From: Kanishk Pachauri Date: Sun, 1 Jun 2025 01:10:51 +0530 Subject: [PATCH 09/16] chore: reformatted --- src/bitssh/utils.py | 4 ---- tests/test_utils.py | 20 +++++++++++++++----- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/bitssh/utils.py b/src/bitssh/utils.py index 52b9795..6c85446 100644 --- a/src/bitssh/utils.py +++ b/src/bitssh/utils.py @@ -1,6 +1,5 @@ import os import re -from pprint import pprint from typing import Dict, List, Tuple CONFIG_FILE_PATH: str = os.path.expanduser("~/.ssh/config") @@ -61,6 +60,3 @@ def get_config_file_row_data(): def get_config_file_host_data() -> List[str]: return [f"🖥️ -> {row[1]}" for row in get_config_file_row_data()] - - -pprint(get_config_content()) diff --git a/tests/test_utils.py b/tests/test_utils.py index 73613a6..acea215 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -3,9 +3,11 @@ from typing import Dict, List, Tuple from unittest.mock import mock_open, patch -# Import the standalone functions instead of the class -from bitssh.utils import (get_config_content, get_config_file_host_data, - get_config_file_row_data) +from bitssh.utils import ( + get_config_content, + get_config_file_host_data, + get_config_file_row_data, +) class TestConfigPathUtility(unittest.TestCase): @@ -23,8 +25,16 @@ def setUp(self) -> None: def test_get_config_content_success(self, mock_exists) -> None: with patch("builtins.open", mock_open(read_data=self.mock_config_data)): expected: Dict[str, Dict[str, str]] = { - "testHost1": {"Hostname": "test.hostname1.com", "User": "testUser1", "Port": "22"}, - "testHost2": {"Hostname": "test.hostname2.com", "User": "testUser2", "Port": "22"}, + "testHost1": { + "Hostname": "test.hostname1.com", + "User": "testUser1", + "Port": "22", + }, + "testHost2": { + "Hostname": "test.hostname2.com", + "User": "testUser2", + "Port": "22", + }, } result = get_config_content() self.assertEqual(result, expected) From 3f99211ddc304399ea5074b4fce098d884d4d676 Mon Sep 17 00:00:00 2001 From: Kanishk Pachauri Date: Sun, 1 Jun 2025 01:11:16 +0530 Subject: [PATCH 10/16] feat: add short version flag '-v' --- src/bitssh/argument_parser.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/bitssh/argument_parser.py b/src/bitssh/argument_parser.py index b6d0f43..d994f08 100644 --- a/src/bitssh/argument_parser.py +++ b/src/bitssh/argument_parser.py @@ -8,6 +8,7 @@ def __init__(self) -> None: ) parser.add_argument( "--version", + "-v", action="store_true", default=False, help="Show the bitssh version.", From ba706d3a053bc46ebbc6e5a3427fa321fe17d5bb Mon Sep 17 00:00:00 2001 From: Kanishk Pachauri Date: Sun, 1 Jun 2025 01:11:52 +0530 Subject: [PATCH 11/16] feat: version and deps bump --- pyproject.toml | 8 ++++---- src/bitssh/__init__.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index a07711e..95cf27d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,10 @@ [build-system] -requires = ["setuptools>=61.0.0", "wheel"] +requires = ["setuptools>=80.9.0", "wheel"] build-backend = "setuptools.build_meta" [project] name = "bitssh" -version = "3.0.0" +version = "3.1.0" description = "A New and Modern SSH connector written in Python." readme = "README.md" authors = [ @@ -23,7 +23,7 @@ classifiers = [ # List of https://pypi.org/classifiers/ ] keywords = ["bitssh", "sshmanager", "commandline"] dependencies = [ - "rich", + "rich>=14.0.0", "pre-commit>=3.6.0", "path>=16.9.0", "InquirerPy>=0.3.4", @@ -41,7 +41,7 @@ Docs = "https://github.com/Mr-Sunglasses/bitssh/blob/master/docs/docs.md" bitssh = "bitssh.__main__:main" [tool.bumpver] -current_version = "3.0.0" +current_version = "3.1.0" version_pattern = "MAJOR.MINOR.PATCH" commit_message = "Bump version {old_version} -> {new_version}" commit = true diff --git a/src/bitssh/__init__.py b/src/bitssh/__init__.py index c279046..6629147 100644 --- a/src/bitssh/__init__.py +++ b/src/bitssh/__init__.py @@ -5,4 +5,4 @@ pass -__version__ = "3.0.0" +__version__ = "3.1.0" From 6af9b5e2812d2bcd336010cd0a9daafe76983427 Mon Sep 17 00:00:00 2001 From: Kanishk Pachauri Date: Sun, 1 Jun 2025 01:22:05 +0530 Subject: [PATCH 12/16] feat: Add CI to publish latest release to DockerHub --- .github/workflows/publish_dockerhub.yml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 .github/workflows/publish_dockerhub.yml diff --git a/.github/workflows/publish_dockerhub.yml b/.github/workflows/publish_dockerhub.yml new file mode 100644 index 0000000..823e9d4 --- /dev/null +++ b/.github/workflows/publish_dockerhub.yml @@ -0,0 +1,23 @@ +name: CI Pipeline + +on: + release: + types: + - created + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Login Dockerhub + env: + DOCKER_USERNAME: ${{secrets.DOCKER_USERNAME}} + DOCKER_PASSWORD: ${{secrets.DOCKER_PASSWORD}} + run: docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD + + - name: Build the Docker image + run: docker build -t mrsunglasses/bitssh . + - name: Push to Dockerhub + run: docker push mrsunglasses/bitssh:latest \ No newline at end of file From 26b6a74b1e6c835b9cc126866a52a88d264efe73 Mon Sep 17 00:00:00 2001 From: Kanishk Pachauri Date: Sun, 1 Jun 2025 01:24:06 +0530 Subject: [PATCH 13/16] Update src/bitssh/utils.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/bitssh/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bitssh/utils.py b/src/bitssh/utils.py index 6c85446..419762f 100644 --- a/src/bitssh/utils.py +++ b/src/bitssh/utils.py @@ -22,7 +22,7 @@ def get_config_content(): host_pattern = re.compile(r"Host\s+(\w+)", re.MULTILINE) hostname_pattern = re.compile(r"(?:HostName|Hostname)\s+(\S+)", re.MULTILINE) user_pattern = re.compile(r"User\s+(\S+)", re.MULTILINE) - port_pattern = re.compile(r"port\s+(\d+)", re.MULTILINE) + port_pattern = re.compile(r"port\s+(\d+)", re.MULTILINE | re.IGNORECASE) host_dict = {} for match in host_pattern.finditer(lines): From 3591255bcfe0b290a525a61977fc1492cfc62a56 Mon Sep 17 00:00:00 2001 From: Kanishk Pachauri Date: Sun, 1 Jun 2025 01:24:19 +0530 Subject: [PATCH 14/16] Update src/bitssh/prompt.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/bitssh/prompt.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/bitssh/prompt.py b/src/bitssh/prompt.py index 99474c6..4351a05 100644 --- a/src/bitssh/prompt.py +++ b/src/bitssh/prompt.py @@ -20,7 +20,10 @@ def ask_host_prompt(): cmd: str = answers _cmd_exec_data = cmd[7::] # clean the data from answers - subprocess.run(["clear"], check=True) + if os.name == "nt": # Windows + subprocess.run(["cls"], shell=True, check=True) + else: # Unix-like systems + subprocess.run(["clear"], check=True) console.print( "Please Wait While Your System is Connecting to the Remote Server 🖥️", style="green", From 0192f91b8940fd3560a5a9208f7fa9de8e66c6ed Mon Sep 17 00:00:00 2001 From: Kanishk Pachauri Date: Sun, 1 Jun 2025 01:26:46 +0530 Subject: [PATCH 15/16] Update src/bitssh/prompt.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/bitssh/prompt.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/bitssh/prompt.py b/src/bitssh/prompt.py index 4351a05..5293e9e 100644 --- a/src/bitssh/prompt.py +++ b/src/bitssh/prompt.py @@ -19,7 +19,10 @@ def ask_host_prompt(): return cmd: str = answers - _cmd_exec_data = cmd[7::] # clean the data from answers + try: + _cmd_exec_data = cmd.split('-> ')[1] # clean the data from answers + except IndexError: + raise ValueError("Invalid format: expected '-> ' delimiter in the answer.") if os.name == "nt": # Windows subprocess.run(["cls"], shell=True, check=True) else: # Unix-like systems From 6c71eacfe2d67e49b0e3e07ed6f6e9da123f048b Mon Sep 17 00:00:00 2001 From: Kanishk Pachauri Date: Sun, 1 Jun 2025 01:34:54 +0530 Subject: [PATCH 16/16] fix: import os and fix exception catch --- src/bitssh/prompt.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/bitssh/prompt.py b/src/bitssh/prompt.py index 5293e9e..f540897 100644 --- a/src/bitssh/prompt.py +++ b/src/bitssh/prompt.py @@ -1,3 +1,4 @@ +import os import subprocess from typing import Dict, List, Optional @@ -20,7 +21,7 @@ def ask_host_prompt(): cmd: str = answers try: - _cmd_exec_data = cmd.split('-> ')[1] # clean the data from answers + _cmd_exec_data = cmd.split("-> ")[1] # clean the data from answers except IndexError: raise ValueError("Invalid format: expected '-> ' delimiter in the answer.") if os.name == "nt": # Windows @@ -33,6 +34,6 @@ def ask_host_prompt(): ) subprocess.run(["ssh", _cmd_exec_data], check=True) except subprocess.CalledProcessError as e: - print(f"Error: {e.stderr}") + print(f"Error: {e.stdout}") except Exception as Error: print(f"\nInterrupted by {Error}")