1+ import argparse
2+ import csv
3+ import fnmatch
4+ import io
5+ import os
6+ import zipfile
7+
8+
9+ def update_record (record_content , patterns ):
10+ """Update the RECORD file to remove entries for deleted files."""
11+ # Parse the existing RECORD
12+ records = []
13+ reader = csv .reader (io .StringIO (record_content ))
14+
15+ for row in reader :
16+ if not row :
17+ continue
18+ file_path = row [0 ]
19+ # Skip files that match removal patterns
20+ if not any (fnmatch .fnmatch (file_path , pattern ) for pattern in patterns ):
21+ records .append (row )
22+
23+ # Rebuild the RECORD content
24+ output = io .StringIO ()
25+ writer = csv .writer (output , lineterminator = '\n ' )
26+ for record in records :
27+ writer .writerow (record )
28+
29+ return output .getvalue ()
30+
31+
32+ def remove_from_zip (zip_filename , patterns ):
33+ temp_zip_filename = f"{ zip_filename } .tmp"
34+ record_content = None
35+
36+ # First pass: read RECORD file if it exists
37+ with zipfile .ZipFile (zip_filename , "r" ) as source_zip :
38+ for file in source_zip .infolist ():
39+ if file .filename .endswith ('.dist-info/RECORD' ):
40+ record_content = source_zip .read (file .filename ).decode ('utf-8' )
41+ break
42+
43+ # Second pass: create new zip without removed files and with updated RECORD
44+ with zipfile .ZipFile (zip_filename , "r" ) as source_zip , zipfile .ZipFile (
45+ temp_zip_filename , "w" , zipfile .ZIP_DEFLATED
46+ ) as temp_zip :
47+ # DEV: Use ZipInfo objects to ensure original file attributes are preserved
48+ for file in source_zip .infolist ():
49+ if any (fnmatch .fnmatch (file .filename , pattern ) for pattern in patterns ):
50+ continue
51+ elif file .filename .endswith ('.dist-info/RECORD' ) and record_content :
52+ # Update the RECORD file
53+ updated_record = update_record (record_content , patterns )
54+ temp_zip .writestr (file , updated_record )
55+ else :
56+ temp_zip .writestr (file , source_zip .read (file .filename ))
57+ os .replace (temp_zip_filename , zip_filename )
58+
59+
60+ def parse_args ():
61+ parser = argparse .ArgumentParser (description = "Remove specified file types from a ZIP archive." )
62+ parser .add_argument ("zipfile" , help = "Name of the ZIP file." )
63+ parser .add_argument ("patterns" , nargs = "+" , help = "File patterns to remove from the ZIP file." )
64+ return parser .parse_args ()
65+
66+
67+ if __name__ == "__main__" :
68+ args = parse_args ()
69+ remove_from_zip (args .zipfile , args .patterns )
0 commit comments