From 7030f6ddef48bea71e2b9b7c0e1584bdb808ba3d Mon Sep 17 00:00:00 2001 From: mirusu400 Date: Mon, 28 Oct 2024 09:16:06 +0900 Subject: [PATCH] feat: Use argparse instead sys.argv[..] (Rework of #19) --- tests/test_main.py | 58 +++++++++++++++++++++++++++------------------- tkreload/main.py | 54 +++++++++++++++++++++++++++++++----------- 2 files changed, 74 insertions(+), 38 deletions(-) diff --git a/tests/test_main.py b/tests/test_main.py index 4ac4b2e..f93431d 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -6,28 +6,31 @@ import time import os -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'src'))) +sys.path.insert( + 0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src")) +) + class TestTkreloadApp(unittest.TestCase): - @patch('tkreload.main.subprocess.Popen') - @patch('tkreload.main.show_progress') + @patch("tkreload.main.subprocess.Popen") + @patch("tkreload.main.show_progress") def test_run_tkinter_app(self, mock_show_progress, mock_popen): - app = TkreloadApp('example/sample_app.py') + app = TkreloadApp("example/sample_app.py") process = Mock() mock_popen.return_value = process - + result = app.run_tkinter_app() mock_show_progress.assert_called_once() - mock_popen.assert_called_once_with([sys.executable, 'example/sample_app.py']) + mock_popen.assert_called_once_with([sys.executable, "example/sample_app.py"]) self.assertEqual(result, process) - - @patch('tkreload.main.Observer') - @patch('tkreload.main.AppFileEventHandler') + + @patch("tkreload.main.Observer") + @patch("tkreload.main.AppFileEventHandler") def test_monitor_file_changes(self, mock_event_handler, mock_observer): - app = TkreloadApp('example/sample_app.py') + app = TkreloadApp("example/sample_app.py") mock_callback = Mock() - + observer = app.monitor_file_changes(mock_callback) mock_event_handler.assert_called_once() mock_observer().schedule.assert_called_once() @@ -39,27 +42,34 @@ def test_monitor_file_changes(self, mock_event_handler, mock_observer): # app = TkreloadApp('example/sample_app.py') # mock_process = Mock() # mock_popen.return_value = mock_process - + # with self.assertRaises(SystemExit): # app.start() - + # mock_process.terminate.assert_called_once() - @patch('tkreload.main.sys.argv', ['tkreload', 'example/sample_app.py']) - @patch('tkreload.main.file_exists', return_value=True) - @patch('tkreload.main.TkreloadApp') + @patch("tkreload.main.sys.argv", ["tkreload", "example/sample_app.py"]) + @patch("tkreload.main.file_exists", return_value=True) + @patch("tkreload.main.TkreloadApp") def test_main_function(self, mock_tkreload_app, mock_file_exists): main() - mock_file_exists.assert_called_once_with('example/sample_app.py') - mock_tkreload_app.assert_called_once_with('example/sample_app.py') + mock_file_exists.assert_called_once_with("example/sample_app.py") + mock_tkreload_app.assert_called_once_with("example/sample_app.py") mock_tkreload_app().start.assert_called_once() - @patch('tkreload.main.sys.argv', ['tkreload']) - @patch('tkreload.main.Console') - def test_main_function_no_file_provided(self, mock_console): - with self.assertRaises(SystemExit): + @patch("tkreload.main.sys.argv", ["tkreload"]) + @patch("tkreload.main.Console") + @patch("tkreload.main.argparse.ArgumentParser") + def test_main_function_no_file_provided(self, mock_parser, mock_console): + mock_parser_instance = Mock() + mock_parser.return_value = mock_parser_instance + mock_parser_instance.parse_args.side_effect = SystemExit(2) + + with self.assertRaises(SystemExit) as cm: main() - mock_console().print.assert_called_once_with("[bold red]Error: No Tkinter app file provided![/bold red]") -if __name__ == '__main__': + self.assertEqual(cm.exception.code, 2) + + +if __name__ == "__main__": unittest.main() diff --git a/tkreload/main.py b/tkreload/main.py index f9de914..fb4dcaf 100644 --- a/tkreload/main.py +++ b/tkreload/main.py @@ -4,6 +4,7 @@ import os import select import platform +import argparse from rich.console import Console from watchdog.observers import Observer from .app_event_handler import AppFileEventHandler @@ -16,6 +17,7 @@ if platform.system() == "Windows": import msvcrt + class TkreloadApp: """Main application class for managing the Tkinter app.""" @@ -39,9 +41,13 @@ def monitor_file_changes(self, on_reload): self.observer.stop() self.observer.join() - event_handler = AppFileEventHandler(on_reload, self.app_file, self.auto_reload_manager) + event_handler = AppFileEventHandler( + on_reload, self.app_file, self.auto_reload_manager + ) self.observer = Observer() - self.observer.schedule(event_handler, path=os.path.dirname(self.app_file) or '.', recursive=False) + self.observer.schedule( + event_handler, path=os.path.dirname(self.app_file) or ".", recursive=False + ) self.observer.start() return self.observer @@ -49,7 +55,9 @@ def restart_app(self): """Restarts the Tkinter app.""" if self.process: self.reload_count += 1 - self.console.log(f"[bold yellow]Restarting the Tkinter app... (x{self.reload_count})[/bold yellow]") + self.console.log( + f"[bold yellow]Restarting the Tkinter app... (x{self.reload_count})[/bold yellow]" + ) self.process.terminate() self.process.wait() time.sleep(1) @@ -61,17 +69,27 @@ def start(self): self.monitor_file_changes(self.restart_app) try: - self.console.print("\n\n\t[bold cyan]Tkreload[/bold cyan] [bold blue]is running ✅\n\t[/bold blue]- Press [bold cyan]H[/bold cyan] for help,\n\t[bold cyan]- R[/bold cyan] to restart,\n\t[bold cyan]- A[/bold cyan] to toggle auto-reload (currently [bold magenta]{}[/bold magenta]),\n\t[bold red]- Ctrl + C[/bold red] to exit.".format("Disabled" if not self.auto_reload_manager.get_status() else "Enabled")) + self.console.print( + "\n\n\t[bold cyan]Tkreload[/bold cyan] [bold blue]is running ✅\n\t[/bold blue]- Press [bold cyan]H[/bold cyan] for help,\n\t[bold cyan]- R[/bold cyan] to restart,\n\t[bold cyan]- A[/bold cyan] to toggle auto-reload (currently [bold magenta]{}[/bold magenta]),\n\t[bold red]- Ctrl + C[/bold red] to exit.".format( + "Disabled" + if not self.auto_reload_manager.get_status() + else "Enabled" + ) + ) while True: if platform.system() == "Windows": if msvcrt.kbhit(): # Check for keyboard input (Windows only) - user_input = msvcrt.getch().decode('utf-8').lower() # Read single character input + user_input = ( + msvcrt.getch().decode("utf-8").lower() + ) # Read single character input self.handle_input(user_input) else: # Use select for Unix-like systems if sys.stdin in select.select([sys.stdin], [], [], 0)[0]: - user_input = sys.stdin.read(1).lower() # Capture a single character input + user_input = sys.stdin.read( + 1 + ).lower() # Capture a single character input self.handle_input(user_input) time.sleep(0.1) @@ -85,11 +103,13 @@ def start(self): def handle_input(self, user_input): """Handles the user input commands.""" - if user_input == 'h': - show_help("Enabled" if self.auto_reload_manager.get_status() else "Disabled") - elif user_input == 'r': + if user_input == "h": + show_help( + "Enabled" if self.auto_reload_manager.get_status() else "Disabled" + ) + elif user_input == "r": self.restart_app() - elif user_input == 'a': + elif user_input == "a": self.toggle_auto_reload() def toggle_auto_reload(self): @@ -99,12 +119,17 @@ def toggle_auto_reload(self): self.reload_count = 0 status = "Enabled" if self.auto_reload_manager.get_status() else "Disabled" + def main(): - if len(sys.argv) < 2: - Console().print("[bold red]Error: No Tkinter app file provided![/bold red]") - sys.exit(1) + parser = argparse.ArgumentParser( + description="Real-time reload Tkinter app", + formatter_class=argparse.RawDescriptionHelpFormatter, + ) + parser.add_argument("app_file", help="Tkinter app file path") - app_file = sys.argv[1] + args = parser.parse_args() + + app_file = args.app_file if not file_exists(app_file): Console().print(f"[bold red]Error: File '{app_file}' not found![/bold red]") @@ -113,6 +138,7 @@ def main(): tkreload_app = TkreloadApp(app_file) tkreload_app.start() + if __name__ == "__main__": clear_terminal() main()