teddecor.TED.markup
TED includes a parser to get literal strings from TED markup, along with a pprint function that outputs the literal string from a TED markup.
Raises: MacroMissingError: If there is an incorrect macro or color specifier MacroError: If there is a general formatting error with a macro
1"""TED includes a parser to get literal strings from TED markup, along with a pprint 2function that outputs the literal string from a TED markup. 3 4Raises: 5 MacroMissingError: If there is an incorrect macro or color specifier 6 MacroError: If there is a general formatting error with a macro 7""" 8from __future__ import annotations 9 10from typing import Iterator, Callable 11from .tokens import Token, Color, Text, Bold, Underline, Formatter, HLink, Reset, Func 12from .formatting import BOLD, UNDERLINE, RESET, LINK, FUNC 13 14__all__ = [ 15 "TED", 16] 17 18 19class TEDParser: 20 """Main class exposed by the library to give access the markup utility functions.""" 21 22 def __init__(self) -> None: 23 self._funcs = FUNC 24 25 def __split_macros(self, text: str) -> Iterator[str]: 26 """Takes a macro, surrounded by brackets `[]` and splits the nested/chained macros. 27 28 Args: 29 text (str): The contents of the macro inside of brackets `[]` 30 31 Yields: 32 Iterator[str]: Iterates from each token to the next until entire macro is consumed 33 """ 34 schars = ["@", "~", "!"] 35 last, index = 0, 0 36 while index < len(text): 37 if index != 0: 38 if text[index] in schars: 39 yield text[last:index] 40 last = index 41 42 index += 1 43 44 if last != index: 45 yield text[last:] 46 47 def __parse_macro(self, text: str) -> list[Token]: 48 """Takes the chained, nested, or single macros and generates a token based on it's type. 49 50 Args: 51 text (str): The macro content inside of brackets `[]` 52 53 Returns: 54 list[Token]: The list of tokens created from the macro content inside of brackets `[]` 55 """ 56 tokens = [] 57 58 if len(text) == 0: 59 tokens.append(Reset()) 60 return tokens 61 62 for sub_macro in self.__split_macros(text): 63 sub_macro = sub_macro.strip() 64 if sub_macro.startswith("@"): 65 tokens.append(Color(sub_macro)) 66 elif sub_macro.startswith("~"): 67 tokens.append(HLink(sub_macro)) 68 elif sub_macro.startswith("^"): 69 tokens.append(Func(sub_macro, self._funcs)) 70 return tokens 71 72 def __optimize(self, tokens: list) -> list: 73 """Takes the generated tokens from the markup string and removes and combines tokens where possible. 74 75 Example: 76 Since there can be combinations such as fg, bg, bold, and underline they can be represented in two ways. 77 * Unoptimized - `\\x1b[1m\\x1b[4m\\x1b[31m\\x1b[41m` 78 * Optimized - `\\x1b[1;4;31;41m` 79 80 81 Also, if many fg, bg, bold, and underline tokens are repeated they will be optimized. 82 * `*Bold* *Still bold` translates to `\\x1b[1mBold still bold\\x1b[0m` 83 * You can see that it removes unnecessary tokens as the affect is null. 84 * `[@> red @> green]Green text` translates to `\\x1b[32mGreen text\\x1b[0m` 85 * Here is an instance of overriding the colors. Order matters here, but since you are applying the foreground repeatedly only the last one will show up. So all previous declerations are removed. 86 87 Args: 88 tokens (list): The list of tokens generated from parsing the TED markup 89 90 Returns: 91 list: The optimized list of tokens. Bold, underline, fg, and bg tokens are combined into Formatter tokens 92 """ 93 open_link = False 94 func = None 95 formatter = Formatter() 96 output = [] 97 for token in tokens: 98 if isinstance(token, Color): 99 formatter.color = token 100 elif isinstance(token, Bold): 101 formatter.bold = token 102 elif isinstance(token, Underline): 103 formatter.underline = token 104 elif isinstance(token, HLink): 105 if token.closing and open_link: 106 open_link = False 107 output.append(token) 108 elif not token.closing and open_link: 109 token.value = LINK.CLOSE + token.value 110 output.append(token) 111 else: 112 open_link = True 113 output.append(token) 114 elif isinstance(token, Func): 115 func = token 116 else: 117 if not formatter.is_empty(): 118 output.append(formatter) 119 formatter = Formatter() 120 if func is not None: 121 new_value = func.exec(token.value) 122 if isinstance(new_value, str): 123 token.value = new_value 124 func = None 125 output.append(token) 126 127 if not formatter.is_empty(): 128 output.append(formatter) 129 if open_link: 130 output.append(HLink("~")) 131 132 return output 133 134 def __parse_tokens(self, string: str): 135 """Splits the TED markup string into tokens. If `*` or `_` are found then a Bold or Underline token will be generated respectively. 136 If `[` is found then it marches to the end of the macro, `]`, and then parses it. All special characters can be escaped with `\\` 137 138 Args: 139 text (str): The TED markup string that will be parsed 140 141 Raises: 142 MacroError: If a macro is not closed 143 144 Returns: 145 str: The translated ansi representation of the given sting 146 """ 147 148 bold_state = BOLD.POP 149 """BOLD: The current state/value of being bold. Either is bold, or is not bold.""" 150 151 underline_state = UNDERLINE.POP 152 """UNDERLINE: The current state/value of being underlined. Either is underlined, or is not underlined.""" 153 154 text: list = [] 155 """The chunks of text between special tokens.""" 156 157 output: list = [] 158 """Final output of the parse.""" 159 160 escaped: bool = False 161 """Indicates whether to escape the next character or not.""" 162 163 index: int = 0 164 """Current index of walking through the markup string.""" 165 166 def consume_macro(index: int): 167 """Starts from start of the macro and grabs characters until at the end of the macro. 168 169 Args: 170 index (int): The current index in the string 171 172 Raises: 173 MacroError: If at the end of the markup string and the macro isn't closed 174 175 Returns: 176 int: Index after moving to the end of the macro 177 """ 178 start = index 179 index += 1 180 char = string[index] 181 macro = [] 182 while char != "]": 183 macro.append(char) 184 index += 1 185 if index == len(string): 186 raise ValueError(f"Macro's must be closed \n {string[start-1:]}") 187 char = string[index] 188 output.extend(self.__parse_macro("".join(macro))) 189 190 return index 191 192 while index < len(string): 193 char = string[index] 194 if char == "*" and not escaped: 195 if len(text) > 0: 196 output.append(Text("".join(text))) 197 text = [] 198 bold_state = BOLD.inverse(bold_state) 199 output.append(Bold(bold_state)) 200 elif char == "_" and not escaped: 201 if len(text) > 0: 202 output.append(Text("".join(text))) 203 text = [] 204 underline_state = UNDERLINE.inverse(underline_state) 205 output.append(Underline(underline_state)) 206 elif char == "[" and not escaped: 207 if len(text) > 0: 208 output.append(Text("".join(text))) 209 text = [] 210 index = consume_macro(index) 211 elif char == "\\" and not escaped: 212 escaped = True 213 else: 214 text.append(char) 215 escaped = False 216 217 index += 1 218 219 if len(text) > 0: 220 output.append(Text("".join(text))) 221 text = [] 222 223 return "".join(str(token) for token in self.__optimize(output)) + RESET 224 225 def define(self, name: str, callback: Callable) -> None: 226 """Adds a callable function to the functions macro. This allows it to be called from withing a macro. 227 Functions must return a string, if it doesn't it will ignore the the return. It will automaticaly grab the next text block and use it for the input of the function. 228 The function should manipulate the text and return the result. 229 230 Args: 231 name (str): The name associated with the function. Used in the macro 232 callback (Callable): The function to call when the macro is executed 233 """ 234 self._funcs.update({name: callback}) 235 236 def parse(self, text: str) -> str: 237 """Parses a TED markup string and returns the translated ansi equivilent. 238 239 Args: 240 text (str): The TED markup string 241 242 Returns: 243 str: The ansi translated string 244 """ 245 return self.__parse_tokens(text) 246 247 def print(self, *args) -> None: 248 """Works similare to the buildin print function. 249 Takes all arguments and passes them through the parser. 250 When finished it will print the results to the screen with a space inbetween the args. 251 252 Args: 253 *args (Any): Any argument that is a string or has a __str__ implementation 254 """ 255 parsed = [] 256 for arg in args: 257 parsed.append(self.parse(str(arg))) 258 259 print(*parsed) 260 261 @staticmethod 262 def encode(text: str) -> str: 263 """Utility to automatically escape/encode special markup characters. 264 265 Args: 266 text (str): The string to encode/escape 267 268 Returns: 269 str: The escaped/encoded version of the given string 270 """ 271 schars = ["*", "_", "["] 272 for char in schars: 273 text = f"\{char}".join(text.split(char)) 274 return text 275 276 @staticmethod 277 def strip(text: str) -> str: 278 """Removes TED specific markup. 279 280 Args: 281 text (str): String to strip markup from. 282 283 Returns: 284 str: Version of text free from markup.S 285 """ 286 from re import sub 287 288 return sub( 289 r"\x1b\[(\d{0,2};?)*m|(?<!\\)\*|(?<!\\)_|(?<!\\)\[[^\[\]]+\]|\\", 290 "", 291 text, 292 ) 293 294 295TED = TEDParser()
TED = <teddecor.TED.markup.TEDParser object>