Skip to content

Commit

Permalink
Merge pull request #21 from mirusu400/main
Browse files Browse the repository at this point in the history
feat: Use argparse instead sys.argv[..] (Rework of #19)
  • Loading branch information
iamDyeus authored Oct 28, 2024
2 parents 92b8be6 + 7030f6d commit 5c5b738
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 38 deletions.
58 changes: 34 additions & 24 deletions tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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()
54 changes: 40 additions & 14 deletions tkreload/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -16,6 +17,7 @@
if platform.system() == "Windows":
import msvcrt


class TkreloadApp:
"""Main application class for managing the Tkinter app."""

Expand All @@ -39,17 +41,23 @@ 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

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)
Expand All @@ -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)
Expand All @@ -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):
Expand All @@ -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]")
Expand All @@ -113,6 +138,7 @@ def main():
tkreload_app = TkreloadApp(app_file)
tkreload_app.start()


if __name__ == "__main__":
clear_terminal()
main()

0 comments on commit 5c5b738

Please sign in to comment.