pytex/PyTeX/format/macros.py

294 lines
9.1 KiB
Python

import re
from typing import List, Union, Tuple, Dict
from .constants import *
from .enums import FormatterProperty, Argument, FormatterMode
from abc import ABC, abstractmethod
from .errors import *
class MacroReplacement:
def __init__(
self,
replacement: str,
*args,
**kwargs,
):
if 'format_type' in kwargs.keys():
self.format_type = kwargs['format_type']
else:
self.format_type = '%'
self.replacement: str = replacement
self.args = args
self.kwargs = kwargs
def make_format_args(self, formatter, *call_args) -> Tuple[Tuple, Dict]:
new_args = []
for arg in self.args:
if type(arg) == FormatterProperty:
try:
new_args.append(formatter.attribute_dict[arg.value])
except:
raise NotImplementedError
elif type(arg) == Argument:
try:
new_args.append(call_args[arg.value - 1])
except:
raise NotImplementedError
elif type(arg) == str:
new_args.append(arg)
else:
raise NotImplementedError
new_kwargs = {}
for kw in self.kwargs.keys():
if type(self.kwargs[kw]) == FormatterProperty:
new_kwargs[kw] = formatter.attribute_dict[self.kwargs[kw].value]
elif type(self.kwargs[kw]) == Argument:
new_kwargs[kw] = call_args[self.kwargs[kw].value - 1]
elif type(self.kwargs[kw]) == str:
new_kwargs[kw] = self.kwargs[kw]
else:
raise NotImplementedError
return tuple(new_args), new_kwargs
def format(self, formatter, *call_args) -> str:
args, kwargs = self.make_format_args(formatter, *call_args)
if self.format_type == '%':
if self.kwargs:
raise NotImplementedError # Currently, not supported
return self.replacement % args
elif self.format_type == '{':
return self.replacement.format(
*args, **kwargs, **formatter.attribute_dict
)
else:
raise NotImplementedError
class Macro(ABC):
@abstractmethod
def __init__(self):
raise NotImplementedError
@abstractmethod
def matches(self, line: str) -> bool:
raise NotImplementedError
@abstractmethod
def apply(self, line: str, formatter) -> Tuple[Union[str, List[str]], bool]:
"""
:param line: Line where macro matches
:param formatter:
:return: First: replacement. Second: indicates direct shipout if True
"""
raise NotImplementedError
class SimpleMacro(Macro):
def __init__(
self,
macroname: str,
macro_replacement: MacroReplacement
):
self.macroname = macroname
self.macro_replacement = macro_replacement
def matches(self, line: str) -> bool:
return line.find(FORMATTER_PREFIX + self.macroname) != -1
def apply(self, line: str, formatter) -> Tuple[Union[str, List[str]], bool]:
return line.replace(
FORMATTER_PREFIX + self.macroname,
self.macro_replacement.format(
formatter
)), False
class SingleLineMacro(Macro, ABC):
def __init__(
self,
strip: str = ' %\n'
):
self.strip = strip
@abstractmethod
def _apply(self, line, formatter) -> Union[Union[str, List[str]], Tuple[Union[str, List[str]], bool]]:
pass
def apply(self, line: str, formatter) -> Tuple[Union[str, List[str]], bool]:
replacement = self._apply(line, formatter)
if isinstance(replacement, tuple):
return replacement
else:
return replacement, True
@abstractmethod
def _matches(self, line: str) -> Optional[str]:
pass
def matches(self, line: str) -> bool:
match = self._matches(line.strip(self.strip))
if match is None:
return False
else:
if not line.strip(self.strip) == match:
raise NotImplementedError
return True
class RegexSingleLineMacro(SingleLineMacro, ABC):
def __init__(
self,
regex: str,
strip: str = ' %\n'
):
self.regex = regex
super(RegexSingleLineMacro, self).__init__(strip)
def _matches(self, line: str) -> Optional[str]:
match = re.search(self.regex, line)
if match is not None:
return match.group()
else:
return None
class SimpleSingleLineMacro(SingleLineMacro, ABC):
def __init__(
self,
chars: str,
strip: str = ' %\n'
):
self.chars = chars
super(SimpleSingleLineMacro, self).__init__(strip)
def _matches(self, line: str) -> Optional[str]:
return self.chars if self.chars in line else None
class GuardMacro(RegexSingleLineMacro):
def __init__(self):
super(GuardMacro, self).__init__(r'<(\*|/|@@=)[a-zA-Z]*>')
def _apply(self, line, formatter) -> Union[str, List[str]]:
match = re.search(self.regex, line)
return '%' + match.group()
class ConfigBeginMacro(SimpleSingleLineMacro):
def __init__(self):
super(ConfigBeginMacro, self).__init__(FORMATTER_PREFIX + INFILE_CONFIG_BEGIN_CONFIG)
def _apply(self, line: str, formatter) -> Union[str, List[str]]:
if formatter.mode.is_drop():
raise NotImplementedError # invalid config begin
formatter.mode = formatter.mode.to_drop()
return []
class ConfigEndMacro(SimpleSingleLineMacro):
def __init__(self):
super(ConfigEndMacro, self).__init__(FORMATTER_PREFIX + INFILE_CONFIG_END_CONFIG)
def _apply(self, line: str, formatter) -> Union[str, List[str]]:
if not formatter.mode.is_drop():
raise NotImplementedError # invalid
formatter.mode = formatter.mode.to_undrop()
return []
class ImplementationBeginMacro(SimpleSingleLineMacro):
def __init__(self):
super(ImplementationBeginMacro, self).__init__(FORMATTER_PREFIX + IMPLEMENTATION_BEGIN_MACRO)
def _apply(self, line, formatter) -> Tuple[Union[str, List[str]], bool]:
return [
r'% \begin{implementation}',
r'',
r'\section{\pkg{!name} implementation}',
r'\begin{macrocode}',
r'<*!outtype>',
r'\end{macrocode}',
r'',
r'\begin{macrocode}',
r'<@@=!!>',
r'\end{macrocode}',
], False
class ImplementationEndMacro(SimpleSingleLineMacro):
def __init__(self):
super(ImplementationEndMacro, self).__init__(FORMATTER_PREFIX + IMPLEMENTATION_END_MACRO)
def _apply(self, line, formatter) -> Tuple[Union[str, List[str]], bool]:
return [
r'\begin{macrocode}',
r'</!outtype>',
r'\end{macrocode}',
r'',
r'% \end{implementation}'
], False
class MacroCodeBeginMacro(SimpleSingleLineMacro):
def __init__(self):
super(MacroCodeBeginMacro, self).__init__(r'\begin{macrocode}')
def _apply(self, line: str, formatter) -> Union[str, List[str]]:
if not formatter.mode == FormatterMode.meta:
raise PyTeXInvalidBeginMacroCodeUsageError(
r"\begin{macrocode} used outside meta context"
)
formatter.mode = FormatterMode.macrocode
return r'% \begin{macrocode}'
class MacroCodeEndMacro(SimpleSingleLineMacro):
def __init__(self):
super(MacroCodeEndMacro, self).__init__(r'\end{macrocode}')
def _apply(self, line: str, formatter) -> Union[str, List[str]]:
if not formatter.mode == FormatterMode.macrocode:
raise PyTeXInvalidEndMacroCodeUsageError(
r"\end{macrocode} used outside macrocode context"
)
formatter.mode = FormatterMode.meta
return r'% \end{macrocode}'
class ArgumentMacro(Macro):
def __init__(
self,
macroname: str,
num_args: int,
macro_replacement: MacroReplacement
):
self.macroname = macroname
self.num_args = num_args
self.macro_replacement: MacroReplacement = macro_replacement
self._search_regex = re.compile(r'{keyword}\({arguments}(?<!@)\)'.format(
keyword=FORMATTER_PREFIX + self.macroname,
arguments=','.join(['(.*?)'] * self.num_args)
))
def matches(self, line: str) -> bool:
if line.find('!!') != -1:
pass
match = re.search(self._search_regex, line)
if match is None:
return False
else:
return True
def apply(self, line: str, formatter) -> Tuple[Union[str, List[str]], bool]:
match = re.search(self._search_regex, line)
if match is None:
raise NotImplementedError
replacement = self.macro_replacement.format(
formatter, match.groups()
)
return line.replace(
match.group(),
replacement
), False