import re
from typing import Dict, Optional, Tuple, Union
PREAMBLE = """
\\newcommand{\\vecteurno}[2]{#1,\\dots,#2}
\\newcommand{\\R}{\\mathbb{R}}
\\newcommand{\\pa}[1]{\\left(#1\\right)}
\\newcommand{\\cro}[1]{\\left[#1\\right]}
\\newcommand{\\acc}[1]{\\left\\{#1\\right\\}}
\\newcommand{\\vecteur}[2]{\\left(#1,\\dots,#2\\right)}
\\newcommand{\\N}[0]{\\mathbb{N}}
\\newcommand{\\indicatrice}[1]{ {1\\!\\!1}_{\\left\\{#1\\right\\}} }
\\newcommand{\\infegal}[0]{\\leqslant}
\\newcommand{\\supegal}[0]{\\geqslant}
\\newcommand{\\ensemble}[2]{\\left\\{#1,\\dots,#2\\right\\}}
\\newcommand{\\fleche}[1]{\\overrightarrow{#1}}
\\newcommand{\\intervalle}[2]{\\left\\{#1,\\cdots,#2\\right\\}}
\\newcommand{\\independent}[0]{\\perp \\!\\!\\! \\perp}
\\newcommand{\\esp}{\\mathbb{E}}
\\newcommand{\\espf}[2]{\\mathbb{E}_{#1}\\left(#2\\right)}
\\newcommand{\\var}{\\mathbb{V}}
\\newcommand{\\pr}[1]{\\mathbb{P}\\left(#1\\right)}
\\newcommand{\\loi}[0]{{\\cal L}}
\\newcommand{\\norm}[1]{\\left\\Vert#1\\right\\Vert}
\\newcommand{\\norme}[1]{\\left\\Vert#1\\right\\Vert}
\\newcommand{\\scal}[2]{\\left<#1,#2\\right>}
\\newcommand{\\dans}[0]{\\rightarrow}
\\newcommand{\\partialfrac}[2]{\\frac{\\partial #1}{\\partial #2}}
\\newcommand{\\partialdfrac}[2]{\\dfrac{\\partial #1}{\\partial #2}}
\\newcommand{\\trace}[1]{tr\\left(#1\\right)}
\\newcommand{\\sac}[0]{|}
\\newcommand{\\abs}[1]{\\left|#1\\right|}
\\newcommand{\\loinormale}[2]{{\\cal N} \\left(#1,#2\\right)}
\\newcommand{\\loibinomialea}[1]{{\\cal B} \\left(#1\\right)}
\\newcommand{\\loibinomiale}[2]{{\\cal B} \\left(#1,#2\\right)}
\\newcommand{\\loimultinomiale}[1]{{\\cal M} \\left(#1\\right)}
\\newcommand{\\variance}[1]{\\mathbb{V}\\left(#1\\right)}
\\newcommand{\\intf}[1]{\\left\\lfloor #1 \\right\\rfloor}
"""
[docs]
def build_regex(text: Optional[str] = None) -> Dict[str, Union[str, Tuple[str, str]]]:
    """
    Parses a preamble in latex and builds regular expressions
    based on it.
    """
    if text is None:
        text = PREAMBLE
    lines = [_ for _ in text.split("\n") if "newcommand" in _]
    reg = re.compile(r"newcommand\{\\([a-zA-Z]+)\}(\[([0-9])\])?\{(.+)\}")
    res = {}
    for i, line in enumerate(lines):
        match = reg.search(line)
        assert match, f"Unable to match pattern reg={reg} in line {i}: {line!r}"
        name, n, pat = match.group(1), match.group(3), match.group(4)
        if n is None or int(n) == 0:
            res[name] = pat
        elif name in {"pa", "cro", "acc", "abs"}:
            # These can be nested expression.
            spl = line.split("#1")
            begin, end = spl[0][-1], spl[-1][-2]
            if begin == "{":
                begin = "\\{"
            if end == "}":
                end = "\\}"
            res[name] = (
                lambda s, name=name, begin=begin, end=end: replace_nested_bracked(
                    s, name=name, begin=begin, end=end
                )
            )
        else:
            look = f"\\\\{name} *" + "\\{(.+?)\\}" * int(n)
            for c in "\\":
                pat = pat.replace(c, f"\\{c}")
            for k in range(int(n)):
                pat = pat.replace(f"#{k+1}", f"\\{k+1}")
            res[name] = (look, pat)
    return res 
def replace_nested_bracked(text: str, name: str, begin: str, end: str) -> str:
    """
    Replaces brackets, nested brackets...
    """
    find_left = f"\\{name}" + "{"
    if find_left not in text:
        return text
    content = [[]]
    i = 0
    while i < len(text):
        if i + len(find_left) < len(text) and text[i : i + len(find_left)] == find_left:
            content.append([])
            content[-1].append(f"\\left{begin}")
            i += len(find_left)
        elif text[i] == "{":
            content.append([])
            content[-1].append(text[i])
            i += 1
        elif text[i] == "}":
            content[-1].append(
                f"\\right{end}" if content[-1][0].startswith("\\left") else text[i]
            )
            content[-2].append("".join(content.pop()))
            i += 1
        else:
            content[-1].append(text[i])
            i += 1
    return "".join(content[0])
[docs]
def replace_latex_command(
    text: str,
    patterns: Optional[Dict[str, Union[str, Tuple[str, str]]]] = None,
    verbose: int = 0,
) -> str:
    """
    Replaces a latex by its raw expression.
    :param text: text
    :param patterns: one in the known list or None for all
    :param verbose: verbosity
    :return: modified text
    The default patterns are defined by:
    .. runpython::
        :showcode:
        from sphinx_runpython.tools.latex_functions import PREAMBLE
        print(PREAMBLE)
    With gives:
    .. runpython::
        :showcode:
        import pprint
        from sphinx_runpython.tools.latex_functions import build_regex
        pprint.pprint(build_regex())
    """
    if patterns is None:
        patterns = build_regex()
    for k, v in patterns.items():
        if verbose:
            text0 = text
        if isinstance(v, str):
            text = text.replace(f"\\{k}", v)
            if verbose and text != text0:
                print(f"[replace_latex_command] (1) {k!r}:[{text0}] -> [{text}]")
        elif isinstance(v, tuple) and len(v) == 2:
            try:
                text = re.sub(v[0], v[1], text)
            except re.error as e:
                raise AssertionError(
                    f"Unable to replace pattern {v[0]!r} by {v[1]!r} for text={text!r}"
                ) from e
            if verbose and text != text0:
                print(f"[replace_latex_command] (2) {k!r}:[{text0}] -> [{text}]")
        elif callable(v):
            text = v(text)
            if verbose and text != text0:
                print(f"[replace_latex_command] (3) {k!r}:[{text0}] -> [{text}]")
        else:
            raise AssertionError(f"Unable to understand v={v!r} for k={k!r}")
    return text