From f8b725d175285380b4d65d74bfb72302379447d7 Mon Sep 17 00:00:00 2001 From: Evgenii Alekseev Date: Thu, 9 May 2024 13:08:21 +0300 Subject: [PATCH] fix: parse array variable from command --- docs/faq.rst | 12 ++++ src/ahriman/application/handlers/patch.py | 5 +- src/ahriman/core/util.py | 33 ----------- src/ahriman/models/pkgbuild_patch.py | 59 ++++++++++++++++--- .../handlers/test_handler_patch.py | 12 +++- tests/ahriman/core/test_util.py | 22 +------ tests/ahriman/models/test_pkgbuild_patch.py | 32 ++++++++++ 7 files changed, 109 insertions(+), 66 deletions(-) diff --git a/docs/faq.rst b/docs/faq.rst index 47c65b4d..e97a5405 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -208,6 +208,18 @@ This command will prompt for new value of the PKGBUILD variable ``version``. You sudo -u ahriman ahriman patch-add ahriman version version.patch +The command also supports arrays, but in this case you need to specify full array, e.g. + +.. code-block:: shell + + sudo -u ahriman ahriman patch-add ahriman depends + + Post new function or variable value below. Press Ctrl-D to finish: + (python python-aiohttp) + ^D + +will set depends PKGBUILD variable (exactly) to array ``["python", "python-aiohttp"]``. + Alternatively you can create full-diff patches, which are calculated by using ``git diff`` from current PKGBUILD master branch: #. diff --git a/src/ahriman/application/handlers/patch.py b/src/ahriman/application/handlers/patch.py index 220a97e4..0f131257 100644 --- a/src/ahriman/application/handlers/patch.py +++ b/src/ahriman/application/handlers/patch.py @@ -102,8 +102,9 @@ class Patch(Handler): patch = "".join(list(sys.stdin)) else: patch = patch_path.read_text(encoding="utf8") - patch = patch.strip() # remove spaces around the patch - return PkgbuildPatch(variable, patch) + # remove spaces around the patch and parse to correct type + parsed = PkgbuildPatch.parse(patch.strip()) + return PkgbuildPatch(variable, parsed) @staticmethod def patch_set_create(application: Application, package_base: str, patch: PkgbuildPatch) -> None: diff --git a/src/ahriman/core/util.py b/src/ahriman/core/util.py index 6df666be..fac8e6c0 100644 --- a/src/ahriman/core/util.py +++ b/src/ahriman/core/util.py @@ -56,7 +56,6 @@ __all__ = [ "srcinfo_property", "srcinfo_property_list", "trim_package", - "unquote", "utcnow", "walk", ] @@ -466,38 +465,6 @@ def trim_package(package_name: str) -> str: return package_name -def unquote(source: str) -> str: - """ - like :func:`shlex.quote()`, but opposite - - Args: - source(str): source string to remove quotes - - Returns: - str: string with quotes removed - - Raises: - ValueError: if no closing quotation - """ - def generator() -> Generator[str, None, None]: - token = None - for char in source: - if token is not None: - if char == token: - token = None # closed quote - else: - yield char # character inside quotes - elif char in ("'", "\""): - token = char # first quote found - else: - yield char # normal character - - if token is not None: - raise ValueError("No closing quotation") - - return "".join(generator()) - - def utcnow() -> datetime.datetime: """ get current time diff --git a/src/ahriman/models/pkgbuild_patch.py b/src/ahriman/models/pkgbuild_patch.py index 68dd9cea..b0ba710b 100644 --- a/src/ahriman/models/pkgbuild_patch.py +++ b/src/ahriman/models/pkgbuild_patch.py @@ -21,9 +21,9 @@ import shlex from dataclasses import dataclass from pathlib import Path -from typing import Any, Self +from typing import Any, Generator, Self -from ahriman.core.util import dataclass_view, unquote +from ahriman.core.util import dataclass_view @dataclass(frozen=True) @@ -81,14 +81,57 @@ class PkgbuildPatch: Self: package properties """ key, *value_parts = variable.split("=", maxsplit=1) - raw_value = next(iter(value_parts), "") # extract raw value - if raw_value.startswith("(") and raw_value.endswith(")"): - value: str | list[str] = shlex.split(raw_value[1:-1]) # arrays for poor - else: - value = unquote(raw_value) + return cls(key, cls.parse(raw_value)) - return cls(key, value) + @staticmethod + def parse(source: str) -> str | list[str]: + """ + parse string value to the PKGBUILD patch value. This method simply takes string, tries to identify it as array + or just string and return the respective value. Functions should be processed correctly, however, not guaranteed + + Args: + source(str): source string to parse + + Returns: + str | list[str]: parsed value either string or list of strings + """ + if source.startswith("(") and source.endswith(")"): + return shlex.split(source[1:-1]) # arrays for poor + return PkgbuildPatch.unquote(source) + + @staticmethod + def unquote(source: str) -> str: + """ + like :func:`shlex.quote()`, but opposite + + Args: + source(str): source string to remove quotes + + Returns: + str: string with quotes removed + + Raises: + ValueError: if no closing quotation + """ + + def generator() -> Generator[str, None, None]: + token = None + for char in source: + if token is not None: + if char == token: + token = None # closed quote + else: + yield char # character inside quotes + elif char in ("'", "\""): + token = char # first quote found + else: + yield char # normal character + + if token is not None: + raise ValueError("No closing quotation") + + return "".join(generator()) def serialize(self) -> str: """ diff --git a/tests/ahriman/application/handlers/test_handler_patch.py b/tests/ahriman/application/handlers/test_handler_patch.py index d8d3cad8..c629714c 100644 --- a/tests/ahriman/application/handlers/test_handler_patch.py +++ b/tests/ahriman/application/handlers/test_handler_patch.py @@ -122,11 +122,10 @@ def test_patch_create_from_function(mocker: MockerFixture) -> None: """ must create function patch from file """ - path = Path("local") patch = PkgbuildPatch("version", "patch") read_mock = mocker.patch("pathlib.Path.read_text", return_value=patch.value) - assert Patch.patch_create_from_function(patch.key, path) == patch + assert Patch.patch_create_from_function(patch.key, Path("local")) == patch read_mock.assert_called_once_with(encoding="utf8") @@ -148,6 +147,15 @@ def test_patch_create_from_function_strip(mocker: MockerFixture) -> None: assert Patch.patch_create_from_function(patch.key, None) == patch +def test_patch_create_from_function_array(mocker: MockerFixture) -> None: + """ + must correctly read array variable + """ + patch = PkgbuildPatch("version", ["array", "patch"]) + mocker.patch("pathlib.Path.read_text", return_value=f"({" ".join(patch.value)})") + assert Patch.patch_create_from_function(patch.key, Path("local")) == patch + + def test_patch_set_list(application: Application, mocker: MockerFixture) -> None: """ must list available patches for the command diff --git a/tests/ahriman/core/test_util.py b/tests/ahriman/core/test_util.py index a1a049c8..3172f475 100644 --- a/tests/ahriman/core/test_util.py +++ b/tests/ahriman/core/test_util.py @@ -12,7 +12,7 @@ from unittest.mock import call as MockCall from ahriman.core.exceptions import BuildError, CalledProcessError, OptionError, UnsafeRunError from ahriman.core.util import check_output, check_user, dataclass_view, enum_values, extract_user, filter_json, \ full_version, minmax, package_like, parse_version, partition, pretty_datetime, pretty_size, safe_filename, \ - srcinfo_property, srcinfo_property_list, trim_package, unquote, utcnow, walk + srcinfo_property, srcinfo_property_list, trim_package, utcnow, walk from ahriman.models.package import Package from ahriman.models.package_source import PackageSource from ahriman.models.repository_id import RepositoryId @@ -445,26 +445,6 @@ def test_trim_package() -> None: assert trim_package("package: a description") == "package" -def test_unquote() -> None: - """ - must remove quotation marks - """ - for source in ( - "abc", - "ab'c", - "ab\"c", - ): - assert unquote(shlex.quote(source)) == source - - -def test_unquote_error() -> None: - """ - must raise value error on invalid quotation - """ - with pytest.raises(ValueError): - unquote("ab'c") - - def test_utcnow() -> None: """ must generate correct timestamp diff --git a/tests/ahriman/models/test_pkgbuild_patch.py b/tests/ahriman/models/test_pkgbuild_patch.py index 4a096c20..e0214d7c 100644 --- a/tests/ahriman/models/test_pkgbuild_patch.py +++ b/tests/ahriman/models/test_pkgbuild_patch.py @@ -1,3 +1,6 @@ +import pytest +import shlex + from pathlib import Path from pytest_mock import MockerFixture from unittest.mock import MagicMock, call @@ -48,6 +51,35 @@ def test_from_env() -> None: assert PkgbuildPatch.from_env("KEY") == PkgbuildPatch("KEY", "") +def test_parse() -> None: + """ + must parse string correctly + """ + assert PkgbuildPatch.parse("VALUE") == "VALUE" + assert PkgbuildPatch.parse("(ARRAY VALUE)") == ["ARRAY", "VALUE"] + assert PkgbuildPatch.parse("""("QU'OUTED" ARRAY VALUE)""") == ["QU'OUTED", "ARRAY", "VALUE"] + + +def test_unquote() -> None: + """ + must remove quotation marks + """ + for source in ( + "abc", + "ab'c", + "ab\"c", + ): + assert PkgbuildPatch.unquote(shlex.quote(source)) == source + + +def test_unquote_error() -> None: + """ + must raise value error on invalid quotation + """ + with pytest.raises(ValueError): + PkgbuildPatch.unquote("ab'c") + + def test_serialize() -> None: """ must correctly serialize string values