#!/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