2323import os
2424import re
2525import sys
26- from typing import Dict , List , Tuple , Any
26+ from typing import Dict , List , Tuple , Any , Optional
2727import yaml
2828
29+
30+ def _safe_read_file (
31+ file_path : str ,
32+ required : bool = True ,
33+ description : str = "file" ,
34+ silent_missing : bool = False
35+ ) -> Optional [str ]:
36+ """
37+ Safely reads file content and handles common file errors.
38+
39+ Args:
40+ file_path: Path to the file.
41+ required: If True, exits the program with code 1 on read failure.
42+ If False, prints a warning (or ignores) and returns None.
43+ description: Description of the file type for error logging.
44+ silent_missing: If True, silently ignores FileNotFoundError (returns None).
45+
46+ Returns:
47+ The file content string, or None if reading failed/was ignored.
48+ """
49+ try :
50+ with open (file_path , 'r' , encoding = 'utf-8' ) as f :
51+ return f .read ()
52+ except FileNotFoundError :
53+ if silent_missing :
54+ return None
55+ if required :
56+ print (f"Error: { description .capitalize ()} not found: { file_path } " , file = sys .stderr )
57+ sys .exit (1 )
58+ else :
59+ print (f"Warning: { description .capitalize ()} not found: { file_path } " , file = sys .stderr )
60+ return None
61+ except PermissionError :
62+ if required :
63+ print (f"Error: Permission denied reading { description } : { file_path } " , file = sys .stderr )
64+ sys .exit (1 )
65+ else :
66+ print (f"Warning: Permission denied reading { description } : { file_path } " , file = sys .stderr )
67+ return None
68+ except IOError as e :
69+ if required :
70+ print (f"Error reading { description } { file_path } : { e } " , file = sys .stderr )
71+ sys .exit (1 )
72+ else :
73+ print (f"Warning: Error reading { description } { file_path } : { e } " , file = sys .stderr )
74+ return None
75+
76+
2977class ConfigManager :
3078 """
3179 Handles loading, validation, and interpolation of the regex configuration rules.
@@ -106,15 +154,9 @@ def _compute_variables(self) -> Dict[str, str]:
106154
107155 def load_config (self ) -> List [Dict [str , str ]]:
108156 """Load and resolve rules from config."""
157+ content = _safe_read_file (self .config_path , required = True , description = "config file" )
109158 try :
110- with open (self .config_path , 'r' , encoding = 'utf-8' ) as f :
111- config = yaml .safe_load (f )
112- except FileNotFoundError :
113- print (f"Error: Config file not found: { self .config_path } " , file = sys .stderr )
114- sys .exit (1 )
115- except PermissionError :
116- print (f"Error: Permission denied reading config file: { self .config_path } " , file = sys .stderr )
117- sys .exit (1 )
159+ config = yaml .safe_load (content )
118160 except yaml .YAMLError as e :
119161 print (f"Error parsing config file: { e } " , file = sys .stderr )
120162 sys .exit (1 )
@@ -330,14 +372,12 @@ def load_ignore_file(file_path: str) -> List[str]:
330372 Read ignore paths from a file.
331373 """
332374 ignore_dirs = []
333- try :
334- with open (file_path , 'r' , encoding = 'utf-8' ) as f :
335- for line in f :
336- line = line .strip ()
337- if line and not line .startswith ('#' ):
338- ignore_dirs .append (line )
339- except FileNotFoundError :
340- pass
375+ content = _safe_read_file (file_path , required = False , silent_missing = True )
376+ if content :
377+ for line in content .splitlines ():
378+ line = line .strip ()
379+ if line and not line .startswith ('#' ):
380+ ignore_dirs .append (line )
341381 return ignore_dirs
342382
343383
@@ -449,21 +489,12 @@ def read_package_file(file_path: str) -> List[str]:
449489 A list of package paths.
450490 """
451491 packages = []
452- try :
453- with open (file_path , 'r' , encoding = 'utf-8' ) as f :
454- for line in f :
455- line = line .strip ()
456- if line and not line .startswith ('#' ):
457- packages .append (line )
458- except FileNotFoundError :
459- print (f"Error: Package file not found: { file_path } " , file = sys .stderr )
460- sys .exit (1 )
461- except PermissionError :
462- print (f"Error: Permission denied reading package file: { file_path } " , file = sys .stderr )
463- sys .exit (1 )
464- except IOError as e :
465- print (f"Error reading package file: { e } " , file = sys .stderr )
466- sys .exit (1 )
492+ content = _safe_read_file (file_path , required = True , description = "package file" )
493+ if content :
494+ for line in content .splitlines ():
495+ line = line .strip ()
496+ if line and not line .startswith ('#' ):
497+ packages .append (line )
467498 return packages
468499
469500
@@ -597,18 +628,11 @@ def parse_targets_file(file_path: str) -> List[Tuple[str, str]]:
597628 """
598629 Parses a YAML targets file into a list of (dependency, version) tuples.
599630 """
600- if not os .path .exists (file_path ):
601- print (f"Error: Targets file not found: { file_path } " , file = sys .stderr )
602- sys .exit (1 )
603-
631+ content = _safe_read_file (file_path , required = True , description = "targets file" )
604632 try :
605- with open (file_path , 'r' , encoding = 'utf-8' ) as f :
606- raw_targets = yaml .safe_load (f )
607- except PermissionError :
608- print (f"Error: Permission denied reading targets file: { file_path } " , file = sys .stderr )
609- sys .exit (1 )
633+ raw_targets = yaml .safe_load (content )
610634 except Exception as e :
611- print (f"Error reading or parsing targets file { file_path } : { e } " , file = sys .stderr )
635+ print (f"Error parsing targets YAML mapping : { e } " , file = sys .stderr )
612636 sys .exit (1 )
613637
614638 if not isinstance (raw_targets , dict ):
@@ -632,7 +656,6 @@ def parse_targets_file(file_path: str) -> List[Tuple[str, str]]:
632656 return targets
633657
634658
635-
636659def main ():
637660 script_dir = os .path .dirname (os .path .abspath (__file__ ))
638661 default_config = os .path .join (script_dir , "regex_config.yaml" )
0 commit comments