mirror of
				https://github.com/aykhans/AzSuicideDataVisualization.git
				synced 2025-11-03 11:29:59 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			213 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			213 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
"""Export to PDF via latex"""
 | 
						|
 | 
						|
# Copyright (c) IPython Development Team.
 | 
						|
# Distributed under the terms of the Modified BSD License.
 | 
						|
 | 
						|
import os
 | 
						|
import shutil
 | 
						|
import subprocess
 | 
						|
import sys
 | 
						|
from tempfile import TemporaryDirectory
 | 
						|
 | 
						|
from traitlets import Bool, Instance, Integer, List, Unicode, default
 | 
						|
 | 
						|
from ..utils import _contextlib_chdir
 | 
						|
from .latex import LatexExporter
 | 
						|
 | 
						|
 | 
						|
class LatexFailed(IOError):
 | 
						|
    """Exception for failed latex run
 | 
						|
 | 
						|
    Captured latex output is in error.output.
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, output):
 | 
						|
        self.output = output
 | 
						|
 | 
						|
    def __unicode__(self):
 | 
						|
        return "PDF creating failed, captured latex output:\n%s" % self.output
 | 
						|
 | 
						|
    def __str__(self):
 | 
						|
        u = self.__unicode__()
 | 
						|
        return u
 | 
						|
 | 
						|
 | 
						|
def prepend_to_env_search_path(varname, value, envdict):
 | 
						|
    """Add value to the environment variable varname in envdict
 | 
						|
 | 
						|
    e.g. prepend_to_env_search_path('BIBINPUTS', '/home/sally/foo', os.environ)
 | 
						|
    """
 | 
						|
    if not value:
 | 
						|
        return  # Nothing to add
 | 
						|
 | 
						|
    envdict[varname] = value + os.pathsep + envdict.get(varname, "")
 | 
						|
 | 
						|
 | 
						|
class PDFExporter(LatexExporter):
 | 
						|
    """Writer designed to write to PDF files.
 | 
						|
 | 
						|
    This inherits from `LatexExporter`. It creates a LaTeX file in
 | 
						|
    a temporary directory using the template machinery, and then runs LaTeX
 | 
						|
    to create a pdf.
 | 
						|
    """
 | 
						|
 | 
						|
    export_from_notebook = "PDF via LaTeX"
 | 
						|
 | 
						|
    latex_count = Integer(3, help="How many times latex will be called.").tag(config=True)
 | 
						|
 | 
						|
    latex_command = List(
 | 
						|
        ["xelatex", "{filename}", "-quiet"], help="Shell command used to compile latex."
 | 
						|
    ).tag(config=True)
 | 
						|
 | 
						|
    bib_command = List(["bibtex", "{filename}"], help="Shell command used to run bibtex.").tag(
 | 
						|
        config=True
 | 
						|
    )
 | 
						|
 | 
						|
    verbose = Bool(False, help="Whether to display the output of latex commands.").tag(config=True)
 | 
						|
 | 
						|
    texinputs = Unicode(help="texinputs dir. A notebook's directory is added")
 | 
						|
    writer = Instance("nbconvert.writers.FilesWriter", args=(), kw={"build_directory": "."})
 | 
						|
 | 
						|
    output_mimetype = "application/pdf"
 | 
						|
 | 
						|
    _captured_output = List()
 | 
						|
 | 
						|
    @default("file_extension")
 | 
						|
    def _file_extension_default(self):
 | 
						|
        return ".pdf"
 | 
						|
 | 
						|
    @default("template_extension")
 | 
						|
    def _template_extension_default(self):
 | 
						|
        return ".tex.j2"
 | 
						|
 | 
						|
    def run_command(self, command_list, filename, count, log_function, raise_on_failure=None):
 | 
						|
        """Run command_list count times.
 | 
						|
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        command_list : list
 | 
						|
            A list of args to provide to Popen. Each element of this
 | 
						|
            list will be interpolated with the filename to convert.
 | 
						|
        filename : unicode
 | 
						|
            The name of the file to convert.
 | 
						|
        count : int
 | 
						|
            How many times to run the command.
 | 
						|
        raise_on_failure: Exception class (default None)
 | 
						|
            If provided, will raise the given exception for if an instead of
 | 
						|
            returning False on command failure.
 | 
						|
 | 
						|
        Returns
 | 
						|
        -------
 | 
						|
        success : bool
 | 
						|
            A boolean indicating if the command was successful (True)
 | 
						|
            or failed (False).
 | 
						|
        """
 | 
						|
        command = [c.format(filename=filename) for c in command_list]
 | 
						|
 | 
						|
        # This will throw a clearer error if the command is not found
 | 
						|
        cmd = shutil.which(command_list[0])
 | 
						|
        if cmd is None:
 | 
						|
            link = "https://nbconvert.readthedocs.io/en/latest/install.html#installing-tex"
 | 
						|
            raise OSError(
 | 
						|
                "{formatter} not found on PATH, if you have not installed "
 | 
						|
                "{formatter} you may need to do so. Find further instructions "
 | 
						|
                "at {link}.".format(formatter=command_list[0], link=link)
 | 
						|
            )
 | 
						|
 | 
						|
        times = "time" if count == 1 else "times"
 | 
						|
        self.log.info("Running %s %i %s: %s", command_list[0], count, times, command)
 | 
						|
 | 
						|
        shell = sys.platform == "win32"
 | 
						|
        if shell:
 | 
						|
            command = subprocess.list2cmdline(command)
 | 
						|
        env = os.environ.copy()
 | 
						|
        prepend_to_env_search_path("TEXINPUTS", self.texinputs, env)
 | 
						|
        prepend_to_env_search_path("BIBINPUTS", self.texinputs, env)
 | 
						|
        prepend_to_env_search_path("BSTINPUTS", self.texinputs, env)
 | 
						|
 | 
						|
        with open(os.devnull, "rb") as null:
 | 
						|
            stdout = subprocess.PIPE if not self.verbose else None
 | 
						|
            for _ in range(count):
 | 
						|
                p = subprocess.Popen(
 | 
						|
                    command,
 | 
						|
                    stdout=stdout,
 | 
						|
                    stderr=subprocess.STDOUT,
 | 
						|
                    stdin=null,
 | 
						|
                    shell=shell,
 | 
						|
                    env=env,
 | 
						|
                )
 | 
						|
                out, _ = p.communicate()
 | 
						|
                if p.returncode:
 | 
						|
                    if self.verbose:
 | 
						|
                        # verbose means I didn't capture stdout with PIPE,
 | 
						|
                        # so it's already been displayed and `out` is None.
 | 
						|
                        out = ""
 | 
						|
                    else:
 | 
						|
                        out = out.decode("utf-8", "replace")
 | 
						|
                    log_function(command, out)
 | 
						|
                    self._captured_output.append(out)
 | 
						|
                    if raise_on_failure:
 | 
						|
                        raise raise_on_failure(
 | 
						|
                            'Failed to run "{command}" command:\n{output}'.format(
 | 
						|
                                command=command, output=out
 | 
						|
                            )
 | 
						|
                        )
 | 
						|
                    return False  # failure
 | 
						|
        return True  # success
 | 
						|
 | 
						|
    def run_latex(self, filename, raise_on_failure=LatexFailed):
 | 
						|
        """Run xelatex self.latex_count times."""
 | 
						|
 | 
						|
        def log_error(command, out):
 | 
						|
            self.log.critical("%s failed: %s\n%s", command[0], command, out)
 | 
						|
 | 
						|
        return self.run_command(
 | 
						|
            self.latex_command, filename, self.latex_count, log_error, raise_on_failure
 | 
						|
        )
 | 
						|
 | 
						|
    def run_bib(self, filename, raise_on_failure=False):
 | 
						|
        """Run bibtex one time."""
 | 
						|
        filename = os.path.splitext(filename)[0]
 | 
						|
 | 
						|
        def log_error(command, out):
 | 
						|
            self.log.warning(
 | 
						|
                "%s had problems, most likely because there were no citations", command[0]
 | 
						|
            )
 | 
						|
            self.log.debug("%s output: %s\n%s", command[0], command, out)
 | 
						|
 | 
						|
        return self.run_command(self.bib_command, filename, 1, log_error, raise_on_failure)
 | 
						|
 | 
						|
    def from_notebook_node(self, nb, resources=None, **kw):
 | 
						|
        latex, resources = super().from_notebook_node(nb, resources=resources, **kw)
 | 
						|
        # set texinputs directory, so that local files will be found
 | 
						|
        if resources and resources.get("metadata", {}).get("path"):
 | 
						|
            self.texinputs = resources["metadata"]["path"]
 | 
						|
        else:
 | 
						|
            self.texinputs = os.getcwd()
 | 
						|
 | 
						|
        self._captured_outputs = []
 | 
						|
        with TemporaryDirectory() as td, _contextlib_chdir.chdir(td):
 | 
						|
            notebook_name = "notebook"
 | 
						|
            resources["output_extension"] = ".tex"
 | 
						|
            tex_file = self.writer.write(latex, resources, notebook_name=notebook_name)
 | 
						|
            self.log.info("Building PDF")
 | 
						|
            self.run_latex(tex_file)
 | 
						|
            if self.run_bib(tex_file):
 | 
						|
                self.run_latex(tex_file)
 | 
						|
 | 
						|
            pdf_file = notebook_name + ".pdf"
 | 
						|
            if not os.path.isfile(pdf_file):
 | 
						|
                raise LatexFailed("\n".join(self._captured_output))
 | 
						|
            self.log.info("PDF successfully created")
 | 
						|
            with open(pdf_file, "rb") as f:
 | 
						|
                pdf_data = f.read()
 | 
						|
 | 
						|
        # convert output extension to pdf
 | 
						|
        # the writer above required it to be tex
 | 
						|
        resources["output_extension"] = ".pdf"
 | 
						|
        # clear figure outputs, extracted by latex export,
 | 
						|
        # so we don't claim to be a multi-file export.
 | 
						|
        resources.pop("outputs", None)
 | 
						|
 | 
						|
        return pdf_data, resources
 |