#wip(py): Basic Python starting.
This commit is contained in:
parent
86424a0ca3
commit
2f4258d815
168
Python/Drivers/FilesDirver.py
Normal file
168
Python/Drivers/FilesDirver.py
Normal file
@ -0,0 +1,168 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from typing import Any, Self, Sequence
|
||||
from os.path import dirname as directory_name, abspath as absolute_path, exists as path_exists, isfile as is_file, isdir as is_directory
|
||||
from io import FileIO
|
||||
from json import loads as json_loads
|
||||
from Interfaces.Application.AnPInterface import AnPInterface
|
||||
from Utils.Options import Options
|
||||
from Utils.Validate import Validate
|
||||
from Utils.Patterns import RE
|
||||
|
||||
class FilesDriver:
|
||||
|
||||
ROOT:str = directory_name(absolute_path(__file__))
|
||||
SLASH:str = "/" if "/" in ROOT else "\\"
|
||||
|
||||
GET_ABSOLUTE_PATH_OPTIONS:Options = Options(Options.ALLOW_CHECKS + Options.SHOW_ERRORS)
|
||||
LOAD_OPTIONS:Options = Options(Options.ALLOW_CHECKS + Options.SHOW_ERRORS)
|
||||
LOAD_JSON_OPTIONS:Options = Options(Options.ALLOW_CHECKS + Options.SHOW_ERRORS)
|
||||
|
||||
def __init__(self:Self, anp:AnPInterface) -> None:
|
||||
self.anp:AnPInterface = anp
|
||||
|
||||
self.__roots:list[str] = ["", self.ROOT]
|
||||
|
||||
for _ in range(2):
|
||||
self.__roots += [RE.PARENT_DIRECTORY.sub(r'\1', self.__roots[-1])]
|
||||
|
||||
@classmethod
|
||||
def fix_path(cls:type[Self], path:str) -> str:
|
||||
|
||||
path = RE.SLASHES.sub(cls.SLASH, path)
|
||||
|
||||
return path if path[0] != "\\" else "\\" + path
|
||||
|
||||
def get_absolute_path(self:Self, path:str, custom_options:int = 0) -> tuple[str|None, int]:
|
||||
""" 5 bits. """
|
||||
|
||||
options:Options = Options(custom_options, self.GET_ABSOLUTE_PATH_OPTIONS)
|
||||
error:int = (
|
||||
(Validate.string(path, Validate.TRIM | Validate.EMPTY) << 1) |
|
||||
0) if options.allow_checks else 0
|
||||
absolute:str|None = None
|
||||
|
||||
if not error:
|
||||
for root in self.__roots:
|
||||
try:
|
||||
|
||||
absolute_path:str = self.fix_path((root + self.SLASH if root else "") + path)
|
||||
|
||||
if path_exists(absolute_path):
|
||||
absolute = absolute_path
|
||||
break
|
||||
|
||||
except Exception as exception:
|
||||
error |= 1 << 0
|
||||
|
||||
if absolute is None:
|
||||
error |= 1 << 4
|
||||
|
||||
return absolute, error
|
||||
|
||||
def load(self:Self, path:str, mode:str = "r", custom_options:int = 0) -> tuple[str|bytes|None, int]:
|
||||
""" 10 bits. """
|
||||
|
||||
options:Options = Options(custom_options, self.LOAD_OPTIONS)
|
||||
error:int = (
|
||||
# (Validate.string(path, Validate.TRIM | Validate.EMPTY) << 1) |
|
||||
((
|
||||
Validate.string(mode, Validate.TRIM | Validate.EMPTY) or
|
||||
(0 if mode in ("r", "rb") else 8)
|
||||
) << 6) |
|
||||
0) if options.allow_checks else 0
|
||||
results:str|bytes|None = None
|
||||
absolute:str|None
|
||||
absolute_error:int
|
||||
|
||||
absolute, absolute_error = self.get_absolute_path(path, custom_options)
|
||||
if options.allow_checks:
|
||||
error |= absolute_error << 1
|
||||
|
||||
if not error:
|
||||
try:
|
||||
|
||||
file:FileIO
|
||||
|
||||
with open(absolute, mode) as file:
|
||||
results = file.read()
|
||||
|
||||
except Exception as exception:
|
||||
error |= 1 << 0
|
||||
|
||||
return results, error
|
||||
|
||||
def load_json(self:Self, data:str|Sequence[Any|None]|dict[str, Any|None], custom_options:int = 0) -> tuple[Sequence[Any|None]|dict[str, Any|None]|None, int]:
|
||||
""" 10 bits. """
|
||||
|
||||
options:Options = Options(custom_options, self.LOAD_JSON_OPTIONS)
|
||||
error:int = (
|
||||
(Validate.null_or_undefined(data) << 1) |
|
||||
0) if options.allow_checks else 0
|
||||
results:Sequence[Any|None]|dict[str, Any|None]|None = None
|
||||
|
||||
if isinstance(data, str):
|
||||
|
||||
if not options.allow_checks or not (
|
||||
error := error | Validate.string(data, Validate.FROM_TYPE | Validate.TRIM | Validate.EMPTY) << 1
|
||||
):
|
||||
|
||||
data = data.strip()
|
||||
|
||||
if data[0] + data[-1] in ("{}", "[]"):
|
||||
try:
|
||||
results = json_loads(data)
|
||||
if options.allow_checks and results == None:
|
||||
error |= 1 << 6
|
||||
except Exception as exception:
|
||||
error |= 1 << 0
|
||||
else:
|
||||
|
||||
load_error:int
|
||||
|
||||
data, load_error = self.load(data, "r", custom_options)
|
||||
|
||||
if not options.allow_checks or (
|
||||
error := error | (load_error << 7)
|
||||
) or not (
|
||||
error := error | Validate.string(data, Validate.FROM_TYPE | Validate.TRIM | Validate.EMPTY) << 1
|
||||
):
|
||||
|
||||
data = data.strip()
|
||||
|
||||
if data[0] + data[-1] in ("{}", "[]"):
|
||||
try:
|
||||
results = json_loads(data)
|
||||
if options.allow_checks and results == None:
|
||||
error |= 1 << 6
|
||||
except Exception as exception:
|
||||
error |= 1 << 0
|
||||
else:
|
||||
error |= 1 << 18
|
||||
|
||||
if not error:
|
||||
try:
|
||||
results = json_loads(data)
|
||||
if options.allow_checks and results == None:
|
||||
error |= 1 << 6
|
||||
except Exception as exception:
|
||||
error |= 1 << 0
|
||||
elif isinstance(data, (list, dict)):
|
||||
results = data
|
||||
else:
|
||||
error |= 1 << 3
|
||||
|
||||
return results, error
|
||||
|
||||
def save(self:Self, path:str, content:str|bytes, mode:str = "w", custom_options:int = 0) -> int:
|
||||
pass
|
||||
|
||||
def delete(self:Self, path:str, custom_options:int = 0) -> int:
|
||||
pass
|
||||
|
||||
def is_file(self:Self, path:str, custom_options:int = 0) -> int:
|
||||
pass
|
||||
|
||||
def is_directory(self:Self, path:str, custom_options:int = 0) -> int:
|
||||
pass
|
||||
@ -13,6 +13,14 @@ class Options:
|
||||
ALLOW_NULLS:int = 1 * 3 ** NULLISH
|
||||
NOT_NULLS:int = 2 * 3 ** NULLISH
|
||||
|
||||
CHECKS:int = 2
|
||||
ALLOW_CHECKS:int = 1 * 3 ** CHECKS
|
||||
NO_CHECKS:int = 2 * 3 ** CHECKS
|
||||
|
||||
SHOWING_ERRORS:int = 3
|
||||
SHOW_ERRORS:int = 1 * 3 ** SHOWING_ERRORS
|
||||
HIDE_ERRORS:int = 2 * 3 ** SHOWING_ERRORS
|
||||
|
||||
DEFAULT:Self = Self(NO_OVERWRITE + ALLOW_NULLS)
|
||||
|
||||
@classmethod
|
||||
@ -29,13 +37,17 @@ class Options:
|
||||
|
||||
self.overwrite:bool|None
|
||||
self.allow_null:bool|None
|
||||
self.check_errors:bool|None
|
||||
self.show_errors:bool|None
|
||||
|
||||
if default is None:
|
||||
default = self.DEFAULT
|
||||
|
||||
for key, option in (
|
||||
("overwrite", self.OVERWRITE),
|
||||
("allow_null", self.NULLISH)
|
||||
("allow_null", self.NULLISH),
|
||||
("check_errors", self.CHECKS),
|
||||
("show_errors", self.SHOWING_ERRORS)
|
||||
):
|
||||
|
||||
value:bool|None = self.get_value(options, option)
|
||||
@ -50,7 +62,9 @@ class Options:
|
||||
|
||||
for option, value in (
|
||||
(self.OVERWRITING, self.overwrite),
|
||||
(self.NULLISH, self.allow_null)
|
||||
(self.NULLISH, self.allow_null),
|
||||
(self.CHECKS, self.allow_checks),
|
||||
(self.SHOWING_ERRORS, self.show_errors)
|
||||
):
|
||||
code += (
|
||||
1 if value is True else
|
||||
|
||||
@ -6,3 +6,5 @@ from re import compile as re_compile, Pattern as REPattern, I as RE_IGNORE_CASE
|
||||
class RE:
|
||||
KEY:REPattern = re_compile(r'^[a-z_][a-z0-9_]*$', RE_IGNORE_CASE)
|
||||
STRING_VARIABLES:REPattern = re_compile(r'\{([a-z_][a-z0-9_]*)\}', RE_IGNORE_CASE)
|
||||
SLASHES:REPattern = re_compile(r'[\\\/]+')
|
||||
PARENT_DIRECTORY:REPattern = re_compile(r'^(.+)[\\\/][^\\\/]+[\\\/]?$')
|
||||
49
Python/Utils/Validate.py
Normal file
49
Python/Utils/Validate.py
Normal file
@ -0,0 +1,49 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from typing import Any, Self
|
||||
from Utils.Patterns import RE
|
||||
|
||||
class Validate:
|
||||
|
||||
IGNORE_NULL_OR_UNDEFINED:int = 1 << 0
|
||||
EMPTY:int = 1 << 1
|
||||
TRIM:int = 1 << 2
|
||||
KEY:int = 1 << 3
|
||||
FROM_TYPE:int = 1 << 4
|
||||
|
||||
@classmethod
|
||||
def null_or_undefined(cls:type[Self], value:Any|None, options:int = 0) -> int:
|
||||
""" 2 Options. 2 bits. """
|
||||
return 0 if options & (cls.IGNORE_NULL_OR_UNDEFINED | cls.FROM_TYPE) else (
|
||||
# 1 if value is undefined else
|
||||
2 if value is None else
|
||||
0)
|
||||
|
||||
@staticmethod
|
||||
def null_or_undefined_message(key:str) -> list[str]:
|
||||
return [
|
||||
f"{key}_undefined",
|
||||
f"{key}_null"
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def string(cls:type[Self], value:Any|None, options:int = 0) -> int:
|
||||
""" 7 options. 3 bits. """
|
||||
return cls.null_or_undefined(value, options) or (
|
||||
3 if not (options & cls.FROM_TYPE) and not isinstance(value, str) else
|
||||
(4 if options & cls.EMPTY else 0) if value == "" else
|
||||
(5 if options & cls.TRIM else 0) if value != (value := value.strip()) else
|
||||
(6 if options & (cls.TRIM | cls.EMPTY) else 0) if value == "" else
|
||||
(7 if options & cls.KEY else 0) if not RE.KEY.match(value) else
|
||||
0)
|
||||
|
||||
@classmethod
|
||||
def string_message(cls:type[Self], key:str) -> list[str]:
|
||||
return cls.null_or_undefined_message(key) + [
|
||||
f"{key}_not_string",
|
||||
f"{key}_empty",
|
||||
f"{key}_not_trimmed",
|
||||
f"{key}_empty_trimmed",
|
||||
f"{key}_not_key"
|
||||
]
|
||||
Loading…
Reference in New Issue
Block a user