コード例 #1
0
ファイル: decorators.py プロジェクト: JarryShaw/MacDaily
    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
コード例 #2
0
ファイル: npm.py プロジェクト: JarryShaw/MacDaily
 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
コード例 #3
0
ファイル: apm.py プロジェクト: JarryShaw/MacDaily
 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
コード例 #4
0
 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
コード例 #5
0
 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
コード例 #6
0
ファイル: mas.py プロジェクト: JarryShaw/MacDaily
 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
コード例 #7
0
 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
コード例 #8
0
ファイル: misc.py プロジェクト: JarryShaw/MacDaily
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')
コード例 #9
0
ファイル: pip.py プロジェクト: JarryShaw/MacDaily
 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)
コード例 #10
0
ファイル: decorators.py プロジェクト: JarryShaw/MacDaily
        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
コード例 #11
0
ファイル: pip.py プロジェクト: JarryShaw/MacDaily
        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)
コード例 #12
0
ファイル: decorators.py プロジェクト: JarryShaw/MacDaily
    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
コード例 #13
0
ファイル: pip.py プロジェクト: JarryShaw/MacDaily
    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)
コード例 #14
0
ファイル: pip.py プロジェクト: JarryShaw/MacDaily
    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
コード例 #15
0
ファイル: launch.py プロジェクト: JarryShaw/MacDaily
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)
コード例 #16
0
ファイル: config.py プロジェクト: JarryShaw/MacDaily
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()
コード例 #17
0
ファイル: logging.py プロジェクト: JarryShaw/MacDaily
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)
コード例 #18
0
ファイル: script.py プロジェクト: JarryShaw/MacDaily
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
コード例 #19
0
ファイル: config.py プロジェクト: JarryShaw/MacDaily
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)
コード例 #20
0
    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)
コード例 #21
0
ファイル: cask.py プロジェクト: JarryShaw/MacDaily
    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)
コード例 #22
0
ファイル: archive.py プロジェクト: JarryShaw/MacDaily
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)
コード例 #23
0
    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)
コード例 #24
0
ファイル: install.py プロジェクト: JarryShaw/MacDaily
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)
コード例 #25
0
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)