554 lines
21 KiB
Python
554 lines
21 KiB
Python
|
#!/usr/bin/env python
|
||
|
# -*- coding: utf-8 -*-
|
||
|
|
||
|
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 re import compile as RECompile
|
||
|
from json import loads as json_decode
|
||
|
from inspect import stack as get_stack
|
||
|
from traceback import format_stack as trace_format_stack
|
||
|
from traceback import extract_tb as extract_traceback
|
||
|
import datetime
|
||
|
|
||
|
if "common" not in globals():
|
||
|
class common:pass
|
||
|
|
||
|
class MemWeb:
|
||
|
|
||
|
__default_settings = common.DefaultData.settings
|
||
|
|
||
|
re_key = RECompile(r'^[a-zA-Z0-9_]+$')
|
||
|
re_string_variables = RECompile(r'\{([^\{\}]+)\}')
|
||
|
re_print_type = RECompile(r' ?[a-z0-9_]+ ?')
|
||
|
re_json = RECompile(r'^(\[(.+|[\r\n]+)*\]|\{(.+|[\r\n]+)*\})$')
|
||
|
re_slashes = RECompile(r'[\/\\\\]+')
|
||
|
re_break_lines = RECompile(r'\r\n|[\r\n]')
|
||
|
re_exception_line = RECompile(r'^\s*File "([^"]+)", line ([0-9]+), in (.+)$')
|
||
|
|
||
|
def __set_basic_data(self):
|
||
|
|
||
|
self.__nulls = self.settings("nulls", None, False, False)
|
||
|
self.__default_value = self.settings("default_value", None, None, True)
|
||
|
self.__language = self.settings(("language", "default_language"))
|
||
|
self.__default_language = self.settings(("default_language", "language"))
|
||
|
self.__default_text = self.settings("default_text")
|
||
|
self.__print_format = self.settings("print_format")
|
||
|
self.__settings_overwrite = self.settings(("settings_overwrite", "overwrite"))
|
||
|
self.__debug_print_types = self.settings("debug_print_types")
|
||
|
self.__show_settings_add_ok = self.settings(("show_settings_add_ok_message", "show_ok_message"))
|
||
|
self.__show_settings_add_error = self.settings(("show_settings_add_error_message", "show_error_message"))
|
||
|
self.__show_settings_add_exception = self.settings(("show_settings_add_exception_message", "show_exception_message"))
|
||
|
|
||
|
for key in ("default_print_types", "print_types"):
|
||
|
self.add_print_types(self.settings(key))
|
||
|
|
||
|
def __init__(self, inputs = None):
|
||
|
|
||
|
self.__sentences = common.DefaultData.i18n
|
||
|
self.__settings = {}
|
||
|
self.__inputs = inputs
|
||
|
self.__print_types = []
|
||
|
self.__root_paths = [path_absolute(directory_name(__file__))]
|
||
|
self.__slash = '/' if '/' in self.__root_paths[0] else '\\'
|
||
|
|
||
|
for i in range(3):
|
||
|
self.__root_paths += [self.__root_paths[0 + i] + "/.."]
|
||
|
self.__root_paths += [""]
|
||
|
|
||
|
self.__root_paths.reverse()
|
||
|
|
||
|
self.__set_basic_data()
|
||
|
|
||
|
self._print("info", "mem_web_building")
|
||
|
|
||
|
self._print("info", "mem_web_settings_loading")
|
||
|
for key in ("default_settings_files", "settings_files"):
|
||
|
self.settings_add(self.settings(key), True)
|
||
|
self.__set_basic_data()
|
||
|
self._print("ok", "mem_web_settings_loaded")
|
||
|
|
||
|
self.__i18n_overwrite = self.settings(("i18n_overwrite", "overwrite"))
|
||
|
self.__show_i18n_add_ok = self.settings(("show_i18n_add_ok_message", "show_ok_message"))
|
||
|
self.__show_i18n_add_error = self.settings(("show_i18n_add_error_message", "show_error_message"))
|
||
|
self.__show_i18n_add_exception = self.settings(("show_i18n_add_exception_message", "show_exception_message"))
|
||
|
|
||
|
self._print("info", "mem_web_i18n_loading")
|
||
|
for key in ("default_i18n_files", "i18n_files"):
|
||
|
self.i18n_add(self.settings(key), True)
|
||
|
self._print("ok", "mem_web_i18n_loaded")
|
||
|
|
||
|
self._print("ok", "mem_web_built")
|
||
|
|
||
|
@classmethod
|
||
|
def get_keys(self, keys):
|
||
|
|
||
|
keys = [key.strip() for key in (keys if isinstance(keys, (list, tuple)) else (keys,)) if isinstance(key, str)]
|
||
|
|
||
|
return [key for i, key in enumerate(keys) if key and keys.index(key) == i and self.re_key.match(key)]
|
||
|
|
||
|
@staticmethod
|
||
|
def get_texts(texts):
|
||
|
|
||
|
if not isinstance(texts, (list, tuple)):
|
||
|
texts = (texts,)
|
||
|
|
||
|
return [text for i, text in enumerate(texts) if isinstance(text, str) and texts.index(text) == i]
|
||
|
|
||
|
def nulls(self, nulls = None):
|
||
|
return nulls if isinstance(nulls, bool) else self.__nulls
|
||
|
|
||
|
def default_value(self, default = None, nulls = None):
|
||
|
return default if default != None or self.nulls(nulls) else self.__default_value
|
||
|
|
||
|
def settings(self, keys, inputs = None, default = None, nulls = None):
|
||
|
|
||
|
keys = self.get_keys(keys)
|
||
|
|
||
|
if len(keys):
|
||
|
|
||
|
nulls = self.nulls(nulls)
|
||
|
|
||
|
for subset in (
|
||
|
inputs if isinstance(inputs, tuple) else
|
||
|
tuple(inputs) if isinstance(inputs, list) else
|
||
|
(inputs,)) + (self.__inputs, self.__settings, self.__default_settings):
|
||
|
if isinstance(subset, dict):
|
||
|
for key in keys:
|
||
|
if key in subset and (nulls or subset[key] != None):
|
||
|
return subset[key]
|
||
|
return self.default_value(default, nulls)
|
||
|
|
||
|
@classmethod
|
||
|
def string_variables(self, string, variables = None, default = None):
|
||
|
|
||
|
variables = [subset for subset in (variables if isinstance(variables, (list, tuple)) else (variables,)) if isinstance(subset, dict)]
|
||
|
|
||
|
def callback(matches):
|
||
|
key = matches.group(1)
|
||
|
for subset in variables:
|
||
|
if key in subset:
|
||
|
return str(subset[key])
|
||
|
return matches.group(0) if default == None else str(default)
|
||
|
|
||
|
return self.re_string_variables.sub(callback, string)
|
||
|
|
||
|
def default_text(self, default = None):
|
||
|
return default if isinstance(default, str) else self.__default_text
|
||
|
|
||
|
def __i18n_get(self, texts, default):
|
||
|
|
||
|
texts = self.get_texts(texts)
|
||
|
|
||
|
if len(texts):
|
||
|
|
||
|
languages = (self.__language, self.__default_language) + tuple(self.__sentences.keys())
|
||
|
|
||
|
for i, language in enumerate(languages):
|
||
|
if language and isinstance(language, str) and language in self.__sentences and languages.index(language) == i:
|
||
|
for key in texts:
|
||
|
if key in self.__sentences[language]:
|
||
|
return self.__sentences[language][key]
|
||
|
return texts[0]
|
||
|
return self.default_text(default)
|
||
|
|
||
|
def i18n(self, texts, variables = None, default = None):
|
||
|
|
||
|
text = self.__i18n_get(texts, default)
|
||
|
|
||
|
return self.string_variables("".join(text) if isinstance(text, (list, tuple)) else str(text), variables, default)
|
||
|
|
||
|
def fix_path(self, path):
|
||
|
|
||
|
error = (
|
||
|
1 << 0 if path == None else
|
||
|
1 << 1 if not isinstance(path, str) else
|
||
|
0) << 1
|
||
|
|
||
|
if not error:
|
||
|
try:
|
||
|
path = self.re_slashes.sub(self.__slash, path)
|
||
|
except Exception as exception:
|
||
|
error |= 1 << 0
|
||
|
self.exception(exception, "mem_web_fix_path_exception", {
|
||
|
"path" : path
|
||
|
})
|
||
|
|
||
|
return (path, error)
|
||
|
|
||
|
def get_absolute_path(self, path):
|
||
|
|
||
|
error = (
|
||
|
1 << 0 if path == None else
|
||
|
1 << 1 if not isinstance(path, str) else
|
||
|
0) << 1
|
||
|
absolute_path = None
|
||
|
|
||
|
if not error:
|
||
|
for root in self.__root_paths:
|
||
|
current_path = self.fix_path(root + self.__slash + path)[0]
|
||
|
if path_exists(current_path):
|
||
|
absolute_path = current_path
|
||
|
break
|
||
|
if absolute_path == None:
|
||
|
error |= 1 << 3
|
||
|
|
||
|
return (absolute_path, error)
|
||
|
|
||
|
def load_file(self, path, mode = "r"):
|
||
|
|
||
|
absolute_path, error = self.get_absolute_path(path)
|
||
|
error |= (
|
||
|
1 << 0 if mode == None else
|
||
|
1 << 1 if not isinstance(mode, str) else
|
||
|
1 << 2 if not mode else
|
||
|
1 << 3 if mode not in ("r", "rb") else
|
||
|
0) << 4
|
||
|
response = None
|
||
|
|
||
|
if not error:
|
||
|
try:
|
||
|
with open(absolute_path, mode) as data:
|
||
|
response = data.read()
|
||
|
except Exception as exception:
|
||
|
error |= 1 << 0
|
||
|
self.exception(exception, "mem_web_load_file_exception", {
|
||
|
"path" : path,
|
||
|
"mode" : mode
|
||
|
})
|
||
|
|
||
|
return (response, error)
|
||
|
|
||
|
def add_print_types(self, inputs, show_errors = None):
|
||
|
if isinstance(inputs, (list, tuple)):
|
||
|
|
||
|
has_show_errors = isinstance(show_errors, bool)
|
||
|
|
||
|
for subset in inputs:
|
||
|
|
||
|
error = (
|
||
|
1 << 0 if subset == None else
|
||
|
1 << 1 if not isinstance(subset, (list, tuple)) else
|
||
|
1 << 2 if not len(subset) else
|
||
|
0) << 1
|
||
|
|
||
|
if not error:
|
||
|
|
||
|
i = None
|
||
|
|
||
|
for _type in subset:
|
||
|
|
||
|
suberror = (
|
||
|
1 << 0 if _type == None else
|
||
|
1 << 1 if not isinstance(_type, str) else
|
||
|
1 << 2 if not _type else
|
||
|
1 << 3 if not self.re_print_type.match(_type) else
|
||
|
0) << 1
|
||
|
|
||
|
if suberror:
|
||
|
if not error & 1 << 4:
|
||
|
error |= 1 << 4
|
||
|
continue
|
||
|
|
||
|
if i == None:
|
||
|
for j, print_types in enumerate(self.__print_types):
|
||
|
if _type in print_types:
|
||
|
i = j
|
||
|
break
|
||
|
if i == None:
|
||
|
i = len(self.__print_types)
|
||
|
self.__print_types += [[_type]]
|
||
|
continue
|
||
|
|
||
|
self.__print_types[i] += [_type]
|
||
|
|
||
|
elif isinstance(inputs, str):
|
||
|
if self.re_json.match(inputs.strip()):
|
||
|
try:
|
||
|
self.add_print_types(json_decode(inputs), show_errors)
|
||
|
except Exception as exception:
|
||
|
self.exception(exception, "mem_web_add_print_types_exception")
|
||
|
else:
|
||
|
|
||
|
json = self.load_file(inputs)[0]
|
||
|
|
||
|
if json and self.re_json.match(json.strip()):
|
||
|
try:
|
||
|
self.add_print_types(json_decode(json), show_errors)
|
||
|
except Exception as exception:
|
||
|
self.exception(exception, "mem_web_add_print_types_exception")
|
||
|
|
||
|
def get_print_type(self, _type):
|
||
|
|
||
|
error = (
|
||
|
1 << 0 if _type == None else
|
||
|
1 << 1 if not isinstance(_type, str) else
|
||
|
1 << 2 if not _type else
|
||
|
0) << 1
|
||
|
|
||
|
if not error:
|
||
|
_type = _type.lower()
|
||
|
for print_types in self.__print_types:
|
||
|
if _type in print_types:
|
||
|
return print_types[0]
|
||
|
return self.__print_types[0][0]
|
||
|
|
||
|
@staticmethod
|
||
|
def get_action_data(i = 1):
|
||
|
|
||
|
stack = get_stack()[1 + i]
|
||
|
|
||
|
return {
|
||
|
"file" : stack.filename,
|
||
|
"method" : stack.function,
|
||
|
"line" : stack.lineno
|
||
|
}
|
||
|
|
||
|
def _print(self, _type, message, inputs = None, i = 1):
|
||
|
|
||
|
basic_type = self.get_print_type(_type)
|
||
|
|
||
|
if basic_type.strip() not in self.__debug_print_types:
|
||
|
return
|
||
|
|
||
|
own = {
|
||
|
"raw_type" : _type,
|
||
|
"basic_type" : self.get_print_type(_type),
|
||
|
"i18n" : message,
|
||
|
**self.get_action_data(i),
|
||
|
**{key : value for subinputs in (inputs if isinstance(inputs, (list, tuple)) else (inputs,)) for key, value in (subinputs if isinstance(subinputs, dict) else {}).items()}
|
||
|
}
|
||
|
date = datetime.datetime.now()
|
||
|
|
||
|
own["type"] = own["basic_type"].upper()
|
||
|
|
||
|
for key in ("year", "month", "day", "hour", "minute", "second"):
|
||
|
|
||
|
k = "i" if key == "minute" else key[0]
|
||
|
|
||
|
own[key] = getattr(date, key)
|
||
|
own[k] = own[key] % 100
|
||
|
own[k + k] = ("00" + str(own[k]))[-2:]
|
||
|
|
||
|
own["yyyy"] = own["year"]
|
||
|
|
||
|
own["message"] = self.i18n(message, own)
|
||
|
|
||
|
print(self.string_variables(self.__print_format, own))
|
||
|
|
||
|
@staticmethod
|
||
|
def is_key(key):
|
||
|
return key and isinstance(key, str) and key[0:7] != "MemWeb_" and key[-4:] != "_end" and key[-6:] != "_start"
|
||
|
|
||
|
def settings_add(self, inputs, overwrite = None, show_errors = None):
|
||
|
if isinstance(inputs, (list, tuple)):
|
||
|
for subinputs in inputs:
|
||
|
self.settings_add(subinputs, overwrite)
|
||
|
elif isinstance(inputs, dict):
|
||
|
|
||
|
has_show_errors = isinstance(show_errors, bool)
|
||
|
|
||
|
if not isinstance(overwrite, bool):
|
||
|
overwrite = self.__settings_overwrite
|
||
|
|
||
|
for key, value in inputs.items():
|
||
|
if not self.is_key(key):
|
||
|
continue
|
||
|
|
||
|
error = (
|
||
|
1 << 0 if key == None else
|
||
|
1 << 1 if not isinstance(key, str) else
|
||
|
1 << 2 if not key else
|
||
|
1 << 3 if not self.re_key.match(key) else
|
||
|
1 << 4 if not overwrite and key in self.__settings else
|
||
|
0) << 1
|
||
|
|
||
|
if self.validate(
|
||
|
error,
|
||
|
(
|
||
|
"exception",
|
||
|
"key_null",
|
||
|
"key_not_string",
|
||
|
"key_empty",
|
||
|
"key_not_key",
|
||
|
"key_exists"
|
||
|
),
|
||
|
{
|
||
|
"key" : key,
|
||
|
"value" : value
|
||
|
},
|
||
|
(show_errors if has_show_errors else self.__show_settings_add_error) and "mem_web_settings_add_error",
|
||
|
(show_errors if has_show_errors else True) and self.__show_settings_add_ok and "mem_web_settings_add_ok"
|
||
|
):
|
||
|
self.__settings[key] = value
|
||
|
|
||
|
elif isinstance(inputs, str):
|
||
|
if self.re_json.match(inputs.strip()):
|
||
|
try:
|
||
|
self.settings_add(json_decode(inputs), overwrite, show_errors)
|
||
|
except Exception as exception:
|
||
|
self.__show_settings_add_exception and self.exception(exception, "mem_web_settings_add_exception")
|
||
|
else:
|
||
|
|
||
|
json, error = self.load_file(inputs)
|
||
|
|
||
|
if not error and json and self.re_json.match(json.strip()):
|
||
|
try:
|
||
|
self.settings_add(json_decode(json), overwrite, show_errors)
|
||
|
except Exception as exception:
|
||
|
self.__show_settings_add_exception and self.exception(exception, "mem_web_settings_add_exception")
|
||
|
|
||
|
def i18n_add(self, inputs, overwrite = None, show_errors = None):
|
||
|
if isinstance(inputs, (list, tuple)):
|
||
|
for subinputs in inputs:
|
||
|
self.i18n_add(subinputs, overwrite)
|
||
|
elif isinstance(inputs, dict):
|
||
|
|
||
|
has_show_errors = isinstance(show_errors, bool)
|
||
|
|
||
|
if not isinstance(overwrite, bool):
|
||
|
overwrite = self.__i18n_overwrite
|
||
|
|
||
|
for language, sentences in inputs.items():
|
||
|
|
||
|
error = (
|
||
|
((
|
||
|
1 << 0 if language == None else
|
||
|
1 << 1 if not isinstance(language, str) else
|
||
|
1 << 2 if not language else
|
||
|
1 << 3 if not self.re_key.match(language) else
|
||
|
0) << 0) |
|
||
|
((
|
||
|
1 << 0 if sentences == None else
|
||
|
1 << 1 if not isinstance(sentences, dict) else
|
||
|
1 << 2 if not len(sentences) else
|
||
|
0) << 4) |
|
||
|
0) << 1
|
||
|
|
||
|
if not error:
|
||
|
if language not in self.__sentences:
|
||
|
self.__sentences[language] = {}
|
||
|
for key, sentence in sentences.items():
|
||
|
if not self.is_key(key):
|
||
|
continue
|
||
|
|
||
|
suberror = (
|
||
|
((
|
||
|
1 << 0 if key == None else
|
||
|
1 << 1 if not isinstance(key, str) else
|
||
|
1 << 2 if not key else
|
||
|
1 << 3 if not self.re_key.match(key) else
|
||
|
1 << 5 if not overwrite and key in self.__sentences[language] else
|
||
|
0) << 0) |
|
||
|
((
|
||
|
1 << 0 if sentence == None else
|
||
|
1 << 1 if not isinstance(sentence, (str, list, tuple)) else
|
||
|
0) << 5) |
|
||
|
0) << 1
|
||
|
|
||
|
if self.validate(
|
||
|
suberror,
|
||
|
(
|
||
|
"exception",
|
||
|
"key_null",
|
||
|
"key_not_string",
|
||
|
"key_empty",
|
||
|
"key_not_key",
|
||
|
"key_exists",
|
||
|
"sentence_null",
|
||
|
"sentence_not_string"
|
||
|
),
|
||
|
{
|
||
|
"language" : language,
|
||
|
"key" : key,
|
||
|
"sentence" : sentence
|
||
|
},
|
||
|
(show_errors if has_show_errors else self.__show_i18n_add_error) and "mem_web_i18n_add_key_error",
|
||
|
(show_errors if has_show_errors else True) and self.__show_i18n_add_ok and "mem_web_i18n_add_key_ok"
|
||
|
):
|
||
|
self.__sentences[language][key] = sentence
|
||
|
elif error & 1 << 8:
|
||
|
error |= 1 << 8
|
||
|
|
||
|
self.validate(
|
||
|
error,
|
||
|
(
|
||
|
"exception",
|
||
|
"language_null",
|
||
|
"language_not_string",
|
||
|
"language_empty",
|
||
|
"language_not_key",
|
||
|
"sentences_null",
|
||
|
"sentences_not_dictionary",
|
||
|
"sentences_empty",
|
||
|
"sentences_with_errors"
|
||
|
),
|
||
|
{
|
||
|
"language" : language
|
||
|
},
|
||
|
(show_errors if has_show_errors else self.__show_i18n_add_error) and "mem_web_i18n_add_error",
|
||
|
(show_errors if has_show_errors else True) and self.__show_i18n_add_ok and "mem_web_i18n_add_ok"
|
||
|
)
|
||
|
|
||
|
elif isinstance(inputs, str):
|
||
|
if self.re_json.match(inputs.strip()):
|
||
|
try:
|
||
|
self.i18n_add(json_decode(inputs), overwrite, show_errors)
|
||
|
except Exception as exception:
|
||
|
self.__show_i18n_add_exception and self.exception(exception, "mem_web_i18n_add_json_exception")
|
||
|
else:
|
||
|
|
||
|
json, error = self.load_file(inputs)
|
||
|
|
||
|
if not error and json and self.re_json.match(json.strip()):
|
||
|
try:
|
||
|
self.i18n_add(json_decode(json), overwrite, show_errors)
|
||
|
except Exception as exception:
|
||
|
self.__show_i18n_add_exception and self.exception(exception, "mem_web_i18n_add_file_exception")
|
||
|
|
||
|
def validate(self, error, messages = [], variables = None, error_message = None, ok_message = None):
|
||
|
|
||
|
if error:
|
||
|
if error_message:
|
||
|
|
||
|
own = {
|
||
|
"code" : error,
|
||
|
"messages" : "",
|
||
|
**{key : value for subset in (variables if isinstance(variables, (list, tuple)) else (variables,)) for key, value in (subset if isinstance(subset, dict) else {}).items()}
|
||
|
}
|
||
|
i = 0
|
||
|
l = len(messages)
|
||
|
|
||
|
while 1 << i <= error:
|
||
|
if 1 << i & error:
|
||
|
own["messages"] += "\n [" + str(i) + "] " + self.i18n(messages[i] if i < l and messages[i] else "error_message_" + str(i), own)
|
||
|
|
||
|
self._print("error", error_message, own, 2)
|
||
|
|
||
|
return False
|
||
|
|
||
|
ok_message and self._print("ok", ok_message, variables)
|
||
|
|
||
|
return True
|
||
|
|
||
|
def exception(self, exception, message = None, variables = None, i = 1):
|
||
|
if not message:
|
||
|
return
|
||
|
|
||
|
lines = extract_traceback(exception.__traceback__).format()
|
||
|
line_matches = self.re_exception_line.match(lines[-1])
|
||
|
data = {
|
||
|
**{key : value for subset in (variables if isinstance(variables, (list, tuple)) else (variables,)) for key, value in (subset if isinstance(subset, dict) else {}).items()},
|
||
|
**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 " + self.re_break_lines.split(block.strip())[0]
|
||
|
|
||
|
self._print("exception", message, data, None, i + 1)
|