diff --git a/src/ahriman/application/ahriman.py b/src/ahriman/application/ahriman.py index e1bfc815..33d02940 100644 --- a/src/ahriman/application/ahriman.py +++ b/src/ahriman/application/ahriman.py @@ -59,6 +59,7 @@ def _parser() -> argparse.ArgumentParser: _set_rebuild_parser(subparsers) _set_remove_parser(subparsers) _set_report_parser(subparsers) + _set_search_parser(subparsers) _set_setup_parser(subparsers) _set_sign_parser(subparsers) _set_status_parser(subparsers) @@ -169,6 +170,18 @@ def _set_report_parser(root: SubParserAction) -> argparse.ArgumentParser: return parser +def _set_search_parser(root: SubParserAction) -> argparse.ArgumentParser: + """ + add parser for search subcommand + :param root: subparsers for the commands + :return: created argument parser + """ + parser = root.add_parser("search", help="search for package", description="search for package in AUR using API") + parser.add_argument("search", help="search terms, can be specified multiple times", nargs="+") + parser.set_defaults(handler=handlers.Search, lock=None, no_report=True, unsafe=True) + return parser + + def _set_setup_parser(root: SubParserAction) -> argparse.ArgumentParser: """ add parser for setup subcommand diff --git a/src/ahriman/application/handlers/__init__.py b/src/ahriman/application/handlers/__init__.py index 5428e0d7..e9311280 100644 --- a/src/ahriman/application/handlers/__init__.py +++ b/src/ahriman/application/handlers/__init__.py @@ -25,6 +25,7 @@ from ahriman.application.handlers.dump import Dump from ahriman.application.handlers.rebuild import Rebuild from ahriman.application.handlers.remove import Remove from ahriman.application.handlers.report import Report +from ahriman.application.handlers.search import Search from ahriman.application.handlers.setup import Setup from ahriman.application.handlers.sign import Sign from ahriman.application.handlers.status import Status diff --git a/src/ahriman/application/handlers/search.py b/src/ahriman/application/handlers/search.py new file mode 100644 index 00000000..ba026137 --- /dev/null +++ b/src/ahriman/application/handlers/search.py @@ -0,0 +1,58 @@ +# +# Copyright (c) 2021 ahriman team. +# +# This file is part of ahriman +# (see https://github.com/arcan1s/ahriman). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +import argparse +import aur # type: ignore + +from typing import Callable, Type + +from ahriman.application.handlers.handler import Handler +from ahriman.core.configuration import Configuration + + +class Search(Handler): + """ + packages search handler + """ + + @classmethod + def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, configuration: Configuration) -> None: + """ + callback for command line + :param args: command line args + :param architecture: repository architecture + :param configuration: configuration instance + """ + search = " ".join(args.search) + packages = aur.search(search) + + # it actually always should return string + # explicit cast to string just to avoid mypy warning for untyped library + comparator: Callable[[aur.Package], str] = lambda item: str(item.package_base) + for package in sorted(packages, key=comparator): + Search.log_fn(package) + + @staticmethod + def log_fn(package: aur.Package) -> None: + """ + log package information + :param package: package object as from AUR + """ + print(f"=> {package.package_base} {package.version}") + print(f" {package.description}") diff --git a/src/ahriman/core/configuration.py b/src/ahriman/core/configuration.py index c8b6d31d..d619127a 100644 --- a/src/ahriman/core/configuration.py +++ b/src/ahriman/core/configuration.py @@ -140,7 +140,7 @@ class Configuration(configparser.RawConfigParser): if path == self.logging_path: continue # we don't want to load logging explicitly self.read(path) - except (FileNotFoundError, configparser.NoOptionError): + except (FileNotFoundError, configparser.NoOptionError, configparser.NoSectionError): pass def load_logging(self, logfile: bool) -> None: diff --git a/tests/ahriman/application/conftest.py b/tests/ahriman/application/conftest.py index 67ee365c..8b266ee0 100644 --- a/tests/ahriman/application/conftest.py +++ b/tests/ahriman/application/conftest.py @@ -1,4 +1,5 @@ import argparse +import aur import pytest from pytest_mock import MockerFixture @@ -7,6 +8,7 @@ from ahriman.application.ahriman import _parser from ahriman.application.application import Application from ahriman.application.lock import Lock from ahriman.core.configuration import Configuration +from ahriman.models.package import Package @pytest.fixture @@ -20,6 +22,26 @@ def args() -> argparse.Namespace: return argparse.Namespace(lock=None, force=False, unsafe=False, no_report=True) +@pytest.fixture +def aur_package_ahriman(package_ahriman: Package) -> aur.Package: + return aur.Package( + num_votes=None, + description=package_ahriman.packages[package_ahriman.base].description, + url_path=package_ahriman.web_url, + last_modified=None, + name=package_ahriman.base, + out_of_date=None, + id=None, + first_submitted=None, + maintainer=None, + version=package_ahriman.version, + license=package_ahriman.packages[package_ahriman.base].licenses, + url=None, + package_base=package_ahriman.base, + package_base_id=None, + category_id=None) + + @pytest.fixture def lock(args: argparse.Namespace, configuration: Configuration) -> Lock: return Lock(args, "x86_64", configuration) diff --git a/tests/ahriman/application/handlers/test_handler_search.py b/tests/ahriman/application/handlers/test_handler_search.py new file mode 100644 index 00000000..d9723f4f --- /dev/null +++ b/tests/ahriman/application/handlers/test_handler_search.py @@ -0,0 +1,50 @@ +import argparse +import aur + +from pytest_mock import MockerFixture + +from ahriman.application.handlers import Search +from ahriman.core.configuration import Configuration + + +def _default_args(args: argparse.Namespace) -> argparse.Namespace: + args.search = ["ahriman"] + return args + + +def test_run(args: argparse.Namespace, configuration: Configuration, aur_package_ahriman: aur.Package, + mocker: MockerFixture) -> None: + """ + must run command + """ + args = _default_args(args) + mocker.patch("aur.search", return_value=[aur_package_ahriman]) + log_mock = mocker.patch("ahriman.application.handlers.search.Search.log_fn") + + Search.run(args, "x86_64", configuration) + log_mock.assert_called_once() + + +def test_run_multiple_search(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None: + """ + must run command with multiple search arguments + """ + args = _default_args(args) + args.search = ["ahriman", "is", "cool"] + search_mock = mocker.patch("aur.search") + + Search.run(args, "x86_64", configuration) + search_mock.assert_called_with(" ".join(args.search)) + + +def test_log_fn(args: argparse.Namespace, configuration: Configuration, aur_package_ahriman: aur.Package, + mocker: MockerFixture) -> None: + """ + log function must call print built-in + """ + args = _default_args(args) + mocker.patch("aur.search", return_value=[aur_package_ahriman]) + print_mock = mocker.patch("builtins.print") + + Search.run(args, "x86_64", configuration) + print_mock.assert_called() # we don't really care about call details tbh diff --git a/tests/ahriman/application/test_ahriman.py b/tests/ahriman/application/test_ahriman.py index a530ab7a..65c63d9a 100644 --- a/tests/ahriman/application/test_ahriman.py +++ b/tests/ahriman/application/test_ahriman.py @@ -71,6 +71,16 @@ def test_subparsers_config(parser: argparse.ArgumentParser) -> None: assert args.unsafe +def test_subparsers_search(parser: argparse.ArgumentParser) -> None: + """ + search command must imply lock, no_report and unsafe + """ + args = parser.parse_args(["-a", "x86_64", "search", "ahriman"]) + assert args.lock is None + assert args.no_report + assert args.unsafe + + def test_subparsers_setup(parser: argparse.ArgumentParser) -> None: """ setup command must imply lock, no_report and unsafe diff --git a/tests/ahriman/core/test_configuration.py b/tests/ahriman/core/test_configuration.py index ebb7eda2..c3c0c5d2 100644 --- a/tests/ahriman/core/test_configuration.py +++ b/tests/ahriman/core/test_configuration.py @@ -113,6 +113,14 @@ def test_load_includes_no_option(configuration: Configuration) -> None: configuration.load_includes() +def test_load_includes_no_section(configuration: Configuration) -> None: + """ + must not fail if no option set + """ + configuration.remove_section("settings") + configuration.load_includes() + + def test_load_logging_fallback(configuration: Configuration, mocker: MockerFixture) -> None: """ must fallback to stderr without errors