#!/usr/bin/env python # -*- coding: utf-8 -*- from re import compile as re_compile from os.path import dirname as directory_name from os.path import abspath as path_absolute from os.path import exists as path_exists from os import mkdir as make_directory from json import loads as json_decode from inspect import stack as get_stack from datetime import datetime from traceback import format_stack as trace_format_stack from traceback import extract_tb as extract_traceback if "ltp_basic_texts" not in globals(): ltp_basic_texts = {} class LibreTranslatePlus: re_path_slashes = re_compile(r'[\\\\\/]+') re_root_fix = re_compile(r'[\\\\\/][^\\\\\/]+[\\\\\/]?$') re_string_variables = re_compile(r'\{([^\{\}]+)\}') re_break_lines = re_compile(r'\r\n|[\r\n]') re_exception_line = re_compile(r'^\s*File "([^"]+)", line ([0-9]+), in (.+)$') def __init__(self, inputs = None): self.__print_types = {} self.__print_format = "[{type}] {yyyy}{mm}{dd} {hh}{ii}{ss} {line}-{file}({method}): {message}" self.__default_texts = None texts = globals()["ltp_basic_texts"] if "ltp_basic_texts" in globals() else None if not isinstance(inputs, dict): inputs = {} inputs["default_texts"] = texts if isinstance(texts, dict): keys = tuple(texts.keys()) if len(keys) and isinstance(texts[keys[0]], dict): self.__default_texts = texts[keys[0]] self._print("info", "ltp_welcome") self._print("info", "ltp_building") self.__started = False self.__closed = False self.__root_paths = ["", LibreTranslatePlus.re_root_fix.sub(r'', directory_name(path_absolute(__file__)))] self.__is_dos = "\\" in self.__root_paths[1] self.__path_slash = "\\" if self.__is_dos else "/" if self.__root_paths[1][-1] != self.__path_slash: self.__root_paths += self.__path_slash self.__root_paths[1] = self.fix_path(self.__root_paths[1]) self.settings = LibreTranslatePlus.Settings(self, inputs) if hasattr(LibreTranslatePlus, "Settings") else None self.__print_format = self.settings.get("print_format") or self.__print_format self.i18n = LibreTranslatePlus.I18N(self, inputs) if hasattr(LibreTranslatePlus, "I18N") else None self.threads = LibreTranslatePlus.Threads(self, inputs) if hasattr(LibreTranslatePlus, "Threads") else None self.terminal = LibreTranslatePlus.Terminal(self, inputs) if hasattr(LibreTranslatePlus, "Terminal") else None self.connections = LibreTranslatePlus.Connections(self, inputs) if hasattr(LibreTranslatePlus, "Connections") else None self.models = LibreTranslatePlus.Models(self, inputs) if hasattr(LibreTranslatePlus, "Models") else None self.__set_common_variables() self._print("ok", "ltp_built") self.settings.get("autostart") and self.start() def start(self, callback = None): self._print("info", "ltp_starting") end = lambda status:callable(callback) and callback(status) if self.__started: self._print("warn", "ltp_already_started") end(False) return False self.__started = True if hasattr(self, "settings"): self.settings.start() self.__print_format = self.settings.get("print_format") or self.__print_format self.__set_common_variables() for key in ("i18n", "threads", "terminal", "connections", "models"): if hasattr(self, key): submodule = getattr(self, key) if hasattr(submodule, "start"): substart = getattr(submodule, "start") callable(substart) and substart() self._print("ok", "ltp_started") end(True) return True def __set_common_variables(self): self.__show_save_file_exception = self.settings.get(("show_save_file_exception_message", "show_exception_message")) self.__show_save_file_error = self.settings.get(("show_save_file_error_message", "show_error_message")) self.__show_save_file_ok = self.settings.get(("show_save_file_ok_message", "show_ok_message")) def close(self, callback = None, *_): self._print("info", "ltp_closing") end = lambda status:callable(callback) and callback(status) if self.__closed: self._print("warn", "ltp_already_closed") end(False) return False self.__closed = True hasattr(self, "threads") and self.threads.close() self._print("ok", "ltp_closed") self._print("info", "ltp_bye") end(True) return True def fix_path(self, path): return LibreTranslatePlus.re_path_slashes.sub(self.__path_slash, path) def load_file(self, path, show_exception = None): error = ( 1 << 1 if path == None else 1 << 2 if not isinstance(path, str) else 1 << 3 if not path else 0) results = None if not error: for root in self.__root_paths: full_path = self.fix_path((root or "") + ("/" if root else "") + path) if path_exists(full_path): try: with open(full_path, "r") as opened: results = opened.read() except: error |= 1 << 0 break if results == None: error |= 1 << 4 if ( show_exception if isinstance(show_exception, bool) else self.settings.get(("load_file_show_exception", "show_exception")) if hasattr(self, "settings") else False ): pass return (results, error) def json_decode(self, inputs, show_exception = None): error = ( 1 << 1 if inputs == None else 1 << 2 if not isinstance(inputs, str) else 1 << 3 if not inputs else 0) results = None if not error: inputs = inputs.strip() error |= ( 1 << 4 if not inputs else 1 << 5 if inputs[0] not in ('{', '[') else 1 << 6 if inputs[-1] not in ('}', ']') else 0) if not error: try: results = json_decode(inputs) except: error |= 1 << 0 if ( show_exception if isinstance(show_exception, bool) else self.settings.get(("json_decode_show_exception", "show_exception")) if hasattr(self, "settings") else False ): pass return (results, error) def load_json(self, inputs, show_exception = None): if isinstance(inputs, str) and inputs and inputs[0] not in ('[', '{'): (inputs, error) = self.load_file(inputs, show_exception) (results, suberror) = self.json_decode(inputs, False) return (results, error | (suberror << 5)) @staticmethod def string_variables(string, variables = None, default = None): variables = [_set for _set in (variables if isinstance(variables, (list, tuple)) else [variables]) if isinstance(variables, dict)] def callback(matches): key = matches.group(1) for _set in variables: if key in _set: return str(_set[key]) return matches.group(0) return LibreTranslatePlus.re_string_variables.sub(callback, str(string)) def add_print_types(self, inputs): if isinstance(inputs, (list, tuple)): for subinputs in inputs: self.add_print_types(inputs) elif isinstance(inputs, dict): for key, alternatives in inputs.items(): main_key = None for subkey in [key] + ( [alternatives] if isinstance(alternatives, str) else alternatives if isinstance(alternatives, (list, tuple)) else []): subkey = subkey.lower() if subkey in self.__print_types: if main_key == None: main_key = subkey continue done = False for print_key, subalternatives in self.__print_types.items(): if subkey in subalternatives: done = True if main_key == None: main_key = print_key break if done: continue if main_key: self.__print_types[main_key] += [subkey] else: self.__print_types[main_key] = [] main_key = subkey elif isinstance(inputs, str): self.add_print_types(self.load_json(inputs)) def _get_print_type(self, key): if not isinstance(key, str) or not key: return self.settings.get(("default_print_type", "print_type")) for subkey, alternatives in self.__print_types.items(): if subkey == key or key in alternatives: return subkey return key def get_action_data(self, i = 1): stack = get_stack()[1 + i] return { "file" : stack.filename, "method" : stack.function, "line" : stack.lineno } def _print(self, _type, message, variables = None, level = 0, default = None): date = datetime.now() text = None _type = self._get_print_type(_type).upper()[0:4] l = len(_type) text = self.i18n.get(message, variables, default) if hasattr(self, "i18n") else LibreTranslatePlus.string_variables( ( "".join(self.__default_texts[message]) if isinstance(self.__default_texts[message], (list, tuple)) else self.__default_texts[message] ) if message in self.__default_texts else ("".join(default) if isinstance(default, (list, tuple)) else default) if default != None else message, variables ) own = { "yyyy" : date.year, "year" : date.year, "y" : date.year % 100, "message" : text, "i18n" : message, **self.get_action_data(1 + level), **(variables if isinstance(variables, dict) else {}), "type" : " "[l and int((l - 1) / 2) + 1 or 0:] + _type + " "[int(l / 2):] } own["yy"] = ("00" + str(own["y"]))[-2:] for key in ("month", "day", "hour", "minute", "second"): own[key[0] if key != "minute" else "i"] = own[key] = getattr(date, key) for key in "ymdhis": own[key + key] = ("00" + str(own[key]))[-2:] print(LibreTranslatePlus.string_variables(self.__print_format, own)) def validate(self, error, messages = tuple(), variables = None, error_message = None, ok_message = None): if error: if isinstance(error_message, str) and error_message: if isinstance(messages, str): messages = (messages,) _list = "" i = 0 l = len(messages) if isinstance(messages, (list, tuple)) else 0 while 1 << i <= error: if (1 << i) & error: _list += "\n " + self.i18n.get(messages[i] if i < l and messages[i] else "error_message_" + str(i), variables) i += 1 self._print("error", error_message, {**variables, "code" : error, "list" : _list}, 1) return False isinstance(ok_message, str) and ok_message and self._print("ok", ok_message, variables, 1) return True def exception(self, exception, message = None, variables = None): lines = extract_traceback(exception.__traceback__).format() line_matches = LibreTranslatePlus.re_exception_line.match(lines[-1]) data = { **(variables if isinstance(variables, dict) else {}), **self.get_action_data(1), "lines" : "", "exception_message" : str(exception), "method" : line_matches.group(3), "line" : line_matches.group(2), "file" : line_matches.group(1) } for block in trace_format_stack()[:-2] + lines: if block: data["lines"] += "\n " + LibreTranslatePlus.re_break_lines.split(block.strip())[0] message and self._print("exception", message, data) def save_file(self, path, data): error = ( (( 1 << 0 if path == None else 1 << 1 if not isinstance(path, str) else 1 << 2 if not path else 0) << 0) | (( 1 << 0 if data == None else 1 << 1 if not isinstance(data, str) else 0) << 3) | 0) << 1 try: directory = path[:2] if self.__is_dos else "" for directory_name in LibreTranslatePlus.re_path_slashes.split(path[2 if self.__is_dos else 1:])[:-1]: directory += self.__path_slash + directory_name if not path_exists(directory): make_directory(directory) with open(self.fix_path(path), 'w') as opened: opened.write(data) except Exception as exception: error |= 1 << 0 self.exception(exception, self.__show_save_file_exception and "ltp_save_file_exception", { "path" : path, "length" : len(data) if isinstance(data, str) else None }) self.validate( error, ( "path_null", "path_not_string", "path_empty", "data_null", "data_not_string" ), { "path" : path, "length" : len(data) if isinstance(data, str) else None }, self.__show_save_file_error and "ltp_save_file_error", self.__show_save_file_ok and "ltp_save_file_ok" ) return error def get_root_directory(self): return "" + self.__root_paths[1] @staticmethod def path_exists(path): return path_exists(path) def _echo(self, attributes, parameters): print(parameters[0] if len(parameters) else "")