Source code for seutil.latex.Macro

from pathlib import Path
import re
from typing import *
from .. import LoggingUtils, IOUtils


[docs]class Macro: logger = LoggingUtils.get_logger("latex.Macro") T = TypeVar("T") def __init__(self, key: str, value: Optional[T] = None, value_func: Optional[Callable[[Dict[str, "Macro"]], T]] = None, tostring_func: Optional[Callable[[T], str]] = None): """ Defines a latex macro, using "\DefMacro{key}{value}". :param key: the key of the macro. :param value: the value of the macro, can be None. :param value_func: the (lazily evaluated) function to get the value of the macro, can be None. The function will receive one argument of a dictionary of {macro.key, macro} of macros defined in the same file, and should return the evaluated value. :param tostring_func: the function to format the value to string, can be None. If set to None, __str__() will be used. """ self.key: str = key self.value: Optional[Macro.T] = value self.value_func: Optional[Callable[[Dict[str, Macro]], Macro.T]] = value_func self.tostring_func: Optional[Callable[[Macro.T], str]] = tostring_func return
[docs] def eval_content(self, macros_indexed: Dict[str, "Macro"]) -> str: """ Evaluates the latex macro, and formats to a string that defines the macro (i.e., \DefMacro{key}{value}). If both value and value_func are defined, will use the value_func to update the value. :param macros_indexed: the indexed dictionary of {macro.key, macro}. :requires: not (self.value is None and self.value_func is None) :return: the string representation of the macro. """ if self.value is None and self.value_func is None: raise ValueError("Cannot evaluate a macro without any value definition") # end if if self.value_func is not None: self.value = self.value_func(macros_indexed) # end if self.logger.info(f"Macro {self.key} evaluates to {self.value}") tostring_func = self.tostring_func if self.tostring_func is not None else str return f"\\DefMacro{{{self.key}}}{{{tostring_func(self.value)}}}"
# Deprecated
[docs] @classmethod def define(cls, key: str, value_fmt: Union[str, Any], *values, **values_items) -> "Macro": if len(values) != 0 or len(values_items) != 0: return Macro(key, value=value_fmt.format(*values, **values_items)) else: return Macro(key, value=value_fmt)
# end if
[docs] def use(self) -> str: latex_line = f"\\UseMacro{{{self.key}}}" return latex_line
RE_DEF_MACRO = re.compile(r"\\DefMacro{(?P<key>[^}]+)}{(?P<value>[^}]*)}")
[docs] @classmethod def load_from_file(cls, file: Path) -> Dict[str, "Macro"]: """ Loads the macros from a latex file. Will convert the value of the macros to int or float, if possible. :param file: the latex file. :return: the indexed dictionary of {macro.key, macro}. """ macros_indexed: Dict[str, Macro] = dict() lines: List[str] = IOUtils.load(file, "txt").split("\n") for line in lines: match = cls.RE_DEF_MACRO.fullmatch(line.strip()) if match is not None: key = match.group("key") value = match.group("value") # Try to convert to int, then (if failing) float. try: value = int(value) except: try: value = float(value) except: pass # end try, try macros_indexed[key] = Macro(key, value) # end if # end for return macros_indexed