PyArchiveFile 0.27.6__tar.gz → 0.28.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyArchiveFile
3
- Version: 0.27.6
3
+ Version: 0.28.0
4
4
  Summary: A tar like file format name archivefile.
5
5
  Home-page: https://github.com/GameMaker2k/PyArchiveFile
6
6
  Download-URL: https://github.com/GameMaker2k/PyArchiveFile/archive/master.tar.gz
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyArchiveFile
3
- Version: 0.27.6
3
+ Version: 0.28.0
4
4
  Summary: A tar like file format name archivefile.
5
5
  Home-page: https://github.com/GameMaker2k/PyArchiveFile
6
6
  Download-URL: https://github.com/GameMaker2k/PyArchiveFile/archive/master.tar.gz
@@ -1,7 +1,9 @@
1
1
  LICENSE
2
2
  README.md
3
3
  archivefile.py
4
+ archivefile_py3.py
4
5
  pyarchivefile.py
6
+ pyarchivefile_py3.py
5
7
  pyproject.toml
6
8
  setup.cfg
7
9
  setup.py
@@ -0,0 +1,2 @@
1
+ pyarchivefile
2
+ pyarchivefile_py3
@@ -10,11 +10,11 @@
10
10
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
11
  Revised BSD License for more details.
12
12
 
13
- Copyright 2018-2024 Cool Dude 2k - http://idb.berlios.de/
14
- Copyright 2018-2024 Game Maker 2k - http://intdb.sourceforge.net/
15
- Copyright 2018-2024 Kazuki Przyborowski - https://github.com/KazukiPrzyborowski
13
+ Copyright 2018-2026 Cool Dude 2k - http://idb.berlios.de/
14
+ Copyright 2018-2026 Game Maker 2k - http://intdb.sourceforge.net/
15
+ Copyright 2018-2026 Kazuki Przyborowski - https://github.com/KazukiPrzyborowski
16
16
 
17
- $FileInfo: archivefile.py - Last Update: 11/19/2025 Ver. 0.27.6 RC 1 - Author: cooldude2k $
17
+ $FileInfo: archivefile.py - Last Update: 2/3/2026 Ver. 0.28.0 RC 1 - Author: cooldude2k $
18
18
  '''
19
19
 
20
20
  from __future__ import absolute_import, division, print_function, unicode_literals, generators, with_statement, nested_scopes
@@ -0,0 +1,509 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ '''
4
+ This program is free software; you can redistribute it and/or modify
5
+ it under the terms of the Revised BSD License.
6
+
7
+ This program is distributed in the hope that it will be useful,
8
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
9
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
+ Revised BSD License for more details.
11
+
12
+ Copyright 2018-2026 Cool Dude 2k - http://idb.berlios.de/
13
+ Copyright 2018-2026 Game Maker 2k - http://intdb.sourceforge.net/
14
+ Copyright 2018-2026 Kazuki Przyborowski - https://github.com/KazukiPrzyborowski
15
+
16
+ $FileInfo: archivefile_py3.py - Last Update: 2/3/2026 Ver. 0.28.0 RC 1 - Author: cooldude2k $
17
+ '''
18
+
19
+ from __future__ import annotations
20
+
21
+ import argparse
22
+ import binascii
23
+ import logging
24
+ import os
25
+ import sys
26
+ from io import BytesIO
27
+ from typing import Optional, Dict, Any
28
+
29
+ import pyarchivefile_py3 as pyarchivefile
30
+
31
+ # Keep original behavior: log to stdout with simple message format.
32
+ logging.basicConfig(format="%(message)s", stream=sys.stdout, level=logging.DEBUG)
33
+
34
+ # Unix SIGPIPE handling (exit cleanly on broken pipe).
35
+ if os.name != "nt":
36
+ import signal
37
+
38
+ if hasattr(signal, "SIGPIPE"):
39
+
40
+ def _sigpipe_handler(signum, frame): # noqa: ARG001
41
+ pyarchivefile.VerbosePrintOut("Received SIGPIPE, exiting gracefully.", "info")
42
+ raise SystemExit(0)
43
+
44
+ signal.signal(signal.SIGPIPE, _sigpipe_handler)
45
+
46
+ # Feature flags (re-exported from module; kept for CLI parity)
47
+ rarfile_support = pyarchivefile.rarfile_support
48
+ py7zr_support = pyarchivefile.py7zr_support
49
+
50
+ # Re-export metadata/constants from `pyarchivefile` (kept for --version, defaults, etc.)
51
+ __project__ = pyarchivefile.__project__
52
+ __program_name__ = pyarchivefile.__program_name__
53
+ __file_format_name__ = pyarchivefile.__file_format_name__
54
+ __file_format_magic__ = pyarchivefile.__file_format_magic__
55
+ __file_format_len__ = pyarchivefile.__file_format_len__
56
+ __file_format_hex__ = pyarchivefile.__file_format_hex__
57
+ __file_format_delimiter__ = pyarchivefile.__file_format_delimiter__
58
+ __file_format_dict__ = pyarchivefile.__file_format_dict__
59
+ __file_format_default__ = pyarchivefile.__file_format_default__
60
+ __file_format_multi_dict__ = pyarchivefile.__file_format_multi_dict__
61
+ __use_new_style__ = pyarchivefile.__use_new_style__
62
+ __use_advanced_list__ = pyarchivefile.__use_advanced_list__
63
+ __use_alt_inode__ = pyarchivefile.__use_alt_inode__
64
+ __project_url__ = pyarchivefile.__project_url__
65
+ __version_info__ = pyarchivefile.__version_info__
66
+ __version_date_info__ = pyarchivefile.__version_date_info__
67
+ __version_date__ = pyarchivefile.__version_date__
68
+ __version_date_plusrc__ = pyarchivefile.__version_date_plusrc__
69
+ __version__ = pyarchivefile.__version__
70
+
71
+
72
+ def _build_argparser() -> argparse.ArgumentParser:
73
+ p = argparse.ArgumentParser(
74
+ description="Manipulate archive files.",
75
+ conflict_handler="resolve",
76
+ add_help=True,
77
+ )
78
+
79
+ # Version information
80
+ p.add_argument("-V", "--version", action="version", version=f"{__program_name__} {__version__}")
81
+
82
+ # Input and output specifications
83
+ p.add_argument(
84
+ "-i",
85
+ "--input",
86
+ nargs="+",
87
+ help="Specify the file(s) to concatenate or the archive file to extract.",
88
+ required=True,
89
+ )
90
+ p.add_argument("-o", "--output", default=None, help="Specify the name for the extracted or output archive files.")
91
+
92
+ # Operations
93
+ p.add_argument("-c", "--create", action="store_true", help="Perform only the concatenation operation.")
94
+ p.add_argument("-e", "--extract", action="store_true", help="Perform only the extraction operation.")
95
+ p.add_argument("-t", "--convert", action="store_true", help="Convert a tar/zip/rar/7zip file to an archive file.")
96
+ p.add_argument("-r", "--repack", action="store_true", help="Re-concatenate files, fixing checksum errors if any.")
97
+ p.add_argument("-S", "--filestart", type=int, default=0, help="Start reading file at.")
98
+
99
+ # File manipulation options
100
+ p.add_argument("-F", "--format", default="auto", help="Specify the format to use.")
101
+ p.add_argument(
102
+ "-D",
103
+ "--delimiter",
104
+ default=__file_format_dict__["format_delimiter"],
105
+ help="Specify the delimiter to use.",
106
+ )
107
+ p.add_argument("-m", "--formatver", default=__file_format_dict__["format_ver"], help="Specify the format version.")
108
+ p.add_argument("-l", "--list", action="store_true", help="List files included in the archive file.")
109
+
110
+ # Compression options
111
+ p.add_argument("-P", "--compression", default="auto", help="Specify the compression method to use for concatenation.")
112
+ p.add_argument("-L", "--level", default=None, help="Specify the compression level for concatenation.")
113
+ p.add_argument("-W", "--wholefile", action="store_true", help="Whole file compression method to use for concatenation.")
114
+
115
+ # Checksum and validation
116
+ p.add_argument("-v", "--validate", action="store_true", help="Validate archive file checksums.")
117
+ p.add_argument("-C", "--checksum", default="md5", help="Specify the type of checksum to use. The default is md5.")
118
+ p.add_argument("-s", "--skipchecksum", action="store_true", help="Skip the checksum check of files.")
119
+ p.add_argument("-k", "--insecretkey", default=None, help="Secretkey to use for checksum input.")
120
+ p.add_argument("-K", "--outsecretkey", default=None, help="Secretkey to use for checksum output.")
121
+
122
+ # Permissions and metadata
123
+ p.add_argument("-p", "--preserve", action="store_false", help="Do not preserve permissions and timestamps of files.")
124
+
125
+ # Miscellaneous
126
+ p.add_argument("-d", "--verbose", action="store_true", help="Enable verbose mode to display various debugging information.")
127
+ p.add_argument("-T", "--text", action="store_true", help="Read file locations from a text file.")
128
+
129
+ return p
130
+
131
+
132
+ def _resolve_format(getargs: argparse.Namespace) -> Any:
133
+ """Compute the format dict exactly as the original script did."""
134
+ global __file_format_default__ # keep parity with original module-level behavior
135
+
136
+ fname = getargs.format
137
+ if fname == "auto":
138
+ fnamedict = __file_format_multi_dict__
139
+ __file_format_default__ = getargs.format
140
+ else:
141
+ fnamemagic = fname
142
+ fnamelen = len(fname)
143
+ fnamehex = binascii.hexlify(fname.encode("utf-8")).decode("utf-8")
144
+ __file_format_default__ = fnamemagic
145
+ fnamesty = __use_new_style__
146
+ fnamelst = __use_advanced_list__
147
+ fnameino = __use_alt_inode__
148
+ fnamedict = {
149
+ "format_name": fname,
150
+ "format_magic": fnamemagic,
151
+ "format_len": fnamelen,
152
+ "format_hex": fnamehex,
153
+ "format_delimiter": getargs.delimiter,
154
+ "format_ver": getargs.formatver,
155
+ "new_style": fnamesty,
156
+ "use_advanced_list": fnamelst,
157
+ "use_alt_inode": fnameino,
158
+ }
159
+ return fnamedict
160
+
161
+
162
+ def main(argv: Optional[list[str]] = None) -> int:
163
+ argparser = _build_argparser()
164
+ getargs = argparser.parse_args(argv)
165
+
166
+ fnamedict = _resolve_format(getargs)
167
+
168
+ # Determine the primary action based on user input (same order/behavior as original)
169
+ actions = ("create", "extract", "list", "repack", "validate")
170
+ active_action = next((a for a in actions if getattr(getargs, a)), None)
171
+
172
+ input_file = getargs.input[0]
173
+
174
+ if not active_action:
175
+ # Preserve original behavior: do nothing if no action flag is set.
176
+ return 0
177
+
178
+ if active_action == "create":
179
+ if getargs.convert:
180
+ checkcompressfile = pyarchivefile.CheckCompressionSubType(input_file, fnamedict, 0, True)
181
+ if (
182
+ (pyarchivefile.IsNestedDict(fnamedict) and checkcompressfile in fnamedict)
183
+ or (pyarchivefile.IsSingleDict(fnamedict) and checkcompressfile == fnamedict["format_magic"])
184
+ ):
185
+ tmpout = pyarchivefile.RePackArchiveFile(
186
+ input_file,
187
+ getargs.output,
188
+ "auto",
189
+ getargs.compression,
190
+ getargs.wholefile,
191
+ getargs.level,
192
+ pyarchivefile.compressionlistalt,
193
+ False,
194
+ 0,
195
+ 0,
196
+ 0,
197
+ [getargs.checksum] * 5,
198
+ getargs.skipchecksum,
199
+ [],
200
+ {},
201
+ fnamedict,
202
+ getargs.insecretkey,
203
+ getargs.outsecretkey,
204
+ False,
205
+ getargs.verbose,
206
+ False,
207
+ )
208
+ else:
209
+ tmpout = pyarchivefile.PackArchiveFileFromInFile(
210
+ input_file,
211
+ getargs.output,
212
+ __file_format_default__,
213
+ getargs.compression,
214
+ getargs.wholefile,
215
+ getargs.level,
216
+ pyarchivefile.compressionlistalt,
217
+ [getargs.checksum] * 5,
218
+ [],
219
+ {},
220
+ fnamedict,
221
+ getargs.outsecretkey,
222
+ getargs.verbose,
223
+ False,
224
+ )
225
+ if not tmpout:
226
+ return 1
227
+ else:
228
+ pyarchivefile.PackArchiveFile(
229
+ getargs.input,
230
+ getargs.output,
231
+ getargs.text,
232
+ __file_format_default__,
233
+ getargs.compression,
234
+ getargs.wholefile,
235
+ getargs.level,
236
+ pyarchivefile.compressionlistalt,
237
+ False,
238
+ [getargs.checksum] * 5,
239
+ [],
240
+ {},
241
+ fnamedict,
242
+ getargs.outsecretkey,
243
+ getargs.verbose,
244
+ False,
245
+ )
246
+
247
+ elif active_action == "repack":
248
+ if getargs.convert:
249
+ checkcompressfile = pyarchivefile.CheckCompressionSubType(input_file, fnamedict, 0, True)
250
+ if (
251
+ (pyarchivefile.IsNestedDict(fnamedict) and checkcompressfile in fnamedict)
252
+ or (pyarchivefile.IsSingleDict(fnamedict) and checkcompressfile == fnamedict["format_magic"])
253
+ ):
254
+ tmpout = pyarchivefile.RePackArchiveFile(
255
+ input_file,
256
+ getargs.output,
257
+ "auto",
258
+ getargs.compression,
259
+ getargs.wholefile,
260
+ getargs.level,
261
+ pyarchivefile.compressionlistalt,
262
+ False,
263
+ 0,
264
+ 0,
265
+ 0,
266
+ [getargs.checksum] * 5,
267
+ getargs.skipchecksum,
268
+ [],
269
+ {},
270
+ fnamedict,
271
+ getargs.insecretkey,
272
+ getargs.outsecretkey,
273
+ False,
274
+ getargs.verbose,
275
+ False,
276
+ )
277
+ else:
278
+ tmpout = pyarchivefile.PackArchiveFileFromInFile(
279
+ input_file,
280
+ getargs.output,
281
+ __file_format_default__,
282
+ getargs.compression,
283
+ getargs.wholefile,
284
+ getargs.level,
285
+ pyarchivefile.compressionlistalt,
286
+ [getargs.checksum] * 5,
287
+ [],
288
+ {},
289
+ fnamedict,
290
+ getargs.outsecretkey,
291
+ getargs.verbose,
292
+ False,
293
+ )
294
+ if not tmpout:
295
+ return 1
296
+ else:
297
+ pyarchivefile.RePackArchiveFile(
298
+ input_file,
299
+ getargs.output,
300
+ "auto",
301
+ getargs.compression,
302
+ getargs.wholefile,
303
+ getargs.level,
304
+ pyarchivefile.compressionlistalt,
305
+ False,
306
+ getargs.filestart,
307
+ 0,
308
+ 0,
309
+ [getargs.checksum] * 5,
310
+ getargs.skipchecksum,
311
+ [],
312
+ {},
313
+ fnamedict,
314
+ getargs.insecretkey,
315
+ getargs.outsecretkey,
316
+ False,
317
+ getargs.verbose,
318
+ False,
319
+ )
320
+
321
+ elif active_action == "extract":
322
+ if getargs.convert:
323
+ checkcompressfile = pyarchivefile.CheckCompressionSubType(input_file, fnamedict, 0, True)
324
+ tempout = BytesIO()
325
+ if (
326
+ (pyarchivefile.IsNestedDict(fnamedict) and checkcompressfile in fnamedict)
327
+ or (pyarchivefile.IsSingleDict(fnamedict) and checkcompressfile == fnamedict["format_magic"])
328
+ ):
329
+ tmpout = pyarchivefile.RePackArchiveFile(
330
+ input_file,
331
+ tempout,
332
+ "auto",
333
+ getargs.compression,
334
+ getargs.wholefile,
335
+ getargs.level,
336
+ pyarchivefile.compressionlistalt,
337
+ False,
338
+ 0,
339
+ 0,
340
+ 0,
341
+ [getargs.checksum] * 5,
342
+ getargs.skipchecksum,
343
+ [],
344
+ {},
345
+ fnamedict,
346
+ getargs.insecretkey,
347
+ getargs.outsecretkey,
348
+ False,
349
+ False,
350
+ )
351
+ else:
352
+ tmpout = pyarchivefile.PackArchiveFileFromInFile(
353
+ input_file,
354
+ tempout,
355
+ __file_format_default__,
356
+ getargs.compression,
357
+ getargs.wholefile,
358
+ getargs.level,
359
+ pyarchivefile.compressionlistalt,
360
+ [getargs.checksum] * 5,
361
+ [],
362
+ {},
363
+ fnamedict,
364
+ getargs.outsecretkey,
365
+ False,
366
+ False,
367
+ )
368
+ if not tmpout:
369
+ return 1
370
+ input_file = tempout
371
+
372
+ pyarchivefile.UnPackArchiveFile(
373
+ input_file,
374
+ getargs.output,
375
+ False,
376
+ getargs.filestart,
377
+ 0,
378
+ 0,
379
+ getargs.skipchecksum,
380
+ fnamedict,
381
+ getargs.verbose,
382
+ getargs.preserve,
383
+ getargs.preserve,
384
+ False,
385
+ False,
386
+ )
387
+
388
+ elif active_action == "list":
389
+ if getargs.convert:
390
+ checkcompressfile = pyarchivefile.CheckCompressionSubType(input_file, fnamedict, 0, True)
391
+ if (
392
+ (pyarchivefile.IsNestedDict(fnamedict) and checkcompressfile in fnamedict)
393
+ or (pyarchivefile.IsSingleDict(fnamedict) and checkcompressfile == fnamedict["format_magic"])
394
+ ):
395
+ tmpout = pyarchivefile.ArchiveFileListFiles(
396
+ input_file,
397
+ "auto",
398
+ getargs.filestart,
399
+ 0,
400
+ 0,
401
+ getargs.skipchecksum,
402
+ fnamedict,
403
+ getargs.insecretkey,
404
+ False,
405
+ getargs.verbose,
406
+ False,
407
+ False,
408
+ )
409
+ else:
410
+ tmpout = pyarchivefile.InFileListFiles(
411
+ input_file,
412
+ getargs.verbose,
413
+ fnamedict,
414
+ getargs.insecretkey,
415
+ False,
416
+ False,
417
+ False,
418
+ )
419
+ if not tmpout:
420
+ return 1
421
+ else:
422
+ pyarchivefile.ArchiveFileListFiles(
423
+ input_file,
424
+ "auto",
425
+ getargs.filestart,
426
+ 0,
427
+ 0,
428
+ getargs.skipchecksum,
429
+ fnamedict,
430
+ getargs.insecretkey,
431
+ False,
432
+ getargs.verbose,
433
+ False,
434
+ False,
435
+ )
436
+
437
+ elif active_action == "validate":
438
+ if getargs.convert:
439
+ checkcompressfile = pyarchivefile.CheckCompressionSubType(input_file, fnamedict, 0, True)
440
+ tempout = BytesIO()
441
+ if (
442
+ (pyarchivefile.IsNestedDict(fnamedict) and checkcompressfile in fnamedict)
443
+ or (pyarchivefile.IsSingleDict(fnamedict) and checkcompressfile == fnamedict["format_magic"])
444
+ ):
445
+ tmpout = pyarchivefile.RePackArchiveFile(
446
+ input_file,
447
+ tempout,
448
+ "auto",
449
+ getargs.compression,
450
+ getargs.wholefile,
451
+ getargs.level,
452
+ pyarchivefile.compressionlistalt,
453
+ False,
454
+ 0,
455
+ 0,
456
+ 0,
457
+ [getargs.checksum] * 5,
458
+ getargs.skipchecksum,
459
+ [],
460
+ {},
461
+ fnamedict,
462
+ getargs.insecretkey,
463
+ getargs.outsecretkey,
464
+ False,
465
+ False,
466
+ False,
467
+ )
468
+ else:
469
+ tmpout = pyarchivefile.PackArchiveFileFromInFile(
470
+ input_file,
471
+ tempout,
472
+ __file_format_default__,
473
+ getargs.compression,
474
+ getargs.wholefile,
475
+ getargs.level,
476
+ pyarchivefile.compressionlistalt,
477
+ [getargs.checksum] * 5,
478
+ [],
479
+ {},
480
+ fnamedict,
481
+ getargs.outsecretkey,
482
+ False,
483
+ False,
484
+ )
485
+
486
+ input_file = tempout
487
+ if not tmpout:
488
+ return 1
489
+
490
+ fvalid = pyarchivefile.StackedArchiveFileValidate(
491
+ input_file,
492
+ "auto",
493
+ getargs.filestart,
494
+ fnamedict,
495
+ getargs.insecretkey,
496
+ False,
497
+ getargs.verbose,
498
+ False,
499
+ )
500
+ if fvalid:
501
+ pyarchivefile.VerbosePrintOut("File is valid: \n" + str(input_file))
502
+ else:
503
+ pyarchivefile.VerbosePrintOut("File is invalid: \n" + str(input_file))
504
+
505
+ return 0
506
+
507
+
508
+ if __name__ == "__main__":
509
+ raise SystemExit(main())