mirror of
https://github.com/platomav/BIOSUtilities.git
synced 2025-05-25 20:44:58 -04:00
BIOSUtilities v24.10.09
Added Apple EFI EFI Image Identifier new argument -q Improved Apple EFI EFI Image Identifier detection flow Improved Apple EFI Package Extractor file parsing flow Improved file vs link detection and read access control Improved common switches handling of 7-Zip decompressor Improved non-PATH external executable dependencies names Fixed requirement instruction misalignments at the README
This commit is contained in:
parent
bbf0008384
commit
b174ce40e6
18 changed files with 322 additions and 288 deletions
|
@ -8,17 +8,44 @@ Copyright (C) 2022-2024 Plato Mavropoulos
|
|||
import os
|
||||
import subprocess
|
||||
|
||||
from typing import Final
|
||||
|
||||
from biosutilities.common.externals import szip_path, tiano_path
|
||||
from biosutilities.common.paths import is_dir, is_empty_dir
|
||||
from biosutilities.common.system import printer
|
||||
|
||||
# 7-Zip switches to auto rename, ignore passwords, ignore prompts, ignore wildcards,
|
||||
# eliminate root duplication, set UTF-8 charset, suppress stdout, suppress stderr,
|
||||
# suppress progress, disable headers, disable progress, disable output logs
|
||||
SZIP_COMMON: Final[list[str]] = ['-aou', '-p', '-y', '-spd', '-spe', '-sccUTF-8',
|
||||
'-bso0', '-bse0', '-bsp0', '-ba', '-bd', '-bb0']
|
||||
|
||||
# Success exit codes (0 = OK, 1 = Warnings)
|
||||
SZIP_SUCCESS: Final[list[int]] = [0, 1]
|
||||
|
||||
|
||||
def szip_code_assert(exit_code: int) -> None:
|
||||
""" Check 7-Zip bad exit codes (0 OK, 1 Warning) """
|
||||
|
||||
if exit_code not in (0, 1):
|
||||
if exit_code not in SZIP_SUCCESS:
|
||||
raise ValueError(f'Bad exit code: {exit_code}')
|
||||
|
||||
|
||||
def szip_switches(in_switches: list[str]) -> list[str]:
|
||||
""" Generate 7-Zip command line switches """
|
||||
|
||||
common_switches: list[str] = SZIP_COMMON
|
||||
|
||||
for in_switch in in_switches:
|
||||
for sw_pattern in ('-p', '-ao', '-bs', '-bb', '-scc'):
|
||||
if in_switch.startswith(sw_pattern):
|
||||
common_switches = [sw for sw in common_switches if not sw.startswith(sw_pattern)]
|
||||
|
||||
break
|
||||
|
||||
return [*set(common_switches + in_switches), '--']
|
||||
|
||||
|
||||
def is_szip_supported(in_path: str, padding: int = 0, args: list | None = None, silent: bool = True) -> bool:
|
||||
""" Check if file is 7-Zip supported """
|
||||
|
||||
|
@ -26,7 +53,7 @@ def is_szip_supported(in_path: str, padding: int = 0, args: list | None = None,
|
|||
if args is None:
|
||||
args = []
|
||||
|
||||
szip_c: list[str] = [szip_path(), 't', in_path, *args, '-bso0', '-bse0', '-bsp0']
|
||||
szip_c: list[str] = [szip_path(), 't', *szip_switches(in_switches=[*args]), in_path]
|
||||
|
||||
szip_t: subprocess.CompletedProcess[bytes] = subprocess.run(args=szip_c, check=False)
|
||||
|
||||
|
@ -40,26 +67,23 @@ def is_szip_supported(in_path: str, padding: int = 0, args: list | None = None,
|
|||
return True
|
||||
|
||||
|
||||
def szip_decompress(in_path: str, out_path: str, in_name: str | None, padding: int = 0, args: list | None = None,
|
||||
def szip_decompress(in_path: str, out_path: str, in_name: str = 'archive', padding: int = 0, args: list | None = None,
|
||||
check: bool = False, silent: bool = False) -> bool:
|
||||
""" Archive decompression via 7-Zip """
|
||||
|
||||
if not in_name:
|
||||
in_name = 'archive'
|
||||
|
||||
try:
|
||||
if args is None:
|
||||
args = []
|
||||
|
||||
szip_c: list[str] = [szip_path(), 'x', *args, '-aou', '-bso0', '-bse0', '-bsp0', f'-o{out_path}', in_path]
|
||||
szip_c: list[str] = [szip_path(), 'x', *szip_switches(in_switches=[*args, f'-o{out_path}']), in_path]
|
||||
|
||||
szip_x: subprocess.CompletedProcess[bytes] = subprocess.run(args=szip_c, check=False)
|
||||
|
||||
if check:
|
||||
szip_code_assert(exit_code=szip_x.returncode)
|
||||
|
||||
if not os.path.isdir(out_path):
|
||||
raise OSError(f'Extraction directory not found: {out_path}')
|
||||
if not (is_dir(in_path=out_path) and not is_empty_dir(in_path=out_path)):
|
||||
raise OSError(f'Extraction directory is empty or missing: {out_path}')
|
||||
except Exception as error: # pylint: disable=broad-except
|
||||
if not silent:
|
||||
printer(message=f'Error: 7-Zip could not extract {in_name} file {in_path}: {error}!', padding=padding)
|
||||
|
|
|
@ -16,7 +16,7 @@ from importlib.util import module_from_spec, spec_from_file_location
|
|||
from types import ModuleType
|
||||
from typing import Type
|
||||
|
||||
from biosutilities.common.paths import project_root
|
||||
from biosutilities.common.paths import is_dir, is_file, project_root
|
||||
from biosutilities.common.texts import to_string
|
||||
|
||||
|
||||
|
@ -25,12 +25,12 @@ def get_external_path(cmd: str | list | tuple) -> str:
|
|||
|
||||
external_root: str = os.path.join(project_root(), 'external')
|
||||
|
||||
external_path: str | None = external_root if os.path.isdir(external_root) else None
|
||||
external_path: str | None = external_root if is_dir(in_path=external_root) else None
|
||||
|
||||
for command in cmd if isinstance(cmd, (list, tuple)) else [to_string(in_object=cmd)]:
|
||||
command_path: str | None = shutil.which(cmd=command, path=external_path)
|
||||
|
||||
if command_path and os.path.isfile(path=command_path):
|
||||
if command_path and is_file(in_path=command_path):
|
||||
return command_path
|
||||
|
||||
raise OSError(f'{to_string(in_object=cmd, sep_char=", ")} could not be found!')
|
||||
|
@ -63,7 +63,7 @@ def big_script_tool() -> Type | None:
|
|||
def comextract_path() -> str:
|
||||
""" Get ToshibaComExtractor path """
|
||||
|
||||
return get_external_path(cmd='comextract')
|
||||
return get_external_path(cmd=['comextract', 'ComExtract'])
|
||||
|
||||
|
||||
def szip_path() -> str:
|
||||
|
@ -75,16 +75,16 @@ def szip_path() -> str:
|
|||
def tiano_path() -> str:
|
||||
""" Get TianoCompress path """
|
||||
|
||||
return get_external_path(cmd='TianoCompress')
|
||||
return get_external_path(cmd=['TianoCompress', 'tianocompress'])
|
||||
|
||||
|
||||
def uefifind_path() -> str:
|
||||
""" Get UEFIFind path """
|
||||
|
||||
return get_external_path(cmd='UEFIFind')
|
||||
return get_external_path(cmd=['uefifind', 'UEFIFind'])
|
||||
|
||||
|
||||
def uefiextract_path() -> str:
|
||||
""" Get UEFIExtract path """
|
||||
|
||||
return get_external_path(cmd='UEFIExtract')
|
||||
return get_external_path(cmd=['uefiextract', 'UEFIExtract'])
|
||||
|
|
|
@ -131,7 +131,7 @@ def make_dirs(in_path: str, parents: bool = True, exist_ok: bool = False, delete
|
|||
def delete_dirs(in_path: str) -> None:
|
||||
""" Delete folder(s), if present """
|
||||
|
||||
if Path(in_path).is_dir():
|
||||
if is_dir(in_path=in_path):
|
||||
shutil.rmtree(path=in_path, onerror=clear_readonly_callback) # pylint: disable=deprecated-argument
|
||||
|
||||
|
||||
|
@ -167,18 +167,54 @@ def clear_readonly_callback(in_func: Callable, in_path: str, _) -> None:
|
|||
in_func(path=in_path)
|
||||
|
||||
|
||||
def path_files(in_path: str, follow_links: bool = False) -> list[str]:
|
||||
def path_files(in_path: str, follow_links: bool = False, root_only: bool = False) -> list[str]:
|
||||
""" Walk path to get all files """
|
||||
|
||||
file_paths: list[str] = []
|
||||
|
||||
for root, _, filenames in os.walk(top=in_path, followlinks=follow_links):
|
||||
for filename in filenames:
|
||||
file_paths.append(os.path.abspath(path=os.path.join(root, filename)))
|
||||
for root_path, _, file_names in os.walk(top=in_path, followlinks=follow_links):
|
||||
for file_name in file_names:
|
||||
file_path: str = os.path.abspath(path=os.path.join(root_path, file_name))
|
||||
|
||||
if is_file(in_path=file_path):
|
||||
file_paths.append(file_path)
|
||||
|
||||
if root_only:
|
||||
break
|
||||
|
||||
return file_paths
|
||||
|
||||
|
||||
def is_dir(in_path: str) -> bool:
|
||||
""" Check if path is a directory """
|
||||
|
||||
return Path(in_path).is_dir()
|
||||
|
||||
|
||||
def is_file(in_path: str, allow_broken_links: bool = False) -> bool:
|
||||
""" Check if path is a regural file or symlink (valid or broken) """
|
||||
|
||||
in_path_abs: str = os.path.abspath(path=in_path)
|
||||
|
||||
if os.path.lexists(path=in_path_abs):
|
||||
if not is_dir(in_path=in_path_abs):
|
||||
if allow_broken_links:
|
||||
return os.path.isfile(path=in_path_abs) or os.path.islink(path=in_path_abs)
|
||||
|
||||
return os.path.isfile(path=in_path_abs)
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def is_access(in_path: str, access_mode: int = os.R_OK, follow_links: bool = False) -> bool:
|
||||
""" Check if path is accessible """
|
||||
|
||||
if not follow_links and os.access not in os.supports_follow_symlinks:
|
||||
follow_links = True
|
||||
|
||||
return os.access(path=in_path, mode=access_mode, follow_symlinks=follow_links)
|
||||
|
||||
|
||||
def is_empty_dir(in_path: str, follow_links: bool = False) -> bool:
|
||||
""" Check if directory is empty (file-wise) """
|
||||
|
||||
|
|
|
@ -31,18 +31,6 @@ PAT_APPLE_PBZX: Final[re.Pattern[bytes]] = re.compile(
|
|||
pattern=br'pbzx'
|
||||
)
|
||||
|
||||
PAT_APPLE_PKG_DMG: Final[re.Pattern[bytes]] = re.compile(
|
||||
pattern=br'EFI PART'
|
||||
)
|
||||
|
||||
PAT_APPLE_PKG_TAR: Final[re.Pattern[bytes]] = re.compile(
|
||||
pattern=br'<key>IFPkgDescriptionDescription</key>'
|
||||
)
|
||||
|
||||
PAT_APPLE_PKG_XAR: Final[re.Pattern[bytes]] = re.compile(
|
||||
pattern=br'xar!'
|
||||
)
|
||||
|
||||
PAT_AWARD_LZH: Final[re.Pattern[bytes]] = re.compile(
|
||||
pattern=br'-lh[04567]-'
|
||||
)
|
||||
|
|
|
@ -12,8 +12,8 @@ from argparse import ArgumentParser, Namespace
|
|||
from typing import Final
|
||||
|
||||
from biosutilities import __version__
|
||||
from biosutilities.common.paths import (delete_dirs, extract_folder, is_empty_dir, path_files,
|
||||
path_name, path_parent, real_path, runtime_root)
|
||||
from biosutilities.common.paths import (delete_dirs, extract_folder, is_access, is_dir, is_file, is_empty_dir,
|
||||
path_files, path_name, path_parent, real_path, runtime_root)
|
||||
from biosutilities.common.system import system_platform, python_version, printer
|
||||
from biosutilities.common.texts import remove_quotes, to_boxed, to_ordinal
|
||||
|
||||
|
@ -76,11 +76,11 @@ class BIOSUtility:
|
|||
|
||||
extract_path: str = os.path.join(self._output_path, extract_folder(in_path=input_name))
|
||||
|
||||
if os.path.isdir(extract_path):
|
||||
if is_dir(in_path=extract_path):
|
||||
for suffix in range(2, self.MAX_FAT32_ITEMS):
|
||||
renamed_path: str = f'{os.path.normpath(path=extract_path)}_{to_ordinal(in_number=suffix)}'
|
||||
|
||||
if not os.path.isdir(renamed_path):
|
||||
if not is_dir(in_path=renamed_path):
|
||||
extract_path = renamed_path
|
||||
|
||||
break
|
||||
|
@ -121,9 +121,11 @@ class BIOSUtility:
|
|||
for input_path in [input_path for input_path in input_paths if input_path]:
|
||||
input_path_real: str = real_path(in_path=input_path)
|
||||
|
||||
if os.path.isdir(input_path_real):
|
||||
self._input_files.extend(path_files(input_path_real))
|
||||
else:
|
||||
if is_dir(in_path=input_path_real):
|
||||
for input_file in path_files(in_path=input_path_real):
|
||||
if is_file(in_path=input_file) and is_access(in_path=input_file):
|
||||
self._input_files.append(input_file)
|
||||
elif is_file(in_path=input_path_real) and is_access(in_path=input_path_real):
|
||||
self._input_files.append(input_path_real)
|
||||
|
||||
def _setup_output_dir(self, padding: int = 0) -> None:
|
||||
|
@ -137,7 +139,10 @@ class BIOSUtility:
|
|||
if not output_path and self._input_files:
|
||||
output_path = str(path_parent(in_path=self._input_files[0]))
|
||||
|
||||
self._output_path = output_path or runtime_root()
|
||||
if output_path and is_dir(in_path=output_path) and is_access(in_path=output_path):
|
||||
self._output_path = output_path
|
||||
else:
|
||||
self._output_path = runtime_root()
|
||||
|
||||
def _check_sys_py(self) -> None:
|
||||
""" Check Python Version """
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue