Passed
Push — main ( 34b5d7...c26449 )
by Jochen
06:29
created

syslog2irc.config._get_irc_server()   A

Complexity

Conditions 4

Size

Total Lines 17
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 4

Importance

Changes 0
Metric Value
eloc 14
dl 0
loc 17
ccs 13
cts 13
cp 1
rs 9.7
c 0
b 0
f 0
cc 4
nop 1
crap 4
1
"""
2
syslog2irc.config
3
~~~~~~~~~~~~~~~~~
4
5
Configuration loading
6
7
:Copyright: 2007-2021 Jochen Kupperschmidt
8
:License: MIT, see LICENSE for details.
9
"""
10
11 1
from __future__ import annotations
12 1
from dataclasses import dataclass
13 1
import logging
14 1
from pathlib import Path
15 1
from typing import Any, Iterator, Optional
16
17 1
import rtoml
18
19 1
from .irc import IrcChannel, IrcConfig, IrcServer
20 1
from .network import parse_port
21 1
from .routing import Route
22
23
24 1
DEFAULT_IRC_SERVER_PORT = 6667
25 1
DEFAULT_IRC_REALNAME = 'syslog'
26
27
28 1
logger = logging.getLogger(__name__)
29
30
31 1
class ConfigurationError(Exception):
32
    """Indicates a configuration error."""
33
34
35 1
@dataclass(frozen=True)
36
class Config:
37 1
    log_level: str
38 1
    irc: IrcConfig
39 1
    routes: set[Route]
40
41
42 1
def load_config(path: Path) -> Config:
43
    """Load configuration from file."""
44 1
    data = rtoml.load(path)
45
46 1
    log_level = _get_log_level(data)
47 1
    irc_config = _get_irc_config(data)
48 1
    routes = _get_routes(data, irc_config.channels)
49
50 1
    return Config(log_level=log_level, irc=irc_config, routes=routes)
51
52
53 1
def _get_log_level(data: dict[str, Any]) -> str:
54 1
    level = data.get('log_level', 'debug').upper()
55
56 1
    if level not in {'CRITICAL', 'ERROR', 'WARNING', 'INFO', 'DEBUG'}:
57
        raise ConfigurationError(f'Unknown log level "{level}"')
58
59 1
    return level
60
61
62 1
def _get_irc_config(data: dict[str, Any]) -> IrcConfig:
63 1
    data_irc = data['irc']
64
65 1
    server = _get_irc_server(data_irc)
66 1
    nickname = data_irc['bot']['nickname']
67 1
    realname = data_irc['bot'].get('realname', DEFAULT_IRC_REALNAME)
68 1
    commands = data_irc.get('commands', [])
69 1
    channels = set(_get_irc_channels(data_irc))
70
71 1
    if not channels:
72 1
        logger.warning('No IRC channels to join have been configured.')
73
74 1
    return IrcConfig(
75
        server=server,
76
        nickname=nickname,
77
        realname=realname,
78
        commands=commands,
79
        channels=channels,
80
    )
81
82
83 1
def _get_irc_server(data_irc: Any) -> Optional[IrcServer]:
84 1
    data_server = data_irc.get('server')
85 1
    if data_server is None:
86 1
        return None
87
88 1
    host = data_server.get('host')
89 1
    if not host:
90 1
        return None
91
92 1
    port = int(data_server.get('port', DEFAULT_IRC_SERVER_PORT))
93 1
    ssl = data_server.get('ssl', False)
94 1
    password = data_server.get('password')
95 1
    rate_limit_str = data_server.get('rate_limit')
96 1
    rate_limit = float(rate_limit_str) if rate_limit_str else None
97
98 1
    return IrcServer(
99
        host=host, port=port, ssl=ssl, password=password, rate_limit=rate_limit
100
    )
101
102
103 1
def _get_irc_channels(data_irc: Any) -> Iterator[IrcChannel]:
104 1
    for channel in data_irc.get('channels', []):
105 1
        name = channel['name']
106 1
        password = channel.get('password')
107 1
        yield IrcChannel(name, password)
108
109
110 1
def _get_routes(
111
    data: dict[str, Any], irc_channels: set[IrcChannel]
112
) -> set[Route]:
113 1
    data_routes = data.get('routes', {})
114 1
    if not data_routes:
115 1
        logger.warning('No routes have been configured.')
116
117 1
    known_irc_channel_names = {c.name for c in irc_channels}
118
119 1
    def iterate() -> Iterator[Route]:
120 1
        for syslog_port_str, irc_channel_names in data_routes.items():
121 1
            for irc_channel_name in irc_channel_names:
122 1
                try:
123 1
                    syslog_port = parse_port(syslog_port_str)
124
                except ValueError:
125
                    raise ConfigurationError(
126
                        f'Invalid syslog port "{syslog_port_str}"'
127
                    )
128
129 1
                if irc_channel_name not in known_irc_channel_names:
130
                    raise ConfigurationError(
131
                        f'Route target IRC channel "{irc_channel_name}" '
132
                        'is not configured to be joined.'
133
                    )
134
135 1
                yield Route(
136
                    syslog_port=syslog_port,
137
                    irc_channel_name=irc_channel_name,
138
                )
139
140
    return set(iterate())
141