55from __future__ import absolute_import , division , print_function , unicode_literals
66
77"""
8- neofile.py
9- A lightweight CLI re-creation of archivefile.py that uses the *alt* implementation
10- (pyneofile) you asked for, including:
11- - INI-based format defaults (auto-fallback)
12- - stdlib compression with size-based "auto" (zlib/gzip/bz2, xz on Py3 when available)
13- - robust checksums (CRC-32 padded), JSON/header/content verification
14- - Python 2 and 3 compatibility
15- - Optional conversion from ZIP/TAR (stdlib), and **RAR/7z** when extra libs are installed:
16- * RAR: pip install rarfile
17- * 7z: pip install py7zr
18-
19- Operations:
20- -c / --create pack files/dirs into an archive
21- -e / --extract extract an archive
22- -r / --repack repack (optionally change compression)
23- -l / --list list entries (fast, header-only)
24- -v / --validate validate checksums
25-
26- Convert support:
27- Use -t/--convert with --create/--repack/--list/--validate to convert a foreign archive
28- (zip/tar are stdlib; rar/7z need optional libs) into the alt ArchiveFile format first.
8+ neofile.py — CLI for the PyNeoFile format (.neo).
9+ Uses the pyneofile wrappers (which call into pyneoarc_alt) and prefers pyneofile.ini.
10+ Supports converting from zip/tar (stdlib), and rar/7z when optional libs are installed.
2911"""
3012
31- import os
32- import sys
33- import argparse
34- import tempfile
13+ import os , sys , argparse , tempfile
14+ import pyneofile as N
3515
36- # Graceful SIGPIPE on non-Windows
37- if os .name != 'nt' :
38- try :
39- import signal
40- if hasattr (signal , 'SIGPIPE' ):
41- signal .signal (signal .SIGPIPE , signal .SIG_DFL )
42- except Exception :
43- pass
44-
45- # Import alt core
46- try :
47- import pyneofile as A
48- except Exception as e :
49- sys .stderr .write ("Failed to import pyneofile: %s\n " % (e ,))
50- sys .exit (2 )
51-
52- __program_name__ = "neofile"
53- __version__ = "0.2.0"
16+ __program_name__ = "pyneofile"
17+ __version__ = "0.1.0"
5418
5519def _build_formatspecs_from_args (args ):
56- """Create a formatspecs dict or return None to use INI auto-fallback."""
20+ # Allow explicit override; otherwise let wrappers auto-load pyneofile.ini
5721 if args .format is None or args .format .lower () == "auto" :
58- return None # let alt core load INI or defaults
59- magic = args .format
60- ver = args .formatver if args .formatver is not None else "001"
61- delim = args .delimiter if args .delimiter is not None else "\x00 "
22+ return None
6223 return {
63- "format_name" : magic ,
64- "format_magic" : magic ,
65- "format_ver" : ver , # alt core keeps digits as-is
66- "format_delimiter" : delim ,
24+ "format_name" : args . format ,
25+ "format_magic" : args . format ,
26+ "format_ver" : ( args . formatver or "001" ),
27+ "format_delimiter" : ( args . delimiter or " \x00 " ) ,
6728 "new_style" : True ,
6829 }
6930
70- def _read_listfile (path ):
71- items = []
72- with open (path , 'r' ) as f :
73- for line in f :
74- s = line .strip ()
75- if not s or s .startswith ('#' ):
76- continue
77- items .append (s )
78- return items
79-
8031def _convert_or_fail (infile , outpath , formatspecs , checksum , compression , level ):
81- """Call core convert function and show friendly messages for missing deps."""
8232 try :
83- A . convert_foreign_to_alt (infile , outpath , formatspecs = formatspecs ,
33+ N . convert_foreign_to_neo (infile , outpath , formatspecs = formatspecs ,
8434 checksumtypes = (checksum , checksum , checksum ),
8535 compression = compression , compression_level = level )
8636 return True
@@ -93,148 +43,107 @@ def _convert_or_fail(infile, outpath, formatspecs, checksum, compression, level)
9343 else :
9444 sys .stderr .write ("convert error: %s\n " % msg )
9545 return False
96- except ValueError as e :
97- # Unsupported format (not zip/tar/rar/7z)
98- sys .stderr .write ("convert error: %s\n " % e )
99- return False
10046 except Exception as e :
101- sys .stderr .write ("unexpected convert error: %s\n " % e )
47+ sys .stderr .write ("convert error: %s\n " % e )
10248 return False
10349
10450def main (argv = None ):
105- p = argparse .ArgumentParser (description = "Manipulate ArchiveFile (alt) archives." , conflict_handler = "resolve" , add_help = True )
106-
107- p .add_argument ("-V" , "--version" , action = "version" , version = __program_name__ + " " + __version__ )
108-
109- # IO
110- p .add_argument ("-i" , "--input" , nargs = "+" , required = True , help = "Input file(s) or archive file." )
111- p .add_argument ("-o" , "--output" , default = None , help = "Output file or directory." )
51+ p = argparse .ArgumentParser (description = "PyNeoFile (.neo) archiver" , add_help = True )
52+ p .add_argument ("-V" ,"--version" , action = "version" , version = __program_name__ + " " + __version__ )
11253
113- # Ops
114- p .add_argument ("-c" , "--create" , action = "store_true" , help = "Create an archive from input files/dirs." )
115- p .add_argument ("-e" , "--extract" , action = "store_true" , help = "Extract an archive to --output (directory)." )
116- p .add_argument ("-r" , "--repack" , action = "store_true" , help = "Repack an existing archive (can change compression)." )
117- p .add_argument ("-l" , "--list" , action = "store_true" , help = "List archive entries." )
118- p .add_argument ("-v" , "--validate" , action = "store_true" , help = "Validate archive checksums." )
119- p .add_argument ("-t" , "--convert" , action = "store_true" , help = "Treat input as foreign (zip/tar/rar/7z) and convert to ArchiveFile first." )
54+ p .add_argument ("-i" ,"--input" , nargs = "+" , required = True , help = "Input files/dirs or archive file" )
55+ p .add_argument ("-o" ,"--output" , default = None , help = "Output file or directory" )
12056
121- # Format & delimiter
122- p .add_argument ("-F" , "--format" , default = "auto" , help = "Format magic to use (or 'auto' to read INI)." )
123- p .add_argument ("-D" , "--delimiter" , default = None , help = "Delimiter to use when --format is not 'auto'." )
124- p .add_argument ("-m" , "--formatver" , default = None , help = "Format version digits (e.g. 001)." )
57+ p .add_argument ("-c" ,"--create" , action = "store_true" , help = "Create a .neo archive from inputs" )
58+ p .add_argument ("-e" ,"--extract" , action = "store_true" , help = "Extract an archive to --output" )
59+ p .add_argument ("-r" ,"--repack" , action = "store_true" , help = "Repack an archive (change compression)" )
60+ p .add_argument ("-l" ,"--list" , action = "store_true" , help = "List entries" )
61+ p .add_argument ("-v" ,"--validate" , action = "store_true" , help = "Validate checksums" )
62+ p .add_argument ("-t" ,"--convert" , action = "store_true" , help = "Convert zip/tar/rar/7z → .neo first" )
12563
126- # Compression
127- p .add_argument ("-P" , "--compression" , default = "auto" , help = "Compression: none|zlib|gzip|bz2|xz|auto" )
128- p .add_argument ("-L" , "--level" , default = None , help = "Compression level/preset (int)." )
129- p .add_argument ("-W" , "--wholefile" , action = "store_true" , help = "(Ignored; CLI compatibility)." )
64+ p .add_argument ("-F" ,"--format" , default = "auto" , help = "Format magic (default 'auto' via pyneofile.ini)" )
65+ p .add_argument ("-D" ,"--delimiter" , default = None , help = "Delimiter (when not using 'auto')" )
66+ p .add_argument ("-m" ,"--formatver" , default = None , help = "Version digits (e.g. 001)" )
13067
131- # Validation & extraction behavior
132- p .add_argument ("-C" , "--checksum" , default = "crc32" , help = "Checksum algorithm (header/content/json)." )
133- p .add_argument ("-s" , "--skipchecksum" , action = "store_true" , help = "Skip checksum verification while reading." )
134- p .add_argument ("-p" , "--preserve" , action = "store_false" , help = "Do not preserve permissions/times (kept for compatibility)." )
135-
136- # Misc
137- p .add_argument ("-d" , "--verbose" , action = "store_true" , help = "Verbose logging." )
138- p .add_argument ("-T" , "--text" , action = "store_true" , help = "Treat the first input argument as a text file containing paths (one per line)." )
68+ p .add_argument ("-P" ,"--compression" , default = "auto" , help = "Compression: none|zlib|gzip|bz2|xz|auto" )
69+ p .add_argument ("-L" ,"--level" , default = None , help = "Compression level/preset" )
70+ p .add_argument ("-C" ,"--checksum" , default = "crc32" , help = "Checksum algorithm" )
71+ p .add_argument ("-s" ,"--skipchecksum" , action = "store_true" , help = "Skip checks while reading" )
72+ p .add_argument ("-d" ,"--verbose" , action = "store_true" , help = "Verbose listing" )
13973
14074 args = p .parse_args (argv )
14175
142- # Choose primary action
143- actions = ['create' , 'extract' , 'repack' , 'list' , 'validate' ]
144- active = next ((name for name in actions if getattr (args , name )), None )
145- if not active :
146- p .error ("one of --create/--extract/--repack/--list/--validate is required" )
147-
148- # formatspecs (None => INI auto-fallback inside the alt core)
14976 formatspecs = _build_formatspecs_from_args (args )
150-
151- # Inputs
15277 inputs = args .input
15378 infile0 = inputs [0 ]
154-
155- # Compression/level
15679 compression = args .compression
15780 level = None if args .level in (None , "" ,) else int (args .level )
158-
159- # Checksum triple
16081 checksum = args .checksum
161- checks = (checksum , checksum , checksum )
16282
163- if active == 'create' :
164- if args .text :
165- inputs = _read_listfile (infile0 )
83+ if args .create :
16684 if args .convert :
167- if not args .output :
168- p .error ("--output is required when using --convert" )
85+ if not args .output : p .error ("--output is required" )
16986 ok = _convert_or_fail (infile0 , args .output , formatspecs , checksum , compression , level )
17087 return 0 if ok else 1
171- if not args .output :
172- p .error ("--output is required for --create" )
173- A .pack_alt (inputs if not args .text else inputs , args .output , formatspecs = formatspecs ,
174- checksumtypes = checks , compression = compression , compression_level = level )
175- if args .verbose :
176- sys .stderr .write ("created: %s\n " % args .output )
88+ if not args .output : p .error ("--output is required" )
89+ N .pack_neo (inputs , args .output , formatspecs = formatspecs ,
90+ checksumtypes = (checksum , checksum , checksum ),
91+ compression = compression , compression_level = level )
92+ if args .verbose : sys .stderr .write ("created: %s\n " % args .output )
17793 return 0
17894
179- if active == ' repack' :
95+ if args . repack :
18096 if args .convert :
181- if not args .output :
182- p .error ("--output is required when using --repack --convert" )
97+ if not args .output : p .error ("--output is required" )
18398 ok = _convert_or_fail (infile0 , args .output , formatspecs , checksum , compression , level )
18499 return 0 if ok else 1
185- if not args .output :
186- p .error ("--output is required for --repack" )
187- A .repack_alt (infile0 , args .output , formatspecs = formatspecs ,
188- checksumtypes = checks , compression = compression , compression_level = level )
189- if args .verbose :
190- sys .stderr .write ("repacked: %s -> %s\n " % (infile0 , args .output ))
100+ if not args .output : p .error ("--output is required" )
101+ N .repack_neo (infile0 , args .output , formatspecs = formatspecs ,
102+ checksumtypes = (checksum , checksum , checksum ),
103+ compression = compression , compression_level = level )
104+ if args .verbose : sys .stderr .write ("repacked: %s\n " % args .output )
191105 return 0
192106
193- if active == ' extract' :
107+ if args . extract :
194108 outdir = args .output or "."
195109 if args .convert :
196- tmp_arc = os .path .join (tempfile .gettempdir (), "af_alt_convert.arc " )
110+ tmp_arc = os .path .join (tempfile .gettempdir (), "pyneofile_convert.neo " )
197111 ok = _convert_or_fail (infile0 , tmp_arc , formatspecs , checksum , compression , level )
198- if not ok :
199- return 1
200- infile0_use = tmp_arc
112+ if not ok : return 1
113+ use = tmp_arc
201114 else :
202- infile0_use = infile0
203- A .unpack_alt (infile0_use , outdir , formatspecs = formatspecs , skipchecksum = args .skipchecksum , uncompress = True )
204- if args .verbose :
205- sys .stderr .write ("extracted to: %s\n " % outdir )
115+ use = infile0
116+ N .unpack_neo (use , outdir , formatspecs = formatspecs , skipchecksum = args .skipchecksum , uncompress = True )
117+ if args .verbose : sys .stderr .write ("extracted → %s\n " % outdir )
206118 return 0
207119
208- if active == ' list' :
120+ if args . list :
209121 if args .convert :
210- tmp_arc = os .path .join (tempfile .gettempdir (), "af_alt_convert.arc " )
122+ tmp_arc = os .path .join (tempfile .gettempdir (), "pyneofile_convert.neo " )
211123 ok = _convert_or_fail (infile0 , tmp_arc , formatspecs , checksum , compression , level )
212- if not ok :
213- return 1
124+ if not ok : return 1
214125 use = tmp_arc
215126 else :
216127 use = infile0
217- names = A . archivefilelistfiles_alt (use , formatspecs = formatspecs , advanced = args .verbose , include_dirs = True )
128+ names = N . archivefilelistfiles_neo (use , formatspecs = formatspecs , advanced = args .verbose , include_dirs = True )
218129 if not args .verbose :
219- for n in names :
220- sys .stdout .write (n + "\n " )
130+ for n in names : sys .stdout .write (n + "\n " )
221131 else :
222132 for ent in names :
223133 sys .stdout .write ("%s\t %s\t %s\t %s\n " % (
224134 ent ['type' ], ent ['compression' ], ent ['size' ], ent ['name' ]
225135 ))
226136 return 0
227137
228- if active == ' validate' :
138+ if args . validate :
229139 if args .convert :
230- tmp_arc = os .path .join (tempfile .gettempdir (), "af_alt_convert.arc " )
140+ tmp_arc = os .path .join (tempfile .gettempdir (), "pyneofile_convert.neo " )
231141 ok = _convert_or_fail (infile0 , tmp_arc , formatspecs , checksum , compression , level )
232- if not ok :
233- return 1
142+ if not ok : return 1
234143 use = tmp_arc
235144 else :
236145 use = infile0
237- ok , details = A . archivefilevalidate_alt (use , formatspecs = formatspecs , verbose = args .verbose , return_details = True )
146+ ok , details = N . archivefilevalidate_neo (use , formatspecs = formatspecs , verbose = args .verbose , return_details = True )
238147 if not args .verbose :
239148 sys .stdout .write ("valid: %s\n " % ("yes" if ok else "no" ))
240149 else :
@@ -245,7 +154,7 @@ def main(argv=None):
245154 ))
246155 return 0
247156
248- return 0
157+ p . error ( "one of --create/--extract/--repack/--list/--validate is required" )
249158
250159if __name__ == "__main__" :
251160 sys .exit (main ())
0 commit comments