#wip(py): Basic Python starting.

This commit is contained in:
mbruzon 2026-05-01 10:12:58 +02:00
parent 86424a0ca3
commit 2f4258d815
4 changed files with 236 additions and 3 deletions

View 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

View File

@ -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

View File

@ -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
View 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"
]