mirror of
https://github.com/arcan1s/ahriman.git
synced 2025-09-03 15:29:55 +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
|
||||
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
|
||||
configuration = Configuration(allow_no_value=True)
|
||||
configuration = Configuration(allow_multi_key=False)
|
||||
# preserve case
|
||||
# stupid mypy thinks that it is impossible
|
||||
configuration.optionxform = lambda optionstr: optionstr # type: ignore[method-assign]
|
||||
|
@ -25,6 +25,7 @@ from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
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.exceptions import InitializeError
|
||||
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"
|
||||
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:
|
||||
allow_no_value(bool, optional): copies :class:`configparser.RawConfigParser` behaviour. In case if it is set
|
||||
to ``True``, the keys without values will be allowed (Default value = False)
|
||||
allow_multi_key(bool, optional): if set to ``False``, then the default dictionary class will be used to
|
||||
store keys internally. Otherwise, the special implementation will be used, which supports arrays
|
||||
(Default value = True)
|
||||
"""
|
||||
configparser.RawConfigParser.__init__(
|
||||
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(),
|
||||
converters={
|
||||
"list": shlex.split,
|
||||
"path": self._convert_path,
|
||||
"pathlist": lambda value: [self._convert_path(element) for element in shlex.split(value)],
|
||||
}
|
||||
)
|
||||
},
|
||||
) # type: ignore[call-overload]
|
||||
|
||||
self.repository_id: RepositoryId | 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]
|
||||
host = 127.0.0.1
|
||||
link_path =
|
||||
link_path = http://example.com
|
||||
no_empty_report = no
|
||||
port = 587
|
||||
receivers = mail@example.com
|
||||
@ -72,9 +72,8 @@ templates = ../web/templates
|
||||
use_utf = yes
|
||||
|
||||
[html]
|
||||
path =
|
||||
homepage =
|
||||
link_path =
|
||||
link_path = http://example.com
|
||||
path = local/path
|
||||
template = repo-index.jinja2
|
||||
templates = ../web/templates
|
||||
|
||||
@ -82,17 +81,15 @@ templates = ../web/templates
|
||||
manual = yes
|
||||
|
||||
[rss]
|
||||
path =
|
||||
homepage =
|
||||
link_path =
|
||||
link_path = http://example.com
|
||||
path = local/path
|
||||
template = rss.jinja2
|
||||
templates = ../web/templates
|
||||
|
||||
[telegram]
|
||||
api_key = apikey
|
||||
api_key = api_key
|
||||
chat_id = @ahrimantestchat
|
||||
homepage =
|
||||
link_path =
|
||||
link_path = http://example.com
|
||||
template = telegram-index.jinja2
|
||||
templates = ../web/templates
|
||||
|
||||
@ -101,20 +98,20 @@ target =
|
||||
|
||||
[rsync]
|
||||
command = rsync --archive --verbose --compress --partial --delete
|
||||
remote =
|
||||
remote = remote@example.com
|
||||
|
||||
[disabled]
|
||||
|
||||
[customs3]
|
||||
type = s3
|
||||
access_key =
|
||||
access_key = access_key
|
||||
bucket = bucket
|
||||
region = eu-central-1
|
||||
secret_key =
|
||||
secret_key = secret_key
|
||||
|
||||
[github:x86_64]
|
||||
owner = arcan1s
|
||||
password =
|
||||
password = pa55w0rd
|
||||
repository = ahriman
|
||||
username = arcan1s
|
||||
|
||||
|
Reference in New Issue
Block a user