diff --git a/src/fenrirscreenreader/commands/onKeyInput/80500-numlock.py b/src/fenrirscreenreader/commands/onKeyInput/80500-numlock.py index 4eb2ec0f..10d2bdd5 100644 --- a/src/fenrirscreenreader/commands/onKeyInput/80500-numlock.py +++ b/src/fenrirscreenreader/commands/onKeyInput/80500-numlock.py @@ -24,11 +24,22 @@ class command: def run(self): if self.env["input"]["oldNumLock"] == self.env["input"]["newNumLock"]: return - + # Only announce numlock changes if an actual numlock key was pressed - # This prevents spurious announcements from external numpad automatic state changes + # AND the LED state actually changed (some numpads send spurious NUMLOCK events) current_input = self.env["input"]["currInput"] - if current_input and "KEY_NUMLOCK" in current_input: + + # Check if this is a genuine numlock key press by verifying: + # 1. KEY_NUMLOCK is in the current input sequence + # 2. The LED state has actually changed + # 3. This isn't just a side effect from a KP_ key (which some buggy numpads do) + is_genuine_numlock = ( + current_input and + "KEY_NUMLOCK" in current_input and + not any(key.startswith("KEY_KP") for key in current_input if isinstance(key, str)) + ) + + if is_genuine_numlock: if self.env["input"]["newNumLock"]: self.env["runtime"]["OutputManager"].present_text( _("Numlock on"), interrupt=True diff --git a/src/fenrirscreenreader/commands/vmenu-profiles/KEY/fenrir/management/reset_defaults.py b/src/fenrirscreenreader/commands/vmenu-profiles/KEY/fenrir/management/reset_defaults.py index 2775b87e..33fcb0e9 100644 --- a/src/fenrirscreenreader/commands/vmenu-profiles/KEY/fenrir/management/reset_defaults.py +++ b/src/fenrirscreenreader/commands/vmenu-profiles/KEY/fenrir/management/reset_defaults.py @@ -159,8 +159,7 @@ class command(config_command): except Exception as e: self.present_text( - f"Failed to create default configuration: { - str(e)}", + f"Failed to create default configuration: {str(e)}", interrupt=False, flush=False, ) diff --git a/src/fenrirscreenreader/commands/vmenu-profiles/KEY/fenrir/management/revert_to_saved.py b/src/fenrirscreenreader/commands/vmenu-profiles/KEY/fenrir/management/revert_to_saved.py index 474830a6..d3ccc07e 100644 --- a/src/fenrirscreenreader/commands/vmenu-profiles/KEY/fenrir/management/revert_to_saved.py +++ b/src/fenrirscreenreader/commands/vmenu-profiles/KEY/fenrir/management/revert_to_saved.py @@ -45,8 +45,7 @@ class command: sound_driver.initialize(self.env) except Exception as e: print( - f"revert_to_saved sound_driver: Error reinitializing sound driver: { - str(e)}" + f"revert_to_saved sound_driver: Error reinitializing sound driver: {str(e)}" ) self.env["runtime"]["OutputManager"].present_text( diff --git a/src/fenrirscreenreader/core/dynamicVoiceMenu.py b/src/fenrirscreenreader/core/dynamicVoiceMenu.py index 1faca827..c9b0118b 100644 --- a/src/fenrirscreenreader/core/dynamicVoiceMenu.py +++ b/src/fenrirscreenreader/core/dynamicVoiceMenu.py @@ -189,9 +189,7 @@ class DynamicApplyVoiceCommand: # Debug: verify what was actually set self.env["runtime"]["OutputManager"].present_text( - f"Speech driver now has module: { - SpeechDriver.module}, voice: { - SpeechDriver.voice}", + f"Speech driver now has module: {SpeechDriver.module}, voice: {SpeechDriver.voice}", interrupt=True, ) @@ -233,8 +231,7 @@ class DynamicApplyVoiceCommand: ) self.env["runtime"]["OutputManager"].present_text( - f"Failed to apply voice, reverted: { - str(e)}", + f"Failed to apply voice, reverted: {str(e)}", interrupt=False, flush=False, ) diff --git a/src/fenrirscreenreader/fenrirVersion.py b/src/fenrirscreenreader/fenrirVersion.py index 942205b8..cc9e002c 100644 --- a/src/fenrirscreenreader/fenrirVersion.py +++ b/src/fenrirscreenreader/fenrirVersion.py @@ -4,5 +4,5 @@ # Fenrir TTY screen reader # By Chrys, Storm Dragon, and contributors. -version = "2025.09.12" +version = "2025.09.13" code_name = "testing" diff --git a/tools/pre-commit-hook b/tools/pre-commit-hook index 4bdcd75b..ab92fc61 100755 --- a/tools/pre-commit-hook +++ b/tools/pre-commit-hook @@ -43,11 +43,18 @@ if [ -n "$STAGED_PYTHON_FILES" ]; then for file in $STAGED_PYTHON_FILES; do if [ -f "$file" ]; then - # Check for unterminated strings (the main issue from the email) - if grep -n 'f".*{$' "$file" >/dev/null 2>&1; then - echo -e "${RED}✗ $file: Potential unterminated f-string${NC}" + # Check for broken f-strings (multiline issues that cause syntax errors) + # Pattern 1: f-string with opening brace at end of line (likely broken across lines) + if grep -n 'f"[^"]*{[^}]*$' "$file" >/dev/null 2>&1; then + echo -e "${RED}✗ $file: Potential broken multiline f-string${NC}" + grep -n 'f"[^"]*{[^}]*$' "$file" | head -3 ISSUES_FOUND=1 fi + + # Pattern 2: Lines that end with just an opening brace (common in broken f-strings) + if grep -n '^\s*[^#]*{$' "$file" >/dev/null 2>&1; then + echo -e "${YELLOW}⚠ $file: Lines ending with lone opening brace (check f-strings)${NC}" + fi # Check for missing imports that are commonly used if grep -q 'debug\.DebugLevel\.' "$file" && ! grep -q 'from.*debug' "$file" && ! grep -q 'import.*debug' "$file"; then @@ -110,7 +117,7 @@ if [ -n "$STAGED_PYTHON_FILES" ]; then for file in $STAGED_PYTHON_FILES; do if [ -f "$file" ]; then # Check for potential passwords, keys, tokens - if grep -i -E '(password|passwd|pwd|key|token|secret|api_key).*=.*["\'][^"\']{8,}["\']' "$file" >/dev/null 2>&1; then + if grep -i -E '(password|passwd|pwd|key|token|secret|api_key).*=.*["'"'"'][^"'"'"']{8,}["'"'"']' "$file" >/dev/null 2>&1; then echo -e "${RED}✗ $file: Potential hardcoded secret detected${NC}" SECRETS_FOUND=1 fi @@ -126,7 +133,7 @@ else fi # Summary -echo -e "\n${'='*50}" +echo -e "\n==================================================" if [ $VALIDATION_FAILED -eq 0 ]; then echo -e "${GREEN}✓ All pre-commit validations passed${NC}" echo -e "${GREEN}Commit allowed to proceed${NC}" diff --git a/tools/validate_syntax.py b/tools/validate_syntax.py index 8525923d..ff71c77b 100755 --- a/tools/validate_syntax.py +++ b/tools/validate_syntax.py @@ -13,6 +13,7 @@ Usage: import ast import os +import re import sys import argparse import tempfile @@ -24,13 +25,34 @@ class SyntaxValidator: self.errors = [] self.warnings = [] self.fixed = [] - + + def check_fstring_issues(self, content): + """Check for common f-string formatting issues that cause syntax errors.""" + issues = [] + lines = content.split('\n') + + for i, line in enumerate(lines, 1): + # Check for f-strings that appear to be broken across lines + # Pattern: f"some text { + if re.search(r'f"[^"]*{[^}]*$', line.strip()): + issues.append(f"Line {i}: Potential broken multiline f-string") + + return issues + def validate_file(self, filepath): """Validate syntax of a single Python file.""" try: with open(filepath, 'r', encoding='utf-8') as f: content = f.read() - + + # Check for specific f-string issues before AST parsing + fstring_issues = self.check_fstring_issues(content) + if fstring_issues: + # Create a synthetic syntax error for f-string issues + error_msg = f"F-string issues: {'; '.join(fstring_issues)}" + self.errors.append((filepath, SyntaxError(error_msg), content)) + return False, content + # Parse with AST (catches syntax errors) ast.parse(content, filename=str(filepath)) return True, content