""" Colorize matching parenthesis in a string. The "primary" entry point is ``colorize``, and ``Colored`` the "primary" data structure. """ import io from typing import Literal, Generator from dataclasses import dataclass __version__ = "0.2.2" @dataclass class Stackpointer(): """A "fancy" integer which implements pre-and post decrement.""" depth: int = 0 def __call__(self, c: int): """ Update and return current state. :param c: An integer. Positive integers increment the value after returning it, negative integers decrement the value before returning it, 0 returns the value as is. """ ret: int if c > 0: ret = self.depth self.depth += 1 elif c < 0: self.depth -= 1 ret = self.depth else: ret = self.depth return ret @dataclass class Colored: """ Tag an item with a color "depth". Depth is an arbitarary (positive) integer, from 0 and incrementing by up. Should map to colors. """ depth: int item: str def color(depth: int, c: str) -> Colored: """Write a highlighted string.""" return Colored(depth, c) def colorize(strm: io.TextIOBase) -> Generator[str | Colored, None, None]: """ Color all parenthesis in the given stream. Each character in the stream is checked if it's a either a parenthesis, square bracket, or curly bracket, and if so, a `Colored` token is returned. If a single or double qutation mark is encountered, then "string mode" is entered, in which parentheses are treated like any other character (e.g. not highlighted). String mode continues until the corresponding token is found. If a token is preceeded by a backslash, then first the backslash is yielded, followed by the token as a raw character. .. code-block:: python :caption: Example usage with a string as input >>> import io >>> list(colorize(io.StringIO"(Hello)"))) [ Colored(depth=0, item='('), 'H', 'e', 'l', 'l', 'o', Colored(depth=0, item=')') ] :param strm: Text stream to get contents from. Use ``io.StringIO`` if you want to pass a string. :returns: Yields a stream of tokens, where each token is either a literal string, laternatively, it's a ``Colored`` object, which attaches a color number to the string. """ in_string: Literal[False] | Literal['"'] | Literal["'"] = False paren = 0 brace = 0 brack = 0 depth = Stackpointer() while c := strm.read(1): match [in_string, c]: case ['"', '"']: # The string is ending in_string = False yield c case ["'", "'"]: in_string = False # The string is ending yield c case [False, _]: # We are not in a string, emit the next character # colored if it's a parenthesis (or similar), and # plainly otherwise. # Can also ether strings match c: case '(': yield color(depth(1), c) paren += 1 case ')': yield color(depth(-1), c) paren -= 1 case '[': yield color(depth(1), c) brack += 1 case ']': yield color(depth(-1), c) brack -= 1 case '{': yield color(depth(1), c) brace += 1 case '}': yield color(depth(-1), c) brace -= 1 case "'" | '"': in_string = c yield c case '\\': yield c yield strm.read(1) case c: yield c case [_, _]: yield c