OpoTests/Python/Modules/FormatModule.py

310 lines
9.9 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from Interfaces.OpoTestsInterface import OpoTestsInterface
from typing import Self, Any
from Utils.Patterns import RE
from Utils.Check import Check
from Utils.Utils import Utils
from re import Match as REMatch
from Interfaces.FormatModeInterface import FormatModeInterface
from Modules.Format.CapitalizeFormat import CapitalizeFormat
from Modules.Format.MixFormat import MixFormat
from Modules.Format.PlainFormat import PlainFormat
from Modules.Format.RandomFormat import RandomFormat
from Modules.Format.RangeFormat import RangeFormat
from Modules.Format.SelectFormat import SelectFormat
from Modules.Format.SerieFormat import SerieFormat
class FormatModule:
def __init__(self:Self, op:OpoTestsInterface) -> None:
self.op:OpoTestsInterface = op
self.modes:dict[str, FormatModeInterface] = {}
for keys, mode in (
(("capitalize", "cap", "capital", "Cap", "Capitalize", "Capital"), CapitalizeFormat(self)),
(("mix", "mixin"), MixFormat(self)),
(("Mix", "Mixin"), MixFormat(self, {"capitalized" : True})),
(("plain",), PlainFormat(self)),
(("rand", "random"), RandomFormat(self)),
(("range",), RangeFormat(self)),
(("select",), SelectFormat(self)),
(("Select",), SelectFormat(self, {"capitalized" : True})),
(("serie",), SerieFormat(self)),
):
for key in keys:
self.modes[key] = mode
def start(self:Self) -> None:
pass
@staticmethod
def set_fragments_level(string:str, fragments:list[str]) -> str:
return RE.FRAGMENT_PATTERN.sub(lambda matches:fragments[int(matches.group(1))], string)
@staticmethod
def __build_fragment(matches:REMatch, fragments:list[str]) -> str:
fragments += [matches.group(1)]
return "$$$" + str(len(fragments) - 1) + "$$$"
@classmethod
def build_fragments(cls:type[Self], string:str, fragments:list[str] = []) -> str:
return cls.set_fragments_level(Utils.recursive(string, lambda string:(
RE.FRAGMENT_VARIABLES.sub(lambda matches:(
cls.__build_fragment(matches, fragments)
), string)
)), fragments)
def __process_variable(self:Self,
i:int,
original:str,
key:str,
shared:dict[str, Any|None],
fragments:list[str]
) -> str:
if Check.is_key(key):
if key in shared:
return str(shared[key])
variable:list[Any|None] = self.op.questions.get_value(key, i)
method:str
data:list[Any|None]
if variable is not None:
method, *data = variable
if method in self.modes:
return self.modes[method].get(i, data, shared, fragments)
else:
matches:REMatch|None = RE.FORMAT_SPLIT.match(key)
if matches:
method, *data = matches.groups()[1:3]
if method in self.modes:
return self.modes[method].get(i, data, shared, fragments)
return original
def __execute_varibles(self:Self,
i:int,
string:str,
shared:dict[str, Any|None],
fragments:list[str]
) -> str:
try:
return RE.FRAGMENT_VARIABLES.sub(lambda matches:(
self.__process_variable(i, *matches.groups()[0:2], shared, fragments)
), string)
except Exception as exception:
print(exception)
return string
def execute(self:Self,
i:int,
string:str,
shared:dict[str, Any|None] = {},
fragments:list[str] = []
) -> str:
return Utils.recursive(self.build_fragments(string, fragments), lambda string:(
self.__execute_varibles(i, string, shared, fragments)
))
@staticmethod
def prepare_result(string:str, option:str, shared:dict[str, Any|None]) -> int:
if string.lower().startswith(option.lower()):
if "capitalized" not in shared or shared["capitalized"]:
shared["capitalized"] = False
return len(option)
return -1
def check(self:Self,
i:int,
string:str,
options:str|list[str],
shared:dict[str, Any|None] = {},
fragments:list[str] = [],
check_full:bool = True
) -> tuple[bool, int]:
length:int = 0
option:str
ok:bool = True
string = string.lower()
for option in Utils.get_array(options):
clone:str = "" + string
option_fragmented:str = self.build_fragments(option, fragments)
i:int = 0
length = 0
while True:
matches:REMatch|None = RE.FORMAT_PARTS.search(option_fragmented)
if not matches:
break
part:str = matches.group(0)
m:int = -1
key:str = matches.group(1)
method:str
data:list[Any|None]
if key:
if Check.is_key(key):
method, *data = self.op.questions.get_value(key, i)
if method in self.modes:
m = self.modes[method].check(i, clone, data, shared, fragments, clone, check_full)
# JS FormatModule lines 174-177.
else:
submatches:REMatch|None = RE.FORMAT_SPLIT.match(key)
if submatches:
method, *data = submatches.groups()[1:3]
if method in self.modes:
m = self.modes[method].check(i, clone, data, shared, fragments, clone, check_full)
if m == -1:
m = self.prepare_result(clone, part, shared)
if m != -1:
clone = clone[m:]
length += m
continue
ok = False
break
if ok:
ok = not check_full or not len(clone)
if ok:
break
return (ok, length)
def get_check_length(self:Self,
i:int,
string:str,
options:str|list[str],
shared:dict[str, Any|None] = {},
fragments:list[str] = [],
check_full:bool = True
) -> int:
has:bool
length:int
has, length = self.check(i, string, options, shared, fragments, check_full)
return length if has else -1
def check_select(self:Self,
i:int,
string:str,
options:tuple[tuple[int, int], str, list[str]],
shared:dict[str, Any|None] = {},
fragments:list[str] = []
) -> str|None:
length:int = 0
k:int = 0
check_last:bool = False
minimum:int
maximum:int
separator:str
items:list[str]
separator_length:int
(minimum, maximum), separator, items = options
separator_length = len(separator)
while k < maximum:
ok:bool = False
l:int = len(items)
j:int
item:str
if check_last or (k and k == l - 1):
if not string.startswith(" " + separator + " "):
return -1
length += separator_length + 2
string = string[0, separator_length + 2]
for j, item in enumerate(items):
has_variables:bool = RE.FRAGMENT_VARIABLES.match(
self.set_fragments_level(item, shared, fragments)
) is not None
item_length:int = (
self.get_check_length(i, string, [item], shared, fragments, False) if has_variables else
self.prepare_result(string, item, shared))
if item_length != -1:
string = string[:item_length]
items = items[:j] + items[j + 1:]
length += item_length
ok = True
break
if ok:
if "capitalized" not in shared or shared["capitalized"]:
shared["capitalized"] = False
elif check_last or not k:
return -1
else:
check_last = True
k -= 1
return -1 if k < minimum else length
def __get_list_items(self:Self, i:int, item:str) -> list[str]:
matches:REMatch|None = RE.LIST_ITEM.match(item)
if matches:
key:str = matches.group(1)
method:str
list_items:list[str]
method, *list_items = self.op.questions.get_value(key, i)
if method == "list":
return list_items
return [item]
def get_list(self:Self, i:int, items:list[str]) -> list[str]:
return [subitem for item in items for subitem in self.__get_list_items(i, item)]
@staticmethod
def check_range(string:str, inputs:str|list[str]) -> int:
number_matches:REMatch|None = RE.ABSOLUTE_INTEGER.match(string)
if number_matches:
number:int = int(string)
for range in (
[
int(value) for value in str(Utils.get_random(inputs.split("|"))).split("-")
] if Check.is_string(inputs) else
inputs if Check.is_array(inputs) else
[]):
if Check.is_number(range):
if number == range:
return len(string)
elif len(range) == 1:
if number == range[0]:
return len(string)
else:
if number >= range[0] and number <= range[1]:
return len(string)
return -1