Files
2023-12-16 23:18:55 +03:00

250 lines
7.5 KiB
Python

import glob
import json
import os
import re
import sys
from datetime import datetime
from .Paths import Paths
class SingletonLogger(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(SingletonLogger, cls).__call__(*args, **kwargs)
return cls._instances[cls]
class Logger(metaclass=SingletonLogger):
file = Paths.LOG_PATH
def __init__(self):
self.formatting = _Formatting()
self.levels = {
"FATAL": "r",
"ERROR": "r",
"WARN": "y",
"INFO": "b",
"EMIT": "g",
"DEBUG": "m",
"TRACE": "c",
"LINE": "n"
}
self.__length = max(len(key) for key in self.levels)
info = self.__get_info()
if info["branch"] == "main":
info["branch"] = "v.2"
self.debug(f"{info['branch']} : {info['hex']}")
def __log(self, text, level, display):
text = self.formatting.format(text)
if display:
if level == "LINE":
print()
else:
prefix = self.formatting.format(f"!![f{self.levels.get(level)}b;" + re.escape("[Allor]") + ":" + "] ")
postfix = self.formatting.format(text, 9)
print(f"{prefix}{postfix}")
if Logger.file:
directory = Logger.file.parent
if not directory.exists():
directory.mkdir(parents=True)
if not Logger.file.exists():
log_files = list(glob.glob(str(directory / "log_*.log")))
if len(log_files) > 5:
log_files.sort(key=os.path.getmtime)
os.remove(log_files[0])
Logger.file.touch()
with open(Logger.file, "a") as f:
if level == "LINE":
f.write("\n")
else:
prefix = f"[{datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S')}][{level:<{self.__length}}]: "
postfix = self.formatting.format(text, len(prefix))
postfix = self.formatting.reset(postfix)
f.write(f"{prefix}{postfix}\n")
def fatal(self, text, display=True):
self.__log(text, "FATAL", display)
def error(self, text, display=True):
self.__log(text, "ERROR", display)
def warn(self, text, display=True):
self.__log(text, "WARN", display)
def info(self, text, display=True):
self.__log(text, "INFO", display)
def emit(self, text, display=True):
self.__log(text, "EMIT", display)
def debug(self, text, display=True):
self.__log(text, "DEBUG", display)
def trace(self, text, display=True):
self.__log(text, "TRACE", display)
def line(self, count=1, display=True):
self.__log("\n" * count, "LINE", display)
def warning_unstable_branch(self, branch_name="main"):
warn_messages = (
f"Attention! You are currently using an unstable !![fyb;{branch_name}] update branch. \n"
f"This branch is intended for the development of !![fmb;Allor v.2]. \n\n"
f"Please be aware that changes made in !![fmb;Allor v.2] may disrupt your current workflow. \n"
"Nodes may be renamed, parameters within them may be altered or even removed. \n\n"
"If backward compatibility of your workflow is important to you, consider this. \n"
f"You can change the !![fcb;branch_name_param] parameter to !![fbb;v1] in your !![fgb;config_json]. \n\n"
"If you are prepared for potential changes, you can modify your configuration file. \n"
f"To accept, switch the !![fcb;confirm_unstable_param] parameter in your !![fgb;config_json] to !![fbb;true]. \n"
"This will result in this warning no longer appearing."
)
emit_messages = (
"We appreciate your support and understanding during this transition period. \n"
f"Thank you and welcome to !![fmb;Allor v.2]."
)
self.warn(warn_messages)
self.line()
self.emit(emit_messages)
self.line()
def __get_info(self):
with open(Paths.INFO_PATH, "r") as f:
return json.load(f)
class _Formatting:
COLOR = {
"d": 0, # DARK
"r": 1, # RED
"g": 2, # GREEN
"y": 3, # YELLOW
"b": 4, # BLUE
"m": 5, # MAGENTA
"c": 6, # CYAN
"w": 7, # WHITE
"n": 8 # NORMAL
}
INTENSITY = {
"n": 30, # NORMAL
"b": 90 # BRIGHT
}
ATTRIBUTE = {
"n": 0, # NORMAL
"b": 1, # BOLD
"f": 2, # FAINT
"i": 3, # ITALIC
"u": 4, # UNDERLINE
"l": 5, # BLINKING
"a": 6, # FAST_BLINKING
"r": 7, # REVERSE
"h": 8, # HIDE
"s": 9 # STRIKETHROUGH
}
def __init__(self):
self.__ansi = self.__formatting_support()
# noinspection PyPep8Naming
def __formatting_support(self):
platform = sys.platform
if platform == "win32":
import ctypes
STD_OUTPUT_HANDLE = -11
ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004
kernel32 = ctypes.WinDLL("kernel32")
hStdOut = kernel32.GetStdHandle(STD_OUTPUT_HANDLE)
mode = ctypes.c_ulong()
if not kernel32.GetConsoleMode(hStdOut, ctypes.byref(mode)):
return False
if not mode.value & ENABLE_VIRTUAL_TERMINAL_PROCESSING:
return False
if platform in ("linux", "darwin"):
term = os.getenv("TERM")
if term not in ("xterm", "xterm-256color", "vt100", "ansi", "linux"):
return False
return True
def format(self, text, intend=0):
if self.__ansi:
text = self.__interpolation(text)
else:
text = self.__extraction(text)
if intend > 0:
text = "\n".join(line if i == 0 or not line else " " * intend + line for i, line in enumerate(text.split("\n")))
return text
def reset(self, text):
return re.compile(r"\033\[[0-?]*[ -/]*[@-~]").sub("", text)
def __interpolation(self, input_string):
pattern = re.compile(r"!!\[(.*?)(?<!\\)]")
def replace_func(match):
params = match.group(1).split(';')
fg, bg, attr, text = "fnn", "bnn", "an", None
for param in params:
if (len(param) == 3
and param[0] in ["f", "b"]
and param[1] in self.COLOR
and param[2] in self.INTENSITY):
if param[0] == "f":
fg = param
else:
bg = param
elif len(param) == 2 and param[0] == "a" and param[1] in self.ATTRIBUTE:
attr = param
else:
text = param
text = text.replace("\\", "")
return (f"\033["
f"{self.COLOR[fg[1]] + self.INTENSITY[fg[2]]};"
f"{self.COLOR[bg[1]] + self.INTENSITY[bg[2]] + 10};"
f"{self.ATTRIBUTE[attr[1]]}m{text}\033[0m")
return pattern.sub(replace_func, input_string)
def __extraction(self, input_string):
pattern = re.compile(r"!!\[(.*?)(?<!\\)]")
def replace_func(match):
return match.group(1).split(":")[-1]
return pattern.sub(replace_func, input_string)