Source code for pyexlatex.logic.output.api.builders.base

import os
import subprocess
from subprocess import CalledProcessError
from typing import Optional, List, Sequence

from future.utils import raise_from
from data import Data as I
from data.decorators import data
from shutilwhich import which
from tempdir import TempDir

from latex.exc import LatexBuildError


[docs]class BaseBuilder: """ The base class for LaTeX file builders """ output_extension: str pre_file_output_args: Sequence[str] = ( '-interaction=batchmode', '-halt-on-error', '-shell-escape', '-file-line-error' ) post_file_output_args: Sequence[str] = tuple() default_executable: str
[docs] def __init__(self, executable: Optional[str] = None, bibtex: str = 'bibtex', max_runs: int = 15): if executable is None: executable = self.default_executable self.executable = executable self.bibtex = bibtex self.max_runs = max_runs
@data('source') def build(self, source, texinputs: Optional[List[str]] = None, run_bibtex: bool = False): if texinputs is None: texinputs = [] with TempDir() as tmpdir,\ source.temp_saved(suffix='.latex', dir=tmpdir) as tmp: # close temp file, so other processes can access it also on Windows tmp.close() called_bibtex = False # calculate output filename base_fn = os.path.splitext(tmp.name)[0] output_fn = base_fn + f'.{self.output_extension}' aux_fn = base_fn + '.aux' args = [self.executable, *self.pre_file_output_args, tmp.name, *self.post_file_output_args] # create environment newenv = os.environ.copy() inputs_value = os.pathsep.join(texinputs) + os.pathsep newenv['TEXINPUTS'] = inputs_value newenv['BSTINPUTS'] = inputs_value newenv['BIBINPUTS'] = inputs_value # run until aux file settles prev_aux = None runs_left = self.max_runs self._pre_compile(tmpdir, base_fn) while runs_left: try: subprocess.check_call(args, cwd=tmpdir, env=newenv, stdin=open(os.devnull, 'r'), stdout=open(os.devnull, 'w'), ) except CalledProcessError as e: raise_from(LatexBuildError(base_fn + '.log'), e) # check aux-file aux = open(aux_fn, 'rb').read() if aux == prev_aux: # Stable aux file if run_bibtex and not called_bibtex: called_bibtex = True # ensure only called once bibtex_args = [self.bibtex, os.path.basename(aux_fn)] try: subprocess.check_call(bibtex_args, cwd=tmpdir, env=newenv, stdin=open(os.devnull, 'r'), stdout=open(os.devnull, 'w'), ) except CalledProcessError as e: # TODO [#9]: better handling for LaTeX exceptions # # Parse log file, raise proper exception with open(base_fn + '.blg', 'r') as f: log_contents = f.read() raise Exception(log_contents) continue # go back into the loop to process with biblography break prev_aux = aux runs_left -= 1 else: raise RuntimeError( 'Maximum number of runs ({}) without a stable .aux file ' 'reached.'.format(self.max_runs)) return I(open(output_fn, 'rb').read(), encoding=None)
[docs] def is_available(self): return bool(which(self.executable))
def _pre_compile(self, temp_dir: str, base_file_name: str): pass