1- import mmap
21from dataclasses import dataclass , field
32from tempfile import TemporaryFile
4- from xml .sax import SAXParseException , handler
3+ from xml .sax import handler
54from xml .sax .handler import LexicalHandler
65from xml .sax .saxutils import XMLGenerator
76from xml .sax .xmlreader import AttributesImpl , Locator
1312from codemodder .context import CodemodExecutionContext
1413from codemodder .diff import create_diff
1514from codemodder .file_context import FileContext
15+ from codemodder .logging import logger
1616from codemodder .result import Result
1717
1818
@@ -171,12 +171,7 @@ def apply(
171171 file_context : FileContext ,
172172 results : list [Result ] | None ,
173173 ) -> ChangeSet | None :
174- if file_context .file_path .suffix .lower () not in (".config" , ".xml" ):
175- return None
176-
177- changes = []
178174 with TemporaryFile ("w+" ) as output_file :
179-
180175 # this will fail fast for files that are not XML
181176 try :
182177 transformer_instance = self .xml_transformer (
@@ -187,42 +182,33 @@ def apply(
187182 parser .setProperty (
188183 handler .property_lexical_handler , transformer_instance
189184 )
190- parser .parse (file_context .file_path )
185+ parser .parse (file_path := file_context .file_path )
191186 changes = transformer_instance .changes
192187 output_file .seek (0 )
188+ except Exception :
189+ file_context .add_failure (
190+ file_path , reason := "Failed to parse XML file"
191+ )
192+ logger .exception ("%s %s" , reason , file_path )
193+ return None
193194
194- except SAXParseException :
195+ if not changes :
195196 return None
196197
197- diff = ""
198- # don't calculate diff if no changes were reported
199- if changes :
200- with open (file_context .file_path , "r" ) as original :
201- # TODO there's a failure potential here for very large files
202- diff = create_diff (
203- original .readlines (),
204- output_file .readlines (),
205- )
206-
207- # don't write anything if no changes were issued
208- # avoids simply formatting the file
209- if changes and not context .dry_run :
210- with open (file_context .file_path , "w+b" ) as original :
211- # mmap can't map empty files, write something first
212- original .write (b"a" )
213- # copy contents of result into original file
214- # the snippet below preserves the original file metadata and accounts for large files.
215- output_file .seek (0 )
216- output_mmap = mmap .mmap (output_file .fileno (), 0 )
217-
218- original .truncate ()
219- original_mmap = mmap .mmap (original .fileno (), 0 )
220- original_mmap .resize (len (output_mmap ))
221- original_mmap [:] = output_mmap
222- original_mmap .flush ()
198+ new_lines = output_file .readlines ()
199+ with open (file_path , "r" ) as original :
200+ # TODO there's a failure potential here for very large files
201+ diff = create_diff (
202+ original .readlines (),
203+ new_lines ,
204+ )
205+
206+ if not context .dry_run :
207+ with open (file_path , "w+" ) as original :
208+ original .writelines (new_lines )
223209
224210 return ChangeSet (
225- path = str (file_context . file_path .relative_to (context .directory )),
211+ path = str (file_path .relative_to (context .directory )),
226212 diff = diff ,
227213 changes = changes ,
228214 )
0 commit comments