#!/usr/bin/env python # -*- coding: utf-8 -*- from re import compile as RECompile from re import Pattern as REPattern from re import Match as REMatch from re import MULTILINE as RE_MULTILINE from re import IGNORECASE as RE_IGNORE_CASE from typing import Callable, Optional from os.path import exists as path_exists from os.path import dirname as directory_name from base64 import b64encode as base64_encode from json import dumps as json_encode class WMarkDown: # Languages HTML:int = 1 << 0 # Analyse modes RAW:int = 0 SUBITEM:int = 1 << 0 LINKED:int = 1 << 1 html_special_characters:dict[str, str] = { "<" : "lt", ">" : "gt", "&" : "amp" } quote_special:dict[str, str] = { "?" : "ask", "!" : "warning", ">" : "answer", "Q" : "comment", "#" : "note" } item_mark:tuple[str, str, REPattern] = ("###@&&_", "_&&@###", RECompile(r'\#{3}\@\&{2}_([0-9]+)_\&{2}\@\#{3}')) re_block_html_pattern:str = r'^[ \t]*<\!\-{2}[ \t]*\[{2}[\t ]*(wmd|wmarkdown)[ \t]*\]{2}[\t ]*\-{2}>' re_block_html:REPattern = RECompile(re_block_html_pattern, RE_MULTILINE) re_block_mark:REPattern = RECompile(r'^[ \t]*((" ?){3}|(\' ?){3}|(` ?){3})[ \t]*(wmd|wmarkdown)|' + re_block_html_pattern, RE_MULTILINE) re_new_lines:REPattern = RECompile(r'[\r\n]+') re_new_line:REPattern = RECompile(r'\n|\r\n|\r') re_started_white_spaces:REPattern = RECompile(r'^[ \t]*') re_lines:REPattern = RECompile(r'^[^\r\n]+', RE_MULTILINE) re_list_line:REPattern = RECompile(r'^([ \t]*)(([\-\*#\+]+)|([0-9]+|[a-z]+)\.)?(.*)$', RE_IGNORE_CASE | RE_MULTILINE) re_table_line:REPattern = RECompile(r'^\|([^\r\n"\']+|"([^"\\\\]+|\\\\(.|[\r\n])|[\r\n]+)*"|\'([^\'\\\\]+|\\\\(.|[\r\n])|[\r\n]+)*\')*[\r\n]*', RE_MULTILINE) re_table_item:REPattern = RECompile(r'(\|+)(([^\|\'\"]+|"([^"\\\\]+|\\\\(.|[\r\n])|[\r\n]+)*"|\'([^\'\\\\]+|\\\\(.|[\r\n])|[\r\n]+)*\')*)') re_options:REPattern = RECompile(r'`{3}[ \t]*(wmd|wmarkdown)\-options[ \t]*(\n|\r\n|\r)(([^\r\n`]+|[\r\n]+|`{1,2}[^`])*)`{3}') re_option:REPattern = RECompile(r'^([^=\r\n]+)=([^\r\n]*)$', RE_MULTILINE) re_extension:REPattern = RECompile(r'(([^\.\/\\\\]+)\.)?([^\.\/\\\\]+)$') re_characters_no_id:REPattern = RECompile(r'[^a-z0-9]+', RE_IGNORE_CASE) re_phone_number:REPattern = RECompile(r'^\+?[0-9 ]{5,22}$') re_email_address:REPattern = RECompile(r'^[a-z\.0-9_\-]+@[a-z\.0-9_\-]+\.[a-z0-9]+$') re_class_attribute:REPattern = RECompile(r'(?]+)>|("[^"]*"|\'[^\']*\')') re_start_with_white_spaces:REPattern = RECompile(r'^[ \t]', RE_MULTILINE) re_domain:REPattern = RECompile(r'^[^\:]+\:\/{2}[^\/]+') re_protocol:REPattern = RECompile(r'^([a-z0-9_\-]+)\:', RE_IGNORE_CASE) def __init__(self) -> None: self.modules:dict[str, tuple[int, REPattern]] = { "special_characters" : (WMarkDown.SUBITEM, RECompile(r'\\([\(\{\[\*\\])')), "title" : (WMarkDown.RAW, RECompile(r'^((#{1,6})[\t ]*([^\r\n]+)|(={1,6})[\t ]*([^\r\n=]+)={1,6})(\n|\r\n|\r){2,}', RE_MULTILINE)), "list" : (WMarkDown.RAW, RECompile(r'^([\-\*#\+]+|([0-9]+|[a-z]+)\.)[^\r\n]*(\n|\r\n|\r)([\t ]*(([\-\*#\+]+|([0-9]+|[a-z]+)\.)[^\r\n]*)(\n|\r\n|\r)?)*', RE_IGNORE_CASE | RE_MULTILINE)), "code_block" : (WMarkDown.RAW, RECompile(r'^("{3}([^\r\n]*)(((?!"{3})(.|[\r\n]))+)"{3}|" " "([^\r\n]*)(((?!" " ")(.|[\r\n]))+)" " "|\'{3}([^\r\n]*)(((?!\'{3})(.|[\r\n]))+)\'{3}|\' \' \'([^\r\n]*)(((?!\' \' \')(.|[\r\n]))+)\' \' \'|`{3}([^\r\n]*)(((?!`{3})(.|[\r\n]))+)`{3}|` ` `([^\r\n]*)(((?!` ` `)(.|[\r\n]))+)` ` `)', RE_MULTILINE)), "table" : (WMarkDown.RAW, RECompile(r'^\[\|([^\r\n]*)[\r\n]+((^\|([^\]]([^\r\n"\']|"([^"\\\\]+|\\\\(.|[\r\n])|[\r\n]+)*"|\'([^\'\\\\]+|\\\\(.|[\r\n])|[\r\n]+)*\')*)?[\r\n]+)*)^\|\]', RE_MULTILINE)), "quote" : (WMarkDown.RAW, RECompile(r'^>[ \t]*(\[\![\t ]*([^\] \t]+)([ \t]+([^\]]+))?\])?(([^\r\n]+(\r\n|[\r\n])?)*)', RE_MULTILINE)), "include" : (WMarkDown.RAW, RECompile(r'^\[{2}include[ \t]+([^\]]+)\]{2}', RE_MULTILINE)), "media" : (WMarkDown.SUBITEM | WMarkDown.LINKED, RECompile(r'\({2}\!(image|icon|video|audio|sound|picture)[ \t]+("(([^"]+|[\r\n]+))"|\'(([^\']+|[\r\n]+))\'|([^ \t\)]+))([ \t]*|[ \t]+("(([^\\\\"]+|\\\\.|[\r\n]+)*)"|\'(([^\\\\\']+|\\\\.|[\r\n]+)*)\'|([^\)]+)))\){2}', RE_IGNORE_CASE)), "code_doc" : (WMarkDown.RAW, RECompile(r'^\[{2}@(([^\]]+|\][^\]])+)\]{2}', RE_MULTILINE)), "presentation_links" : (WMarkDown.RAW, RECompile(r'^\[{2}"{3}(([^"]+|[\r\n]+|"{1,2}[^"])*)"{3}\]{2}', RE_MULTILINE)), "paragraph" : (WMarkDown.RAW, RECompile(r'^(([^\r\n]+(\n|\r\n|\r)?)+)', RE_MULTILINE)), # "bold_italic" : (WMarkDown.SUBITEM | WMarkDown.LINKED, RECompile(r'\*{3}(([^\*]+|\*{1,2}[^\*]|[\r\n]+|\\\*)*)\*{3}')), # "bold_italic" : (WMarkDown.SUBITEM | WMarkDown.LINKED, RECompile(r'(?]+|[a-z\.0-9_\-]+@[a-z\.0-9_\-]+\.[a-z0-9]+)', RE_IGNORE_CASE)) } self.__ids_cache:tuple[str] = tuple() def __id(self, string:str) -> str: id:str = WMarkDown.re_characters_no_id.sub(r'-', string).lower() if id[0] == '-': id = id[1:] if id[-1] == '-': id = id[:-1] if id in self.__ids_cache: i:int = 2 while id + "-" + str(i) in self.__ids_cache: i += 1 id += "-" + str(i) self.__ids_cache += (id,) return id def reset_ids(self): self.__ids_cache = tuple() def module_title(self, matches:REMatch, language:Optional[int] = HTML, path:Optional[str] = None) -> str|None: if language & WMarkDown.HTML: level:str = str(len(matches.group(2) or matches.group(4))) content:str = (matches.group(3) or matches.group(5)).strip() id:str = self.__id(content) return ('' + '' + self.analyse(content, language, WMarkDown.SUBITEM, path) + '' + '' + '' + '' + '') return None def module_paragraph(self, matches:REMatch, language:Optional[int] = HTML, path:Optional[str] = None) -> str|None: if language & WMarkDown.HTML: return '

' + self.analyse(matches.group(0).strip(), language, WMarkDown.SUBITEM, path) + '

' return None def module_bold_italic(self, matches:REMatch, language:Optional[int] = HTML, path:Optional[str] = None) -> str|None: if language & WMarkDown.HTML: return '' + self.analyse(matches.group(1), language, WMarkDown.SUBITEM, path) + '' return None def module_bold(self, matches:REMatch, language:Optional[int] = HTML, path:Optional[str] = None) -> str|None: if language & WMarkDown.HTML: return '' + self.analyse(matches.group(1), language, WMarkDown.SUBITEM, path) + '' return None def module_italic(self, matches:REMatch, language:Optional[int] = HTML, path:Optional[str] = None) -> str|None: if language & WMarkDown.HTML: return '' + self.analyse(matches.group(1), language, WMarkDown.SUBITEM, path) + '' return None def module_strike(self, matches:REMatch, language:Optional[int] = HTML, path:Optional[str] = None) -> str|None: if language & WMarkDown.HTML: return '' + self.analyse(matches.group(1), language, WMarkDown.SUBITEM, path) + '' return None def module_underline(self, matches:REMatch, language:Optional[int] = HTML, path:Optional[str] = None) -> str|None: if language & WMarkDown.HTML: return '' + self.analyse(matches.group(1), language, WMarkDown.SUBITEM, path) + '' return None def module_code_item(self, matches:REMatch, language:Optional[int] = HTML, path:Optional[str] = None) -> str|None: if language & WMarkDown.HTML: return '' + self.analyse(matches.group(1), language, WMarkDown.SUBITEM, path) + '' return None @staticmethod def __check_html_module(_type:str, matches:REMatch) -> str: is_radio:bool = _type == "radio" return ('') @classmethod def module_checkbox(self, matches:REMatch, language:Optional[int] = HTML, path:Optional[str] = None) -> str|None: if language & WMarkDown.HTML: return WMarkDown.__check_html_module("checkbox", matches) return None @classmethod def module_radio(self, matches:REMatch, language:Optional[int] = HTML, path:Optional[str] = None) -> str|None: if language & WMarkDown.HTML: return WMarkDown.__check_html_module("radio", matches) return None @classmethod def module_tick(self, matches:REMatch, language:Optional[int] = HTML, path:Optional[str] = None) -> str|None: if language & WMarkDown.HTML: return WMarkDown.__check_html_module("tick", matches) return None @staticmethod def __list_deployed(level:list[int, str, str|None], last_mark:str|None, l:int) -> str: _type:str = last_mark if l and last_mark else level[2] deployable:bool = l and _type and _type in "+-" deployed:str = ( "null" if not deployable else "false" if _type == '-' else "true") return ' data-deployed="' + deployed + '"' + (' data-list-unprocessed="true"' if deployable else '') @classmethod def __list_start(self, unordered:bool, _type:str) -> str: if not unordered: _type = _type[:-1] if WMarkDown.re_integer.search(_type): return ' start="' + _type + '"' return '' def module_list(self, matches:REMatch, language:Optional[int] = HTML, path:Optional[str] = None) -> str|None: if language & WMarkDown.HTML: html:str = '' lines:list[str] = WMarkDown.re_new_line.split(matches.group(0)) levels:list[list[int, str, str|None]] = [[0, 'u', None]] l:int = 0 line:str last_mark:str|None = None subtype:str|None = None for line in lines: matches:REMatch = WMarkDown.re_list_line.search(line) if not matches: continue separator_by_marks:int = len(matches.group(3) or "") separator:int = separator_by_marks if separator_by_marks > 1 else len(matches.group(1)) _type:str = matches.group(2) key:str = matches.group(4) string:str = self.analyse(matches.group(5), language, WMarkDown.SUBITEM, path).strip() unordered:bool = _type and _type[-1] in ('-', '*', '+') subtype = _type[-1] if _type else None if _type: if levels[l][0] == separator: levels[l][1] = 'u' if unordered else 'o' levels[l][2] = subtype html += ('' if html else '<' + str(levels[l][1]) + 'l class="wmd-list"' + WMarkDown.__list_start(unordered, _type) + WMarkDown.__list_deployed(levels[l - 1], last_mark, l) + ( ' type="' + key[0] + '"' if key and not unordered and key[0] in ('a', 'A', 'i', 'I') else '') + '>') + '
  • ' + string elif levels[l][0] < separator: l += 1 levels += [[separator, "u" if unordered else "o", subtype]] html += '<' + levels[l][1] + 'l class="wmd-list"' + WMarkDown.__list_start(unordered, _type) + WMarkDown.__list_deployed(levels[l - 1], last_mark, l) + ( ' type="' + key[0] + '"' if key and not unordered and key[0] in ('a', 'A', 'i', 'I') else '') + '>
  • ' + string else: while levels[l][0] > separator: html += '
  • ' l -= 1 if not l: break html += '
  • ' + string else: html += ' ' + string last_mark = subtype while len(levels): html += '
  • ' return html return None @classmethod def set_class(self, html:str, _class:str|list|tuple) -> str: if not isinstance(_class, (list, tuple)): _class = (_class,) if 'class="' not in html: html = (' ' if html else '') + 'class="' + " ".join(_class) + '"' def callback(matches:REMatch): attribute:str classes_str:str closer:str classes:list[str] class_key:str attribute, classes_str, closer = matches.groups() classes = WMarkDown.re_white_spaces.split(classes_str) return attribute + classes_str + (' ' if classes_str else '') + " ".join([ class_key for class_key in _class if class_key not in classes ]) + closer return WMarkDown.re_class_attribute.sub(callback, html) def module_table(self, matches:REMatch, language:Optional[int] = HTML, path:Optional[str] = None) -> str|None: if language & WMarkDown.HTML: html:dict[str, str] = { "thead" : '', "tbody" : '', "tfoot" : '' } attributes:str = (matches.group(1) or "").strip() data:str = matches.group(2).strip() while True: line_matches:REMatch = WMarkDown.re_table_line.search(data) if not line_matches: break line:str = line_matches.group(0) cell_type:str = 'h' if line[1] in ('^', '_', '=') else 'd' tags:tuple[str] = ( ("thead",) if line[1] == '^' else ("tfoot",) if line[1] == '_' else ("thead", "tfoot") if line[1] == '=' else ("tbody",)) tag:str row:str = '' if cell_type == 'h': line = line[0] + line[2:] while True: cell_matches:REMatch = WMarkDown.re_table_item.search(line) if not cell_matches: break column_span_i:int = len(cell_matches.group(1)) column_span:str = ' colspan="' + str(column_span_i) + '"' if column_span_i > 1 else '' content:str = (cell_matches.group(2) or "").strip() row += '' + self.analyse( content[1:-1] if content and content[0] + content[-1] in ("''", '""') else content, WMarkDown.HTML, WMarkDown.SUBITEM, path ) + '' line = line[cell_matches.span()[1]:] row += '' for i, tag in enumerate(tags): if i: html[tag] = row + html[tag] else: html[tag] += row data = data[line_matches.span()[1]:] return ('
    ' + '' + "".join([ '<' + tag + '>' + content + '' if content else '' for tag, content in html.items() ]) + '
    ' + '
    ') return None @classmethod def module_exclude(self, matches:REMatch, language:Optional[int] = HTML, path:Optional[str] = None) -> str|None: if language & WMarkDown.HTML: return '' + (matches.group(1) or "") + '' return None def module_link(self, matches:REMatch, language:Optional[int] = HTML, path:Optional[str] = None) -> str|None: if language & WMarkDown.HTML: raw_url:str = (matches.group(4) or matches.group(11) or matches.group(14) or "").strip() url = ( "tel:" + raw_url.replace(" ", "") if WMarkDown.re_phone_number.search(raw_url) else "mailto:" + raw_url if WMarkDown.re_email_address.search(raw_url) else raw_url) text:str = matches.group(2) or matches.group(13) or raw_url protocol_matches:REMatch|None = WMarkDown.re_protocol.search(url) return '' + self.analyse(text.strip(), language, WMarkDown.SUBITEM | self.LINKED, path) + '' return None def module_quote(self, matches:REMatch, language:Optional[int] = HTML, path:Optional[str] = None) -> str|None: if language & WMarkDown.HTML: _type:str|None = matches.group(2) type_text:str|None = matches.group(4) if _type in WMarkDown.quote_special: _type = WMarkDown.quote_special[_type] return ('
    ' + (('
    ' + (( '' + '' + _type[1:] + '' ) if _type[0] == "@" else ( '' + ('' + type_text + '' if type_text else '') )) + '
    ') if _type else '') + '
    ' + self.analyse(matches.group(5), language, WMarkDown.SUBITEM, path) + '
    ' + '
    ') return None @classmethod def code_block_data(self, key:str, value:str|int, language:Optional[int] = HTML, path:Optional[str] = None) -> str|None: if language & WMarkDown.HTML: return ('
  • ' + '' + '' + key + '' + '' + str(value) + '' + '
  • ') return None @classmethod def filter_html_special_characters(self, string:str) -> str: l:int = len(WMarkDown.html_special_characters) _:int index:list[int] = [-1 for _ in range(l)] response:str = '' characters:tuple[str] = tuple(WMarkDown.html_special_characters.keys()) while True: j:int character:str i:int|None = None for j, character in enumerate(characters): if index[j] == None: continue if index[j] < 0: try: index[j] = string.index(character) except: index[j] = None continue if i == None or index[i] > index[j]: i = j if i == None: response += string break length:int = index[i] response += string[:length] + '&' + WMarkDown.html_special_characters[characters[i]] + ';' string = string[length + 1:] for j in range(l): if index[j] != None: index[j] -= length + 1 return response @classmethod def module_code_block(self, matches:REMatch, language:Optional[int] = HTML, path:Optional[str] = None) -> str|None: if language & WMarkDown.HTML: _type:str content:str i:int for i in range(6): j:int = 2 + i * 4 _type = matches.group(j) content = matches.group(j + 1) if _type or content: _type = (_type or "unamed").strip().lower() content = (content or "") break if _type: lines:list[str] = WMarkDown.re_new_line.split(content)[2:] _:str return ('
    ' '' + '
    ' + '
      ' + ''.join(['
    1. ' for _ in lines]) + '
    ' + '
    ' + WMarkDown.filter_html_special_characters(content) + '
    ' + '
    ' + '
    ') return 'UNKNOWN_BLOCK' return None def module_special_characters(self, matches:REMatch, language:Optional[int] = HTML, path:Optional[str] = None) -> str|None: if language & WMarkDown.HTML: return matches.group(1) return None @staticmethod def load_file(path:str) -> str|None: if path_exists(path): try: with open(path, "r") as opened: return opened.read() except: pass return None def module_include(self, matches:REMatch, language:Optional[int] = HTML, path:Optional[str] = None) -> str|None: if language & WMarkDown.HTML: relative_path:str = matches.group(1) new_path:str = path + relative_path extension:str = WMarkDown.re_extension.search(new_path).groups()[1:] content:str|None = WMarkDown.load_file(new_path) directory:str = directory_name(new_path) + "/" results:str|None = ( None if content == None else self.process(content, language, directory)[0] if ".".join(extension) == "w.md" else self.analyse(content, language, WMarkDown.RAW, directory) if extension[-1] == "md" else content) return ('
    ' + '' + (results or "") + '
    ') return None @staticmethod def build_html_image(links:list[str]|tuple[str], _type:Optional[str] = "image", title:Optional[str] = None) -> str: attributes:str = ' title="' + title + '" alt="' + title + '"' if title else '' return ('' + '' + '' + '' + ('' + title + '' if title else '') + '') def module_media(self, matches:REMatch, language:Optional[int] = HTML, path:Optional[str] = None) -> str|None: if language & WMarkDown.HTML: _type:str = matches.group(1).lower() links:list[str] = WMarkDown.re_white_spaces.split(matches.group(3) or matches.group(5) or matches.group(7)) text:str = (matches.group(10) or matches.group(12) or matches.group(14) or '').strip() if _type in ("image", "picture", "icon"): return WMarkDown.build_html_image(links, _type, text) if _type in ("video",): if "youtube.com" in links[0] or "youtu.be" in links[0]: _type = "youtube" elif "vimeo.com" in links[0]: _type = "vimeo" return ('' + '' + ( "YOUTUBE" if _type == "youtube" else "VIMEO" if _type == "vimeo" else '') + '') if _type in ("sound", "audio"): if "soundcloud.com" in links[0]: _type = "soundcloud" return ('' + '' + ( "SOUNDCLOUD" if _type == "soundcloud" else '') + '') return None return None @classmethod def mark_replace(self, string:str, matches:REMatch, fragments:list[str]) -> str: if matches: start:int end:int i:int = len(fragments) start, end = matches.span() string = string[:start] + WMarkDown.item_mark[0] + str(len(fragments)) + WMarkDown.item_mark[1] + string[end:] fragments += [matches.group(0)] return string @classmethod def restore_marks(self, string:str, fragments:list[str]) -> str: while True: matches:REMatch = WMarkDown.item_mark[2].search(string) if not matches: break i:int = int(matches.group(1)) _from:int _to:int _from, _to = matches.span() string = string[:_from] + (fragments[i] if i >= 0 and i < len(fragments) else "") + string[_to:] return string @classmethod def __doc_typed_format(self, typed:str) -> str: return WMarkDown.filter_html_special_characters(typed.replace(' ', "").replace(',', ", ")) def module_code_doc(self, matches:REMatch, language:Optional[int] = HTML, path:Optional[str] = None) -> str|None: if language & WMarkDown.HTML: # print(matches.groups()) data:str = matches.group(1).strip() base:REMatch = WMarkDown.re_code_doc.search(data) return_type:str = WMarkDown.__doc_typed_format(base.group(2) or "void") access:str = base.group(3) or "public" name_space:str|None = base.group(5) environment:str = base.group(6) or "global" method:str = base.group(7) parameters:str|None = base.group(9) has_parameters:bool = parameters != None default_value:str|None = base.group(11) full_method:str = (name_space + "." if name_space else "") + method.strip() arguments:str = '' arguments_definition:str = '' header:str = ('' + 'Name' + 'Required' + 'Nullable' + 'Typed' + 'Default Value' + '') fragments:list[str] = [] matches:REMatch arguments_l:int = 0 if access == "?": access = "protected" elif access == "!": access = "private" if environment == ":": environment = "static" elif environment == ".": environment = "object" if parameters: while True: matches = WMarkDown.re_code_doc_subarguments.search(parameters) if matches: parameters = WMarkDown.mark_replace(parameters, matches, fragments) else: break else: parameters = "" for parameter in parameters.split(","): matches = WMarkDown.re_code_doc_arguments.search(parameter.strip()) if matches: scopes:str = matches.group(1) or "" required:bool = "!" in scopes nullish:bool = "?" in scopes typed:str = WMarkDown.__doc_typed_format(WMarkDown.restore_marks(matches.group(2), fragments)) name:str = matches.group(3).strip() default_value:str = WMarkDown.filter_html_special_characters(WMarkDown.restore_marks((matches.group(5) or "").strip(), fragments)) arguments += ('' + ('Required' if required else '') + ('Nullish' if nullish else '') + '' + typed + '' + '' + name + '' + ('' + default_value + '' if default_value else '') + '') arguments_definition += ('' + '' + name + '' + '' + ('True' if required else 'False') + '' + '' + ('True' if nullish else 'False') + '' + '' + typed + '' + '' + default_value + '' + '') arguments_l += 1 return ('
    ' + '' + full_method + '' + '
    ' + '' + return_type + '' + '' + environment + '' + '' + access + '' + '' + full_method + '' + ('' + arguments + '' if has_parameters else '') + ('' + default_value + '' if default_value else '') + '
    ' + '
    ' + '' + '' + header + '' + '' + arguments_definition + '' + '' + header + '' + '
    ' + '
    ' + '
    ') return None def module_color_sample(self, matches:REMatch, language:Optional[int] = HTML, path:Optional[str] = None) -> str|None: if language & WMarkDown.HTML: color:str = matches.group(2) or matches.group(3) return ('' + '' + '' + color + '' + '') return None def module_presentation_links(self, matches:REMatch, language:Optional[int] = HTML, path:Optional[str] = None) -> str|None: if language & WMarkDown.HTML: items:list = [] i:int = -1 title:str avatars:list[str] links:list[str] html_avatars:str = '' html_list:str = '' for line in WMarkDown.re_new_lines.split(matches.group(1)): has_spaces:bool = WMarkDown.re_start_with_white_spaces.search(line) != None data:str = line.strip() if not data: continue if has_spaces: if i == -1: continue if data[0] == "*": items[i][1] += [data[1:]] if ".k3y.pw/" in data: items[i][1] += [data[1:].replace(".k3y.pw/", ".local/")] else: items[i][2] += [data] else: i += 1 items += [[line.strip(), [], []]] for i, (title, avatars, links) in enumerate(items): link:str avatar:str html_avatars += ('
  • ' + ('' + WMarkDown.build_html_image(avatars) + '' if len(links) else WMarkDown.build_html_image(avatars)) + ''.join(['' for link in links]) + '
  • ') html_list += ('
  • ' + '' + title + '' + '' + '
  • ') return ('') return None @classmethod def remove_default_tabulations(self, data:str) -> str: line:str lines:list[str] = WMarkDown.re_new_lines.split(data) spaces:int = len(data) def callback(matches:REMatch): return matches.group(0)[spaces:] for line in lines: if not line: continue white_spaces:int = len(WMarkDown.re_started_white_spaces.search(line).group(0)) if white_spaces < spaces: spaces = white_spaces return WMarkDown.re_lines.sub(callback, data) def __build(self, data:str, language:int, path:str|None) -> str: return ('
    ' + self.analyse(WMarkDown.remove_default_tabulations(data), language, WMarkDown.RAW, path) + '
    ' + '
    ') def process(self, data:str, language:Optional[int] = HTML, path:Optional[str] = None) -> str: results:str = "" variables:dict[str, str] = {} options:REMatch = WMarkDown.re_options.search(data) if options: _from:int _to:int options_data:str = options.group(3) _from, _to = options.span() while True: option:REMatch = WMarkDown.re_option.search(options_data) if not option: break variables[option.group(1).strip()] = option.group(2).strip() options_data = options_data[option.span()[1]:] data = data[:_from] + data[_to:] while True: matches:REMatch = WMarkDown.re_block_mark.search(data) if not matches: results += data break open_from:int open_to:int mark:str = matches.group(1) re_close:REPattern = RECompile(r'^\s*' + mark, RE_MULTILINE) if mark else WMarkDown.re_block_html open_from, open_to = matches.span() results += data[:open_from] data = data[open_to:] close_matches:REMatch = re_close.search(data) if close_matches: close_from:int close_to:int close_from, close_to = close_matches.span() results += self.__build(data[:close_from], language, path) data = data[close_to:] else: results += self.__build(data, language, path) break return results, variables def analyse(self, data:str, language:Optional[int] = HTML, mode:Optional[int] = RAW, path:Optional[str] = None) -> str: response:str = "" item_mode:int items:dict[str, list[bool, int, int, REMatch]] = {key : [True, -1, -1, None] for key, (item_mode, pattern) in self.modules.items() if mode == WMarkDown.RAW or mode | item_mode == item_mode} # j:int = 0 # m:int = 10 while len(items): key:str exists:bool _from:int _to:int matches:REMatch l:int = len(data) selected:str|None = None for key, (exists, _from, _to, matches) in items.items(): if not exists: continue pattern:REPattern = self.modules[key][1] if _from < 0: items[key][3] = matches = pattern.search(data) if matches: _from, _to = items[key][1], items[key][2] = matches.span() else: items[key][0] = False continue if _from < l: l = _from selected = key if selected == None: response += data break _from, _to, matches = items[selected][1:] response += data[:_from] + getattr(self, "module_" + selected)(matches, language, path) data = data[_to:] for key in items.keys(): if items[key][3]: items[key][1] -= _to items[key][2] -= _to # j += 1 # if j >= m: # break return response def to_html(self, data:str, path:Optional[str] = None) -> str: return self.analyse(data, WMarkDown.HTML, WMarkDown.RAW, path)