from __future__ import annotations import logging import logging.config from configparser import ConfigParser from typing import Any, Dict, Mapping, Optional, Union class Config(ConfigParser): DEFAULTS: Mapping[str, Mapping[str, Union[str, int]]] = { "sampy": { "host": "0.0.0.0", "port": 7777, "hostname": "sam.py", "password": "", "rcon_password": "changeme", "max_players": 50, "mode": "", "language": "English", }, "sampy.rules": { "weburl": "https://git.osufx.com/Sunpy/sampy", }, "logging.loggers": { "keys": "root", }, "logging.handlers": { "keys": "console", }, "logging.formatters": { "keys": "simple", }, "logging.logger_root": { "level": "INFO", "handlers": "console", }, "logging.handler_console": { "class": "StreamHandler", "formatter": "simple", "args": "(sys.stdout,)", }, "logging.formatter_simple": { "format": "%(levelname)s - %(message)s", "datefmt": "%Y-%m-%d %H:%M:%S", }, } def __init__( self, *filenames, dictionary: Mapping[str, Mapping[str, Union[str, int]]] = {}, logging_level: Optional[int] = None, ): super().__init__(interpolation=None) if logging_level is not None: logging.root.setLevel(logging_level) self.read_dict(self.DEFAULTS) self.read_dict(dictionary) found = self.read(filenames, encoding="utf-8-sig") missing = set(filenames) - set(found) if len(missing): logging.warn("Config files not found: %s" % missing) logging_config = self.get_logging_config() if logging_config: logging.config.fileConfig(logging_config) if logging_level is not None: logging.root.setLevel(logging_level) logging.debug("Logging module has been configured") else: logging.warn("Logging module was not configured") def get_logging_config(self) -> ConfigParser: config = ConfigParser(interpolation=None) for section in self.sections(): if not section.startswith("logging."): continue config[section.replace("logging.", "")] = self[section] return config @property def host(self) -> str: return self.get("sampy", "host") @property def port(self) -> int: return self.getint("sampy", "port") @property def hostname(self) -> str: return self.get("sampy", "hostname") @property def password(self) -> str: return self.get("sampy", "password") @property def rcon_password(self) -> str: return self.get("sampy", "rcon_password") @property def max_players(self) -> int: return self.getint("sampy", "max_players") @property def mode(self) -> str: return self.get("sampy", "mode") @property def language(self) -> str: return self.get("sampy", "language") @property def rules(self) -> Dict[str, str]: return self["sampy.rules"] class LogRecordProxy: def __init__(self, record: logging.LogRecord): self._record = record def __getattribute__(self, name: str) -> Any: attr = { k: v for k, v in object.__getattribute__(self, "__dict__").items() if k != "_record" } if name in attr: return attr[name] elif name == "__dict__": # Combine dicts return {**object.__getattribute__(self, "_record").__dict__, **attr} return object.__getattribute__(self, "_record").__getattribute__(name) class ColorFormatter(logging.Formatter): COLORS: Dict[str, str] = { "0": "30", # Black "1": "34", # Blue "2": "32", # Green "3": "36", # Cyan "4": "31", # Red, "5": "35", # Purple/Magenta "6": "33", # Yellow/Gold "7": "37", # White/Light Gray "8": "30;1", # Dark Gray "9": "34;1", # Light Blue "a": "32;1", # Light Green "b": "36;1", # Light Cyan "c": "31;1", # Light Red "d": "35;1", # Light Purple/Magenta "e": "33;1", # Yellow "f": "37;1", # White "r": "0", # Reset "l": "1", # Bold "n": "4", # Underline } LEVEL_COLOR = { logging.CRITICAL: "31", logging.ERROR: "31", logging.WARNING: "33", logging.INFO: "32", logging.DEBUG: "35", logging.NOTSET: "37", } def format(self, record: logging.LogRecord) -> str: record = LogRecordProxy(record) level_color = ColorFormatter.LEVEL_COLOR.get(record.levelno, None) if level_color is not None: record.levelname = "\x1b[%sm%s\x1b[0m" % (level_color, record.levelname) message = super().format(record) for k, v in ColorFormatter.COLORS.items(): message = message.replace("ยง%s" % k, "\x1b[%sm" % v) return message