mirror of
				https://github.com/arcan1s/ahriman.git
				synced 2025-10-31 05:43:41 +00:00 
			
		
		
		
	fix: parse array variable from command
This commit is contained in:
		
							
								
								
									
										12
									
								
								docs/faq.rst
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								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: | ||||
|  | ||||
| #. | ||||
|  | ||||
| @ -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: | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -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: | ||||
|         """ | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
		Reference in New Issue
	
	Block a user