mirror of
				https://github.com/arcan1s/ahriman.git
				synced 2025-10-31 05:43:41 +00:00 
			
		
		
		
	pkgbuild parser impl
This commit is contained in:
		| @ -27,7 +27,7 @@ import re | |||||||
| import selectors | import selectors | ||||||
| import subprocess | import subprocess | ||||||
|  |  | ||||||
| from collections.abc import Callable, Generator, Iterable | from collections.abc import Callable, Generator, Iterable, Mapping | ||||||
| from dataclasses import asdict | from dataclasses import asdict | ||||||
| from enum import Enum | from enum import Enum | ||||||
| from pathlib import Path | from pathlib import Path | ||||||
| @ -407,7 +407,7 @@ def safe_filename(source: str) -> str: | |||||||
|     return re.sub(r"[^A-Za-z\d\-._~:\[\]@]", "-", source) |     return re.sub(r"[^A-Za-z\d\-._~:\[\]@]", "-", source) | ||||||
|  |  | ||||||
|  |  | ||||||
| def srcinfo_property(key: str, srcinfo: dict[str, Any], package_srcinfo: dict[str, Any], *, | def srcinfo_property(key: str, srcinfo: Mapping[str, Any], package_srcinfo: Mapping[str, Any], *, | ||||||
|                      default: Any = None) -> Any: |                      default: Any = None) -> Any: | ||||||
|     """ |     """ | ||||||
|     extract property from SRCINFO. This method extracts property from package if this property is presented in |     extract property from SRCINFO. This method extracts property from package if this property is presented in | ||||||
| @ -416,8 +416,8 @@ def srcinfo_property(key: str, srcinfo: dict[str, Any], package_srcinfo: dict[st | |||||||
|  |  | ||||||
|     Args: |     Args: | ||||||
|         key(str): key to extract |         key(str): key to extract | ||||||
|         srcinfo(dict[str, Any]): root structure of SRCINFO |         srcinfo(Mapping[str, Any]): root structure of SRCINFO | ||||||
|         package_srcinfo(dict[str, Any]): package specific SRCINFO |         package_srcinfo(Mapping[str, Any]): package specific SRCINFO | ||||||
|         default(Any, optional): the default value for the specified key (Default value = None) |         default(Any, optional): the default value for the specified key (Default value = None) | ||||||
|  |  | ||||||
|     Returns: |     Returns: | ||||||
| @ -426,7 +426,7 @@ def srcinfo_property(key: str, srcinfo: dict[str, Any], package_srcinfo: dict[st | |||||||
|     return package_srcinfo.get(key) or srcinfo.get(key) or default |     return package_srcinfo.get(key) or srcinfo.get(key) or default | ||||||
|  |  | ||||||
|  |  | ||||||
| def srcinfo_property_list(key: str, srcinfo: dict[str, Any], package_srcinfo: dict[str, Any], *, | def srcinfo_property_list(key: str, srcinfo: Mapping[str, Any], package_srcinfo: Mapping[str, Any], *, | ||||||
|                           architecture: str | None = None) -> list[Any]: |                           architecture: str | None = None) -> list[Any]: | ||||||
|     """ |     """ | ||||||
|     extract list property from SRCINFO. Unlike :func:`srcinfo_property()` it supposes that default return value is |     extract list property from SRCINFO. Unlike :func:`srcinfo_property()` it supposes that default return value is | ||||||
| @ -435,8 +435,8 @@ def srcinfo_property_list(key: str, srcinfo: dict[str, Any], package_srcinfo: di | |||||||
|  |  | ||||||
|     Args: |     Args: | ||||||
|         key(str): key to extract |         key(str): key to extract | ||||||
|         srcinfo(dict[str, Any]): root structure of SRCINFO |         srcinfo(Mapping[str, Any]): root structure of SRCINFO | ||||||
|         package_srcinfo(dict[str, Any]): package specific SRCINFO |         package_srcinfo(Mapping[str, Any]): package specific SRCINFO | ||||||
|         architecture(str | None, optional): package architecture if set (Default value = None) |         architecture(str | None, optional): package architecture if set (Default value = None) | ||||||
|  |  | ||||||
|     Returns: |     Returns: | ||||||
|  | |||||||
| @ -37,6 +37,7 @@ from ahriman.core.log import LazyLogging | |||||||
| from ahriman.core.utils import check_output, dataclass_view, full_version, parse_version, srcinfo_property_list, utcnow | from ahriman.core.utils import check_output, dataclass_view, full_version, parse_version, srcinfo_property_list, utcnow | ||||||
| from ahriman.models.package_description import PackageDescription | from ahriman.models.package_description import PackageDescription | ||||||
| from ahriman.models.package_source import PackageSource | from ahriman.models.package_source import PackageSource | ||||||
|  | from ahriman.models.pkgbuild import Pkgbuild | ||||||
| from ahriman.models.remote_source import RemoteSource | from ahriman.models.remote_source import RemoteSource | ||||||
| from ahriman.models.repository_paths import RepositoryPaths | from ahriman.models.repository_paths import RepositoryPaths | ||||||
|  |  | ||||||
| @ -255,25 +256,23 @@ class Package(LazyLogging): | |||||||
|  |  | ||||||
|         Returns: |         Returns: | ||||||
|             Self: package properties |             Self: package properties | ||||||
|  |  | ||||||
|         Raises: |  | ||||||
|             PackageInfoError: if there are parsing errors |  | ||||||
|         """ |         """ | ||||||
|         srcinfo_source = check_output("makepkg", "--printsrcinfo", cwd=path) |         pkgbuild = Pkgbuild.from_file(path / "PKGBUILD") | ||||||
|         srcinfo, errors = parse_srcinfo(srcinfo_source) |  | ||||||
|         if errors: |  | ||||||
|             raise PackageInfoError(errors) |  | ||||||
|  |  | ||||||
|         packages = { |         packages = { | ||||||
|             package: PackageDescription( |             package: PackageDescription( | ||||||
|                 depends=srcinfo_property_list("depends", srcinfo, properties, architecture=architecture), |                 depends=srcinfo_property_list("depends", pkgbuild, properties, architecture=architecture), | ||||||
|                 make_depends=srcinfo_property_list("makedepends", srcinfo, properties, architecture=architecture), |                 make_depends=srcinfo_property_list("makedepends", pkgbuild, properties, architecture=architecture), | ||||||
|                 opt_depends=srcinfo_property_list("optdepends", srcinfo, properties, architecture=architecture), |                 opt_depends=srcinfo_property_list("optdepends", pkgbuild, properties, architecture=architecture), | ||||||
|                 check_depends=srcinfo_property_list("checkdepends", srcinfo, properties, architecture=architecture), |                 check_depends=srcinfo_property_list("checkdepends", pkgbuild, properties, architecture=architecture), | ||||||
|             ) |             ) | ||||||
|             for package, properties in srcinfo["packages"].items() |             for package, properties in pkgbuild.packages().items() | ||||||
|         } |         } | ||||||
|         version = full_version(srcinfo.get("epoch"), srcinfo["pkgver"], srcinfo["pkgrel"]) |         version = full_version( | ||||||
|  |             pkgbuild.get_as("epoch", str, default=None), | ||||||
|  |             pkgbuild.get_as("pkgver", str), | ||||||
|  |             pkgbuild.get_as("pkgrel", str), | ||||||
|  |         ) | ||||||
|  |  | ||||||
|         remote = RemoteSource( |         remote = RemoteSource( | ||||||
|             source=PackageSource.Local, |             source=PackageSource.Local, | ||||||
| @ -284,7 +283,7 @@ class Package(LazyLogging): | |||||||
|         ) |         ) | ||||||
|  |  | ||||||
|         return cls( |         return cls( | ||||||
|             base=srcinfo["pkgbase"], |             base=pkgbuild.get_as("pkgbase", str), | ||||||
|             version=version, |             version=version, | ||||||
|             remote=remote, |             remote=remote, | ||||||
|             packages=packages, |             packages=packages, | ||||||
| @ -363,16 +362,12 @@ class Package(LazyLogging): | |||||||
|         Raises: |         Raises: | ||||||
|             PackageInfoError: if there are parsing errors |             PackageInfoError: if there are parsing errors | ||||||
|         """ |         """ | ||||||
|         srcinfo_source = check_output("makepkg", "--printsrcinfo", cwd=path) |         pkgbuild = Pkgbuild.from_file(path / "PKGBUILD") | ||||||
|         srcinfo, errors = parse_srcinfo(srcinfo_source) |  | ||||||
|         if errors: |  | ||||||
|             raise PackageInfoError(errors) |  | ||||||
|  |  | ||||||
|         # we could use arch property, but for consistency it is better to call special method |         # we could use arch property, but for consistency it is better to call special method | ||||||
|         architectures = Package.supported_architectures(path) |         architectures = Package.supported_architectures(path) | ||||||
|  |  | ||||||
|         for architecture in architectures: |         for architecture in architectures: | ||||||
|             for source in srcinfo_property_list("source", srcinfo, {}, architecture=architecture): |             for source in srcinfo_property_list("source", pkgbuild, {}, architecture=architecture): | ||||||
|                 if "::" in source: |                 if "::" in source: | ||||||
|                     _, source = source.split("::", 1)  # in case if filename is specified, remove it |                     _, source = source.split("::", 1)  # in case if filename is specified, remove it | ||||||
|  |  | ||||||
| @ -383,7 +378,7 @@ class Package(LazyLogging): | |||||||
|  |  | ||||||
|                 yield Path(source) |                 yield Path(source) | ||||||
|  |  | ||||||
|         if (install := srcinfo.get("install", None)) is not None: |         if isinstance(install := pkgbuild.get("install"), str):  # well, in reality it is either None or str | ||||||
|             yield Path(install) |             yield Path(install) | ||||||
|  |  | ||||||
|     @staticmethod |     @staticmethod | ||||||
| @ -396,15 +391,9 @@ class Package(LazyLogging): | |||||||
|  |  | ||||||
|         Returns: |         Returns: | ||||||
|             set[str]: list of package supported architectures |             set[str]: list of package supported architectures | ||||||
|  |  | ||||||
|         Raises: |  | ||||||
|             PackageInfoError: if there are parsing errors |  | ||||||
|         """ |         """ | ||||||
|         srcinfo_source = check_output("makepkg", "--printsrcinfo", cwd=path) |         pkgbuild = Pkgbuild.from_file(path / "PKGBUILD") | ||||||
|         srcinfo, errors = parse_srcinfo(srcinfo_source) |         return set(pkgbuild.get("arch", [])) | ||||||
|         if errors: |  | ||||||
|             raise PackageInfoError(errors) |  | ||||||
|         return set(srcinfo.get("arch", [])) |  | ||||||
|  |  | ||||||
|     def _package_list_property(self, extractor: Callable[[PackageDescription], list[str]]) -> list[str]: |     def _package_list_property(self, extractor: Callable[[PackageDescription], list[str]]) -> list[str]: | ||||||
|         """ |         """ | ||||||
|  | |||||||
							
								
								
									
										298
									
								
								src/ahriman/models/pkgbuild.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										298
									
								
								src/ahriman/models/pkgbuild.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,298 @@ | |||||||
|  | # | ||||||
|  | # Copyright (c) 2021-2024 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 <http://www.gnu.org/licenses/>. | ||||||
|  | # | ||||||
|  | import re | ||||||
|  | import shlex | ||||||
|  |  | ||||||
|  | from collections.abc import Generator, Iterator, Mapping | ||||||
|  | from dataclasses import dataclass | ||||||
|  | from enum import StrEnum | ||||||
|  | from io import StringIO | ||||||
|  | from pathlib import Path | ||||||
|  | from typing import IO, Self, TypeVar, cast | ||||||
|  |  | ||||||
|  | from ahriman.models.pkgbuild_patch import PkgbuildPatch | ||||||
|  |  | ||||||
|  |  | ||||||
|  | T = TypeVar("T", str, list[str]) | ||||||
|  | U = TypeVar("U", str, list[str], None) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class PkgbuildToken(StrEnum): | ||||||
|  |     """ | ||||||
|  |     well-known tokens dictionary | ||||||
|  |  | ||||||
|  |     Attributes: | ||||||
|  |         ArrayStarts(PkgbuildToken): (class attribute) array starts token | ||||||
|  |         ArrayEnds(PkgbuildToken): (class attribute) array ends token | ||||||
|  |         FunctionDeclaration(PkgbuildToken): (class attribute) function declaration token | ||||||
|  |         FunctionStarts(PkgbuildToken): (class attribute) function starts token | ||||||
|  |         FunctionEnds(PkgbuildToken): (class attribute) function ends token | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     ArrayStarts = "(" | ||||||
|  |     ArrayEnds = ")" | ||||||
|  |  | ||||||
|  |     FunctionDeclaration = "()" | ||||||
|  |     FunctionStarts = "{" | ||||||
|  |     FunctionEnds = "}" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @dataclass(frozen=True) | ||||||
|  | class Pkgbuild(Mapping[str, str | list[str]]): | ||||||
|  |     """ | ||||||
|  |     simple pkgbuild reader implementation in pure python, because others sucks | ||||||
|  |  | ||||||
|  |     Attributes: | ||||||
|  |         fields(dict[str, PkgbuildPatch]): PKGBUILD fields | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     fields: dict[str, PkgbuildPatch] | ||||||
|  |  | ||||||
|  |     _ARRAY_ASSIGNMENT_REGEX = re.compile(r"^(?P<key>\w+)=$") | ||||||
|  |     _STRING_ASSIGNMENT_REGEX = re.compile(r"^(?P<key>\w+)=(?P<value>.+)$") | ||||||
|  |     # in addition functions can have dash to usual assignment | ||||||
|  |     _FUNCTION_DECLARATION_REGEX = re.compile(r"^(?P<key>[\w-]+)$") | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def variables(self) -> dict[str, str]: | ||||||
|  |         """ | ||||||
|  |         list of variables defined and (maybe) used in this PKGBUILD | ||||||
|  |  | ||||||
|  |         Returns: | ||||||
|  |             dict[str, str]: map of variable name to its value. The value will be included here in case if it presented | ||||||
|  |             in the internal dictionary, it is not a function and the value has string type | ||||||
|  |         """ | ||||||
|  |         return { | ||||||
|  |             key: value.value | ||||||
|  |             for key, value in self.fields.items() | ||||||
|  |             if not value.is_function and isinstance(value.value, str) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |     @classmethod | ||||||
|  |     def from_file(cls, path: Path) -> Self: | ||||||
|  |         """ | ||||||
|  |         parse PKGBUILD from the file | ||||||
|  |  | ||||||
|  |         Args: | ||||||
|  |             path(Path): path to the PKGBUILD file | ||||||
|  |  | ||||||
|  |         Returns: | ||||||
|  |             Self: constructed instance of self | ||||||
|  |         """ | ||||||
|  |         with path.open() as input_file: | ||||||
|  |             return cls.from_io(input_file) | ||||||
|  |  | ||||||
|  |     @classmethod | ||||||
|  |     def from_io(cls, stream: IO[str]) -> Self: | ||||||
|  |         """ | ||||||
|  |         parse PKGBUILD from input stream | ||||||
|  |  | ||||||
|  |         Args: | ||||||
|  |             stream: IO[str]: input stream containing PKGBUILD content | ||||||
|  |  | ||||||
|  |         Returns: | ||||||
|  |             Self: constructed instance of self | ||||||
|  |         """ | ||||||
|  |         fields = {} | ||||||
|  |  | ||||||
|  |         parser = shlex.shlex(stream, posix=True, punctuation_chars=True) | ||||||
|  |         while token := parser.get_token(): | ||||||
|  |             try: | ||||||
|  |                 key, value = cls._parse_token(token, parser) | ||||||
|  |                 fields[key] = value | ||||||
|  |             except StopIteration: | ||||||
|  |                 break | ||||||
|  |  | ||||||
|  |         return cls(fields) | ||||||
|  |  | ||||||
|  |     @staticmethod | ||||||
|  |     def _parse_array(parser: shlex.shlex) -> list[str]: | ||||||
|  |         """ | ||||||
|  |         parse array from the PKGBUILD. This method will extract tokens from parser until it matches closing array, | ||||||
|  |         modifying source parser state | ||||||
|  |  | ||||||
|  |         Args: | ||||||
|  |             parser(shlex.shlex): shell parser instance | ||||||
|  |  | ||||||
|  |         Returns: | ||||||
|  |             list[str]: extracted arrays elements | ||||||
|  |  | ||||||
|  |         Raises: | ||||||
|  |             ValueError: if array is not closed | ||||||
|  |         """ | ||||||
|  |         def extract() -> Generator[str, None, None]: | ||||||
|  |             while token := parser.get_token(): | ||||||
|  |                 if token == PkgbuildToken.ArrayEnds: | ||||||
|  |                     break | ||||||
|  |                 yield token | ||||||
|  |  | ||||||
|  |             if token != PkgbuildToken.ArrayEnds: | ||||||
|  |                 raise ValueError("No closing array bracket found") | ||||||
|  |  | ||||||
|  |         return list(extract()) | ||||||
|  |  | ||||||
|  |     @staticmethod | ||||||
|  |     def _parse_function(parser: shlex.shlex) -> str: | ||||||
|  |         """ | ||||||
|  |         parse function from the PKGBUILD. This method will extract tokens from parser until it matches closing function, | ||||||
|  |         modifying source parser state. Instead of trying to combine tokens together, it uses positions of the file | ||||||
|  |         and read content again in this range | ||||||
|  |  | ||||||
|  |         Args: | ||||||
|  |             parser(shlex.shlex): shell parser instance | ||||||
|  |  | ||||||
|  |         Returns: | ||||||
|  |             str: function body | ||||||
|  |  | ||||||
|  |         Raises: | ||||||
|  |             ValueError: if function body wasn't found or parser input stream doesn't support position reading | ||||||
|  |         """ | ||||||
|  |         io: IO[str] = parser.instream  # type: ignore[assignment] | ||||||
|  |  | ||||||
|  |         # find start and end positions | ||||||
|  |         start_position, end_position = -1, -1 | ||||||
|  |         while token := parser.get_token(): | ||||||
|  |             match token: | ||||||
|  |                 case PkgbuildToken.FunctionStarts: | ||||||
|  |                     start_position = io.tell() | ||||||
|  |                 case PkgbuildToken.FunctionEnds: | ||||||
|  |                     end_position = io.tell() | ||||||
|  |                     break | ||||||
|  |  | ||||||
|  |         if not 0 < start_position < end_position: | ||||||
|  |             raise ValueError("Function body wasn't found") | ||||||
|  |  | ||||||
|  |         # read the specified interval from source stream | ||||||
|  |         io.seek(start_position - 1)  # start from the previous symbol ({) | ||||||
|  |         content = io.read(end_position - start_position + 1) | ||||||
|  |  | ||||||
|  |         return content | ||||||
|  |  | ||||||
|  |     @staticmethod | ||||||
|  |     def _parse_token(token: str, parser: shlex.shlex) -> tuple[str, PkgbuildPatch]: | ||||||
|  |         """ | ||||||
|  |         parse single token to the PKGBUILD field | ||||||
|  |  | ||||||
|  |         Args: | ||||||
|  |             token(str): current token | ||||||
|  |             parser(shlex.shlex): shell parser instance | ||||||
|  |  | ||||||
|  |         Returns: | ||||||
|  |             tuple[str, PkgbuildPatch]: extracted a pair of key and its value | ||||||
|  |  | ||||||
|  |         Raises: | ||||||
|  |             StopIteration: if iteration reaches the end of the file' | ||||||
|  |         """ | ||||||
|  |         # simple assignment rule | ||||||
|  |         if (match := Pkgbuild._STRING_ASSIGNMENT_REGEX.match(token)) is not None: | ||||||
|  |             key = match.group("key") | ||||||
|  |             value = match.group("value") | ||||||
|  |             return key, PkgbuildPatch(key, value) | ||||||
|  |  | ||||||
|  |         match parser.get_token(): | ||||||
|  |             # array processing. Arrays will be sent as "key=", "(", values, ")" | ||||||
|  |             case PkgbuildToken.ArrayStarts if (match := Pkgbuild._ARRAY_ASSIGNMENT_REGEX.match(token)) is not None: | ||||||
|  |                 key = match.group("key") | ||||||
|  |                 value = Pkgbuild._parse_array(parser) | ||||||
|  |                 return key, PkgbuildPatch(key, value) | ||||||
|  |  | ||||||
|  |             # functions processing. Function will be sent as "name", "()", "{", body, "}" | ||||||
|  |             case PkgbuildToken.FunctionDeclaration if Pkgbuild._FUNCTION_DECLARATION_REGEX.match(token): | ||||||
|  |                 key = f"{token}{PkgbuildToken.FunctionDeclaration}" | ||||||
|  |                 value = Pkgbuild._parse_function(parser) | ||||||
|  |                 return token, PkgbuildPatch(key, value)  # this is not mistake, assign to token without () | ||||||
|  |  | ||||||
|  |             # some random token received without continuation, lets guess it is empty assignment (i.e. key=) | ||||||
|  |             case other if other is not None: | ||||||
|  |                 return Pkgbuild._parse_token(other, parser) | ||||||
|  |  | ||||||
|  |             # reached the end of the parser | ||||||
|  |             case None: | ||||||
|  |                 raise StopIteration | ||||||
|  |  | ||||||
|  |     def get_as(self, key: str, return_type: type[T], **kwargs: T | U) -> T | U: | ||||||
|  |         """ | ||||||
|  |         type guard for getting value by key | ||||||
|  |  | ||||||
|  |         Args: | ||||||
|  |             key(str): key name | ||||||
|  |             return_type(type[T]): return type, either ``str`` or ``list[str]`` | ||||||
|  |             default(U): default value to return if no key found | ||||||
|  |  | ||||||
|  |         Returns: | ||||||
|  |             T | U: value associated with key or default value if no value found and fallback is provided | ||||||
|  |  | ||||||
|  |         Raises: | ||||||
|  |             KeyError: if no key found and no default has been provided | ||||||
|  |         """ | ||||||
|  |         del return_type | ||||||
|  |  | ||||||
|  |         if key not in self: | ||||||
|  |             if "default" in kwargs: | ||||||
|  |                 return kwargs["default"] | ||||||
|  |             raise KeyError(key) | ||||||
|  |  | ||||||
|  |         return cast(T, self[key]) | ||||||
|  |  | ||||||
|  |     def packages(self) -> dict[str, Self]: | ||||||
|  |         """ | ||||||
|  |         extract properties from internal package functions | ||||||
|  |  | ||||||
|  |         Returns: | ||||||
|  |             dict[str, Self]: map of package name to its inner properties if defined | ||||||
|  |         """ | ||||||
|  |         packages = [self["pkgname"]] if isinstance(self["pkgname"], str) else self["pkgname"] | ||||||
|  |  | ||||||
|  |         def io(package_name: str) -> IO[str]: | ||||||
|  |             # try to read package specific function and fallback to default otherwise | ||||||
|  |             content = self.get_as(f"package_{package_name}", str, default=None) or self.get_as("package", str) | ||||||
|  |             return StringIO(content) | ||||||
|  |  | ||||||
|  |         return {package: self.from_io(io(package)) for package in packages} | ||||||
|  |  | ||||||
|  |     def __getitem__(self, key: str) -> str | list[str]: | ||||||
|  |         """ | ||||||
|  |         get the field of the PKGBUILD | ||||||
|  |  | ||||||
|  |         Args: | ||||||
|  |             key(str): key name | ||||||
|  |  | ||||||
|  |         Returns: | ||||||
|  |             str | list[str]: value by the key | ||||||
|  |         """ | ||||||
|  |         return self.fields[key].substitute(self.variables) | ||||||
|  |  | ||||||
|  |     def __iter__(self) -> Iterator[str]: | ||||||
|  |         """ | ||||||
|  |         iterate over the fields | ||||||
|  |  | ||||||
|  |         Returns: | ||||||
|  |             Iterator[str]: keys iterator | ||||||
|  |         """ | ||||||
|  |         return iter(self.fields) | ||||||
|  |  | ||||||
|  |     def __len__(self) -> int: | ||||||
|  |         """ | ||||||
|  |         get length of the mapping | ||||||
|  |  | ||||||
|  |         Returns: | ||||||
|  |             int: amount of the fields in this PKGBUILD | ||||||
|  |         """ | ||||||
|  |         return len(self.fields) | ||||||
| @ -21,6 +21,7 @@ import shlex | |||||||
|  |  | ||||||
| from dataclasses import dataclass, fields | from dataclasses import dataclass, fields | ||||||
| from pathlib import Path | from pathlib import Path | ||||||
|  | from string import Template | ||||||
| from typing import Any, Generator, Self | from typing import Any, Generator, Self | ||||||
|  |  | ||||||
| from ahriman.core.utils import dataclass_view, filter_json | from ahriman.core.utils import dataclass_view, filter_json | ||||||
| @ -167,6 +168,20 @@ class PkgbuildPatch: | |||||||
|             return f"{self.key} {self.value}"  # no quoting enabled here |             return f"{self.key} {self.value}"  # no quoting enabled here | ||||||
|         return f"""{self.key}={PkgbuildPatch.quote(self.value)}""" |         return f"""{self.key}={PkgbuildPatch.quote(self.value)}""" | ||||||
|  |  | ||||||
|  |     def substitute(self, variables: dict[str, str]) -> str | list[str]: | ||||||
|  |         """ | ||||||
|  |         substitute variables into the value | ||||||
|  |  | ||||||
|  |         Args: | ||||||
|  |             variables(dict[str, str]): map of variables available for usage | ||||||
|  |  | ||||||
|  |         Returns: | ||||||
|  |             str | list[str]: substituted value. All unknown variables will remain the same | ||||||
|  |         """ | ||||||
|  |         if isinstance(self.value, str): | ||||||
|  |             return Template(self.value).safe_substitute(variables) | ||||||
|  |         return [Template(value).safe_substitute(variables) for value in self.value] | ||||||
|  |  | ||||||
|     def view(self) -> dict[str, Any]: |     def view(self) -> dict[str, Any]: | ||||||
|         """ |         """ | ||||||
|         generate json patch view |         generate json patch view | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user