never raise keyerror instead return empty string

This commit is contained in:
Evgenii Alekseev 2024-09-13 13:43:16 +03:00
parent 67d05932cd
commit 42b6637d63
3 changed files with 41 additions and 51 deletions

View File

@ -197,7 +197,7 @@ class Watcher(LazyLogging):
proxy methods for reporter client proxy methods for reporter client
Args: Args:
item(str): property name: item(str): property name
Returns: Returns:
Any: attribute by its name Any: attribute by its name

View File

@ -266,11 +266,7 @@ class Package(LazyLogging):
) )
for package, properties in pkgbuild.packages().items() for package, properties in pkgbuild.packages().items()
} }
version = full_version( version = full_version(pkgbuild.epoch, pkgbuild.pkgver, pkgbuild.pkgrel)
pkgbuild.get_as("epoch", default=None),
pkgbuild.get_as("pkgver"),
pkgbuild.get_as("pkgrel"),
)
remote = RemoteSource( remote = RemoteSource(
source=PackageSource.Local, source=PackageSource.Local,
@ -439,11 +435,7 @@ class Package(LazyLogging):
pkgbuild = Pkgbuild.from_file(paths.cache_for(self.base) / "PKGBUILD") pkgbuild = Pkgbuild.from_file(paths.cache_for(self.base) / "PKGBUILD")
return full_version( return full_version(pkgbuild.epoch, pkgbuild.pkgver, pkgbuild.pkgrel)
pkgbuild.get_as("epoch", default=None),
pkgbuild.get_as("pkgver"),
pkgbuild.get_as("pkgrel"),
)
except Exception: except Exception:
self.logger.exception("cannot determine version of VCS package") self.logger.exception("cannot determine version of VCS package")
finally: finally:

View File

@ -25,15 +25,11 @@ from dataclasses import dataclass
from enum import StrEnum from enum import StrEnum
from io import StringIO from io import StringIO
from pathlib import Path from pathlib import Path
from typing import IO, Self, TypeVar, cast from typing import Any, IO, Self
from ahriman.models.pkgbuild_patch import PkgbuildPatch from ahriman.models.pkgbuild_patch import PkgbuildPatch
T = TypeVar("T", str, list[str])
U = TypeVar("U", str, list[str], None)
class PkgbuildToken(StrEnum): class PkgbuildToken(StrEnum):
""" """
well-known tokens dictionary well-known tokens dictionary
@ -119,12 +115,12 @@ class Pkgbuild(Mapping[str, str | list[str]]):
parser.commenters = "" parser.commenters = ""
while token := parser.get_token(): while token := parser.get_token():
try: try:
key, value = cls._parse_token(token, parser) patch = cls._parse_token(token, parser)
fields[key] = value fields[patch.key] = patch
except StopIteration: except StopIteration:
break break
return cls(fields) return cls({key: value for key, value in fields.items() if key})
@staticmethod @staticmethod
def _parse_array(parser: shlex.shlex) -> list[str]: def _parse_array(parser: shlex.shlex) -> list[str]:
@ -175,7 +171,7 @@ class Pkgbuild(Mapping[str, str | list[str]]):
while token := parser.get_token(): while token := parser.get_token():
match token: match token:
case PkgbuildToken.FunctionStarts: case PkgbuildToken.FunctionStarts:
start_position = io.tell() start_position = io.tell() - 1
case PkgbuildToken.FunctionEnds: case PkgbuildToken.FunctionEnds:
end_position = io.tell() end_position = io.tell()
break break
@ -184,13 +180,13 @@ class Pkgbuild(Mapping[str, str | list[str]]):
raise ValueError("Function body wasn't found") raise ValueError("Function body wasn't found")
# read the specified interval from source stream # read the specified interval from source stream
io.seek(start_position - 1) # start from the previous symbol ("{") io.seek(start_position - 1) # start from the previous symbol
content = io.read(end_position - start_position + 1) content = io.read(end_position - start_position)
return content return content
@staticmethod @staticmethod
def _parse_token(token: str, parser: shlex.shlex) -> tuple[str, PkgbuildPatch]: def _parse_token(token: str, parser: shlex.shlex) -> PkgbuildPatch:
""" """
parse single token to the PKGBUILD field parse single token to the PKGBUILD field
@ -199,7 +195,7 @@ class Pkgbuild(Mapping[str, str | list[str]]):
parser(shlex.shlex): shell parser instance parser(shlex.shlex): shell parser instance
Returns: Returns:
tuple[str, PkgbuildPatch]: extracted a pair of key and its value PkgbuildPatch: extracted a PKGBUILD node
Raises: Raises:
StopIteration: if iteration reaches the end of the file StopIteration: if iteration reaches the end of the file
@ -208,20 +204,20 @@ class Pkgbuild(Mapping[str, str | list[str]]):
if (match := Pkgbuild._STRING_ASSIGNMENT.match(token)) is not None: if (match := Pkgbuild._STRING_ASSIGNMENT.match(token)) is not None:
key = match.group("key") key = match.group("key")
value = match.group("value") value = match.group("value")
return key, PkgbuildPatch(key, value) return PkgbuildPatch(key, value)
match parser.get_token(): match parser.get_token():
# array processing. Arrays will be sent as "key=", "(", values, ")" # array processing. Arrays will be sent as "key=", "(", values, ")"
case PkgbuildToken.ArrayStarts if (match := Pkgbuild._ARRAY_ASSIGNMENT.match(token)) is not None: case PkgbuildToken.ArrayStarts if (match := Pkgbuild._ARRAY_ASSIGNMENT.match(token)) is not None:
key = match.group("key") key = match.group("key")
value = Pkgbuild._parse_array(parser) value = Pkgbuild._parse_array(parser)
return key, PkgbuildPatch(key, value) return PkgbuildPatch(key, value)
# functions processing. Function will be sent as "name", "()", "{", body, "}" # functions processing. Function will be sent as "name", "()", "{", body, "}"
case PkgbuildToken.FunctionDeclaration if Pkgbuild._FUNCTION_DECLARATION.match(token): case PkgbuildToken.FunctionDeclaration if Pkgbuild._FUNCTION_DECLARATION.match(token):
key = f"{token}{PkgbuildToken.FunctionDeclaration}" key = f"{token}{PkgbuildToken.FunctionDeclaration}"
value = Pkgbuild._parse_function(parser) value = Pkgbuild._parse_function(parser)
return token, PkgbuildPatch(key, value) # this is not mistake, assign to token without () return PkgbuildPatch(key, value) # this is not mistake, assign to token without ()
# special function case, where "(" and ")" are separated tokens, e.g. "pkgver ( )" # special function case, where "(" and ")" are separated tokens, e.g. "pkgver ( )"
case PkgbuildToken.ArrayStarts if Pkgbuild._FUNCTION_DECLARATION.match(token): case PkgbuildToken.ArrayStarts if Pkgbuild._FUNCTION_DECLARATION.match(token):
@ -239,27 +235,6 @@ class Pkgbuild(Mapping[str, str | list[str]]):
case None: case None:
raise StopIteration raise StopIteration
def get_as(self, key: str, **kwargs: T | U) -> T | U:
"""
type guard for getting value by key
Args:
key(str): key name
default(U, optional): 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
"""
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]: def packages(self) -> dict[str, Self]:
""" """
extract properties from internal package functions extract properties from internal package functions
@ -271,14 +246,29 @@ class Pkgbuild(Mapping[str, str | list[str]]):
def io(package_name: str) -> IO[str]: def io(package_name: str) -> IO[str]:
# try to read package specific function and fallback to default otherwise # try to read package specific function and fallback to default otherwise
content = self.get_as(f"package_{package_name}", default=None) or self.get_as("package") # content = self.get_as(f"package_{package_name}") or self.get_as("package")
content = getattr(self, f"package_{package_name}") or self.package
return StringIO(content) return StringIO(content)
return {package: self.from_io(io(package)) for package in packages} return {package: self.from_io(io(package)) for package in packages}
def __getattr__(self, item: str) -> Any:
"""
proxy method for PKGBUILD properties
Args:
item(str): property name
Returns:
Any: attribute by its name
"""
return self[item]
def __getitem__(self, key: str) -> str | list[str]: def __getitem__(self, key: str) -> str | list[str]:
""" """
get the field of the PKGBUILD get the field of the PKGBUILD. This method tries to get exact key value if possible; if none found, it tries to
fetch function with the same name. And, finally, it returns empty value if nothing found, so this function never
raises an ``KeyError``.exception``
Args: Args:
key(str): key name key(str): key name
@ -286,7 +276,15 @@ class Pkgbuild(Mapping[str, str | list[str]]):
Returns: Returns:
str | list[str]: value by the key str | list[str]: value by the key
""" """
return self.fields[key].substitute(self.variables) value = self.fields.get(key)
# if the key wasn't found and user didn't ask for function explicitly, we can try to get by function name
if value is None and not key.endswith(PkgbuildToken.FunctionDeclaration):
value = self.fields.get(f"{key}{PkgbuildToken.FunctionDeclaration}")
# if we still didn't find anything, we fall back to empty value (just like shell)
if value is None:
value = PkgbuildPatch(key, "")
return value.substitute(self.variables)
def __iter__(self) -> Iterator[str]: def __iter__(self) -> Iterator[str]:
""" """