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"!!\[(.*?)(?