SpaceLife-Updater/venv/lib64/python3.12/site-packages/PyInstaller/utils/cliutils/archive_viewer.py

269 lines
9.0 KiB
Python
Executable File

#-----------------------------------------------------------------------------
# Copyright (c) 2013-2023, PyInstaller Development Team.
#
# Distributed under the terms of the GNU General Public License (version 2
# or later) with exception for distributing the bootloader.
#
# The full license is in the file COPYING.txt, distributed with this software.
#
# SPDX-License-Identifier: (GPL-2.0-or-later WITH Bootloader-exception)
#-----------------------------------------------------------------------------
"""
Viewer for PyInstaller-generated archives.
"""
import argparse
import os
import sys
import PyInstaller.log
from PyInstaller.archive.readers import CArchiveReader, ZlibArchiveReader
try:
from argcomplete import autocomplete
except ImportError:
def autocomplete(parser):
return None
class ArchiveViewer:
def __init__(self, filename, interactive_mode, recursive_mode, brief_mode):
self.filename = filename
self.interactive_mode = interactive_mode
self.recursive_mode = recursive_mode
self.brief_mode = brief_mode
self.stack = []
# Recursive mode implies non-interactive mode
if self.recursive_mode:
self.interactive_mode = False
def main(self):
# Open top-level (initial) archive
archive = self._open_toplevel_archive(self.filename)
archive_name = os.path.basename(self.filename)
self.stack.append((archive_name, archive))
# Not-interactive mode
if not self.interactive_mode:
return self._non_interactive_processing()
# Interactive mode; show top-level archive
self._show_archive_contents(archive_name, archive)
# Interactive command processing
while True:
# Read command
try:
tokens = input('? ').split(None, 1)
except EOFError:
# Ctrl-D
print(file=sys.stderr) # Clear line.
break
# Print usage?
if not tokens:
self._print_usage()
continue
# Process
command = tokens[0].upper()
if command == 'Q':
break
elif command == 'U':
self._move_up_the_stack()
elif command == 'O':
self._open_embedded_archive(*tokens[1:])
elif command == 'X':
self._extract_file(*tokens[1:])
elif command == 'S':
archive_name, archive = self.stack[-1]
self._show_archive_contents(archive_name, archive)
else:
self._print_usage()
def _non_interactive_processing(self):
archive_count = 0
while self.stack:
archive_name, archive = self.stack.pop()
archive_count += 1
if archive_count > 1:
print("")
self._show_archive_contents(archive_name, archive)
if not self.recursive_mode:
continue
# Scan for embedded archives
if isinstance(archive, CArchiveReader):
for name, (*_, typecode) in archive.toc.items():
if typecode == 'z':
try:
embedded_archive = archive.open_embedded_archive(name)
except Exception as e:
print(f"Could not open embedded archive {name!r}: {e}", file=sys.stderr)
self.stack.append((name, embedded_archive))
def _print_usage(self):
print("U: go up one level", file=sys.stderr)
print("O <name>: open embedded archive with given name", file=sys.stderr)
print("X <name>: extract file with given name", file=sys.stderr)
print("S: list the contents of current archive again", file=sys.stderr)
print("Q: quit", file=sys.stderr)
def _move_up_the_stack(self):
if len(self.stack) > 1:
self.stack.pop()
archive_name, archive = self.stack[-1]
self._show_archive_contents(archive_name, archive)
else:
print("Already in the top archive!", file=sys.stderr)
def _open_toplevel_archive(self, filename):
if not os.path.isfile(filename):
print(f"Archive {filename} does not exist!", file=sys.stderr)
sys.exit(1)
if filename[-4:].lower() == '.pyz':
return ZlibArchiveReader(filename)
return CArchiveReader(filename)
def _open_embedded_archive(self, archive_name=None):
# Ask for name if not provided
if not archive_name:
archive_name = input('Open name? ')
archive_name = archive_name.strip()
# No name given; abort
if not archive_name:
return
# Open the embedded archive
_, parent_archive = self.stack[-1]
if not hasattr(parent_archive, 'open_embedded_archive'):
print("Archive does not support embedded archives!", file=sys.stderr)
return
try:
archive = parent_archive.open_embedded_archive(archive_name)
except Exception as e:
print(f"Could not open embedded archive {archive_name!r}: {e}", file=sys.stderr)
return
# Add to stack and display contents
self.stack.append((archive_name, archive))
self._show_archive_contents(archive_name, archive)
def _extract_file(self, name=None):
# Ask for name if not provided
if not name:
name = input('Extract name? ')
name = name.strip()
# Archive
archive_name, archive = self.stack[-1]
# Retrieve data
try:
if isinstance(archive, CArchiveReader):
data = archive.extract(name)
elif isinstance(archive, ZlibArchiveReader):
data = archive.extract(name, raw=True)
else:
raise NotImplementedError(f"Extraction from archive type {type(archive)} not implemented!")
except Exception as e:
print(f"Failed to extract data for entry {name!r} from {archive_name!r}: {e}", file=sys.stderr)
# Write to file
filename = input('Output filename? ')
if not filename:
print(repr(data))
else:
with open(filename, 'wb') as fp:
fp.write(data)
def _show_archive_contents(self, archive_name, archive):
if isinstance(archive, CArchiveReader):
if archive.options:
print(f"Options in {archive_name!r} (PKG/CArchive):")
for option in archive.options:
print(f" {option}")
print(f"Contents of {archive_name!r} (PKG/CArchive):")
if self.brief_mode:
for name in archive.toc.keys():
print(f" {name}")
else:
print(" position, length, uncompressed_length, is_compressed, typecode, name")
for name, (position, length, uncompressed_length, is_compressed, typecode) in archive.toc.items():
print(f" {position}, {length}, {uncompressed_length}, {is_compressed}, {typecode!r}, {name!r}")
elif isinstance(archive, ZlibArchiveReader):
print(f"Contents of {archive_name!r} (PYZ):")
if self.brief_mode:
for name in archive.toc.keys():
print(f" {name}")
else:
print(" is_package, position, length, name")
for name, (is_package, position, length) in archive.toc.items():
print(f" {is_package}, {position}, {length}, {name!r}")
else:
print(f"Contents of {name} (unknown)")
print(f"FIXME: implement content listing for archive type {type(archive)}!")
def run():
parser = argparse.ArgumentParser()
parser.add_argument(
'-l',
'--list',
default=False,
action='store_true',
dest='listing_mode',
help='List the archive contents and exit (default: %(default)s).',
)
parser.add_argument(
'-r',
'--recursive',
default=False,
action='store_true',
dest='recursive',
help='Recursively print an archive log (default: %(default)s). Implies --list.',
)
parser.add_argument(
'-b',
'--brief',
default=False,
action='store_true',
dest='brief',
help='When displaying archive contents, show only file names. (default: %(default)s).',
)
PyInstaller.log.__add_options(parser)
parser.add_argument(
'filename',
metavar='pyi_archive',
help="PyInstaller archive to process.",
)
autocomplete(parser)
args = parser.parse_args()
PyInstaller.log.__process_options(parser, args)
try:
viewer = ArchiveViewer(
filename=args.filename,
interactive_mode=not args.listing_mode,
recursive_mode=args.recursive,
brief_mode=args.brief,
)
viewer.main()
except KeyboardInterrupt:
raise SystemExit("Aborted by user.")
if __name__ == '__main__':
run()