2023-03-15 07:12:47 +01:00
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
import logging
|
2023-03-19 01:43:14 +01:00
|
|
|
import logging.config
|
2023-03-15 07:12:47 +01:00
|
|
|
from configparser import ConfigParser
|
2023-03-19 01:43:14 +01:00
|
|
|
from typing import Any, Dict, Mapping, Optional, Union
|
2023-03-15 07:12:47 +01:00
|
|
|
|
|
|
|
|
|
|
|
class Config(ConfigParser):
|
|
|
|
DEFAULTS: Mapping[str, Mapping[str, Union[str, int]]] = {
|
|
|
|
"sampy": {
|
|
|
|
"host": "0.0.0.0",
|
|
|
|
"port": 7777,
|
2023-03-19 05:14:30 +01:00
|
|
|
"hostname": "sam.py",
|
|
|
|
"password": "",
|
|
|
|
"rcon_password": "changeme",
|
2023-03-15 07:12:47 +01:00
|
|
|
"max_players": 50,
|
2023-03-19 05:14:30 +01:00
|
|
|
"mode": "",
|
|
|
|
"language": "English",
|
2023-03-15 07:12:47 +01:00
|
|
|
},
|
|
|
|
"sampy.rules": {
|
|
|
|
"weburl": "https://git.osufx.com/Sunpy/sampy",
|
|
|
|
},
|
2023-03-19 01:43:14 +01:00
|
|
|
"logging.loggers": {
|
|
|
|
"keys": "root",
|
|
|
|
},
|
|
|
|
"logging.handlers": {
|
|
|
|
"keys": "console",
|
|
|
|
},
|
|
|
|
"logging.formatters": {
|
|
|
|
"keys": "simple",
|
|
|
|
},
|
|
|
|
"logging.logger_root": {
|
2023-03-15 07:12:47 +01:00
|
|
|
"level": "INFO",
|
2023-03-19 01:43:14 +01:00
|
|
|
"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",
|
2023-03-15 07:12:47 +01:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2023-03-19 01:43:14 +01:00
|
|
|
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)
|
2023-03-15 07:12:47 +01:00
|
|
|
|
|
|
|
self.read_dict(self.DEFAULTS)
|
2023-03-19 01:43:14 +01:00
|
|
|
self.read_dict(dictionary)
|
2023-03-15 07:12:47 +01:00
|
|
|
|
2023-03-19 01:43:14 +01:00
|
|
|
found = self.read(filenames, encoding="utf-8-sig")
|
2023-03-15 07:12:47 +01:00
|
|
|
missing = set(filenames) - set(found)
|
|
|
|
|
|
|
|
if len(missing):
|
|
|
|
logging.warn("Config files not found: %s" % missing)
|
|
|
|
|
2023-03-19 01:43:14 +01:00
|
|
|
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
|
2023-03-15 07:12:47 +01:00
|
|
|
|
|
|
|
@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"]
|
2023-03-19 01:43:14 +01:00
|
|
|
|
|
|
|
|
|
|
|
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
|