Пример #1
0
def show_unread_news():
    """
    Shows unread news from archlinux.org
    """
    # load list of already seen news
    try:
        os.makedirs(Package.cache_dir, mode=0o700, exist_ok=True)
    except OSError:
        logging.error("Creating cache dir {} failed".format(Package.cache_dir))
        raise InvalidInput("Creating cache dir {} failed".format(
            Package.cache_dir))

    seen_ids_file = os.path.join(Package.cache_dir, "seen_news_ids")
    if not os.path.isfile(seen_ids_file):
        open(seen_ids_file, 'a').close()

    with open(seen_ids_file, 'r') as seenidsfile:
        seen_ids: Set[str] = set(
            [line for line in seenidsfile.read().strip().splitlines()])

    # fetch current news and filter unseen news
    news_to_show = list(
        reversed(
            list(
                filter(
                    lambda entry: entry['id'] not in seen_ids,
                    feedparser.parse(
                        "https://www.archlinux.org/feeds/news/").entries))))

    # if no unread news, return
    if not news_to_show:
        return

    # show unseen news
    aurman_status("There are {} unseen news on archlinux.org".format(
        Colors.BOLD(Colors.LIGHT_MAGENTA(len(news_to_show)))))
    for entry in news_to_show:
        aurman_note("{} [{}]".format(
            Colors.BOLD(Colors.LIGHT_MAGENTA(entry['title'])),
            entry['published']),
                    new_line=True)
        print(re.sub('<[^<]+?>', '', entry['summary']))

    if ask_user("Have you read the news?", False, True):
        with open(seen_ids_file, 'a') as seenidsfile:
            seenidsfile.write(
                '\n'.join([entry['id'] for entry in news_to_show]) + '\n')
    else:
        logging.error(
            "User did not read the unseen news, but wanted to install packages on the system"
        )
        raise InvalidInput(
            "User did not read the unseen news, but wanted to install packages on the system"
        )
Пример #2
0
def read_config() -> 'configparser.ConfigParser':
    """
    Reads the aurman config and returns it

    :return:    The aurman config
    """
    # config dir
    config_dir = os.path.join(
        os.environ.get("XDG_CONFIG_HOME",
                       os.path.expanduser(os.path.join("~", ".config"))),
        "aurman")
    config_file = os.path.join(config_dir, "aurman_config")

    # create config dir if it does not exist
    if not os.path.exists(config_dir):
        if run("install -dm700 '{}'".format(config_dir),
               shell=True,
               stdout=DEVNULL,
               stderr=DEVNULL).returncode != 0:
            logging.error("Creating config dir of aurman failed")
            raise InvalidInput("Creating config dir of aurman failed")

    # create empty config if config does not exist
    if not os.path.isfile(config_file):
        open(config_file, 'a').close()

    config = configparser.ConfigParser(allow_no_value=True)
    # make the config case sensitive
    config.optionxform = str
    # read the config
    config.read(config_file)
    AurmanConfig.aurman_config = config

    return config
Пример #3
0
    def append_operation(operation: str):
        if args_to_return.operation is not None:
            logging.error("Tried to define more than one operation")
            raise InvalidInput("Tried to define more than one operation")

        args_to_return.operation = pacman_operations[operation]
        return "targets", 2
Пример #4
0
def read_config() -> 'configparser.ConfigParser':
    """
    Reads the aurman config and returns it

    :return:    The aurman config
    """
    # config dir
    config_dir = os.path.join(
        os.environ.get("XDG_CONFIG_HOME",
                       os.path.expanduser(os.path.join("~", ".config"))),
        "aurman")
    config_file = os.path.join(config_dir, "aurman_config")

    # create config dir if it does not exist
    if not os.path.exists(config_dir):
        try:
            os.makedirs(config_dir, mode=0o700, exist_ok=True)
        except OSError:
            logging.error(
                "Creating config dir of aurman {} failed".format(config_dir))
            raise InvalidInput(
                "Creating config dir of aurman {} failed".format(config_dir))

    # create empty config if config does not exist
    if not os.path.isfile(config_file):
        open(config_file, 'a').close()

    config = configparser.ConfigParser(allow_no_value=True)
    # make the config case sensitive
    config.optionxform = str
    # read the config
    config.read(config_file)
    AurmanConfig.aurman_config = config

    return config
Пример #5
0
def split_query_helper(max_length: int, base_length_of_query: int, length_per_append: int, to_append: Sequence[str]) -> \
        List[List[str]]:
    """
    Helper for splitting long queries.

    :param max_length:              The max length of a query
    :param base_length_of_query:    The base length of the query, e.g. length of expac -S '...'
    :param length_per_append:       A constant which is being added to the query length for every append of a parameter
                                    e.g. 1 for a space
    :param to_append:               A sequence containing the parameters as str
    :return:                        A list of lists
                                    where the inner lists contain the parameters for a single query
                                    len(return_value) yields the number of queries which have to be done all in all
    """
    current_query_length = base_length_of_query
    current_list = []
    return_list = [current_list]

    for append in to_append:
        append_length = len(append.encode("utf8"))
        if current_query_length + append_length + length_per_append <= max_length:
            current_list.append(append)
            current_query_length += append_length + length_per_append
        else:
            current_list = [append]
            return_list.append(current_list)
            current_query_length = base_length_of_query + append_length + length_per_append

        if current_query_length > max_length:
            logging.error("Query too long because of '{}'".format(append))
            raise InvalidInput("Query too long because of '{}'".format(append))

    return return_list
Пример #6
0
def makepkg(options_as_list: List[str], fetch_output: bool,
            dir_to_execute: str) -> List[str]:
    """
    makepkg wrapper. see: https://www.archlinux.org/pacman/makepkg.8.html
    provide the makepkg options as string via "options_as_string".
    e.g. "--printsrcinfo"

    :param options_as_list:     the makepkg options as string
    :param fetch_output:        True if you want to receive the output of makepkg, False otherwise
    :param dir_to_execute:      provide the directory in which the makepkg command should be executed
    :return:                    empty list in case of "fetch_output"=False, otherwise the lines of the makepkg output as list.
                                one line of output is one item in the list.
    """
    makepkg_query = ["makepkg"] + options_as_list
    if fetch_output:
        makepkg_return = run(makepkg_query,
                             stdout=PIPE,
                             universal_newlines=True,
                             cwd=dir_to_execute)
    else:
        makepkg_return = run(makepkg_query, cwd=dir_to_execute)

    if makepkg_return.returncode != 0:
        logging.error("makepkg query {} failed in directory {}".format(
            makepkg_query, dir_to_execute))
        raise InvalidInput("makepkg query {} failed in directory {}".format(
            makepkg_query, dir_to_execute))

    if fetch_output:
        return makepkg_return.stdout.strip().splitlines()

    return []
Пример #7
0
def expac(option: str, formatting: Sequence[str], targets: Sequence[str]) -> List[str]:
    """
    expac wrapper. see: https://github.com/falconindy/expac
    provide "option", "formatting" and "targets" as in this example:

    option as string: "-S"
    formatting as sequence containing strings: ("n", "v")
    targets as sequence containing strings: ("package1", "package2")

    :param option:      option as in https://github.com/falconindy/expac
    :param formatting:  formatting as in https://github.com/falconindy/expac
    :param targets:     sequence containing strings as targets as in https://github.com/falconindy/expac
    :return:            list containing the lines of the expac output.
                        one line of output is one item in the list.
                        the formatters are joined by '?!', so ('n', 'v') becomes %n?!%v in the output
    """

    max_query_length = 131071
    expac_base_query = "expac {} '{}'".format(option, "?!".join(["%{}".format(formatter) for formatter in formatting]))
    base_query_length = len(expac_base_query.encode("utf8"))
    queries_parameters = split_query_helper(max_query_length, base_query_length, 1, targets)

    return_list = []

    for query_parameters in queries_parameters:
        query = "{}{}".format(expac_base_query, ''.join([" {}".format(parameter) for parameter in query_parameters]))
        expac_return = run(query, shell=True, stdout=PIPE, stderr=DEVNULL, universal_newlines=True)
        if expac_return.returncode != 0:
            logging.error("expac query {} failed".format(query))
            raise InvalidInput("expac query {} failed".format(query))

        return_list.extend(expac_return.stdout.strip().splitlines())

    return return_list
Пример #8
0
def pacman_conf(option_as_string: str) -> List[str]:
    """
    returns all values for a given option as received by executing pacman-conf
    e.g. calling with "HoldPkg" returns all "hold packages" declared in the pacman.conf
    :param option_as_string: the option to receive the values for
    :return: the values for the given option as strings in a list
    """
    pacman_conf_return = run("pacman-conf",
                             shell=True,
                             stdout=PIPE,
                             stderr=DEVNULL,
                             universal_newlines=True)

    if pacman_conf_return.returncode != 0:
        logging.error("pacman-conf not available")
        raise InvalidInput("pacman-conf not available")

    to_return: List[str] = []

    for line in pacman_conf_return.stdout.strip().splitlines():
        to_split_at: str = "{} =".format(option_as_string)
        if line.startswith(to_split_at):
            to_return.append(line.split(to_split_at)[1].strip())

    return to_return
Пример #9
0
def pacman(options_as_string: str,
           fetch_output: bool,
           dir_to_execute: str = None,
           sudo: bool = True,
           use_ask: bool = False) -> List[str]:
    """
    pacman wrapper. see: https://www.archlinux.org/pacman/pacman.8.html
    provide the pacman options as string via "options_as_string".
    e.g. "-Syu package1 package2"

    :param options_as_string:   the pacman options as string
    :param fetch_output:        True if you want to receive the output of pacman, False otherwise
    :param dir_to_execute:      if you want to execute the pacman command in a specific directory, provide the directory
    :param sudo:                True if you want to execute pacman with sudo, False otherwise
    :param use_ask:             Use --ask=4 when calling pacman, see: https://git.archlinux.org/pacman.git/commit/?id=90e3e026d1236ad89c142b427d7eeb842bbb7ff4
    :return:                    empty list in case of "fetch_output"=False, otherwise the lines of the pacman output as list.
                                one line of output is one item in the list.
    """
    if use_ask:
        options_as_string += " --ask=4"

    if sudo:
        pacman_query = "sudo pacman {}".format(options_as_string)
    else:
        pacman_query = "pacman {}".format(options_as_string)

    if fetch_output:
        if dir_to_execute is not None:
            pacman_return = run(pacman_query,
                                shell=True,
                                stdout=PIPE,
                                stderr=DEVNULL,
                                universal_newlines=True,
                                cwd=dir_to_execute)
        else:
            pacman_return = run(pacman_query,
                                shell=True,
                                stdout=PIPE,
                                stderr=DEVNULL,
                                universal_newlines=True)
    else:
        if dir_to_execute is not None:
            pacman_return = run(pacman_query, shell=True, cwd=dir_to_execute)
        else:
            pacman_return = run(pacman_query, shell=True)

    if pacman_return.returncode != 0:
        logging.error("pacman query {} failed".format(pacman_query))
        raise InvalidInput("pacman query {} failed".format(pacman_query))

    if fetch_output:
        return pacman_return.stdout.strip().splitlines()

    return []
Пример #10
0
def packages_from_other_sources() -> Tuple[Set[str], Dict[str, str]]:
    """
    Returns the packages which should be installed
    from sources where they normally would not be installed from.

    :return:        A tuple containing two items:
                        First item:
                            Set containing the names of packages to install from the aur

                        Second item:
                            Dict containing names from known repo packages as keys
                            and the repo to install those packages from as values
    """
    config = AurmanConfig.aurman_config
    if config is None:
        aurman_error("aurman config not loaded")
        raise InvalidInput("aurman config not loaded")

    aur_set = set()
    if 'aur_packages' in config:
        for aur_package_name in config['aur_packages']:
            aur_set.add(aur_package_name)

    repo_dict = {}
    if 'repo_packages' in config:
        for repo_package_name in config['repo_packages']:
            if config['repo_packages'][repo_package_name] is None:
                continue

            repo_dict[repo_package_name] = str(
                config['repo_packages'][repo_package_name])

    for name in aur_set:
        if name in repo_dict:
            aurman_error("Package {} listed for aur and repo.".format(
                Colors.BOLD(Colors.LIGHT_MAGENTA(name))))
            raise InvalidInput("Package {} listed for aur and repo.".format(
                Colors.BOLD(Colors.LIGHT_MAGENTA(name))))

    return aur_set, repo_dict
Пример #11
0
def acquire_sudo():
    """
    sudo loop since we want sudo forever
    """
    def sudo_loop():
        while True:
            if run(["sudo", "--non-interactive", "-v"]).returncode != 0:
                logging.error("acquire sudo failed")
            time.sleep(SudoLoop.timeout)

    if run(["sudo", "-v"]).returncode != 0:
        logging.error("acquire sudo failed")
        raise InvalidInput("acquire sudo failed")
    t = threading.Thread(target=sudo_loop)
    t.daemon = True
    t.start()
Пример #12
0
def acquire_sudo():
    """
    sudo loop since we want sudo forever
    """
    def sudo_loop():
        while True:
            if run("sudo -v", shell=True, stdout=DEVNULL).returncode != 0:
                logging.error("acquire sudo failed")
            time.sleep(120)

    if run("sudo -v", shell=True).returncode != 0:
        logging.error("acquire sudo failed")
        raise InvalidInput("acquire sudo failed")
    t = threading.Thread(target=sudo_loop)
    t.daemon = True
    t.start()
Пример #13
0
def get_aur_info(package_names: Sequence[str], search: bool = False, by_name: bool = False) -> List[Dict]:
    """
    Fetches AUR infos for package_names via AurJson.
    https://wiki.archlinux.org/index.php/AurJson

    :param package_names:   The names of the packages in a sequence
    :param search:          True if one wants to search instead of getting info
    :param by_name:         If one wants to search by name only
    :return:                A list containing the "results" values of the RPC answer.
    """

    max_query_length = 8000
    if not search:
        query_url = aur_domain + "/rpc/?v=5&type=info"
        query_prefix = "&arg[]="
    else:
        query_url = aur_domain + "/rpc/?v=5&type=search"
        if by_name:
            query_url += "&by=name"
        query_prefix = "&arg="
    query_url_length = len(query_url.encode("utf8"))
    query_prefix_length = len(query_prefix.encode("utf8"))

    # quote_plus needed for packages like libc++
    package_names = [quote_plus(package_name) for package_name in package_names]

    queries_parameters = split_query_helper(max_query_length, query_url_length, query_prefix_length, package_names)

    results_list = []
    for query_parameters in queries_parameters:
        try:
            results_list.extend(json.loads(requests.get("{}{}".format(query_url, ''.join(
                ["{}{}".format(query_prefix, parameter) for parameter in query_parameters])), timeout=5).text)[
                                    'results'])
        except requests.exceptions.RequestException:
            logging.error("Connection problem while requesting AUR info for {}".format(package_names), exc_info=True)
            raise ConnectionProblem("Connection problem while requesting AUR info for {}".format(package_names))
        except json.JSONDecodeError:
            logging.error("Decoding problem while requesting AUR info for {}".format(package_names), exc_info=True)
            raise InvalidInput("Decoding problem while requesting AUR info for {}".format(package_names))

    return results_list
Пример #14
0
def expac(option: str, formatting: Sequence[str],
          targets: Sequence[str]) -> List[str]:
    """
    expac wrapper. see: https://github.com/falconindy/expac
    provide "option", "formatting" and "targets" as in this example:

    option as string: "-S"
    formatting as sequence containing strings: ("n", "v")
    targets as sequence containing strings: ("package1", "package2")

    :param option:      option as in https://github.com/falconindy/expac
    :param formatting:  formatting as in https://github.com/falconindy/expac
    :param targets:     sequence containing strings as targets as in https://github.com/falconindy/expac
    :return:            list containing the lines of the expac output.
                        one line of output is one item in the list.
                        the formatters are joined by '?!', so ('n', 'v') becomes %n?!%v in the output
    """

    cmd = [
        "expac", option,
        "?!".join(["%{}".format(formatter) for formatter in formatting])
    ]
    if targets:
        cmd += ["-"]

    expac_return = run(cmd,
                       input='\n'.join(targets),
                       stdout=PIPE,
                       stderr=DEVNULL,
                       universal_newlines=True)
    if expac_return.returncode != 0:
        query_stringified = ' '.join(quote(i) for i in cmd[1:])
        logging.error("expac query {} for targets {} failed".format(
            query_stringified, targets))
        raise InvalidInput("expac query {} for targets {} failed".format(
            query_stringified, targets))

    return expac_return.stdout.strip().splitlines()
Пример #15
0
def search_and_print(names: Sequence[str], installed_system,
                     pacman_params: str, repo: bool, aur: bool):
    """
    Searches for something and prints the results

    :param names:               The things to search for
    :param installed_system:    A system containing the installed packages
    :param pacman_params:       parameters for pacman as string
    :param repo:                search only in repo
    :param aur:                 search only in aur
    """
    if not names:
        return

    if not aur:
        # escape for pacman
        to_escape = list("()+?|{}")
        for char in to_escape:
            pacman_params = pacman_params.replace(char, "\{}".format(char))

        run("pacman {}".format(pacman_params), shell=True)

    if not repo:
        # see: https://docs.python.org/3/howto/regex.html
        regex_chars = list("^.+*?$[](){}\|")

        regex_patterns = [
            regex.compile(name, regex.IGNORECASE) for name in names
        ]
        names_beginnings_without_regex = []
        for name in names:
            index_start = -1
            index_end = len(name)
            for i, char in enumerate(name):
                if char not in regex_chars and index_start == -1:
                    index_start = i
                elif char in regex_chars and index_start != -1:
                    # must be at least two consecutive non regex chars
                    if i - index_start < 2:
                        index_start = -1
                        continue
                    index_end = i
                    break

            if index_start == -1 or index_end - index_start < 2:
                aurman_error(
                    "Your query {} contains not enough non regex chars!".
                    format(Colors.BOLD(Colors.LIGHT_MAGENTA(name))))
                raise InvalidInput(
                    "Your query {} contains not enough non regex chars!".
                    format(Colors.BOLD(Colors.LIGHT_MAGENTA(name))))

            names_beginnings_without_regex.append(name[index_start:index_end])

        found_names = set(
            ret_dict['Name'] for ret_dict in get_aur_info(
                [names_beginnings_without_regex[0]], True)
            if regex_patterns[0].findall(ret_dict['Name'])
            or isinstance(ret_dict['Description'], str)
            and regex_patterns[0].findall(ret_dict['Description']))

        for i in range(1, len(names)):
            found_names &= set(
                ret_dict['Name'] for ret_dict in get_aur_info(
                    [names_beginnings_without_regex[i]], True)
                if regex_patterns[i].findall(ret_dict['Name'])
                or isinstance(ret_dict['Description'], str)
                and regex_patterns[i].findall(ret_dict['Description']))

        search_return = get_aur_info(found_names)

        for ret_dict in sorted(search_return,
                               key=lambda x: float(x['Popularity']),
                               reverse=True):
            repo_with_slash = Colors.BOLD(Colors.LIGHT_MAGENTA("aur/"))
            name = Colors.BOLD(ret_dict['Name'])
            if ret_dict['OutOfDate'] is None:
                version = Colors.BOLD(Colors.GREEN(ret_dict['Version']))
            else:
                version = Colors.BOLD(Colors.RED(ret_dict['Version']))

            first_line = "{}{} {} ({}, {})".format(repo_with_slash, name,
                                                   version,
                                                   ret_dict['NumVotes'],
                                                   ret_dict['Popularity'])
            if ret_dict['Name'] in installed_system.all_packages_dict:
                if version_comparison(
                        ret_dict['Version'], "=",
                        installed_system.all_packages_dict[
                            ret_dict['Name']].version):
                    first_line += " {}".format(
                        Colors.BOLD(Colors.CYAN("[installed]")))
                else:
                    first_line += " {}".format(
                        Colors.BOLD(
                            Colors.CYAN("[installed: {}]".format(
                                installed_system.all_packages_dict[
                                    ret_dict['Name']].version))))
            print(first_line)
            print("    {}".format(ret_dict['Description']))
Пример #16
0
def parse_pacman_args(args: Sequence[str]) -> 'PacmanArgs':
    """
    Own parsing. Fills a concrete instance of PacmanArgs

    :param args:    the args to parse
    :return:        the instance of PacmanArgs containing the parsed parameters
    """

    def append_operation(operation: str):
        if args_to_return.operation is not None:
            logging.error("Tried to define more than one operation")
            raise InvalidInput("Tried to define more than one operation")

        args_to_return.operation = pacman_operations[operation]
        return "targets", 2

    def append_bool(param: str):
        new_current_field = pacman_options[param][0]
        # dirty hack for things like -yy or -cc
        if hasattr(args_to_return, new_current_field):
            setattr(args_to_return, new_current_field, ['something'])
        else:
            setattr(args_to_return, new_current_field, True)
        return new_current_field, 0

    args_to_return = PacmanArgs()
    current_field = "targets"
    number_of_valid_arguments = 2
    only_targets = False
    multiple_append_allowed = False

    for arg in args:
        arg_length = len(arg)

        if only_targets:
            dashes = 0
        elif arg == '--':
            only_targets = True
            current_field = "targets"
            number_of_valid_arguments = 2
            continue
        elif arg.startswith("-"):
            if arg.startswith("--"):
                dashes = 2
            else:
                dashes = 1
        else:
            dashes = 0

        arg = arg.replace("-", "", dashes)

        if dashes == 2:
            if arg_length < 4:
                logging.error("{} is too short".format(arg))
                raise InvalidInput("{} is too short".format(arg))

            if arg in pacman_options and pacman_options[arg][4]:
                multiple_append_allowed = True

        elif dashes == 1:
            if arg_length < 2:
                logging.error("{} is too short".format(arg))
                raise InvalidInput("{} is too short".format(arg))

            if arg_length > 2:
                for i in range(0, len(arg) - 1):
                    curr_char = arg[i]
                    if curr_char in pacman_operations:
                        current_field, number_of_valid_arguments = append_operation(curr_char)
                    elif curr_char not in pacman_options or pacman_options[curr_char][1] != 0:
                        args_to_return.invalid_args.append(curr_char)
                    else:
                        current_field, number_of_valid_arguments = append_bool(curr_char)
                arg = arg[len(arg) - 1]

                if arg in pacman_options and pacman_options[arg][4]:
                    multiple_append_allowed = True
        else:
            if isinstance(getattr(args_to_return, current_field), bool) or \
                    (not multiple_append_allowed
                     and number_of_valid_arguments < 2
                     and len(getattr(args_to_return, current_field)) + 1 > number_of_valid_arguments):
                current_field = "targets"
                number_of_valid_arguments = 2

            getattr(args_to_return, current_field).append(arg)
            multiple_append_allowed = False

        if dashes > 0:
            if arg not in pacman_options or not pacman_options[arg][4]:
                multiple_append_allowed = False

            if arg in pacman_operations:
                current_field, number_of_valid_arguments = append_operation(arg)
            elif arg not in pacman_options:
                args_to_return.invalid_args.append(arg)
            elif pacman_options[arg][1] == 0:
                current_field, number_of_valid_arguments = append_bool(arg)
            else:
                current_field = pacman_options[arg][0]
                number_of_valid_arguments = pacman_options[arg][1]

                if not hasattr(args_to_return, current_field):
                    setattr(args_to_return, current_field, [])

    if args_to_return.operation is None:
        logging.error("No operation defined")
        raise InvalidInput("No operation defined")

    checked_fields = set()

    for pacman_option in pacman_options.values():
        current_field = pacman_option[0]
        if current_field in checked_fields:
            continue
        number_of_valid_arguments = pacman_option[1]

        if not hasattr(args_to_return, current_field):
            if number_of_valid_arguments == 0:
                setattr(args_to_return, current_field, False)
            else:
                setattr(args_to_return, current_field, [])
        else:
            if number_of_valid_arguments > 0 and not getattr(args_to_return, current_field):
                logging.error("Parameters for {} are needed".format(current_field))
                raise InvalidInput("Parameters for {} are needed".format(current_field))

        checked_fields.add(current_field)

    return args_to_return