diff --git a/PyTeX/format/macros.py b/PyTeX/format/macros.py new file mode 100644 index 0000000..23f9bd9 --- /dev/null +++ b/PyTeX/format/macros.py @@ -0,0 +1,25 @@ +from typing import List, Union +from enum import Enum + + +class MacroReplacement(Enum): + author = 'author' + shortauthor = 'shortauthor' + date = 'date' + year = 'year' + version = 'version', + file_name = 'file_name' + name = 'name' # class or package name + repo_version = 'repo_version' + pytex_version = 'pytex_version' + + +class Macro: + def __init__(self): + raise NotImplementedError + + def matches(self, line: str) -> bool: + raise NotImplementedError + + def apply(self, line: str, *args, **kwargs) -> Union[str, List[str]]: + raise NotImplementedError diff --git a/PyTeX/format/tex_formatter.py b/PyTeX/format/tex_formatter.py index f1249d5..7ca259d 100644 --- a/PyTeX/format/tex_formatter.py +++ b/PyTeX/format/tex_formatter.py @@ -1,4 +1,4 @@ -from typing import List, TextIO +from typing import List, TextIO, Optional, Tuple, Dict from pathlib import Path from .pytex_formatter import PyTeXFormatter from .macros import Macro @@ -9,6 +9,7 @@ class LineStream: self._file = filename self._handle = open(filename, 'r') self._cached_lines: List[str] = [] + self._file_exhausted: bool = False def current_line(self): return self.future_line(0) @@ -43,21 +44,66 @@ class LineStream: ) if self._cached_lines[-1] == '': self._handle.close() + self._file_exhausted = True + @property + def exhausted(self) -> bool: + return self._file_exhausted & len(self._cached_lines) == 0 class TexFormatter(PyTeXFormatter): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._macros: List[Macro] = [] + self._line_stream: LineStream = None self._output_file: TextIO = None + @property + def line_stream(self) -> LineStream: + if self._line_stream is None: + self._line_stream = LineStream(self.input_file) + return self._line_stream + + @property + def macros(self) -> List[Macro]: + return self._macros + def _handle_macro(self, macro: Macro): - self._current_line = macro.apply(self._current_line, self) + res = macro.apply( + self.line_stream.current_line(), + self + ) + if isinstance(res, str): + self.line_stream.set_line(res) + else: + self.line_stream.pop_line() + self.line_stream.push_lines(res) def _shipout_line(self): - self.write_line(self._current_line.rstrip()) + line = self.line_stream.pop_line().rstrip() + self.write_line(line) def write_line(self, line: str): - self._output_file.writelines([line]) + self._output_file.write(line) + def open_output_stream(self): + raise NotImplementedError + + @property + def future_config(self) -> Optional[List[Tuple[str, Dict]]]: + raise NotImplementedError + + def format(self, build_dir: Path, overwrite: bool = False) -> Optional[List[Tuple[str, Dict]]]: + self.open_output_stream() + while not self.line_stream.exhausted: + recent_replacement = True + while recent_replacement: + recent_replacement = False + for macro in self.macros: + if macro.matches(self.line_stream.current_line()): + self._handle_macro(macro) + recent_replacement = True + break + self._shipout_line() + self._output_file.close() + return self.future_config