Passed
Push — main ( 4dacf5...aae69a )
by Jochen
02:11
created

syslog2irc.config   A

Complexity

Total Complexity 15

Size/Duplication

Total Lines 129
Duplicated Lines 0 %

Test Coverage

Coverage 95.31%

Importance

Changes 0
Metric Value
eloc 78
dl 0
loc 129
rs 10
c 0
b 0
f 0
ccs 61
cts 64
cp 0.9531
wmc 15

5 Functions

Rating   Name   Duplication   Size   Complexity  
B _get_routes() 0 31 6
A _get_irc_server() 0 17 4
A load_config() 0 8 1
A _get_irc_channels() 0 5 2
A _get_irc_config() 0 18 2
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 dataclasses import dataclass
12 1
import logging
13 1
from pathlib import Path
14 1
from typing import Any, Dict, Iterator, Optional, Set
15
16 1
import rtoml
17
18 1
from .irc import IrcChannel, IrcConfig, IrcServer
19 1
from .network import parse_port
20 1
from .router import Route
21
22
23 1
DEFAULT_IRC_SERVER_PORT = 6667
24 1
DEFAULT_IRC_REALNAME = 'syslog'
25
26
27 1
logger = logging.getLogger(__name__)
28
29
30 1
class ConfigurationError(Exception):
31
    """Indicates a configuration error."""
32
33
34 1
@dataclass(frozen=True)
35
class Config:
36 1
    irc: IrcConfig
37 1
    routes: Set[Route]
38
39
40 1
def load_config(path: Path) -> Config:
41
    """Load configuration from file."""
42 1
    data = rtoml.load(path)
43
44 1
    irc_config = _get_irc_config(data)
45 1
    routes = _get_routes(data, irc_config.channels)
46
47 1
    return Config(irc=irc_config, routes=routes)
48
49
50 1
def _get_irc_config(data: Dict[str, Any]) -> IrcConfig:
51 1
    data_irc = data['irc']
52
53 1
    server = _get_irc_server(data_irc)
54 1
    nickname = data_irc['bot']['nickname']
55 1
    realname = data_irc['bot'].get('realname', DEFAULT_IRC_REALNAME)
56 1
    commands = data_irc.get('commands', [])
57 1
    channels = set(_get_irc_channels(data_irc))
58
59 1
    if not channels:
60 1
        logger.warning('No IRC channels to join have been configured.')
61
62 1
    return IrcConfig(
63
        server=server,
64
        nickname=nickname,
65
        realname=realname,
66
        commands=commands,
67
        channels=channels,
68
    )
69
70
71 1
def _get_irc_server(data_irc: Any) -> Optional[IrcServer]:
72 1
    data_server = data_irc.get('server')
73 1
    if data_server is None:
74 1
        return None
75
76 1
    host = data_server.get('host')
77 1
    if not host:
78 1
        return None
79
80 1
    port = int(data_server.get('port', DEFAULT_IRC_SERVER_PORT))
81 1
    ssl = data_server.get('ssl', False)
82 1
    password = data_server.get('password')
83 1
    rate_limit_str = data_server.get('rate_limit')
84 1
    rate_limit = float(rate_limit_str) if rate_limit_str else None
85
86 1
    return IrcServer(
87
        host=host, port=port, ssl=ssl, password=password, rate_limit=rate_limit
88
    )
89
90
91 1
def _get_irc_channels(data_irc: Any) -> Iterator[IrcChannel]:
92 1
    for channel in data_irc.get('channels', []):
93 1
        name = channel['name']
94 1
        password = channel.get('password')
95 1
        yield IrcChannel(name, password)
96
97
98 1
def _get_routes(
99
    data: Dict[str, Any], irc_channels: Set[IrcChannel]
100
) -> Set[Route]:
101 1
    data_routes = data.get('routes', {})
102 1
    if not data_routes:
103 1
        logger.warning('No routes have been configured.')
104
105 1
    known_irc_channel_names = {c.name for c in irc_channels}
106
107 1
    def iterate() -> Iterator[Route]:
108 1
        for syslog_port_str, irc_channel_names in data_routes.items():
109 1
            for irc_channel_name in irc_channel_names:
110 1
                try:
111 1
                    syslog_port = parse_port(syslog_port_str)
112
                except ValueError:
113
                    raise ConfigurationError(
114
                        f'Invalid syslog port "{syslog_port_str}"'
115
                    )
116
117 1
                if irc_channel_name not in known_irc_channel_names:
118
                    raise ConfigurationError(
119
                        f'Route target IRC channel "{irc_channel_name}" '
120
                        'is not configured to be joined.'
121
                    )
122
123 1
                yield Route(
124
                    syslog_port=syslog_port,
125
                    irc_channel_name=irc_channel_name,
126
                )
127
128
    return set(iterate())
129