mirror of
https://github.com/arcan1s/ahriman.git
synced 2025-07-04 01:25:48 +00:00
feat: allow cross reference in the configuration (#131)
This commit is contained in:
@ -120,8 +120,7 @@ class Application(ApplicationPackages, ApplicationRepository):
|
||||
process_dependencies(bool): if no set, dependencies will not be processed
|
||||
|
||||
Returns:
|
||||
list[Package]: updated packages list. Packager for dependencies will be copied from
|
||||
original package
|
||||
list[Package]: updated packages list. Packager for dependencies will be copied from the original package
|
||||
|
||||
Examples:
|
||||
In the most cases, in order to avoid build failure, it is required to add missing packages, which can be
|
||||
|
@ -236,7 +236,7 @@ class PackageArchive:
|
||||
extract list of the installed packages and their content
|
||||
|
||||
Returns:
|
||||
dict[str, FilesystemPackage]; map of package name to list of directories and files contained
|
||||
dict[str, FilesystemPackage]: map of package name to list of directories and files contained
|
||||
by this package
|
||||
"""
|
||||
result = {}
|
||||
|
@ -19,21 +19,93 @@
|
||||
#
|
||||
import configparser
|
||||
import os
|
||||
import sys
|
||||
|
||||
from collections.abc import Mapping, MutableMapping
|
||||
from collections.abc import Generator, Mapping, MutableMapping
|
||||
from string import Template
|
||||
|
||||
|
||||
class ExtendedTemplate(Template):
|
||||
"""
|
||||
extension to the default :class:`Template` class, which also enabled braces regex to lookup in sections
|
||||
|
||||
Attributes:
|
||||
braceidpattern(str): regular expression to match a colon inside braces
|
||||
"""
|
||||
|
||||
braceidpattern = r"(?a:[_a-z0-9][_a-z0-9:]*)"
|
||||
|
||||
|
||||
class ShellInterpolator(configparser.Interpolation):
|
||||
"""
|
||||
custom string interpolator, because we cannot use defaults argument due to config validation
|
||||
"""
|
||||
|
||||
DATA_LINK_ESCAPE = "\x10"
|
||||
|
||||
@staticmethod
|
||||
def _extract_variables(parser: MutableMapping[str, Mapping[str, str]], value: str,
|
||||
defaults: Mapping[str, str]) -> Generator[tuple[str, str], None, None]:
|
||||
"""
|
||||
extract keys and values (if available) from the configuration. In case if a key is not available, it will be
|
||||
silently skipped from the result
|
||||
|
||||
Args:
|
||||
parser(MutableMapping[str, Mapping[str, str]]): option parser
|
||||
value(str): source (not-converted) value
|
||||
defaults(Mapping[str, str]): default values
|
||||
|
||||
Yields:
|
||||
tuple[str, str]: variable name used for substitution and its value
|
||||
"""
|
||||
def identifiers() -> Generator[tuple[str | None, str], None, None]:
|
||||
# extract all found identifiers and parse them
|
||||
for identifier in ExtendedTemplate(value).get_identifiers():
|
||||
match identifier.split(":"):
|
||||
case [lookup_option]: # single option from the same section
|
||||
yield None, lookup_option
|
||||
case [lookup_section, lookup_option]: # reference to another section
|
||||
yield lookup_section, lookup_option
|
||||
|
||||
for section, option in identifiers():
|
||||
# key to be substituted
|
||||
key = f"{section}:{option}" if section else option
|
||||
|
||||
if section is not None: # foreign section case
|
||||
if section not in parser:
|
||||
continue # section was not found, silently skip it
|
||||
values = parser[section]
|
||||
else: # same section
|
||||
values = defaults
|
||||
|
||||
if (raw := values.get(option)) is not None:
|
||||
yield key, raw
|
||||
|
||||
@staticmethod
|
||||
def environment() -> dict[str, str]:
|
||||
"""
|
||||
extract environment variables
|
||||
|
||||
Returns:
|
||||
dict[str, str]: environment variables and some custom variables
|
||||
"""
|
||||
return os.environ | {
|
||||
"prefix": sys.prefix,
|
||||
}
|
||||
|
||||
def before_get(self, parser: MutableMapping[str, Mapping[str, str]], section: str, option: str, value: str,
|
||||
defaults: Mapping[str, str]) -> str:
|
||||
"""
|
||||
interpolate option value
|
||||
|
||||
Notes:
|
||||
This method is using :class:`string.Template` class in order to render both cross-references and
|
||||
environment variables, because it seems that it is the most legit way to handle it. In addition,
|
||||
we are using shell-like variables in some cases (see :attr:`alpm.mirror` option), thus we would like
|
||||
to keep them alive.
|
||||
|
||||
First this method resolves substitution from the configuration and then renders environment variables
|
||||
|
||||
Args:
|
||||
parser(MutableMapping[str, Mapping[str, str]]): option parser
|
||||
section(str): section name
|
||||
@ -44,8 +116,15 @@ class ShellInterpolator(configparser.Interpolation):
|
||||
Returns:
|
||||
str: substituted value
|
||||
"""
|
||||
# At the moment it seems that it is the most legit way to handle environment variables
|
||||
# Template behaviour is literally the same as shell
|
||||
# In addition, we are using shell-like variables in some cases (see :attr:`alpm.mirror` option),
|
||||
# thus we would like to keep them alive
|
||||
return Template(value).safe_substitute(os.environ)
|
||||
# because any substitution effectively replace escaped $ ($$) in result, we have to escape it manually
|
||||
escaped = value.replace("$$", self.DATA_LINK_ESCAPE)
|
||||
|
||||
# resolve internal references
|
||||
variables = dict(self._extract_variables(parser, value, defaults))
|
||||
internal = ExtendedTemplate(escaped).safe_substitute(variables)
|
||||
|
||||
# resolve enriched environment variables by using default Template class
|
||||
environment = Template(internal).safe_substitute(self.environment())
|
||||
|
||||
# replace escaped values back
|
||||
return environment.replace(self.DATA_LINK_ESCAPE, "$")
|
||||
|
@ -161,10 +161,10 @@ class Package(LazyLogging):
|
||||
bool: ``True`` in case if package base looks like VCS package and ``False`` otherwise
|
||||
"""
|
||||
return self.base.endswith("-bzr") \
|
||||
or self.base.endswith("-csv")\
|
||||
or self.base.endswith("-darcs")\
|
||||
or self.base.endswith("-git")\
|
||||
or self.base.endswith("-hg")\
|
||||
or self.base.endswith("-csv") \
|
||||
or self.base.endswith("-darcs") \
|
||||
or self.base.endswith("-git") \
|
||||
or self.base.endswith("-hg") \
|
||||
or self.base.endswith("-svn")
|
||||
|
||||
@property
|
||||
@ -358,7 +358,7 @@ class Package(LazyLogging):
|
||||
|
||||
Yields:
|
||||
Path: list of paths of files which belong to the package and distributed together with this tarball.
|
||||
All paths are relative to the ``path``
|
||||
All paths are relative to the ``path``
|
||||
|
||||
Raises:
|
||||
PackageInfoError: if there are parsing errors
|
||||
@ -504,8 +504,8 @@ class Package(LazyLogging):
|
||||
timestamp(float | int): timestamp to check build date against
|
||||
|
||||
Returns:
|
||||
bool: ``True`` in case if package was built after the specified date and ``False`` otherwise. In case if build date
|
||||
is not set by any of packages, it returns False
|
||||
bool: ``True`` in case if package was built after the specified date and ``False`` otherwise.
|
||||
In case if build date is not set by any of packages, it returns False
|
||||
"""
|
||||
return any(
|
||||
package.build_date > timestamp
|
||||
@ -550,8 +550,8 @@ class Package(LazyLogging):
|
||||
|
||||
Returns:
|
||||
str | None: new generated package release version if any. In case if the release contains dot (e.g. 1.2),
|
||||
the minor part will be incremented by 1. If the release does not contain major.minor notation, the minor
|
||||
version equals to 1 will be appended
|
||||
the minor part will be incremented by 1. If the release does not contain major.minor notation, the minor
|
||||
version equals to 1 will be appended
|
||||
"""
|
||||
if local_version is None:
|
||||
return None # local version not found, keep upstream pkgrel
|
||||
|
@ -97,7 +97,7 @@ class Waiter:
|
||||
Attributes:
|
||||
interval(float): interval in seconds between checks
|
||||
wait_timeout(float): timeout in seconds to wait for. Negative value will result in immediate exit. Zero value
|
||||
means infinite timeout
|
||||
means infinite timeout
|
||||
"""
|
||||
|
||||
wait_timeout: float
|
||||
|
Reference in New Issue
Block a user