diff --git a/ahriman-core/src/ahriman/core/alpm/pkgbuild_parser.py b/ahriman-core/src/ahriman/core/alpm/pkgbuild_parser.py index c5239285..06c6d2e2 100644 --- a/ahriman-core/src/ahriman/core/alpm/pkgbuild_parser.py +++ b/ahriman-core/src/ahriman/core/alpm/pkgbuild_parser.py @@ -21,7 +21,7 @@ import re import shlex import tree_sitter_bash -from collections.abc import Iterator +from collections.abc import Iterable, Iterator from enum import StrEnum from tree_sitter import Language, Node, Parser from typing import IO, cast @@ -78,7 +78,7 @@ class PkgbuildNodeField(StrEnum): Value = "value" -class PkgbuildParser: +class PkgbuildParser(Iterable[PkgbuildPatch]): """ PKGBUILD parser implementation in python @@ -97,24 +97,29 @@ class PkgbuildParser: original source, with their formatting preserved. Assignments inside shell statements and control-flow nodes are extracted without checking whether those statements would run. + Each iteration parses the stored source independently, starting from the beginning. + Examples: Parse package assignments:: >>> source = b"pkgname=example\\npkgver=1\\n" - >>> for patch in PkgbuildParser(BytesIO(source)).parse(): + >>> for patch in PkgbuildParser(BytesIO(source)): ... print(patch.key, patch.value) Function bodies are returned as function patches:: >>> source = b"pkgver() { printf 1; }" - >>> for patch in list(PkgbuildParser(BytesIO(source)).parse()) + >>> for patch in PkgbuildParser(BytesIO(source)): ... print(patch.key, patch.value) """ _BRACE_EXPANSION = re.compile(r"(.*)\{([^{}]+)\}(.*)") def __init__(self, stream: IO[bytes]) -> None: - """""" + """ + Args: + stream(IO[bytes]): input PKGBUILD stream + """ self._content = stream.read() @staticmethod @@ -256,3 +261,12 @@ class PkgbuildParser: ) yield from self._read(tree.root_node) + + def __iter__(self) -> Iterator[PkgbuildPatch]: + """ + iterate over the parsed entries + + Returns: + Iterator[PkgbuildPatch]: parsed entries + """ + return self.parse() diff --git a/ahriman-core/src/ahriman/models/pkgbuild.py b/ahriman-core/src/ahriman/models/pkgbuild.py index 48b27041..7597466f 100644 --- a/ahriman-core/src/ahriman/models/pkgbuild.py +++ b/ahriman-core/src/ahriman/models/pkgbuild.py @@ -80,8 +80,7 @@ class Pkgbuild(Mapping[str, Any]): Returns: Self: constructed instance of self """ - parser = PkgbuildParser(stream) - fields = {patch.key: patch for patch in parser.parse()} + fields = {patch.key: patch for patch in PkgbuildParser(stream)} # pkgbase is optional field, the pkgname must be used instead if not set # however, pkgname is not presented is "package()" functions which we are parsing here too, diff --git a/ahriman-core/tests/ahriman/core/alpm/test_pkgbuild_parser.py b/ahriman-core/tests/ahriman/core/alpm/test_pkgbuild_parser.py index fb2a2609..f7d425bf 100644 --- a/ahriman-core/tests/ahriman/core/alpm/test_pkgbuild_parser.py +++ b/ahriman-core/tests/ahriman/core/alpm/test_pkgbuild_parser.py @@ -71,7 +71,7 @@ def test_parse_array_exception() -> None: """ parser = PkgbuildParser(BytesIO(b"var=(first second")) with pytest.raises(PkgbuildParserError): - assert list(parser.parse()) + list(parser.parse()) def test_parse_function() -> None: @@ -119,11 +119,11 @@ def test_parse_function_exception() -> None: """ parser = PkgbuildParser(BytesIO(b"var() echo hello world } ")) with pytest.raises(PkgbuildParserError): - assert list(parser.parse()) + list(parser.parse()) parser = PkgbuildParser(BytesIO(b"var() { echo hello world")) with pytest.raises(PkgbuildParserError): - assert list(parser.parse()) + list(parser.parse()) def test_parse_function_inner_shell() -> None: diff --git a/ahriman-core/tests/ahriman/models/test_pkgbuild.py b/ahriman-core/tests/ahriman/models/test_pkgbuild.py index 05a73882..24f385e8 100644 --- a/ahriman-core/tests/ahriman/models/test_pkgbuild.py +++ b/ahriman-core/tests/ahriman/models/test_pkgbuild.py @@ -50,7 +50,7 @@ def test_from_io(pkgbuild_ahriman: Pkgbuild, mocker: MockerFixture) -> None: must correctly load from io """ load_mock = mocker.patch("ahriman.core.alpm.pkgbuild_parser.PkgbuildParser.parse", - return_value=pkgbuild_ahriman.fields.values()) + return_value=iter(pkgbuild_ahriman.fields.values())) assert Pkgbuild.from_io(BytesIO(b"mock")) == pkgbuild_ahriman load_mock.assert_called_once_with() @@ -60,9 +60,9 @@ def test_from_io_pkgbase(pkgbuild_ahriman: Pkgbuild, mocker: MockerFixture) -> N must assign missing pkgbase if pkgname is presented """ mocker.patch("ahriman.core.alpm.pkgbuild_parser.PkgbuildParser.parse", side_effect=[ - [value for key, value in pkgbuild_ahriman.fields.items() if key not in ("pkgbase",)], - [value for key, value in pkgbuild_ahriman.fields.items() if key not in ("pkgbase", "pkgname",)], - [value for key, value in pkgbuild_ahriman.fields.items()] + [PkgbuildPatch("pkgbase", "pkgbase")], + iter([value for key, value in pkgbuild_ahriman.fields.items() if key not in ("pkgbase",)]), + iter([value for key, value in pkgbuild_ahriman.fields.items() if key not in ("pkgbase", "pkgname",)]), + iter([value for key, value in pkgbuild_ahriman.fields.items()] + [PkgbuildPatch("pkgbase", "pkgbase")]), ]) assert Pkgbuild.from_io(BytesIO(b"mock"))["pkgbase"] == pkgbuild_ahriman["pkgname"] @@ -75,7 +75,7 @@ def test_from_io_empty(pkgbuild_ahriman: Pkgbuild, mocker: MockerFixture) -> Non must skip empty patches """ mocker.patch("ahriman.core.alpm.pkgbuild_parser.PkgbuildParser.parse", - return_value=list(pkgbuild_ahriman.fields.values()) + [PkgbuildPatch("", "")]) + return_value=iter(list(pkgbuild_ahriman.fields.values()) + [PkgbuildPatch("", "")])) assert Pkgbuild.from_io(BytesIO(b"mock")) == pkgbuild_ahriman