AnP/Python/Utils/Common.py

274 lines
7.8 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from typing import Any, Optional, Sequence, Self
from re import Match as REMatch
from os.path import abspath as absolute_path, dirname as directory_name, exists as path_exists, isfile as is_file
from json import loads as json_decode
from io import FileIO
from mimetypes import guess_type as get_mime_by_extension
from inspect import FrameInfo, stack as get_stack
from Utils.Checks import Check
from Utils.Patterns import RE
ROOT_PATH:str = absolute_path(directory_name(__file__))
SLASH:str = "/" if "/" in ROOT_PATH else "\\"
class Common:
ROOT_PATH:str = ROOT_PATH
SLASH:str = SLASH
ROOTS_PATH:list[str] = [""] + [ROOT_PATH + (SLASH + "..") * i for i in range(4)]
SPECIAL_REGULAR_EXPRESSION_CHARACTERS:dict[str, str] = {
"\r" : "r",
"\n" : "n",
"\t" : "t"
}
@classmethod
def get_keys(cls:type[Self], *items:list[Any|None]) -> list[str]:
keys:list[str] = []
item:Any|None
for item in items:
if Check.is_key(item):
item not in keys and keys.append(item)
elif Check.is_array(item):
key:str
for key in cls.get_keys(*item):
key not in keys and keys.append(key)
return keys
@classmethod
def get_texts(cls:type[Self], *items:list[Any|None]) -> list[str]:
texts:list[str] = []
item:Any|None
for item in items:
if Check.is_string(item):
texts.append(item)
elif Check.is_array(item):
text:str
for text in cls.get_texts(*item):
texts.append(text)
return texts
@classmethod
def get_dictionaries(cls:type[Self], *items:list[Any|None]) -> list[dict[str, Any|None]]:
dictionaries:list[dict[str, Any|None]] = []
item:Any|None
for item in items:
if Check.is_dictionary(item):
dictionaries.append(item)
elif Check.is_array(item):
dictionary:dict[str, Any|None]
for dictionary in cls.get_dictionaries(*item):
dictionaries.append(dictionary)
return dictionaries
@classmethod
def get_dictionary(cls:type[Self], inputs:Any|None, overwrite:bool = False) -> dict[str, Any|None]:
dictionary:dict[str, Any|None] = {}
if Check.is_dictionary(inputs):
key:str
value:Any|None
for key, value in inputs.items():
if overwrite or key not in dictionary:
dictionary[key] = value
elif Check.is_array(inputs):
subinputs:dict[str, Any|None]
for subinputs in inputs:
key:str
value:Any|None
for key, value in cls.get_dictionaries(subinputs).items():
if overwrite or key not in dictionary:
dictionary[key] = value
return dictionary
@classmethod
def get_value(cls:type[Self],
keys:str|Sequence[str],
inputs:dict[str, Any|None]|Sequence[Any|None],
default:Optional[Any|None] = None
) -> Any|None:
if len(keys := cls.get_keys(keys)):
subinputs:dict[str, Any|None]
for subinputs in cls.get_dictionaries(inputs):
key:str
for key in keys:
if key in subinputs:
return subinputs[key]
return default
@classmethod
def string_variables(cls:type[Self],
string:str,
inputs:dict[str, Any|None]|Sequence[Any|None],
default:Optional[str] = None
) -> str:
inputs = cls.get_dictionary(inputs)
def replace(matches:REMatch) -> str:
key:str = matches.group(1)
return str(
inputs[key] if key in inputs else
default if default is not None else
matches.group(0))
return RE.STRING_VARIABLES.sub(replace, str(string))
@classmethod
def fix_path(cls:type[Self], path:str) -> str:
return RE.SLASHES.sub(cls.SLASH, path)
@classmethod
def get_absolute_path(cls:type[Self], path:str) -> str|None:
root:str
for root in cls.ROOTS_PATH:
absolute_path:str = cls.fix_path((root + cls.SLASH if root else "") + path)
if absolute_path[0] == "\\":
absolute_path = "\\" + absolute_path
if path_exists(absolute_path):
return absolute_path
return None
@classmethod
def load_file(cls:type[Self], path:str, mode:str = "r") -> str|bytes|None:
try:
file:FileIO
if mode == "r":
for format in (
"utf8", "cp1252", "ascii", "cp850", "latin1", "iso8859_1",
"utf16", "utf16le", "utf16be", "utf8sig", "iso8859_15"
):
try:
with open(cls.get_absolute_path(path), mode, encoding = format) as file:
return file.read()
except Exception as exception:
pass
elif mode == "rb":
with open(cls.get_absolute_path(path), mode) as file:
return file.read()
except Exception as exception:
pass
return None
@classmethod
def load_json(cls:type[Self],
data:str|dict[str, Any|None]|Sequence[Any|None],
only_dictionaries:bool = True
) -> list[dict[str, Any|None]|Sequence[Any|None]]:
json:list[dict[str, Any|None]|Sequence[Any|None]] = []
if isinstance(data, dict):
json.append(data)
elif isinstance(data, str):
subdata:dict[str, Any|None]|Sequence[Any|None]|None
try:
if subdata := json_decode(data):
json.extend(cls.load_json(subdata, only_dictionaries))
return
except Exception as exception:
pass
try:
json.extend(cls.load_json(json_decode(cls.load_file(data, "r")), only_dictionaries))
except Exception as exception:
pass
elif isinstance(data, (list, tuple)):
if only_dictionaries:
item:Any|None
for item in data:
json.extend(cls.load_json(item, only_dictionaries))
else:
json.append(data)
return json
@staticmethod
def get_action_data(i:int = 0) -> dict[str, str|int]:
stack:FrameInfo = get_stack()[i]
return {
"file" : stack.filename,
"method" : stack.function,
"line" : stack.lineno
}
@classmethod
def to_regular_expression(cls:type[Self], string:str) -> str:
def callback(matches:REMatch) -> str:
character:str = matches.group(0)
return "\\" + (
cls.SPECIAL_REGULAR_EXPRESSION_CHARACTERS[character] if character in cls.SPECIAL_REGULAR_EXPRESSION_CHARACTERS else
character)
return RE.TO_REGULAR_EXPRESSION.sub(callback, string)
@staticmethod
def get_mime_from_path(path:str) -> str|None:
return get_mime_by_extension(path)[0]
@classmethod
def is_mark_key(cls:type[Self], key:str, marks:str|Sequence[str] = "AnP") -> bool:
mark:str
for mark in cls.get_keys(marks):
if key.startswith(mark + "_") and (
key.endswith("_start") or
key.endswith("_end")
):
return True
return False
@staticmethod
def is_file(path:str) -> bool:
return is_file(path)