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>
This commit is contained in:
Anay Garodia
2026-07-25 13:04:52 -07:00
committed by GitHub
co-authored by Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
parent 81bfe9345b
commit eb16a34328
2 changed files with 56 additions and 3 deletions
+13 -3
View File
@@ -8,6 +8,8 @@ import atexit
import logging
import sys
from .version import __version__
# Configure logging
logging.basicConfig(
level=logging.INFO,
@@ -21,7 +23,13 @@ logger = logging.getLogger(__name__)
def parse_arguments():
"""Parse command line arguments."""
parser = argparse.ArgumentParser(description="Vocalinux")
parser = argparse.ArgumentParser(prog="vocalinux", description="Vocalinux")
parser.add_argument(
"--version",
action="version",
version=f"%(prog)s {__version__}",
help="Show the installed Vocalinux version and exit",
)
parser.add_argument("--debug", action="store_true", help="Enable debug logging")
# default model, language and engine are loaded from default config
# due to priority of args over config
@@ -214,6 +222,10 @@ def check_appindicator_support():
def main():
"""Main entry point for the application."""
# Parse arguments first so flags like --version work even when
# another instance already holds the single-instance lock
args = parse_arguments()
# Check for single instance BEFORE any initialization
from . import single_instance
@@ -241,8 +253,6 @@ def main():
# Register cleanup to release lock on exit
atexit.register(single_instance.release_lock)
args = parse_arguments()
# Configure debug logging if requested
if args.debug:
logging.getLogger().setLevel(logging.DEBUG)
+43
View File
@@ -8,6 +8,8 @@ Covers normal startup, debug mode, version flag, keyboard interrupt, and excepti
import os
import sys
import unittest
from contextlib import redirect_stdout
from io import StringIO
from unittest.mock import MagicMock, call, patch
import pytest
@@ -21,6 +23,14 @@ def _restore_sys_modules():
added = set(sys.modules.keys()) - set(saved.keys())
for k in added:
del sys.modules[k]
# Also drop the stale attribute from the parent package so that
# mock.patch and a later re-import resolve to the same fresh module
parent, _, child = k.rpartition(".")
if parent and parent in sys.modules:
try:
delattr(sys.modules[parent], child)
except AttributeError:
pass
for k, v in saved.items():
if k not in sys.modules or sys.modules[k] is not v:
sys.modules[k] = v
@@ -50,6 +60,20 @@ class TestParseArguments(unittest.TestCase):
args = parse_arguments()
assert args.debug is True
def test_parse_args_version_flag(self):
"""Test that --version prints the package version and exits."""
from vocalinux.version import __version__
output = StringIO()
with patch.object(sys, "argv", ["vocalinux", "--version"]):
from vocalinux.main import parse_arguments
with redirect_stdout(output), self.assertRaises(SystemExit) as exit_context:
parse_arguments()
assert exit_context.exception.code == 0
assert output.getvalue() == f"vocalinux {__version__}\n"
def test_parse_args_model_argument(self):
"""Test parsing with model argument."""
with patch.object(sys, "argv", ["vocalinux", "--model", "small"]):
@@ -282,6 +306,25 @@ class TestMainFunction(unittest.TestCase):
main()
assert exc_info.value.code == 1
def test_main_version_flag_with_lock_held(self):
"""Test that --version works even when another instance holds the lock."""
from vocalinux.main import main
from vocalinux.version import __version__
mock_single_instance = MagicMock()
mock_single_instance.acquire_lock.return_value = False
output = StringIO()
with patch.dict(sys.modules, {"vocalinux.single_instance": mock_single_instance}):
with patch.object(sys, "argv", ["vocalinux", "--version"]):
with redirect_stdout(output):
with pytest.raises(SystemExit) as exc_info:
main()
assert exc_info.value.code == 0
assert output.getvalue() == f"vocalinux {__version__}\n"
mock_single_instance.acquire_lock.assert_not_called()
@patch("vocalinux.main.check_display_available")
@patch("vocalinux.main.logging")
@patch("vocalinux.main.check_dependencies")