diff --git a/IPython/nbconvert/__init__.py b/IPython/nbconvert/__init__.py index 121eed9..d5e7352 100755 --- a/IPython/nbconvert/__init__.py +++ b/IPython/nbconvert/__init__.py @@ -3,4 +3,5 @@ from .exporters import * import filters import transformers +import post_processors import writers diff --git a/IPython/nbconvert/nbconvertapp.py b/IPython/nbconvert/nbconvertapp.py index eec4732..88be306 100755 --- a/IPython/nbconvert/nbconvertapp.py +++ b/IPython/nbconvert/nbconvertapp.py @@ -30,7 +30,7 @@ from IPython.utils.traitlets import ( from IPython.utils.importstring import import_item from .exporters.export import export_by_name, get_export_names, ExporterNameError -from IPython.nbconvert import exporters, transformers, writers +from IPython.nbconvert import exporters, transformers, writers, post_processors from .utils.base import NbConvertBase from .utils.exceptions import ConversionException @@ -38,6 +38,19 @@ from .utils.exceptions import ConversionException #Classes and functions #----------------------------------------------------------------------------- +class DottedOrNone(DottedObjectName): + """ + A string holding a valid dotted object name in Python, such as A.b3._c + Also allows for None type.""" + + default_value = u'' + + def validate(self, obj, value): + if value is not None and len(value) > 0: + return super(DottedOrNone, self).validate(obj, value) + else: + return value + nbconvert_aliases = {} nbconvert_aliases.update(base_aliases) nbconvert_aliases.update({ @@ -45,6 +58,7 @@ nbconvert_aliases.update({ 'template' : 'Exporter.template_file', 'notebooks' : 'NbConvertApp.notebooks', 'writer' : 'NbConvertApp.writer_class', + 'post': 'NbConvertApp.post_processor_class' }) nbconvert_flags = {} @@ -53,11 +67,6 @@ nbconvert_flags.update({ 'stdout' : ( {'NbConvertApp' : {'writer_class' : "StdoutWriter"}}, "Write notebook output to stdout instead of files." - ), - - 'pdf' : ( - {'NbConvertApp' : {'writer_class' : "PDFWriter"}}, - "Compile notebook output to a PDF (requires `--to latex`)." ) }) @@ -120,6 +129,7 @@ class NbConvertApp(BaseIPythonApplication): > ipython nbconvert --config mycfg.py """.format(get_export_names())) + # Writer specific variables writer = Instance('IPython.nbconvert.writers.base.WriterBase', help="""Instance of the writer class used to write the @@ -138,6 +148,23 @@ class NbConvertApp(BaseIPythonApplication): new = self.writer_aliases[new] self.writer_factory = import_item(new) + # Post-processor specific variables + post_processor = Instance('IPython.nbconvert.post_processors.base.PostProcessorBase', + help="""Instance of the PostProcessor class used to write the + results of the conversion.""") + + post_processor_class = DottedOrNone(config=True, + help="""PostProcessor class used to write the + results of the conversion""") + post_processor_aliases = {'PDF': 'IPython.nbconvert.post_processors.pdf.PDFPostProcessor'} + post_processor_factory = Type() + + def _post_processor_class_changed(self, name, old, new): + if new in self.post_processor_aliases: + new = self.post_processor_aliases[new] + if new: + self.post_processor_factory = import_item(new) + # Other configurable variables export_format = CaselessStrEnum(get_export_names(), @@ -157,6 +184,8 @@ class NbConvertApp(BaseIPythonApplication): self.init_syspath() self.init_notebooks() self.init_writer() + self.init_post_processor() + def init_syspath(self): @@ -201,6 +230,15 @@ class NbConvertApp(BaseIPythonApplication): self._writer_class_changed(None, self.writer_class, self.writer_class) self.writer = self.writer_factory(parent=self) + def init_post_processor(self): + """ + Initialize the post_processor (which is stateless) + """ + self._post_processor_class_changed(None, self.post_processor_class, + self.post_processor_class) + if self.post_processor_factory: + self.post_processor = self.post_processor_factory(parent=self) + def start(self): """ Ran after initialization completed @@ -242,7 +280,11 @@ class NbConvertApp(BaseIPythonApplication): file=sys.stderr) self.exit(1) else: - self.writer.write(output, resources, notebook_name=notebook_name) + write_resultes = self.writer.write(output, resources, notebook_name=notebook_name) + + #Post-process if post processor has been defined. + if hasattr(self, 'post_processor') and self.post_processor: + self.post_processor(write_resultes) conversion_success += 1 # If nothing was converted successfully, help the user. diff --git a/IPython/nbconvert/post_processors/__init__.py b/IPython/nbconvert/post_processors/__init__.py new file mode 100644 index 0000000..72c4819 --- /dev/null +++ b/IPython/nbconvert/post_processors/__init__.py @@ -0,0 +1,2 @@ +from .base import PostProcessorBase +from .pdf import PDFPostProcessor diff --git a/IPython/nbconvert/post_processors/base.py b/IPython/nbconvert/post_processors/base.py new file mode 100644 index 0000000..ba480c8 --- /dev/null +++ b/IPython/nbconvert/post_processors/base.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python +""" +Basic 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 +#----------------------------------------------------------------------------- + +from ..utils.base import NbConvertBase + + +#----------------------------------------------------------------------------- +# Classes +#----------------------------------------------------------------------------- +class PostProcessorBase(NbConvertBase): + + def __call__(self, input): + """ + See def call() ... + """ + self.call(input) + + + def call(self, input): + """ + Post-process output from a writer. + """ + raise NotImplementedError('call') diff --git a/IPython/nbconvert/post_processors/pdf.py b/IPython/nbconvert/post_processors/pdf.py index 4224a8a..db54464 100644 --- a/IPython/nbconvert/post_processors/pdf.py +++ b/IPython/nbconvert/post_processors/pdf.py @@ -17,14 +17,14 @@ Contains writer for writing nbconvert output to PDF. import subprocess import os -from IPython.utils.traitlets import Integer, Unicode +from IPython.utils.traitlets import Integer, Unicode, Bool -from .files import FilesWriter +from .base import PostProcessorBase #----------------------------------------------------------------------------- # Classes #----------------------------------------------------------------------------- -class PDFWriter(FilesWriter): +class PDFPostProcessor(PostProcessorBase): """Writer designed to write to PDF files""" iteration_count = Integer(3, config=True, help=""" @@ -34,14 +34,19 @@ class PDFWriter(FilesWriter): compiler = Unicode(u'pdflatex {0}', config=True, help=""" Shell command used to compile PDF.""") - def write(self, output, resources, notebook_name=None, **kw): + verbose = Bool(False, config=True, help=""" + Whether or not to display the output of the compile call. + """) + + def call(self, input): """ Consume and write Jinja output a PDF. See files.py for more... """ - dest = super(PDFWriter, self).write(output, resources, - notebook_name=notebook_name, **kw) - command = self.compiler.format(dest) - + command = self.compiler.format(input) for index in range(self.iteration_count): - subprocess.Popen(command, shell=True, stdout=open(os.devnull, 'wb')) + if self.verbose: + subprocess.Popen(command, shell=True) + else: + with open(os.devnull, 'wb') as null: + subprocess.Popen(command, shell=True, stdout=null) diff --git a/IPython/nbconvert/tests/test_nbconvertapp.py b/IPython/nbconvert/tests/test_nbconvertapp.py index f3548e9..7737bd6 100644 --- a/IPython/nbconvert/tests/test_nbconvertapp.py +++ b/IPython/nbconvert/tests/test_nbconvertapp.py @@ -17,6 +17,7 @@ import os from .base import TestsBase from IPython.utils import py3compat +from IPython.testing import decorators as dec #----------------------------------------------------------------------------- @@ -80,6 +81,19 @@ class TestNbConvertApp(TestsBase): assert os.path.isfile('notebook2.py') + #@dec.skip_known_failure + def test_post_processor(self): + """ + Do post processors work? + """ + with self.create_temp_cwd(['notebook1.ipynb']): + assert not 'error' in self.call([IPYTHON, 'nbconvert', '--to="latex"', + 'notebook1', '--post="PDF"', 'PDFPostProcessor.verbose=True']).lower() + assert os.path.isfile('notebook1.tex') + print("\n\n\t" + "\n\t".join([f for f in os.listdir('.') if os.path.isfile(f)]) + "\n\n") + assert os.path.isfile('notebook1.pdf') + + def test_template(self): """ Do export templates work?