Add support for running in non-interactive mode

- Exits with non-zero exit status on errors.
- Does not hang on prompts.
- Just prints text without fancy terminal stuff.
This commit is contained in:
Jakub Jirutka 2023-11-21 21:36:57 +01:00
parent a93490cf99
commit 1e82ba0813
2 changed files with 76 additions and 47 deletions

View file

@ -9,8 +9,9 @@ else:
class Utils: class Utils:
def __init__(self, name = "Python Script"): def __init__(self, name = "Python Script", interactive = True):
self.name = name self.name = name
self.interactive = interactive
# Init our colors before we need to print anything # Init our colors before we need to print anything
cwd = os.getcwd() cwd = os.getcwd()
os.chdir(os.path.dirname(os.path.realpath(__file__))) os.chdir(os.path.dirname(os.path.realpath(__file__)))
@ -138,6 +139,8 @@ class Utils:
# returning the result # returning the result
timeout = kwargs.get("timeout", 0) timeout = kwargs.get("timeout", 0)
default = kwargs.get("default", None) default = kwargs.get("default", None)
if not self.interactive:
return default
# If we don't have a timeout - then skip the timed sections # If we don't have a timeout - then skip the timed sections
if timeout <= 0: if timeout <= 0:
if sys.version_info >= (3, 0): if sys.version_info >= (3, 0):
@ -170,11 +173,13 @@ class Utils:
return default return default
def cls(self): def cls(self):
os.system('cls' if os.name=='nt' else 'clear') if not self.interactive:
return
os.system('cls' if os.name=='nt' else 'clear')
def cprint(self, message, **kwargs): def cprint(self, message, **kwargs):
strip_colors = kwargs.get("strip_colors", False) strip_colors = kwargs.get("strip_colors", False)
if os.name == "nt": if os.name == "nt" or not self.interactive:
strip_colors = True strip_colors = True
reset = u"\u001b[0m" reset = u"\u001b[0m"
# Requires sys import # Requires sys import
@ -216,6 +221,9 @@ class Utils:
# Header drawing method # Header drawing method
def head(self, text = None, width = 55): def head(self, text = None, width = 55):
if not self.interactive:
print(text)
return
if text == None: if text == None:
text = self.name text = self.name
self.cls() self.cls()

View file

@ -2,10 +2,17 @@
from Scripts import downloader,utils,run,plist from Scripts import downloader,utils,run,plist
import os, shutil, time, sys, argparse, re, json import os, shutil, time, sys, argparse, re, json
class ProgramError(Exception):
def __init__(self, message, title = "Error"):
super().__init__(message)
self.title = title
class gibMacOS: class gibMacOS:
def __init__(self): def __init__(self, interactive = True):
self.interactive = interactive
self.d = downloader.Downloader() self.d = downloader.Downloader()
self.u = utils.Utils("gibMacOS") self.u = utils.Utils("gibMacOS", interactive=interactive)
self.r = run.Run() self.r = run.Run()
self.min_w = 80 self.min_w = 80
self.min_h = 24 self.min_h = 24
@ -73,6 +80,8 @@ class gibMacOS:
) )
def resize(self, width=0, height=0): def resize(self, width=0, height=0):
if not self.interactive:
return
width = width if width > self.min_w else self.min_w width = width if width > self.min_w else self.min_w
height = height if height > self.min_h else self.min_h height = height if height > self.min_h else self.min_h
self.u.resize(width, height) self.u.resize(width, height)
@ -84,22 +93,18 @@ class gibMacOS:
try: try:
json.dump(self.settings,open(self.settings_path,"w"),indent=2) json.dump(self.settings,open(self.settings_path,"w"),indent=2)
except Exception as e: except Exception as e:
self.u.head("Error Saving Settings") raise ProgramError(
print("") "Failed to save settings to:\n\n{}\n\nWith error:\n\n - {}\n".format(self.settings_path,repr(e)),
print("Failed to save settings to:\n\n{}\n\nWith error:\n\n - {}\n".format(self.settings_path,repr(e))) title="Error Saving Settings")
self.u.grab("Press [enter] to continue...")
def set_prods(self): def set_prods(self):
self.resize() self.resize()
if not self.get_catalog_data(self.save_local): if not self.get_catalog_data(self.save_local):
self.u.head("Catalog Data Error") message += "The currently selected catalog ({}) was not reachable\n".format(self.current_catalog)
print("")
print("The currently selected catalog ({}) was not reachable".format(self.current_catalog))
if self.save_local: if self.save_local:
print("and I was unable to locate a valid {} file in the\n{} directory.".format(self.plist, self.scripts)) message += "and I was unable to locate a valid {} file in the\n{} directory.\n".format(self.plist, self.scripts)
print("Please ensure you have a working internet connection.") message += "Please ensure you have a working internet connection."
print("") raise ProgramError(message, title="Catalog Data Error")
self.u.grab("Press [enter] to exit...")
self.u.head("Parsing Data") self.u.head("Parsing Data")
print("") print("")
print("Scanning products after catalog download...\n") print("Scanning products after catalog download...\n")
@ -297,12 +302,7 @@ class gibMacOS:
# add it to the list # add it to the list
dl_list.append(x["URL"]) dl_list.append(x["URL"])
if not len(dl_list): if not len(dl_list):
self.u.head("Error") raise ProgramError("There were no files to download")
print("")
print("There were no files to download")
print("")
self.u.grab("Press [enter] to return...")
return
c = 0 c = 0
done = [] done = []
if self.print_urls: if self.print_urls:
@ -310,8 +310,9 @@ class gibMacOS:
print("") print("")
print("{}:\n".format(name)) print("{}:\n".format(name))
print("\n".join([" - {} \n --> {}".format(os.path.basename(x), x) for x in dl_list])) print("\n".join([" - {} \n --> {}".format(os.path.basename(x), x) for x in dl_list]))
print("") if self.interactive:
self.u.grab("Press [enter] to return...") print("")
self.u.grab("Press [enter] to return...")
return return
# Only check the dirs if we need to # Only check the dirs if we need to
cwd = os.getcwd() cwd = os.getcwd()
@ -322,6 +323,8 @@ class gibMacOS:
print("") print("")
print("It looks like you've already downloaded {}".format(name)) print("It looks like you've already downloaded {}".format(name))
print("") print("")
if not self.interactive:
return
menu = self.u.grab("Redownload? (y/n): ") menu = self.u.grab("Redownload? (y/n): ")
if not len(menu): if not len(menu):
continue continue
@ -371,7 +374,10 @@ class gibMacOS:
print("Files saved to:") print("Files saved to:")
print(" {}".format(os.path.join(os.getcwd(), self.saves, self.current_catalog, name))) print(" {}".format(os.path.join(os.getcwd(), self.saves, self.current_catalog, name)))
print("") print("")
self.u.grab("Press [enter] to return...") if self.interactive:
self.u.grab("Press [enter] to return...")
elif len(failed):
raise ProgramError("{} files failed to download".format(len(failed)))
def show_catalog_url(self): def show_catalog_url(self):
self.resize() self.resize()
@ -381,9 +387,9 @@ class gibMacOS:
print("Max macOS Version: {}".format(self.num_to_macos(self.current_macos,for_url=False))) print("Max macOS Version: {}".format(self.num_to_macos(self.current_macos,for_url=False)))
print("") print("")
print("{}".format(self.build_url())) print("{}".format(self.build_url()))
print("") if self.interactive:
menu = self.u.grab("Press [enter] to return...") print("")
return self.u.grab("Press [enter] to return...")
def pick_catalog(self): def pick_catalog(self):
self.resize() self.resize()
@ -540,8 +546,7 @@ class gibMacOS:
if device_id: if device_id:
prod = next(p for p in prods if device_id.lower() in p["device_ids"]) prod = next(p for p in prods if device_id.lower() in p["device_ids"])
if not prod: if not prod:
print("No version found for Device ID '{}'".format(device_id)) raise ProgramError("No version found for Device ID '{}'".format(device_id))
return
else: else:
prod = prods[0] prod = prods[0]
self.download_prod(prod, dmg) self.download_prod(prod, dmg)
@ -553,7 +558,7 @@ class gibMacOS:
if p["product"] == prod: if p["product"] == prod:
self.download_prod(p, dmg) self.download_prod(p, dmg)
return return
print("{} not found".format(prod)) raise ProgramError("{} not found".format(prod))
def get_for_version(self, vers, build = None, device_id = None, dmg = False): def get_for_version(self, vers, build = None, device_id = None, dmg = False):
self.u.head("Downloading for {} {}".format(vers, build or "")) self.u.head("Downloading for {} {}".format(vers, build or ""))
@ -593,7 +598,7 @@ class gibMacOS:
if (n in pt) and not len(name_match): if (n in pt) and not len(name_match):
self.download_prod(p, dmg) self.download_prod(p, dmg)
return return
print("'{}' '{}' not found".format(vers, build)) raise ProgramError("'{}' '{}' not found".format(vers, build or ""))
if __name__ == '__main__': if __name__ == '__main__':
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
@ -609,9 +614,10 @@ if __name__ == '__main__':
parser.add_argument("-m", "--maxos", help="sets the max macOS version to consider when building the url - eg 10.14") parser.add_argument("-m", "--maxos", help="sets the max macOS version to consider when building the url - eg 10.14")
parser.add_argument("-D", "--device-id", help="use with --version or --latest to search for versions supporting the specified Device ID - eg VMM-x86_64 for any x86_64") parser.add_argument("-D", "--device-id", help="use with --version or --latest to search for versions supporting the specified Device ID - eg VMM-x86_64 for any x86_64")
parser.add_argument("-i", "--print-urls", help="only prints the download URLs, does not actually download them", action="store_true") parser.add_argument("-i", "--print-urls", help="only prints the download URLs, does not actually download them", action="store_true")
parser.add_argument("--no-interactive", help="run in non-interactive mode", action="store_true")
args = parser.parse_args() args = parser.parse_args()
g = gibMacOS() g = gibMacOS(interactive=not args.no_interactive)
if args.recovery: if args.recovery:
args.dmg = False args.dmg = False
g.find_recovery = args.recovery g.find_recovery = args.recovery
@ -635,18 +641,33 @@ if __name__ == '__main__':
# Set the catalog # Set the catalog
g.set_catalog(args.catalog) g.set_catalog(args.catalog)
# Done setting up pre-requisites try:
g.set_prods() # Done setting up pre-requisites
g.set_prods()
if args.latest: if args.latest:
g.get_latest(device_id=args.device_id, dmg=args.dmg) g.get_latest(device_id=args.device_id, dmg=args.dmg)
exit() elif args.product != None:
if args.product != None: g.get_for_product(args.product, args.dmg)
g.get_for_product(args.product, args.dmg) elif args.version != None:
exit() g.get_for_version(args.version, args.build, device_id=args.device_id, dmg=args.dmg)
if args.version != None: elif g.interactive:
g.get_for_version(args.version, args.build, device_id=args.device_id, dmg=args.dmg) while True:
exit() try:
g.main(args.dmg)
while True: except ProgramError as e:
g.main(args.dmg) g.u.head(e.title)
print("")
print(str(e))
print("")
g.u.grab("Press [enter] to return...")
else:
raise ProgramError("No command specified")
except ProgramError as e:
print(str(e))
if g.interactive:
print("")
g.u.grab("Press [enter] to exit...")
else:
exit(1)
exit(0)