418 lines
15 KiB
Python
418 lines
15 KiB
Python
|
#!/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 "")
|