mirror of
https://github.com/arcan1s/ahriman.git
synced 2025-09-10 18:59:57 +00:00
add list config options
This commit is contained in:
@ -200,9 +200,9 @@ class Setup(Handler):
|
|||||||
multilib(bool): add or do not multilib repository to the configuration
|
multilib(bool): add or do not multilib repository to the configuration
|
||||||
repository_server(str): url of the repository
|
repository_server(str): url of the repository
|
||||||
"""
|
"""
|
||||||
# allow_no_value=True is required because pacman uses boolean configuration in which just keys present
|
# allow_multi_key=False is required because pacman uses boolean configuration in which just keys present
|
||||||
# (e.g. NoProgressBar) which will lead to exception
|
# (e.g. NoProgressBar) which will lead to exception
|
||||||
configuration = Configuration(allow_no_value=True)
|
configuration = Configuration(allow_multi_key=False)
|
||||||
# preserve case
|
# preserve case
|
||||||
# stupid mypy thinks that it is impossible
|
# stupid mypy thinks that it is impossible
|
||||||
configuration.optionxform = lambda optionstr: optionstr # type: ignore[method-assign]
|
configuration.optionxform = lambda optionstr: optionstr # type: ignore[method-assign]
|
||||||
|
@ -25,6 +25,7 @@ from collections.abc import Callable
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Self
|
from typing import Any, Self
|
||||||
|
|
||||||
|
from ahriman.core.configuration.configuration_multi_dict import ConfigurationMultiDict
|
||||||
from ahriman.core.configuration.shell_interpolator import ShellInterpolator
|
from ahriman.core.configuration.shell_interpolator import ShellInterpolator
|
||||||
from ahriman.core.exceptions import InitializeError
|
from ahriman.core.exceptions import InitializeError
|
||||||
from ahriman.models.repository_id import RepositoryId
|
from ahriman.models.repository_id import RepositoryId
|
||||||
@ -69,22 +70,25 @@ class Configuration(configparser.RawConfigParser):
|
|||||||
SYSTEM_CONFIGURATION_PATH = Path(sys.prefix) / "share" / "ahriman" / "settings" / "ahriman.ini"
|
SYSTEM_CONFIGURATION_PATH = Path(sys.prefix) / "share" / "ahriman" / "settings" / "ahriman.ini"
|
||||||
converters: dict[str, Callable[[str], Any]] # typing guard
|
converters: dict[str, Callable[[str], Any]] # typing guard
|
||||||
|
|
||||||
def __init__(self, allow_no_value: bool = False) -> None:
|
def __init__(self, allow_multi_key: bool = True) -> None:
|
||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
allow_no_value(bool, optional): copies :class:`configparser.RawConfigParser` behaviour. In case if it is set
|
allow_multi_key(bool, optional): if set to ``False``, then the default dictionary class will be used to
|
||||||
to ``True``, the keys without values will be allowed (Default value = False)
|
store keys internally. Otherwise, the special implementation will be used, which supports arrays
|
||||||
|
(Default value = True)
|
||||||
"""
|
"""
|
||||||
configparser.RawConfigParser.__init__(
|
configparser.RawConfigParser.__init__(
|
||||||
self,
|
self,
|
||||||
allow_no_value=allow_no_value,
|
dict_type=ConfigurationMultiDict if allow_multi_key else dict,
|
||||||
|
strict=False,
|
||||||
|
empty_lines_in_values=False,
|
||||||
interpolation=ShellInterpolator(),
|
interpolation=ShellInterpolator(),
|
||||||
converters={
|
converters={
|
||||||
"list": shlex.split,
|
"list": shlex.split,
|
||||||
"path": self._convert_path,
|
"path": self._convert_path,
|
||||||
"pathlist": lambda value: [self._convert_path(element) for element in shlex.split(value)],
|
"pathlist": lambda value: [self._convert_path(element) for element in shlex.split(value)],
|
||||||
}
|
},
|
||||||
)
|
) # type: ignore[call-overload]
|
||||||
|
|
||||||
self.repository_id: RepositoryId | None = None
|
self.repository_id: RepositoryId | None = None
|
||||||
self.path: Path | None = None
|
self.path: Path | None = None
|
||||||
|
85
src/ahriman/core/configuration/configuration_multi_dict.py
Normal file
85
src/ahriman/core/configuration/configuration_multi_dict.py
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
#
|
||||||
|
# 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/>.
|
||||||
|
#
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from ahriman.core.exceptions import OptionError
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigurationMultiDict(dict[str, Any]):
|
||||||
|
"""
|
||||||
|
wrapper around :class:`collections.OrderedDict` to handle multiple configuration keys as lists
|
||||||
|
if they end with ``[]``.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
This class is designed to be used only with :class:`configparser.RawConfigParser` class, but idea is that
|
||||||
|
if the key ends with ``[]`` it will be treated as array and the result will be appended to the current value.
|
||||||
|
In addition, if the value is empty, then it will clear previous values, e.g.:
|
||||||
|
|
||||||
|
>>> data = ConfigurationMultiDict()
|
||||||
|
>>>
|
||||||
|
>>> data["single"] = "value" # append normal key
|
||||||
|
>>> print(data) # {"single": "value"}
|
||||||
|
>>>
|
||||||
|
>>> data["array[]"] = ["value1"] # append array value
|
||||||
|
>>> data["array[]"] = ["value2"]
|
||||||
|
>>> print(data) # {"single": "value", "array": ["value1 value2"]}
|
||||||
|
>>>
|
||||||
|
>>> data["array[]"] = [""] # clear previous values
|
||||||
|
>>> data["array[]"] = ["value3"]
|
||||||
|
>>> print(data) # {"single": "value", "array": ["value3"]}
|
||||||
|
"""
|
||||||
|
|
||||||
|
def _set_array(self, key: str, value: list[Any]) -> None:
|
||||||
|
"""
|
||||||
|
set array value. If the key already exists in the dictionary, it will be prepended to the value
|
||||||
|
|
||||||
|
Args:
|
||||||
|
key(str): key to insert
|
||||||
|
value(list[Any]): value of the related key
|
||||||
|
"""
|
||||||
|
current_value = self.get(key)
|
||||||
|
if current_value is not None:
|
||||||
|
value = [f"{left} {right}" for left, right in zip(current_value, value)]
|
||||||
|
super().__setitem__(key, value)
|
||||||
|
|
||||||
|
def __setitem__(self, key: str, value: Any) -> None:
|
||||||
|
"""
|
||||||
|
set ``key`` to ``value``. If the value equals to ``[""]`` (array with empty string), then the key
|
||||||
|
will be removed (as expected from :class:`configparser.RawConfigParser`). If the key ends with
|
||||||
|
``[]``, the value will be treated as an array and vice versa.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
key(str): key to insert
|
||||||
|
value(Any): value of the related key
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
OptionError: if ``key`` contains ``[]``, but not at the end of the string (e.g. ``prefix[]suffix``)
|
||||||
|
"""
|
||||||
|
real_key, is_array, remaining = key.partition("[]")
|
||||||
|
if remaining:
|
||||||
|
raise OptionError(key)
|
||||||
|
|
||||||
|
if value == [""]: # em[ty value key
|
||||||
|
if real_key in self:
|
||||||
|
del self[real_key]
|
||||||
|
elif is_array and isinstance(value, list): # update array value
|
||||||
|
self._set_array(real_key, value)
|
||||||
|
else: # normal key
|
||||||
|
super().__setitem__(real_key, value)
|
@ -0,0 +1,44 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
|
from ahriman.core.configuration.configuration_multi_dict import ConfigurationMultiDict
|
||||||
|
from ahriman.core.exceptions import OptionError
|
||||||
|
|
||||||
|
|
||||||
|
def test_setitem_non_list() -> None:
|
||||||
|
"""
|
||||||
|
must insert not list correctly
|
||||||
|
"""
|
||||||
|
instance = ConfigurationMultiDict()
|
||||||
|
instance["key"] = "value"
|
||||||
|
assert instance["key"] == "value"
|
||||||
|
|
||||||
|
|
||||||
|
def test_setitem_remove() -> None:
|
||||||
|
"""
|
||||||
|
must remove key
|
||||||
|
"""
|
||||||
|
instance = ConfigurationMultiDict()
|
||||||
|
instance["key"] = "value"
|
||||||
|
instance["key"] = [""]
|
||||||
|
|
||||||
|
assert "key" not in instance
|
||||||
|
|
||||||
|
|
||||||
|
def test_setitem_array() -> None:
|
||||||
|
"""
|
||||||
|
must set array correctly
|
||||||
|
"""
|
||||||
|
instance = ConfigurationMultiDict()
|
||||||
|
instance["key[]"] = ["value1"]
|
||||||
|
instance["key[]"] = ["value2"]
|
||||||
|
|
||||||
|
assert instance["key"] == ["value1 value2"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_setitem_exception() -> None:
|
||||||
|
"""
|
||||||
|
must raise exception on invalid key
|
||||||
|
"""
|
||||||
|
instance = ConfigurationMultiDict()
|
||||||
|
with pytest.raises(OptionError):
|
||||||
|
instance["prefix[]suffix"] = "value"
|
@ -60,7 +60,7 @@ target = console
|
|||||||
|
|
||||||
[email]
|
[email]
|
||||||
host = 127.0.0.1
|
host = 127.0.0.1
|
||||||
link_path =
|
link_path = http://example.com
|
||||||
no_empty_report = no
|
no_empty_report = no
|
||||||
port = 587
|
port = 587
|
||||||
receivers = mail@example.com
|
receivers = mail@example.com
|
||||||
@ -72,9 +72,8 @@ templates = ../web/templates
|
|||||||
use_utf = yes
|
use_utf = yes
|
||||||
|
|
||||||
[html]
|
[html]
|
||||||
path =
|
link_path = http://example.com
|
||||||
homepage =
|
path = local/path
|
||||||
link_path =
|
|
||||||
template = repo-index.jinja2
|
template = repo-index.jinja2
|
||||||
templates = ../web/templates
|
templates = ../web/templates
|
||||||
|
|
||||||
@ -82,17 +81,15 @@ templates = ../web/templates
|
|||||||
manual = yes
|
manual = yes
|
||||||
|
|
||||||
[rss]
|
[rss]
|
||||||
path =
|
link_path = http://example.com
|
||||||
homepage =
|
path = local/path
|
||||||
link_path =
|
|
||||||
template = rss.jinja2
|
template = rss.jinja2
|
||||||
templates = ../web/templates
|
templates = ../web/templates
|
||||||
|
|
||||||
[telegram]
|
[telegram]
|
||||||
api_key = apikey
|
api_key = api_key
|
||||||
chat_id = @ahrimantestchat
|
chat_id = @ahrimantestchat
|
||||||
homepage =
|
link_path = http://example.com
|
||||||
link_path =
|
|
||||||
template = telegram-index.jinja2
|
template = telegram-index.jinja2
|
||||||
templates = ../web/templates
|
templates = ../web/templates
|
||||||
|
|
||||||
@ -101,20 +98,20 @@ target =
|
|||||||
|
|
||||||
[rsync]
|
[rsync]
|
||||||
command = rsync --archive --verbose --compress --partial --delete
|
command = rsync --archive --verbose --compress --partial --delete
|
||||||
remote =
|
remote = remote@example.com
|
||||||
|
|
||||||
[disabled]
|
[disabled]
|
||||||
|
|
||||||
[customs3]
|
[customs3]
|
||||||
type = s3
|
type = s3
|
||||||
access_key =
|
access_key = access_key
|
||||||
bucket = bucket
|
bucket = bucket
|
||||||
region = eu-central-1
|
region = eu-central-1
|
||||||
secret_key =
|
secret_key = secret_key
|
||||||
|
|
||||||
[github:x86_64]
|
[github:x86_64]
|
||||||
owner = arcan1s
|
owner = arcan1s
|
||||||
password =
|
password = pa55w0rd
|
||||||
repository = ahriman
|
repository = ahriman
|
||||||
username = arcan1s
|
username = arcan1s
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user