From fee8acedca9dbfc7c54d5682971ef385ce45186a 2014-04-22 20:15:22 From: Thomas Kluyver Date: 2014-04-22 20:15:22 Subject: [PATCH] Merge pull request #5586 from minrk/pdf-exporter add PDFExporter --- diff --git a/IPython/html/nbconvert/handlers.py b/IPython/html/nbconvert/handlers.py index 2910205..1cb9ba9 100644 --- a/IPython/html/nbconvert/handlers.py +++ b/IPython/html/nbconvert/handlers.py @@ -73,7 +73,7 @@ class NbconvertFileHandler(IPythonHandler): @web.authenticated def get(self, format, path='', name=None): - exporter = get_exporter(format, config=self.config) + exporter = get_exporter(format, config=self.config, log=self.log) path = path.strip('/') model = self.notebook_manager.get_notebook(name=name, path=path) diff --git a/IPython/html/static/notebook/js/menubar.js b/IPython/html/static/notebook/js/menubar.js index bb365dc..7e496b6 100644 --- a/IPython/html/static/notebook/js/menubar.js +++ b/IPython/html/static/notebook/js/menubar.js @@ -1,9 +1,5 @@ -//---------------------------------------------------------------------------- -// Copyright (C) 2008-2011 The IPython Development Team -// -// Distributed under the terms of the BSD License. The full license is in -// the file COPYING, distributed as part of this software. -//---------------------------------------------------------------------------- +// Copyright (c) IPython Development Team. +// Distributed under the terms of the Modified BSD License. //============================================================================ // MenuBar @@ -125,6 +121,10 @@ var IPython = (function (IPython) { that._nbconvert('rst', true); }); + this.element.find('#download_pdf').click(function () { + that._nbconvert('pdf', true); + }); + this.element.find('#rename_notebook').click(function () { IPython.save_widget.rename_notebook(); }); diff --git a/IPython/html/templates/notebook.html b/IPython/html/templates/notebook.html index c0abbf4..45ab6fa 100644 --- a/IPython/html/templates/notebook.html +++ b/IPython/html/templates/notebook.html @@ -84,6 +84,7 @@ class="notebook_app"
  • Python (.py)
  • HTML (.html)
  • reST (.rst)
  • +
  • PDF (.pdf)
  • diff --git a/IPython/nbconvert/exporters/__init__.py b/IPython/nbconvert/exporters/__init__.py index 23f6195..6397472 100644 --- a/IPython/nbconvert/exporters/__init__.py +++ b/IPython/nbconvert/exporters/__init__.py @@ -4,6 +4,7 @@ from .slides import SlidesExporter from .templateexporter import TemplateExporter from .latex import LatexExporter from .markdown import MarkdownExporter +from .pdf import PDFExporter from .python import PythonExporter from .rst import RSTExporter from .exporter import Exporter diff --git a/IPython/nbconvert/exporters/export.py b/IPython/nbconvert/exporters/export.py index e9fff80..62f909e 100644 --- a/IPython/nbconvert/exporters/export.py +++ b/IPython/nbconvert/exporters/export.py @@ -1,17 +1,7 @@ -""" -Module containing single call export functions. -""" -#----------------------------------------------------------------------------- -# Copyright (c) 2013, the IPython Development Team. -# -# Distributed under the terms of the Modified BSD License. -# -# The full license is in the file COPYING.txt, distributed with this software. -#----------------------------------------------------------------------------- +"""Module containing single call export functions.""" -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. from functools import wraps @@ -24,6 +14,7 @@ from .templateexporter import TemplateExporter from .html import HTMLExporter from .slides import SlidesExporter from .latex import LatexExporter +from .pdf import PDFExporter from .markdown import MarkdownExporter from .python import PythonExporter from .rst import RSTExporter @@ -79,6 +70,7 @@ __all__ = [ 'export_custom', 'export_slides', 'export_latex', + 'export_pdf', 'export_markdown', 'export_python', 'export_rst', @@ -134,6 +126,7 @@ exporter_map = dict( html=HTMLExporter, slides=SlidesExporter, latex=LatexExporter, + pdf=PDFExporter, markdown=MarkdownExporter, python=PythonExporter, rst=RSTExporter, diff --git a/IPython/nbconvert/postprocessors/pdf.py b/IPython/nbconvert/exporters/pdf.py similarity index 56% rename from IPython/nbconvert/postprocessors/pdf.py rename to IPython/nbconvert/exporters/pdf.py index cfd85b7..0ef2b2d 100644 --- a/IPython/nbconvert/postprocessors/pdf.py +++ b/IPython/nbconvert/exporters/pdf.py @@ -1,53 +1,41 @@ -""" -Contains writer for writing nbconvert output to PDF. -""" -#----------------------------------------------------------------------------- -#Copyright (c) 2013, the IPython Development Team. -# -#Distributed under the terms of the Modified BSD License. -# -#The full license is in the file COPYING.txt, distributed with this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- +"""Export to PDF via latex""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. import subprocess import os import sys -from IPython.utils.traitlets import Integer, List, Bool +from IPython.utils.traitlets import Integer, List, Bool, Instance +from IPython.utils.tempdir import TemporaryWorkingDirectory +from .latex import LatexExporter -from .base import PostProcessorBase -#----------------------------------------------------------------------------- -# Classes -#----------------------------------------------------------------------------- -class PDFPostProcessor(PostProcessorBase): +class PDFExporter(LatexExporter): """Writer designed to write to PDF files""" - latex_count = Integer(3, config=True, help=""" - How many times pdflatex will be called. - """) + latex_count = Integer(3, config=True, + help="How many times latex will be called." + ) - latex_command = List([u"pdflatex", u"{filename}"], config=True, help=""" - Shell command used to compile PDF.""") + latex_command = List([u"pdflatex", u"{filename}"], config=True, + help="Shell command used to compile latex." + ) - bib_command = List([u"bibtex", u"{filename}"], config=True, help=""" - Shell command used to run bibtex.""") + bib_command = List([u"bibtex", u"{filename}"], config=True, + help="Shell command used to run bibtex." + ) - verbose = Bool(False, config=True, help=""" - Whether or not to display the output of the compile call. - """) + verbose = Bool(False, config=True, + help="Whether to display the output of latex commands." + ) - temp_file_exts = List(['.aux', '.bbl', '.blg', '.idx', '.log', '.out'], - config=True, help=""" - Filename extensions of temp files to remove after running. - """) - pdf_open = Bool(False, config=True, help=""" - Whether or not to open the pdf after the compile call. - """) + temp_file_exts = List(['.aux', '.bbl', '.blg', '.idx', '.log', '.out'], config=True, + help="File extensions of temp files to remove after running." + ) + + writer = Instance("IPython.nbconvert.writers.FilesWriter", args=()) def run_command(self, command_list, filename, count, log_function): """Run command_list count times. @@ -64,7 +52,7 @@ class PDFPostProcessor(PostProcessorBase): Returns ------- - continue : bool + success : bool A boolean indicating if the command was successful (True) or failed (False). """ @@ -124,33 +112,30 @@ class PDFPostProcessor(PostProcessorBase): except OSError: pass - def open_pdf(self, filename): - """Open the pdf in the default viewer.""" - if sys.platform.startswith('darwin'): - subprocess.call(('open', filename)) - elif os.name == 'nt': - os.startfile(filename) - elif os.name == 'posix': - subprocess.call(('xdg-open', filename)) - return - - def postprocess(self, filename): - """Build a PDF by running pdflatex and bibtex""" - self.log.info("Building PDF") - cont = self.run_latex(filename) - if cont: - cont = self.run_bib(filename) - else: - self.clean_temp_files(filename) - return - if cont: - cont = self.run_latex(filename) - self.clean_temp_files(filename) - filename = os.path.splitext(filename)[0] - if os.path.isfile(filename+'.pdf'): + def from_notebook_node(self, nb, resources=None, **kw): + latex, resources = super(PDFExporter, self).from_notebook_node( + nb, resources=resources, **kw + ) + with TemporaryWorkingDirectory() as td: + notebook_name = "notebook" + tex_file = self.writer.write(latex, resources, notebook_name=notebook_name) + self.log.info("Building PDF") + rc = self.run_latex(tex_file) + if not rc: + rc = self.run_bib(tex_file) + if not rc: + rc = self.run_latex(tex_file) + + pdf_file = notebook_name + '.pdf' + if not os.path.isfile(pdf_file): + raise RuntimeError("PDF creating failed") self.log.info('PDF successfully created') - if self.pdf_open: - self.log.info('Viewer called') - self.open_pdf(filename+'.pdf') - return - + 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' + + return pdf_data, resources + diff --git a/IPython/nbconvert/exporters/tests/test_pdf.py b/IPython/nbconvert/exporters/tests/test_pdf.py new file mode 100644 index 0000000..6272605 --- /dev/null +++ b/IPython/nbconvert/exporters/tests/test_pdf.py @@ -0,0 +1,36 @@ +"""Tests for PDF export""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +import logging +import os + +from IPython.testing import decorators as dec + +from .base import ExportersTestsBase +from ..pdf import PDFExporter + + +#----------------------------------------------------------------------------- +# Class +#----------------------------------------------------------------------------- + +class TestPDF(ExportersTestsBase): + """Test PDF export""" + + exporter_class = PDFExporter + + def test_constructor(self): + """Can a PDFExporter be constructed?""" + self.exporter_class() + + + @dec.onlyif_cmds_exist('pdflatex') + @dec.onlyif_cmds_exist('pandoc') + def test_export(self): + """Smoke test PDFExporter""" + (output, resources) = self.exporter_class(latex_count=1).from_filename(self._get_notebook()) + self.assertIsInstance(output, bytes) + assert len(output) > 0 + diff --git a/IPython/nbconvert/nbconvertapp.py b/IPython/nbconvert/nbconvertapp.py index 3649fda..d5b15bf 100755 --- a/IPython/nbconvert/nbconvertapp.py +++ b/IPython/nbconvert/nbconvertapp.py @@ -1,21 +1,12 @@ #!/usr/bin/env python -"""NBConvert is a utility for conversion of .ipynb files. +"""NbConvert is a utility for conversion of .ipynb files. Command-line interface for the NbConvert conversion utility. """ -#----------------------------------------------------------------------------- -#Copyright (c) 2013, the IPython Development Team. -# -#Distributed under the terms of the Modified BSD License. -# -#The full license is in the file COPYING.txt, distributed with this software. -#----------------------------------------------------------------------------- -#----------------------------------------------------------------------------- -#Imports -#----------------------------------------------------------------------------- +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. -# Stdlib imports from __future__ import print_function import logging @@ -23,7 +14,6 @@ import sys import os import glob -# From IPython from IPython.core.application import BaseIPythonApplication, base_aliases, base_flags from IPython.core.profiledir import ProfileDir from IPython.config import catch_config_error, Configurable @@ -128,9 +118,9 @@ class NbConvertApp(BaseIPythonApplication): > ipython nbconvert mynotebook.ipynb --stdout - A post-processor can be used to compile a PDF + PDF is generated via latex - > ipython nbconvert mynotebook.ipynb --to latex --post PDF + > ipython nbconvert mynotebook.ipynb --to pdf You can get (and serve) a Reveal.js-powered slideshow @@ -174,8 +164,7 @@ class NbConvertApp(BaseIPythonApplication): postprocessor_class = DottedOrNone(config=True, help="""PostProcessor class used to write the results of the conversion""") - postprocessor_aliases = {'pdf': 'IPython.nbconvert.postprocessors.pdf.PDFPostProcessor', - 'serve': 'IPython.nbconvert.postprocessors.serve.ServePostProcessor'} + postprocessor_aliases = {'serve': 'IPython.nbconvert.postprocessors.serve.ServePostProcessor'} postprocessor_factory = Type() def _postprocessor_class_changed(self, name, old, new): diff --git a/IPython/nbconvert/postprocessors/__init__.py b/IPython/nbconvert/postprocessors/__init__.py index 9f954f3..75ff947 100644 --- a/IPython/nbconvert/postprocessors/__init__.py +++ b/IPython/nbconvert/postprocessors/__init__.py @@ -1,5 +1,4 @@ from .base import PostProcessorBase -from .pdf import PDFPostProcessor # protect against unavailable tornado try: diff --git a/IPython/nbconvert/postprocessors/tests/test_pdf.py b/IPython/nbconvert/postprocessors/tests/test_pdf.py deleted file mode 100644 index 11da2cb..0000000 --- a/IPython/nbconvert/postprocessors/tests/test_pdf.py +++ /dev/null @@ -1,69 +0,0 @@ -""" -Module with tests for the PDF post-processor -""" - -#----------------------------------------------------------------------------- -# Copyright (c) 2013, the IPython Development Team. -# -# Distributed under the terms of the Modified BSD License. -# -# The full license is in the file COPYING.txt, distributed with this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -import logging -import os - -from IPython.testing import decorators as dec - -from ...tests.base import TestsBase -from ..pdf import PDFPostProcessor - - -#----------------------------------------------------------------------------- -# Constants -#----------------------------------------------------------------------------- - -HELLO_WORLD = r"""% hello.tex - Our first LaTeX example! -\documentclass{article} -\begin{document} -Hello World! -\end{document}""" - - -#----------------------------------------------------------------------------- -# Class -#----------------------------------------------------------------------------- - -class TestPDF(TestsBase): - """Contains test functions for pdf.py""" - - - def test_constructor(self): - """Can a PDFPostProcessor be constructed?""" - PDFPostProcessor() - - - @dec.onlyif_cmds_exist('pdflatex') - def test_pdf(self): - """Can a PDF be made using the PDFPostProcessor?""" - - # Work in a temporary directory with hello world latex in it. - with self.create_temp_cwd(): - with open('a.tex', 'w') as f: - f.write(HELLO_WORLD) - - # Construct post-processor - processor = PDFPostProcessor(log=logging.getLogger()) - processor.verbose = False - processor('a.tex') - - # Check that the PDF was created. - assert os.path.isfile('a.pdf') - - # Make sure that temp files are cleaned up - for ext in processor.temp_file_exts: - assert not os.path.isfile('a'+ext) diff --git a/IPython/nbconvert/tests/test_nbconvertapp.py b/IPython/nbconvert/tests/test_nbconvertapp.py index 9cb4388..24139a8 100644 --- a/IPython/nbconvert/tests/test_nbconvertapp.py +++ b/IPython/nbconvert/tests/test_nbconvertapp.py @@ -1,22 +1,15 @@ # -*- coding: utf-8 -*- """Test NbConvertApp""" -#----------------------------------------------------------------------------- -# Copyright (C) 2013 The IPython Development Team -# -# Distributed under the terms of the BSD License. The full license is in -# the file COPYING, distributed as part of this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. import os import glob import sys from .base import TestsBase +from ..postprocessors import PostProcessorBase import IPython.testing.tools as tt from IPython.testing import decorators as dec @@ -25,6 +18,10 @@ from IPython.testing import decorators as dec # Classes and functions #----------------------------------------------------------------------------- +class DummyPost(PostProcessorBase): + def postprocess(self, filename): + print("Dummy:%s" % filename) + class TestNbConvertApp(TestsBase): """Collection of NbConvertApp tests""" @@ -79,24 +76,19 @@ class TestNbConvertApp(TestsBase): """ with self.create_temp_cwd(['notebook2.ipynb']): os.rename('notebook2.ipynb', 'notebook with spaces.ipynb') - self.call('nbconvert --log-level 0 --to latex ' - '"notebook with spaces" --post PDF ' - '--PDFPostProcessor.verbose=True') - assert os.path.isfile('notebook with spaces.tex') - assert os.path.isdir('notebook with spaces_files') + self.call('nbconvert --log-level 0 --to pdf' + ' "notebook with spaces"' + ' --PDFExporter.latex_count=1' + ' --PDFExporter.verbose=True' + ) assert os.path.isfile('notebook with spaces.pdf') - @dec.onlyif_cmds_exist('pdflatex') - @dec.onlyif_cmds_exist('pandoc') def test_post_processor(self): - """ - Do post processors work? - """ + """Do post processors work?""" with self.create_temp_cwd(['notebook1.ipynb']): - self.call('nbconvert --log-level 0 --to latex notebook1 ' - '--post PDF --PDFPostProcessor.verbose=True') - assert os.path.isfile('notebook1.tex') - assert os.path.isfile('notebook1.pdf') + out, err = self.call('nbconvert --log-level 0 --to python notebook1 ' + '--post IPython.nbconvert.tests.test_nbconvertapp.DummyPost') + self.assertIn('Dummy:notebook1.py', out) @dec.onlyif_cmds_exist('pandoc') def test_spurious_cr(self): @@ -195,10 +187,9 @@ class TestNbConvertApp(TestsBase): """ with self.create_temp_cwd(): self.create_empty_notebook(u'nb1_análisis.ipynb') - self.call('nbconvert --log-level 0 --to latex ' - '"nb1_*" --post PDF ' - '--PDFPostProcessor.verbose=True') - assert os.path.isfile(u'nb1_análisis.tex') + self.call('nbconvert --log-level 0 --to pdf "nb1_*"' + ' --PDFExporter.latex_count=1' + ' --PDFExporter.verbose=True') assert os.path.isfile(u'nb1_análisis.pdf') def test_cwd_plugin(self): diff --git a/IPython/nbconvert/writers/files.py b/IPython/nbconvert/writers/files.py index 35aca20..1e3e7d2 100644 --- a/IPython/nbconvert/writers/files.py +++ b/IPython/nbconvert/writers/files.py @@ -1,17 +1,7 @@ -""" -Contains writer for writing nbconvert output to filesystem. -""" -#----------------------------------------------------------------------------- -#Copyright (c) 2013, the IPython Development Team. -# -#Distributed under the terms of the Modified BSD License. -# -#The full license is in the file COPYING.txt, distributed with this software. -#----------------------------------------------------------------------------- +"""Contains writer for writing nbconvert output to filesystem.""" -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. import io import os @@ -19,6 +9,7 @@ import glob from IPython.utils.traitlets import Unicode from IPython.utils.path import link_or_copy +from IPython.utils.py3compat import unicode_type from .base import WriterBase @@ -110,6 +101,11 @@ class FilesWriter(WriterBase): # Write conversion results. self.log.info("Writing %i bytes to %s", len(output), dest) - with io.open(dest, 'w', encoding='utf-8') as f: - f.write(output) + if isinstance(output, unicode_type): + with io.open(dest, 'w', encoding='utf-8') as f: + f.write(output) + else: + with io.open(dest, 'wb') as f: + f.write(output) + return dest diff --git a/docs/source/whatsnew/pr/incompat-rm-pdf-post.rst b/docs/source/whatsnew/pr/incompat-rm-pdf-post.rst new file mode 100644 index 0000000..54145db --- /dev/null +++ b/docs/source/whatsnew/pr/incompat-rm-pdf-post.rst @@ -0,0 +1,2 @@ +Creating PDFs with LaTeX no longer uses a post processor. +Use `nbconvert --to pdf` instead of `nbconvert --to latex --post pdf`.