feat: restore iterable method in parser

This commit is contained in:
arcanis committed 2026-10-24 12:20:29 +03:00
1 parent f776b33310
commit c38cb5f680
4 files changed
+28 -15

No files matched your search

@@ -21,7 +21,7 @@ import re
import shlex import shlex
import tree_sitter_bash import tree_sitter_bash
from collections.abc import Iterator from collections.abc import Iterable, Iterator
from enum import StrEnum from enum import StrEnum
from tree_sitter import Language, Node, Parser from tree_sitter import Language, Node, Parser
from typing import IO, cast from typing import IO, cast
@@ -78,7 +78,7 @@ class PkgbuildNodeField(StrEnum):
Value = "value" Value = "value"
class PkgbuildParser: class PkgbuildParser(Iterable[PkgbuildPatch]):
""" """
PKGBUILD parser implementation in python 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 original source, with their formatting preserved. Assignments inside shell statements and control-flow nodes are
extracted without checking whether those statements would run. extracted without checking whether those statements would run.
Each iteration parses the stored source independently, starting from the beginning.
Examples: Examples:
Parse package assignments:: Parse package assignments::
>>> source = b"pkgname=example\\npkgver=1\\n" >>> 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) ... print(patch.key, patch.value)
Function bodies are returned as function patches:: Function bodies are returned as function patches::
>>> source = b"pkgver() { printf 1; }" >>> source = b"pkgver() { printf 1; }"
>>> for patch in list(PkgbuildParser(BytesIO(source)).parse()) >>> for patch in PkgbuildParser(BytesIO(source)):
... print(patch.key, patch.value) ... print(patch.key, patch.value)
""" """
_BRACE_EXPANSION = re.compile(r"(.*)\{([^{}]+)\}(.*)") _BRACE_EXPANSION = re.compile(r"(.*)\{([^{}]+)\}(.*)")
def __init__(self, stream: IO[bytes]) -> None: def __init__(self, stream: IO[bytes]) -> None:
"""""" """
Args:
stream(IO[bytes]): input PKGBUILD stream
"""
self._content = stream.read() self._content = stream.read()
@staticmethod @staticmethod
@@ -256,3 +261,12 @@ class PkgbuildParser:
) )
yield from self._read(tree.root_node) 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()
+1 -2
View File
@@ -80,8 +80,7 @@ class Pkgbuild(Mapping[str, Any]):
Returns: Returns:
Self: constructed instance of self Self: constructed instance of self
""" """
parser = PkgbuildParser(stream) fields = {patch.key: patch for patch in PkgbuildParser(stream)}
fields = {patch.key: patch for patch in parser.parse()}
# pkgbase is optional field, the pkgname must be used instead if not set # 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, # however, pkgname is not presented is "package()" functions which we are parsing here too,
@@ -71,7 +71,7 @@ def test_parse_array_exception() -> None:
""" """
parser = PkgbuildParser(BytesIO(b"var=(first second")) parser = PkgbuildParser(BytesIO(b"var=(first second"))
with pytest.raises(PkgbuildParserError): with pytest.raises(PkgbuildParserError):
assert list(parser.parse()) list(parser.parse())
def test_parse_function() -> None: def test_parse_function() -> None:
@@ -119,11 +119,11 @@ def test_parse_function_exception() -> None:
""" """
parser = PkgbuildParser(BytesIO(b"var() echo hello world } ")) parser = PkgbuildParser(BytesIO(b"var() echo hello world } "))
with pytest.raises(PkgbuildParserError): with pytest.raises(PkgbuildParserError):
assert list(parser.parse()) list(parser.parse())
parser = PkgbuildParser(BytesIO(b"var() { echo hello world")) parser = PkgbuildParser(BytesIO(b"var() { echo hello world"))
with pytest.raises(PkgbuildParserError): with pytest.raises(PkgbuildParserError):
assert list(parser.parse()) list(parser.parse())
def test_parse_function_inner_shell() -> None: def test_parse_function_inner_shell() -> None:
@@ -50,7 +50,7 @@ def test_from_io(pkgbuild_ahriman: Pkgbuild, mocker: MockerFixture) -> None:
must correctly load from io must correctly load from io
""" """
load_mock = mocker.patch("ahriman.core.alpm.pkgbuild_parser.PkgbuildParser.parse", 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 assert Pkgbuild.from_io(BytesIO(b"mock")) == pkgbuild_ahriman
load_mock.assert_called_once_with() 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 must assign missing pkgbase if pkgname is presented
""" """
mocker.patch("ahriman.core.alpm.pkgbuild_parser.PkgbuildParser.parse", side_effect=[ 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",)], iter([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",)], iter([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()] + [PkgbuildPatch("pkgbase", "pkgbase")]),
]) ])
assert Pkgbuild.from_io(BytesIO(b"mock"))["pkgbase"] == pkgbuild_ahriman["pkgname"] 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 must skip empty patches
""" """
mocker.patch("ahriman.core.alpm.pkgbuild_parser.PkgbuildParser.parse", 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 assert Pkgbuild.from_io(BytesIO(b"mock")) == pkgbuild_ahriman