def wrapper(*args, **kwargs): from macdaily.util.tools.get import get_logfile, get_boolean if get_boolean('NULL_PASSWORD'): return PASS SUDO_PASSWORD = os.getenv('SUDO_PASSWORD') if SUDO_PASSWORD is None: password = func(*args, **kwargs) else: password = SUDO_PASSWORD if not get_boolean('MACDAILY_NO_CHECK'): try: subprocess.run(['sudo', '--reset-timestamp'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) with make_pipe(password, redirect=True) as pipe: subprocess.check_call( ['sudo', '--stdin', '--prompt=""', "true"], stdin=pipe.stdout, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) except subprocess.CalledProcessError: global ERR_FLAG ERR_FLAG = False print_term( f'macdaily: {red}error{reset}: incorrect password {dim}{password!r}{reset} for ' f'{bold}{USER}{reset} ({under}{USR}{reset})', get_logfile()) raise IncorrectPassword from None return password
def _check_exec(self): self._var__exec_path = shutil.which('npm') flag = (self._var__exec_path is not None) if not flag: print(f'macdaily-{self.cmd}: {red_bg}{flash}npm{reset}: command not found', file=sys.stderr) text = (f'macdaily-{self.cmd}: {red}npm{reset}: you may download Node.js from ' f'{purple_bg}{under}https://nodejs.org{reset}') print_term(text, self._file, redirect=self._qflag) return flag
def _check_exec(self): self._var__exec_path = (shutil.which('apm'), shutil.which('apm-beta')) flag = self._var__exec_path != (None, None) if not flag: print( f'macdaily-{self.cmd}: {red_bg}{flash}apm{reset}: command not found', file=sys.stderr) text = ( f'macdaily-{self.cmd}: {red}apm{reset}: you may download Atom from ' f'{purple_bg}{under}https://atom.io{reset}') print_term(text, self._file, redirect=self._qflag) return flag
def _check_exec(self): self._var__exec_path = shutil.which('mas') flag = (self._var__exec_path is not None) if not flag: print( f'macdaily-{self.cmd}: {red_bg}{flash}mas{reset}: command not found', file=sys.stderr) text = ( f'macdaily-{self.cmd}: {red}mas{reset}: you may download MAS through following command -- ' f"`{bold}brew install mas{reset}'") print_term(text, self._file, redirect=self._qflag) return flag
def _check_exec(self): self._var__exec_path = shutil.which('softwareupdate') flag = (self._var__exec_path is not None) if not flag: print( f'macdaily-{self.cmd}: {red_bg}{flash}system{reset}: command not found', file=sys.stderr) text = ( f'macdaily-{self.cmd}: {red}system{reset}: ' "you may add `softwareupdate' to PATH through the following command -- " f"""`{bold}echo 'export PATH="/usr/sbin:$PATH"' >> ~/.bash_profile{reset}'""" ) print_term(text, self._file, redirect=self._qflag) return flag
def _check_exec(self): try: subprocess.check_call(['brew', 'command', 'bundle'], stdout=subprocess.DEVNULL, stderr=make_stderr(self._vflag)) except subprocess.CalledProcessError: print_text(traceback.format_exc(), self._file, redirect=self._vflag) print(f'macdaily-{self.cmd}: {red_bg}{flash}mas{reset}: command not found', file=sys.stderr) text = (f'macdaily-{self.cmd}: {red}mas{reset}: you may find Bundler on ' f'{purple_bg}{under}https://github.com/Homebrew/homebrew-bundle{reset}, ' f'or install Bundler through following command -- ' f"`{bold}brew tap homebrew/bundle{reset}'") print_term(text, self._file, redirect=self._qflag) return False self._var__exec_path = shutil.which('brew') return True
def _check_exec(self): self._var__exec_path = shutil.which('brew') flag = (self._var__exec_path is not None) if not flag: print( f'macdaily-{self.cmd}: {red_bg}{flash}brew{reset}: command not found', file=sys.stderr) text = ( f'macdaily-{self.cmd}: {red}brew{reset}: you may find Homebrew on ' f'{purple_bg}{under}https://brew.sh{reset}, or install Homebrew through following command -- ' f'`{bold}/usr/bin/ruby -e "$(curl -fsSL ' f"""https://raw.githubusercontent.com/Homebrew/install/master/install)"{reset}'""" ) print_term(text, self._file, redirect=self._qflag) return flag
def run_script( argv, quiet=False, verbose=False, sudo=False, # pylint: disable=dangerous-default-value password=None, logfile=os.devnull, env=os.environ): args = ' '.join(argv) print_scpt(args, logfile, verbose) with open(logfile, 'a') as file: file.write(f'Script started on {date()}\n') file.write(f'command: {args!r}\n') try: if sudo: if password is not None: sudo_argv = ['sudo', '--stdin', '--prompt=Password:\n'] sudo_argv.extend(argv) with make_pipe(password, verbose) as pipe: proc = subprocess.check_output(sudo_argv, stdin=pipe.stdout, stderr=make_stderr(verbose), env=env) else: sudo_argv = ['sudo'] sudo_argv.extend(argv) proc = subprocess.check_output(sudo_argv, stderr=make_stderr(verbose), env=env) else: proc = subprocess.check_output(argv, stderr=make_stderr(verbose), env=env) except subprocess.CalledProcessError as error: print_text(traceback.format_exc(), logfile, redirect=verbose) print_term( f"macdaily: {red}error{reset}: " f"command `{bold}{' '.join(error.cmd)!r}{reset}' failed", logfile, redirect=quiet) raise else: context = proc.decode() print_text(context, logfile, redirect=verbose) finally: with open(logfile, 'a') as file: file.write(f'Script done on {date()}\n')
def _proc_confirm(): pkgs = f'{reset}, {bold}'.join(_deps_pkgs) text = f'macdaily-{self.cmd}: {yellow}pip{reset}: found broken dependencies: {bold}{pkgs}{reset}' print_term(text, self._file, redirect=self._qflag) if self._yes or self._quiet: return True while True: ans = get_input( self._confirm, 'Would you like to reinstall?', prefix= f'Found broken dependencies: {", ".join(_deps_pkgs)}.\n\n', suffix=f' ({green}y{reset}/{red}N{reset}) ') if re.match(r'[yY]', ans): return True if re.match(r'[nN]', ans): return False print('Invalid input.', file=sys.stderr)
def wrapper(*args, **kwargs): from macdaily.util.tools.get import get_logfile if sys.stdin.isatty(): # pylint: disable=no-else-return return func(*args, **kwargs) else: # timeout interval from macdaily.util.tools.get import get_int TIMEOUT = get_int('TIMEOUT', 60) QUEUE = multiprocessing.Queue(1) kwargs['queue'] = QUEUE for _ in range(3): proc = multiprocessing.Process(target=func, args=args, kwargs=kwargs) timer = threading.Timer(TIMEOUT, function=proc.kill) timer.start() proc.start() proc.join() timer.cancel() if proc.exitcode == 0: break if proc.exitcode != 9: print_term( f'macdaily: {yellow}error{reset}: function {func.__qualname__!r} ' f'exits with exit status {proc.exitcode} on child process', get_logfile()) raise ChildExit else: print_term( f'macdaily: {red}error{reset}: function {func.__qualname__!r} ' f'retry timeout after {TIMEOUT} seconds', get_logfile()) raise TimeExpired try: return QUEUE.get(block=False) except queue.Empty: return default
def _print_dependency_text(package, dependencies): def _list_dependency(dependencies): _list_pkgs = list() for package, deps_pkgs in dependencies.items(): _list_pkgs.append(package) _list_pkgs.extend(_list_dependency(deps_pkgs)) return _list_pkgs _list_pkgs = list() for item in reversed(_list_dependency(dependencies)): if item in _list_pkgs: continue _list_pkgs.append(item) if not self._topological: _list_pkgs.sort() if self._qflag: if _list_pkgs: print_term(f"{package}: {' '.join(_list_pkgs)}", self._file) else: print_term(f"{package} (independent)", self._file) else: print_text(os.linesep.join(_list_pkgs), self._file)
def wrapper(*args, **kwargs): from macdaily.util.tools.get import get_logfile if platform.system() != 'Darwin': print_term('macdaily: error: script runs only on macOS', get_logfile()) raise UnsupportedOS def _finale(epilogue): global FUNC_FLAG if FUNC_FLAG: FUNC_FLAG = False sys.stdout.write(reset) sys.stderr.write(reset) kill(os.getpid(), signal.SIGSTOP) return epilogue def _funeral(last_words): global ERR_FLAG ERR_FLAG = False # sys.tracebacklimit = 0 sys.stdout.write(reset) sys.stderr.write(reset) kill(os.getpid(), signal.SIGKILL) print(last_words, file=sys.stderr) try: return _finale(func(*args, **kwargs)) except KeyboardInterrupt: if ERR_FLAG: _funeral(f'macdaily: {red}error{reset}: operation interrupted') raise except Exception: if ERR_FLAG: _funeral(f'macdaily: {red}error{reset}: operation failed') raise
def _proc_fixmissing(self, path): text = f'Checking broken {self.desc[0]} dependencies' print_info(text, self._file, redirect=self._qflag) def _proc_check(): argv = [path, '-m', 'pip', 'check'] args = ' '.join(argv) print_scpt(args, self._file, redirect=self._vflag) with open(self._file, 'a') as file: file.write(f'Script started on {date()}\n') file.write(f'command: {args!r}\n') _deps_pkgs = list() try: # pip check exits with a non-zero status if any packages are missing dependencies proc = subprocess.run(argv, stdout=subprocess.PIPE, stderr=make_stderr(self._vflag)) except subprocess.SubprocessError: print_text(traceback.format_exc(), self._file, redirect=self._vflag) else: context = proc.stdout.decode() print_text(context, self._file, redirect=self._vflag) for line in filter(None, context.strip().splitlines()): if line == 'No broken requirements found.': return set() if 'which is not installed' in line: _deps_pkgs.append(line.split()[3][:-1]) else: _deps_pkgs.append(line.split()[4][:-1]) finally: with open(self._file, 'a') as file: file.write(f'Script done on {date()}\n') return set(_deps_pkgs) def _proc_confirm(): pkgs = f'{reset}, {bold}'.join(_deps_pkgs) text = f'macdaily-{self.cmd}: {yellow}pip{reset}: found broken dependencies: {bold}{pkgs}{reset}' print_term(text, self._file, redirect=self._qflag) if self._yes or self._quiet: return True while True: ans = get_input( self._confirm, 'Would you like to reinstall?', prefix= f'Found broken dependencies: {", ".join(_deps_pkgs)}.\n\n', suffix=f' ({green}y{reset}/{red}N{reset}) ') if re.match(r'[yY]', ans): return True if re.match(r'[nN]', ans): return False print('Invalid input.', file=sys.stderr) _deps_pkgs = _proc_check() - self._ignore if not _deps_pkgs: text = f'macdaily-{self.cmd}: {green}pip{reset}: no broken dependencies' print_term(text, self._file, redirect=self._qflag) return text = f'Fixing broken {self.desc[0]} dependencies' print_info(text, self._file, redirect=self._qflag) if _proc_confirm(): argv = [path, '-m', 'pip', 'reinstall'] if self._quiet: argv.append('--quiet') if self._verbose: argv.append('--verbose') args = ' '.join(argv) _done_pkgs = set() while _deps_pkgs: for package in _deps_pkgs: real_name = re.split(r'[<>=!]', package, maxsplit=1)[0] print_scpt(f'{args} {package}', self._file, redirect=self._qflag) temp = copy.copy(argv) temp[3] = 'uninstall' print_scpt(f'{" ".join(temp)} {real_name}', self._file, redirect=self._qflag) sudo(f'{path} -m pip uninstall {real_name} --yes', self._file, self._password, redirect=self._qflag, verbose=self._vflag, sethome=True, timeout=self._timeout) temp = copy.copy(argv) temp[3] = 'install' print_scpt(f'{" ".join(temp)} {package}', self._file, redirect=self._qflag) if not sudo(f'{path} -m pip install {package}', self._file, self._password, redirect=self._qflag, verbose=self._vflag, sethome=True, timeout=self._timeout): with contextlib.suppress(ValueError): self._pkgs.remove(real_name) _done_pkgs |= _deps_pkgs _deps_pkgs = _proc_check() - _done_pkgs - self._ignore text = f'macdaily-{self.cmd}: {green}pip{reset}: all broken dependencies fixed' else: text = f'macdaily-{self.cmd}: {red}pip{reset}: all broken dependencies remain' print_term(text, self._file, redirect=self._qflag)
def _proc_dependency(self, path): text = f'Querying dependencies of {self.desc[1]}' print_info(text, self._file, redirect=self._qflag) def _fetch_dependency(package, depth): if depth == 0: return dict() depth -= 1 dependencies = _data_pkgs.get(package) if dependencies is not None: return dependencies text = f'Searching dependencies of {self.desc[0]} {under}{package}{reset}' print_info(text, self._file, redirect=self._vflag) argv = [path, '-m', 'pip', 'show', package] args = ' '.join(argv) print_scpt(args, self._file, redirect=self._vflag) with open(self._file, 'a') as file: file.write(f'Script started on {date()}\n') file.write(f'command: {args!r}\n') _deps_pkgs = dict() try: proc = subprocess.check_output(argv, stderr=make_stderr(self._vflag)) except subprocess.CalledProcessError: self._fail.append(package) with contextlib.suppress(KeyError): self._var__temp_pkgs.remove(package) print_text(traceback.format_exc(), self._file, redirect=self._vflag) else: context = proc.decode() print_text(context, self._file, redirect=self._vflag) requirements = set() for line in context.strip().splitlines(): match = re.match(r'Requires: (.*)', line) if match is not None: requirements = set(match.groups()[0].split(', ')) break _list_pkgs.append(package) for item in filter(None, requirements): if item in self._var__temp_pkgs: self._var__temp_pkgs.remove(item) _deps_pkgs[item] = _fetch_dependency(item, depth) _data_pkgs.update(_deps_pkgs) finally: with open(self._file, 'a') as file: file.write(f'Script done on {date()}\n') return _deps_pkgs _list_pkgs = list() _deps_pkgs = dict() _data_pkgs = dict() _temp_pkgs = copy.copy(self._var__temp_pkgs) for package in _temp_pkgs: _deps_pkgs[package] = _fetch_dependency(package, self._depth) self._pkgs.extend(_list_pkgs) def _print_dependency_tree(package, dependencies): with tempfile.NamedTemporaryFile() as dumpfile: dumper = dictdumper.Tree(dumpfile.name, quiet=True) dumper(dependencies, name=package) with open(dumpfile.name) as file: for line in filter(None, file): print_text(line.strip(), self._file) def _print_dependency_text(package, dependencies): def _list_dependency(dependencies): _list_pkgs = list() for package, deps_pkgs in dependencies.items(): _list_pkgs.append(package) _list_pkgs.extend(_list_dependency(deps_pkgs)) return _list_pkgs _list_pkgs = list() for item in reversed(_list_dependency(dependencies)): if item in _list_pkgs: continue _list_pkgs.append(item) if not self._topological: _list_pkgs.sort() if self._qflag: if _list_pkgs: print_term(f"{package}: {' '.join(_list_pkgs)}", self._file) else: print_term(f"{package} (independent)", self._file) else: print_text(os.linesep.join(_list_pkgs), self._file) text = f'Listing dependencies of {self.desc[1]}' print_info(text, self._file, redirect=self._vflag) argv = [path, 'dependency'] if self._tree: argv.append('--tree') if self._quiet: argv.append('--quiet') if self._verbose: argv.append('--verbose') if self._topological: argv.append('--topological') if self._depth != -1: argv.append(f'--depth={self._depth}') argv.append('') if self._qflag: print_scpt(path, get_logfile()) for package in sorted(self._var__temp_pkgs): argv[-1] = package print_scpt(' '.join(argv), self._file, redirect=self._qflag) if self._tree: try: import dictdumper except ImportError: print_term( f'macdaily-dependency: {yellow}pip{reset}: {bold}DictDumper{reset} not installed, ' f"which is mandatory for using `{bold}--tree{reset}' option", self._file, redirect=self._vflag) print( f'macdaily-dependency: {red}pip{reset}: broken dependency', file=sys.stderr) raise _print_dependency_tree(package, _deps_pkgs[package]) else: _print_dependency_text(package, _deps_pkgs[package]) del self._var__temp_pkgs
def launch(argv=None): # parse args & set context redirection flags args = parse_args(argv) quiet = args.quiet verbose = (args.quiet or not args.verbose) # parse config & change environ config = parse_config(quiet, verbose) os.environ['SUDO_ASKPASS'] = config['Miscellaneous']['askpass'] # fetch current time today = datetime.datetime.today() logdate = datetime.date.strftime(today, r'%y%m%d') logtime = datetime.date.strftime(today, r'%H%M%S') # mkdir for logs logpath = pathlib.Path(os.path.join(config['Path']['logdir'], 'launch', logdate)) logpath.mkdir(parents=True, exist_ok=True) # prepare command paras filename = os.path.join(logpath, f'{logtime}-{uuid.uuid4()!s}.log') os.environ['MACDAILY_LOGFILE'] = filename askpass = config['Miscellaneous']['askpass'] # record program status text = f'{bold}{green}|🚨|{reset} {bold}Running MacDaily version {__version__}{reset}' print_term(text, filename, redirect=quiet) record(filename, args, today, config, redirect=verbose) # ask for password text = f'{bold}{purple}|🔑|{reset} {bold}Your {under}sudo{reset}{bold} password may be necessary{reset}' print_term(text, filename, redirect=quiet) password = get_pass(askpass) # update program list if args.all: args.program.extend(('askpass', 'confirm', 'daemons')) prog_dict = dict() for program in set(args.program): if re.match(r'^(askpass|confirm|daemons)$', program, re.IGNORECASE) is None: parser = get_launch_parser() parser.error(f"argument PROG: invalid choice: {program!r} (choose from 'askpass', 'confirm', 'daemons')") # launch program launch_func = globals()[f'launch_{program.lower()}'] path = launch_func(quiet=quiet, verbose=verbose, config=config, password=password, logfile=filename) # record program prog_dict[program] = path archive = None if not args.no_cleanup: archive = make_archive(config, 'launch', today, quiet=quiet, verbose=verbose, logfile=filename) text = f'{bold}{green}|📖|{reset} {bold}MacDaily report of launch command{reset}' print_term(text, filename, redirect=quiet) for prog, path in prog_dict.items(): text = f'Launched helper program {under}{prog}{reset}{bold} at {under}{path}{reset}' print_misc(text, filename, redirect=quiet) if archive: formatted_list = f'{reset}{bold}, {under}'.join(archive) text = (f'Archived following ancient logs: {under}{formatted_list}{reset}') print_misc(text, filename, redirect=quiet) if len(prog_dict) == 0: text = f'macdaily: {purple}launch{reset}: no program launched' print_term(text, filename, redirect=quiet) if args.show_log: try: subprocess.check_call(['open', '-a', '/Applications/Utilities/Console.app', filename]) except subprocess.CalledProcessError: print_text(traceback.format_exc(), filename, redirect=verbose) print(f'macdaily: {red}launch{reset}: cannot show log file {filename!r}', file=sys.stderr) mode_str = ', '.join(prog_dict) if prog_dict else 'none' text = (f'{bold}{green}|🍺|{reset} {bold}MacDaily successfully performed launch process ' f'for {mode_str} helper programs{reset}') print_term(text, filename, redirect=quiet)
def config(argv=None): # parse args & set context redirection flags args = parse_args(argv) quiet = args.quiet verbose = (args.quiet or not args.verbose) # enter interactive setup process if args.interactive: # record program status text = f'{bold}{green}|🚨|{reset} {bold}Running MacDaily version {__version__}{reset}' print_term(text, get_logfile(), redirect=quiet) record(get_logfile(), args, datetime.datetime.today(), redirect=verbose) # make config make_config(quiet, verbose) text = (f'{bold}{green}|🍺|{reset} {bold}MacDaily successfully performed config process') print_term(text, get_logfile(), redirect=quiet) return # parse config & change environ config = parse_config(quiet, verbose) # pylint: disable=redefined-outer-name os.environ['SUDO_ASKPASS'] = config['Miscellaneous']['askpass'] os.environ['TIMEOUT'] = str(config['Miscellaneous']['retry']) # list existing config if args.list: for key, value in config.items(): for k, v, in value.items(): print(f'{key}.{k}={v}') return # then key is mandatory if args.key is None: parser = get_config_parser() parser.error('the following arguments are required: key') # validate given key match = re.match(r'(\w+)\.(\w+)', args.key.strip()) if match is None: parser = get_config_parser() parser.error(f"argument KEY: invalid value: {args.key!r}") section, option = match.groups() # fetch value of a given key if args.get: pprint.pprint(config[section].get(option), indent=2, width=length) return try: import configupdater except ImportError: print_term(f'macdaily-config: {yellow}error{reset}: {bold}ConfigUpdater{reset} not installed, ' f"which is mandatory for modification of configuration", get_logfile(), redirect=verbose) print(f'macdaily-config: {red}error{reset}: broken dependency', file=sys.stderr) raise # make ConfigUpdater updater = configupdater.ConfigUpdater(allow_no_value=True, inline_comment_prefixes=';') with open(os.path.expanduser('~/.dailyrc')) as file: updater.read_file(file) # then value is also mandatory check_value = sum((args.true, args.false, (args.value is not None))) if check_value > 1: parser = get_config_parser() parser.error(f"conflicting option(s): '--true', '--false' and {args.value!r}") elif check_value == 0: parser = get_config_parser() parser.error("the following arguments are required: '--true', '--false', or value") elif args.false: value = 'false' elif args.true: value = 'true' else: value = args.value formatted_value = value.ljust(56 - len(option)) # fetch comment origin = updater[section].get(option) if origin is None: comment = str() else: comment = ''.join(origin.value.split(maxsplit=1)[1:]) # modify value for a given key if args.add and args.unset: parser = get_config_parser() parser.error("conflicting option(s): '--all' and '--unset'") elif args.unset: updater[section][option] = comment elif args.add: if origin is None: updater[section][option] = f'{formatted_value} {comment}' else: updater[section][option] = f'{origin}{os.linesep} {value}' else: updater[section][option] = f'{formatted_value} {comment}' # update config file updater.update_file()
def logging(argv=None): # parse args & set context redirection flags args = parse_args(argv) quiet = args.quiet verbose = (args.quiet or not args.verbose) # parse config & change environ config = parse_config(quiet, verbose) os.environ['SUDO_ASKPASS'] = config['Miscellaneous']['askpass'] # fetch current time today = datetime.datetime.today() logdate = datetime.date.strftime(today, r'%y%m%d') logtime = datetime.date.strftime(today, r'%H%M%S') # prepare command paras confirm = config['Miscellaneous']['confirm'] askpass = config['Miscellaneous']['askpass'] timeout = config['Miscellaneous']['limit'] disk_dir = config['Path']['arcdir'] brew_renew = None # record program status text_record = f'{bold}{green}|🚨|{reset} {bold}Running MacDaily version {__version__}{reset}' print_term(text_record, get_logfile(), redirect=quiet) record(get_logfile(), args, today, config, redirect=verbose) # ask for password text_askpass = f'{bold}{purple}|🔑|{reset} {bold}Your {under}sudo{reset}{bold} password may be necessary{reset}' print_term(text_askpass, get_logfile(), redirect=quiet) password = get_pass(askpass) cmd_list = list() log_list = list() file_list = list() for mode in { 'apm', 'app', 'brew', 'cask', 'gem', 'mas', 'npm', 'pip', 'tap' }: # mkdir for logs logpath = pathlib.Path( os.path.join(config['Path']['logdir'], 'logging', mode, logdate)) logpath.mkdir(parents=True, exist_ok=True) filename = os.path.join(logpath, f'{logtime}-{uuid.uuid4()!s}.log') os.environ['MACDAILY_LOGFILE'] = filename # redo program status records print_term(text_record, filename, redirect=True) record(filename, args, today, config, redirect=True) print_term(text_askpass, filename, redirect=True) # skip disabled commands if (not config['Mode'].get(mode, False)) or getattr( args, f'no_{mode}', False): text = f'macdaily-logging: {yellow}{mode}{reset}: command disabled' print_term(text, filename, redirect=verbose) continue # update logging specifications namespace = getattr(args, mode, vars(args)) # check master controlling flags if args.quiet: namespace['quiet'] = True if args.verbose: namespace['verbose'] = True if args.show_log: namespace['show_log'] = True if args.no_cleanup: namespace['no_cleanup'] = True # run command cmd_cls = globals()[f'{mode.capitalize()}Logging'] command = cmd_cls(make_namespace(namespace), filename, timeout, confirm, askpass, password, disk_dir, brew_renew) # record command cmd_list.append(command) log_list.append(filename) brew_renew = command.time if not namespace['no_cleanup']: archive = make_archive(config, f'logging/{mode}', today, zipfile=False, quiet=quiet, verbose=verbose, logfile=filename) file_list.extend(archive) if namespace.get('show_log', False): try: subprocess.check_call([ 'open', '-a', '/Applications/Utilities/Console.app', filename ]) except subprocess.CalledProcessError: print_text(traceback.format_exc(), filename, redirect=verbose) print( f'macdaily: {red}logging{reset}: cannot show log file {filename!r}', file=sys.stderr) if not args.no_cleanup: storage = make_storage(config, today, quiet=quiet, verbose=verbose, logfile=filename) file_list.extend(storage) text = f'{bold}{green}|📖|{reset} {bold}MacDaily report of logging command{reset}' print_term(text, get_logfile(), redirect=quiet) for file in log_list: print_term(text, file, redirect=True) for command in cmd_list: text = f'Recorded existing {under}{command.desc[1]}{reset}{bold} at {under}{command.sample}{reset}' print_misc(text, get_logfile(), redirect=quiet) for file in log_list: print_misc(text, file, redirect=True) if file_list: formatted_list = f'{reset}{bold}, {under}'.join(file_list) text = ( f'Archived following ancient logs: {under}{formatted_list}{reset}') print_misc(text, filename, redirect=quiet) if len(cmd_list) == 0: # pylint: disable=len-as-condition text = f'macdaily: {purple}logging{reset}: no packages recorded' print_term(text, get_logfile(), redirect=quiet) for file in log_list: print_term(text, file, redirect=True) mode_lst = [command.mode for command in cmd_list] mode_str = ', '.join(mode_lst) if mode_lst else 'none' text = ( f'{bold}{green}|🍺|{reset} {bold}MacDaily successfully performed logging process ' f'for {mode_str} package managers{reset}') print_term(text, get_logfile(), redirect=quiet) for file in log_list: print_term(text, file, redirect=True)
def _spawn(argv=SHELL, file='typescript', password=None, yes=None, redirect=False, executable=SHELL, prefix=None, suffix=None, timeout=None, shell=False): try: import ptyng except ImportError: print_term( f"macdaily: {yellow}misc{reset}: `{bold}unbuffer{reset}' and `{bold}script{reset}'" f'not found in your {under}PATH{reset}, {bold}PTYng{reset} not installed', get_logfile(), redirect=redirect) print(f'macdaily: {red}misc{reset}: broken dependency', file=sys.stderr) raise if suffix is not None: argv = f'{_merge(argv)} {suffix}' if prefix is not None: argv = f'{prefix} {_merge(argv)}' if shell or isinstance(argv, str): argv = [executable, '-c', _merge(argv)] if password is not None: bpwd = password.encode() bdim = dim.encode() repl = rb'\1' + bdim # test = bytes() def master_read_ng(fd, replace=None): data = os.read(fd, 1024).replace(b'^D\x08\x08', b'') if replace is not None: data = data.replace(replace, b'') if password is not None: data = data.replace(bpwd, PWD.pw_passwd.encode()) data = data.replace(b'Password:'******'Password:\r\n') text = re.sub(rb'\033\[[0-9][0-9;]*m', rb'', data, flags=re.IGNORECASE) typescript.write(text) byte = bdim + re.sub( rb'(\033\[[0-9][0-9;]*m)', repl, data, flags=re.IGNORECASE) # nonlocal test # test = byte return byte if yes is None: def master_read(fd): return master_read_ng(fd) def stdin_read(fd): return os.read(fd, 1024) else: if isinstance(yes, str): yes = yes.encode() txt = re.sub(rb'[\r\n]*$', rb'', yes) old = txt + b'\r\n' exp = txt + b'\n' def master_read(fd): return master_read_ng(fd, replace=old) def stdin_read(fd): # pylint: disable=unused-argument return exp with open(file, 'ab') as typescript: returncode = ptyng.spawn(argv, master_read, stdin_read, timeout=timeout, env=os.environ) # if not test.decode().endswith(os.linesep): # sys.stdout.write(os.linesep) return returncode
def make_config(quiet=False, verbose=False): if not sys.stdin.isatty(): raise OSError(5, 'Input/output error') print_wrap(f'Entering interactive command line setup procedure...') print_wrap(f'Default settings are shown as in the square brackets.') print_wrap( f'Please directly {bold}{under}ENTER{reset} if you prefer the default settings.' ) rcpath = os.path.expanduser('~/.dailyrc') try: with open(rcpath, 'w') as config_file: config_file.writelines( map(lambda s: f'{s}{os.linesep}', CONFIG[:4])) # pylint: disable=map-builtin-not-iterating print() print_wrap( f'For logging utilities, we recommend you to set up your {bold}hard disk{reset} path.' ) print_wrap( f'You may change other path preferences in configuration `{under}~/.dailyrc{reset}` later.' ) print_wrap( f'Please note that all paths must be valid under all circumstances.' ) dskdir = input('Name of your external hard disk []: ').ljust(41) config_file.write( f'dskdir = /Volumes/{dskdir} ; path where your hard disk lies\n' ) config_file.writelines( map(lambda s: f'{s}{os.linesep}', CONFIG[5:38])) # pylint: disable=map-builtin-not-iterating print() print_wrap( f'In default, we will run {bold}update{reset} and {bold}logging{reset} commands twice a day.' ) print_wrap( f'You may change daily commands preferences in configuration `{under}~/.dailyrc{reset}` later.' ) print_wrap( f'Please enter schedule as {bold}{under}HH:MM[-CMD]{reset} format, ' f'and each separates with {under}comma{reset}.') timing = input( 'Time for daily scripts [10:00-update,22:30-logging,23:00-archive]: ' ) if timing: config_file.writelines([ '\t', '\n\t'.join(map(lambda s: s.strip(), timing.split(','))), '\n' ]) else: config_file.writelines( map(lambda s: f'{s}{os.linesep}', CONFIG[38:41])) # pylint: disable=map-builtin-not-iterating config_file.writelines( map(lambda s: f'{s}{os.linesep}', CONFIG[41:52])) # pylint: disable=map-builtin-not-iterating print() print_wrap( f'For better stability, {bold}MacDaily{reset} depends on several helper programs.' ) print_wrap( 'Your password may be necessary during the launch process.') askpass = launch_askpass(quiet=quiet, verbose=verbose) confirm = launch_confirm(quiet=quiet, verbose=verbose) config_file.write( f'askpass = {askpass.ljust(49)} ; SUDO_ASKPASS utility for Homebrew Casks\n' ) config_file.write( f'confirm = {confirm.ljust(49)} ; confirm utility for MacDaily\n' ) print() print_wrap( f'Also, {bold}MacDaily{reset} supports several different environment setups.' ) print_wrap( 'You may set up these variables here, ' f'or later manually in configuration `{under}~/.dailyrc{reset}`.' ) print_wrap( f'Please enter these specifications as instructed below.') shtout = ( input('Timeout limit for shell scripts in seconds [1,000]: ') or '1000').ljust(49) config_file.write( f'limit = {shtout} ; timeout limit for shell scripts in seconds\n' ) retout = (input('Retry timeout for input prompts in seconds [60]:') or '60').ljust(49) config_file.write( f'retry = {retout} ; retry timeout for input prompts in seconds\n' ) print() print_wrap( f'Configuration for {bold}MacDaily{reset} finished. Now launching...\n' ) except BaseException: os.remove(rcpath) print(reset) raise # parse config config = parse_config(quiet, verbose) askpass = config['Miscellaneous']['askpass'] # ask for password text = f'{bold}{purple}|🔑|{reset} {bold}Your {under}sudo{reset}{bold} password may be necessary{reset}' print_term(text, get_logfile(), redirect=quiet) password = get_pass(askpass) # launch daemons path = launch_daemons(config, password, quiet, verbose) text = f'Launched helper program {under}daemons{reset}{bold} at {under}{path}{reset}' print_misc(text, get_logfile(), redirect=quiet)
def _proc_fixmissing(self, path): text = f'Checking broken {self.desc[0]} dependencies' print_info(text, self._file, redirect=self._qflag) def _proc_check(): argv = [path, 'missing', f'--hide={",".join(self._ignore)!r}'] args = ' '.join(argv) print_scpt(args, self._file, redirect=self._vflag) with open(self._file, 'a') as file: file.write(f'Script started on {date()}\n') file.write(f'command: {args!r}\n') _deps_pkgs = list() try: # brew missing exits with a non-zero status if any formulae are missing dependencies proc = subprocess.run(argv, stdout=subprocess.PIPE, stderr=make_stderr(self._vflag)) except subprocess.SubprocessError: print_text(traceback.format_exc(), self._file, redirect=self._vflag) else: context = proc.stdout.decode() print_text(context, self._file, redirect=self._vflag) for line in filter(None, context.strip().splitlines()): _deps_pkgs.extend(line.split()[1:]) finally: with open(self._file, 'a') as file: file.write(f'Script done on {date()}\n') return set(_deps_pkgs) def _proc_confirm(): pkgs = f'{reset}, {bold}'.join(_deps_pkgs) text = f'macdaily-{self.cmd}: {yellow}brew{reset}: found broken dependencies: {bold}{pkgs}{reset}' print_term(text, self._file, redirect=self._qflag) if self._yes or self._quiet: return True while True: ans = get_input( self._confirm, 'Would you like to reinstall?', prefix= f'Found broken dependencies: {", ".join(_deps_pkgs)}.\n\n', suffix=f' ({green}y{reset}/{red}N{reset}) ') if re.match(r'[yY]', ans): return True if re.match(r'[nN]', ans): return False print('Invalid input.', file=sys.stderr) _deps_pkgs = _proc_check() - self._ignore if not _deps_pkgs: text = f'macdaily-{self.cmd}: {green}brew{reset}: no broken dependencies' print_term(text, self._file, redirect=self._qflag) return text = f'Fixing broken {self.desc[0]} dependencies' print_info(text, self._file, redirect=self._qflag) if _proc_confirm(): argv = [path, 'reinstall'] if self._quiet: argv.append('--quiet') if self._verbose: argv.append('--verbose') argv.append('') _done_pkgs = set() while _deps_pkgs: for package in _deps_pkgs: argv[-1] = package print_scpt(' '.join(argv), self._file, redirect=self._qflag) if not run(argv, self._file, redirect=self._qflag, timeout=self._timeout, verbose=self._vflag): with contextlib.suppress(ValueError): self._pkgs.remove(package) _done_pkgs |= _deps_pkgs _deps_pkgs = _proc_check() - _done_pkgs - self._ignore text = f'macdaily-{self.cmd}: {green}brew{reset}: all broken dependencies fixed' else: text = f'macdaily-{self.cmd}: {red}brew{reset}: all broken dependencies remain' print_term(text, self._file, redirect=self._qflag)
def _proc_cleanup(self): if self._no_cleanup: return text = 'Pruning caches and archives' print_info(text, self._file, redirect=self._qflag) if not os.path.isdir(self._disk_dir): text = ( f'macdaily-{self.cmd}: {yellow}cask{reset}: ' f'archive directory {bold}{self._disk_dir}{reset} not found') print_term(text, self._file, redirect=self._vflag) return argv = ['brew', 'cask', 'cleanup'] if self._verbose: argv.append('--verbose') if self._quiet: argv.append('--quiet') print_scpt(' '.join(argv), self._file, redirect=self._qflag) path_cask = os.path.join(self._disk_dir, 'Homebrew', 'Cask') path_down = os.path.join(self._disk_dir, 'Homebrew', 'download') pathlib.Path(path_cask).mkdir(parents=True, exist_ok=True) pathlib.Path(path_down).mkdir(parents=True, exist_ok=True) for path in self._exec: argv = [path, '--cache'] args = ' '.join(argv) print_scpt(args, self._file, redirect=self._vflag) with open(self._file, 'a') as file: file.write(f'Script started on {date()}\n') file.write(f'command: {args!r}\n') fail = False try: proc = subprocess.check_output(argv, stderr=make_stderr(self._vflag)) except subprocess.CalledProcessError: print_text(traceback.format_exc(), self._file, redirect=self._vflag) fail = True else: context = proc.decode() print_text(context, self._file, redirect=self._vflag) finally: with open(self._file, 'a') as file: file.write(f'Script done on {date()}\n') if fail: continue cache = context.strip() if os.path.isdir(cache): argv = [path, 'cask', 'caches', 'archive'] if self._verbose: argv.append('--verbose') if self._quiet: argv.append('--quiet') print_scpt(' '.join(argv), self._file, redirect=self._qflag) file_list = list() link_list = glob.glob(os.path.join(cache, 'Cask/*')) cask_list = [os.path.realpath(name) for name in link_list] for link in link_list: file_list.append(link) try: shutil.move(link, path_cask) except (shutil.Error, FileExistsError): os.remove(link) for cask in filter( lambda p: os.path.splitext(p)[1] != '.incomplete', cask_list): try: shutil.move(cask, path_down) except (shutil.Error, FileExistsError): os.remove(cask) file_list.append(cask) print_text(os.linesep.join(sorted(file_list)), self._file, redirect=self._vflag)
def archive(argv=None): # parse args & set context redirection flags args = parse_args(argv) quiet = args.quiet verbose = (args.quiet or not args.verbose) # parse config & change environ config = parse_config(quiet, verbose) os.environ['SUDO_ASKPASS'] = config['Miscellaneous']['askpass'] # fetch current time today = datetime.datetime.today() logdate = datetime.date.strftime(today, r'%y%m%d') logtime = datetime.date.strftime(today, r'%H%M%S') # mkdir for logs logpath = pathlib.Path( os.path.join(config['Path']['logdir'], 'archive', logdate)) logpath.mkdir(parents=True, exist_ok=True) # prepare command paras filename = os.path.join(logpath, f'{logtime}-{uuid.uuid4()!s}.log') os.environ['MACDAILY_LOGFILE'] = filename disk_dir = config['Path']['arcdir'] # record program status text = f'{bold}{green}|🚨|{reset} {bold}Running MacDaily version {__version__}{reset}' print_term(text, filename, redirect=quiet) record(filename, args, today, config, redirect=verbose) log_pth = { 'logging/apm', 'logging/app', 'logging/brew', 'logging/cask', 'logging/gem', 'logging/mas', 'logging/npm', 'logging/pip', 'logging/tap' } all_pth = log_pth | { 'archive', 'cleanup', 'dependency', 'postinstall', 'reinstall', 'tarfile', 'uninstall', 'update' } # update path list if args.all: args.path.extend(all_pth) if 'logging' in args.path: args.path.remove('logging') args.path.extend(log_pth) file_dict = dict() for mode in set(args.path): if mode not in all_pth: tmp_pth = all_pth.add('logging') with open(filename, 'a') as file: file.write(f'macdaily: archive: invalid option: {mode!r}') parser = get_archive_parser() parser.error( f"argument CMD: invalid choice: {mode!r} (choose from {', '.join(sorted(tmp_pth))})" ) # make archives file_list = make_archive(config, mode, today, zipfile=False, quiet=quiet, verbose=verbose) # record file list file_dict[mode] = copy.copy(file_list) storage = None if not args.no_storage: storage = make_storage(config, today, quiet=quiet, verbose=verbose) text = f'{bold}{green}|📖|{reset} {bold}MacDaily report of archive command{reset}' print_term(text, filename, redirect=quiet) for mode, file_list in file_dict.items(): if file_list: formatted_list = f'{reset}{bold}, {under}'.join(file_list) text = f'Archived following ancient logs of {pink}{mode}{reset}{bold}: {under}{formatted_list}{reset}' else: text = f'No ancient logs of {under}{mode}{reset}{bold} archived' print_misc(text, filename, redirect=quiet) if len(file_dict) == 0: # pylint: disable=len-as-condition text = f'macdaily: {purple}archive{reset}: no logs archived' print_term(text, filename, redirect=quiet) if storage: formatted_list = f'{reset}{bold}, {under}'.join(storage) text = ( f'Stored following ancient archived into {pink}external hard disk{reset} ' f'{bold} at {under}{disk_dir}{reset}{bold}: {under}{formatted_list}{reset}' ) print_misc(text, filename, redirect=quiet) if args.show_log: try: subprocess.check_call([ 'open', '-a', '/Applications/Utilities/Console.app', filename ]) except subprocess.CalledProcessError: print_text(traceback.format_exc(), filename, redirect=verbose) print( f'macdaily: {red}archive{reset}: cannot show log file {filename!r}', file=sys.stderr) mode_str = ', '.join(file_dict) if file_dict else 'none' text = ( f'{bold}{green}|🍺|{reset} {bold}MacDaily successfully performed archive process ' f'for {mode_str} helper programs{reset}') print_term(text, filename, redirect=quiet)
def _proc_cleanup(self): if self._no_cleanup: return text = 'Pruning caches and archives' print_info(text, self._file, redirect=self._qflag) argv = ['brew', 'cleanup'] if self._verbose: argv.append('--verbose') if self._quiet: argv.append('--quiet') args = ' '.join(argv) print_scpt(args, self._file, redirect=self._qflag) flag = (not os.path.isdir(self._disk_dir)) for path in self._exec: logs = os.path.expanduser('~/Library/Logs/Homebrew/') if os.path.isdir(logs): argv = [path, 'logs', 'cleanup'] if self._verbose: argv.append('--verbose') if self._quiet: argv.append('--quiet') args = ' '.join(argv) print_scpt(args, self._file, redirect=self._vflag) argv = ['rm', '-rf'] if self._verbose: argv.append('-v') argv.append(logs) sudo(argv, self._file, self._password, redirect=self._qflag, verbose=self._vflag) # if external disk not attached if flag: continue argv = [path, '--cache'] args = ' '.join(argv) print_scpt(args, self._file, redirect=self._vflag) with open(self._file, 'a') as file: file.write(f'Script started on {date()}\n') file.write(f'command: {args!r}\n') fail = False try: proc = subprocess.check_output(argv, stderr=make_stderr(self._vflag)) except subprocess.CalledProcessError: print_text(traceback.format_exc(), self._file, redirect=self._vflag) fail = True else: context = proc.decode() print_text(context, self._file, redirect=self._vflag) finally: with open(self._file, 'a') as file: file.write(f'Script done on {date()}\n') if fail: continue cache = context.strip() if os.path.isdir(cache): argv = [path, 'caches', 'archive'] if self._verbose: argv.append('--verbose') if self._quiet: argv.append('--quiet') args = ' '.join(argv) print_scpt(args, self._file, redirect=self._qflag) def _move(root, stem): arch = os.path.join(self._disk_dir, stem) pathlib.Path(arch).mkdir(parents=True, exist_ok=True) file_list = list() for name in os.listdir(root): path = os.path.join(root, name) if os.path.isdir(path): if name != 'Cask': file_list.extend( _move(path, os.path.join(stem, name))) elif os.path.splitext(name)[ 1] != '.incomplete' and path not in cask_list: # pylint: disable=cell-var-from-loop try: shutil.move(path, os.path.join(arch, name)) except (shutil.Error, FileExistsError): os.remove(path) # with contextlib.suppress(shutil.Error, FileExistsError): # shutil.move(path, os.path.join(arch, name)) file_list.append(path) return file_list cask_list = [ os.path.realpath(name) for name in glob.glob(os.path.join(cache, 'Cask/*')) ] file_list = _move(cache, 'Homebrew') print_text(os.linesep.join(sorted(file_list)), self._file, redirect=self._vflag) if flag: text = ( f'macdaily-{self.cmd}: {yellow}brew{reset}: ' f'archive directory {bold}{self._disk_dir}{reset} not found') print_term(text, self._file, redirect=self._vflag)
def install(argv=None): # parse args & set context redirection flags args = parse_args(argv) quiet = args.quiet verbose = (args.quiet or not args.verbose) # parse config & change environ config = parse_config(quiet, verbose) os.environ['SUDO_ASKPASS'] = config['Miscellaneous']['askpass'] # fetch current time today = datetime.datetime.today() logdate = datetime.date.strftime(today, r'%y%m%d') logtime = datetime.date.strftime(today, r'%H%M%S') # mkdir for logs logpath = pathlib.Path( os.path.join(config['Path']['logdir'], 'install', logdate)) logpath.mkdir(parents=True, exist_ok=True) # prepare command paras filename = os.path.join(logpath, f'{logtime}-{uuid.uuid4()!s}.log') os.environ['MACDAILY_LOGFILE'] = filename confirm = config['Miscellaneous']['confirm'] askpass = config['Miscellaneous']['askpass'] timeout = config['Miscellaneous']['limit'] disk_dir = config['Path']['arcdir'] brew_renew = None # record program status text = f'{bold}{green}|🚨|{reset} {bold}Running MacDaily version {__version__}{reset}' print_term(text, filename, redirect=quiet) record(filename, args, today, config, redirect=verbose) # ask for password text = f'{bold}{purple}|🔑|{reset} {bold}Your {under}sudo{reset}{bold} password may be necessary{reset}' print_term(text, filename, redirect=quiet) password = get_pass(askpass) cmd_list = list() for mode in {'apm', 'brew', 'cask', 'gem', 'mas', 'npm', 'pip', 'system'}: # skip disabled commands if not config['Mode'].get(mode, False): text = f'macdaily-install: {yellow}{mode}{reset}: command disabled' print_term(text, filename, redirect=verbose) continue # skip commands with no package spec packages = getattr(args, f'{mode}_pkgs', list()) namespace = getattr(args, mode, None) if not (packages or namespace): text = f'macdaily-install: {yellow}{mode}{reset}: nothing to install' print_term(text, filename, redirect=verbose) continue # update package specifications if namespace is None: namespace = dict(vars(args), packages=list()) namespace['packages'].extend(packages) # check master controlling flags if args.yes: namespace['yes'] = True if args.quiet: namespace['quiet'] = True if args.verbose: namespace['verbose'] = True if args.no_cleanup: namespace['no_cleanup'] = True # run command cmd_cls = globals()[f'{mode.capitalize()}Install'] command = cmd_cls(make_namespace(namespace), filename, timeout, confirm, askpass, password, disk_dir, brew_renew) # record command cmd_list.append(command) brew_renew = command.time archive = None if not args.no_cleanup: archive = make_archive(config, 'install', today, quiet=quiet, verbose=verbose, logfile=filename) text = f'{bold}{green}|📖|{reset} {bold}MacDaily report of install command{reset}' print_term(text, filename, redirect=quiet) for command in cmd_list: desc = make_description(command) pkgs = f'{reset}{bold}, {green}'.join(command.packages) fail = f'{reset}{bold}, {red}'.join(command.failed) if pkgs: flag = (len(pkgs) == 1) text = f'Installed following {under}{desc(flag)}{reset}{bold}: {green}{pkgs}{reset}' print_misc(text, filename, redirect=quiet) else: text = f'No {under}{desc(False)}{reset}{bold} installed' print_misc(text, filename, redirect=quiet) if fail: flag = (len(fail) == 1) text = f'Installation of following {under}{desc(flag)}{reset}{bold} failed: {red}{fail}{reset}' print_misc(text, filename, redirect=quiet) else: verb, noun = ('s', '') if len(fail) == 1 else ('', 's') text = f'All {under}{desc(False)}{reset}{bold} installation{noun} succeed{verb}' print_misc(text, filename, redirect=verbose) if archive: formatted_list = f'{reset}{bold}, {under}'.join(archive) text = ( f'Archived following ancient logs: {under}{formatted_list}{reset}') print_misc(text, filename, redirect=quiet) if len(cmd_list) == 0: # pylint: disable=len-as-condition text = f'macdaily: {purple}install{reset}: no packages installed' print_term(text, filename, redirect=quiet) if args.show_log: try: subprocess.check_call([ 'open', '-a', '/Applications/Utilities/Console.app', filename ]) except subprocess.CalledProcessError: print_text(traceback.format_exc(), filename, redirect=verbose) print( f'macdaily: {red}install{reset}: cannot show log file {filename!r}', file=sys.stderr) mode_lst = [command.mode for command in cmd_list] mode_str = ', '.join(mode_lst) if mode_lst else 'none' text = ( f'{bold}{green}|🍺|{reset} {bold}MacDaily successfully performed install process ' f'for {mode_str} package managers{reset}') print_term(text, filename, redirect=quiet)
def postinstall(argv=None): # parse args & set context redirection flags args = parse_args(argv) quiet = args.quiet verbose = (args.quiet or not args.verbose) # parse config & change environ config = parse_config(quiet, verbose) os.environ['SUDO_ASKPASS'] = config['Miscellaneous']['askpass'] # fetch current time today = datetime.datetime.today() logdate = datetime.date.strftime(today, r'%y%m%d') logtime = datetime.date.strftime(today, r'%H%M%S') # mkdir for logs logpath = pathlib.Path( os.path.join(config['Path']['logdir'], 'postinstall', logdate)) logpath.mkdir(parents=True, exist_ok=True) # prepare command paras filename = os.path.join(logpath, f'{logtime}-{uuid.uuid4()!s}.log') os.environ['MACDAILY_LOGFILE'] = filename confirm = config['Miscellaneous']['confirm'] askpass = config['Miscellaneous']['askpass'] timeout = config['Miscellaneous']['limit'] disk_dir = config['Path']['arcdir'] brew_renew = None # record program status text = f'{bold}{green}|🚨|{reset} {bold}Running MacDaily version {__version__}{reset}' print_term(text, filename, redirect=quiet) record(filename, args, today, config, redirect=verbose) # ask for password text = f'{bold}{purple}|🔑|{reset} {bold}Your {under}sudo{reset}{bold} password may be necessary{reset}' print_term(text, filename, redirect=quiet) password = get_pass(askpass) # check if disabled enabled = (not config['Mode'].get('brew', False)) # run command if enabled: command = PostinstallCommand(make_namespace(args), filename, timeout, confirm, askpass, password, disk_dir, brew_renew) else: text = f'macdaily-postinstall: {yellow}brew{reset}: command disabled' print_term(text, filename, redirect=verbose) archive = None if not args.no_cleanup: archive = make_archive(config, 'postinstall', today, quiet=quiet, verbose=verbose, logfile=filename) text = f'{bold}{green}|📖|{reset} {bold}MacDaily report of postinstall command{reset}' print_term(text, filename, redirect=quiet) if enabled: desc = make_description(command) pkgs = f'{reset}{bold}, {green}'.join(command.packages) miss = f'{reset}{bold}, {yellow}'.join(command.notfound) ilst = f'{reset}{bold}, {pink}'.join(command.ignored) fail = f'{reset}{bold}, {red}'.join(command.failed) if pkgs: flag = (len(pkgs) == 1) text = f'Postinstalled following {under}{desc(flag)}{reset}{bold}: {green}{pkgs}{reset}' print_misc(text, filename, redirect=quiet) else: text = f'No {under}{desc(False)}{reset}{bold} postinstalled' print_misc(text, filename, redirect=quiet) if fail: flag = (len(fail) == 1) text = f'Postinstallation of following {under}{desc(flag)}{reset}{bold} failed: {red}{fail}{reset}' print_misc(text, filename, redirect=quiet) else: verb, noun = ('s', '') if len(fail) == 1 else ('', 's') text = f'All {under}{desc(False)}{reset}{bold} postinstallation{noun} succeed{verb}' print_misc(text, filename, redirect=verbose) if ilst: flag = (len(ilst) == 1) text = f'Ignored postinstallation of following {under}{desc(flag)}{reset}{bold}: {pink}{ilst}{reset}' print_misc(text, filename, redirect=quiet) else: text = f'No {under}{desc(False)}{reset}{bold} ignored' print_misc(text, filename, redirect=verbose) if miss: flag = (len(miss) == 1) text = f'Following {under}{desc(flag)}{reset}{bold} not found: {yellow}{miss}{reset}' print_misc(text, filename, redirect=quiet) else: text = f'Hit all {under}{desc(False)}{reset}{bold} specifications' print_misc(text, filename, redirect=verbose) if archive: formatted_list = f'{reset}{bold}, {under}'.join(archive) text = ( f'Archived following ancient logs: {under}{formatted_list}{reset}') print_misc(text, filename, redirect=quiet) if not enabled: text = f'macdaily: {purple}postinstall{reset}: no Homebrew formulae postinstalled' print_term(text, filename, redirect=quiet) if args.show_log: try: subprocess.check_call([ 'open', '-a', '/Applications/Utilities/Console.app', filename ]) except subprocess.CalledProcessError: print_text(traceback.format_exc(), filename, redirect=verbose) print( f'macdaily: {red}postinstall{reset}: cannot show log file {filename!r}', file=sys.stderr) text = ( f'{bold}{green}|🍺|{reset} {bold}MacDaily successfully performed postinstall process{reset}' ) print_term(text, filename, redirect=quiet)