Skip to content

Commit eb16a34

Browse files
feat(cli): add --version option 🤖🤖🤖 (#563)
* feat(cli): add version flag * fix(cli): parse args before acquiring single-instance lock so --version works while app is running Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
1 parent 81bfe93 commit eb16a34

2 files changed

Lines changed: 56 additions & 3 deletions

File tree

src/vocalinux/main.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
import logging
99
import sys
1010

11+
from .version import __version__
12+
1113
# Configure logging
1214
logging.basicConfig(
1315
level=logging.INFO,
@@ -21,7 +23,13 @@
2123

2224
def parse_arguments():
2325
"""Parse command line arguments."""
24-
parser = argparse.ArgumentParser(description="Vocalinux")
26+
parser = argparse.ArgumentParser(prog="vocalinux", description="Vocalinux")
27+
parser.add_argument(
28+
"--version",
29+
action="version",
30+
version=f"%(prog)s {__version__}",
31+
help="Show the installed Vocalinux version and exit",
32+
)
2533
parser.add_argument("--debug", action="store_true", help="Enable debug logging")
2634
# default model, language and engine are loaded from default config
2735
# due to priority of args over config
@@ -214,6 +222,10 @@ def check_appindicator_support():
214222

215223
def main():
216224
"""Main entry point for the application."""
225+
# Parse arguments first so flags like --version work even when
226+
# another instance already holds the single-instance lock
227+
args = parse_arguments()
228+
217229
# Check for single instance BEFORE any initialization
218230
from . import single_instance
219231

@@ -241,8 +253,6 @@ def main():
241253
# Register cleanup to release lock on exit
242254
atexit.register(single_instance.release_lock)
243255

244-
args = parse_arguments()
245-
246256
# Configure debug logging if requested
247257
if args.debug:
248258
logging.getLogger().setLevel(logging.DEBUG)

tests/test_main_args_deps.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
import os
99
import sys
1010
import unittest
11+
from contextlib import redirect_stdout
12+
from io import StringIO
1113
from unittest.mock import MagicMock, call, patch
1214

1315
import pytest
@@ -21,6 +23,14 @@ def _restore_sys_modules():
2123
added = set(sys.modules.keys()) - set(saved.keys())
2224
for k in added:
2325
del sys.modules[k]
26+
# Also drop the stale attribute from the parent package so that
27+
# mock.patch and a later re-import resolve to the same fresh module
28+
parent, _, child = k.rpartition(".")
29+
if parent and parent in sys.modules:
30+
try:
31+
delattr(sys.modules[parent], child)
32+
except AttributeError:
33+
pass
2434
for k, v in saved.items():
2535
if k not in sys.modules or sys.modules[k] is not v:
2636
sys.modules[k] = v
@@ -50,6 +60,20 @@ def test_parse_args_debug_flag(self):
5060
args = parse_arguments()
5161
assert args.debug is True
5262

63+
def test_parse_args_version_flag(self):
64+
"""Test that --version prints the package version and exits."""
65+
from vocalinux.version import __version__
66+
67+
output = StringIO()
68+
with patch.object(sys, "argv", ["vocalinux", "--version"]):
69+
from vocalinux.main import parse_arguments
70+
71+
with redirect_stdout(output), self.assertRaises(SystemExit) as exit_context:
72+
parse_arguments()
73+
74+
assert exit_context.exception.code == 0
75+
assert output.getvalue() == f"vocalinux {__version__}\n"
76+
5377
def test_parse_args_model_argument(self):
5478
"""Test parsing with model argument."""
5579
with patch.object(sys, "argv", ["vocalinux", "--model", "small"]):
@@ -282,6 +306,25 @@ def test_main_single_instance_already_running(
282306
main()
283307
assert exc_info.value.code == 1
284308

309+
def test_main_version_flag_with_lock_held(self):
310+
"""Test that --version works even when another instance holds the lock."""
311+
from vocalinux.main import main
312+
from vocalinux.version import __version__
313+
314+
mock_single_instance = MagicMock()
315+
mock_single_instance.acquire_lock.return_value = False
316+
317+
output = StringIO()
318+
with patch.dict(sys.modules, {"vocalinux.single_instance": mock_single_instance}):
319+
with patch.object(sys, "argv", ["vocalinux", "--version"]):
320+
with redirect_stdout(output):
321+
with pytest.raises(SystemExit) as exc_info:
322+
main()
323+
324+
assert exc_info.value.code == 0
325+
assert output.getvalue() == f"vocalinux {__version__}\n"
326+
mock_single_instance.acquire_lock.assert_not_called()
327+
285328
@patch("vocalinux.main.check_display_available")
286329
@patch("vocalinux.main.logging")
287330
@patch("vocalinux.main.check_dependencies")

0 commit comments

Comments
 (0)