exporter.py
348 lines
| 11.9 KiB
| text/x-python
|
PythonLexer
|
r10690 | """This module defines Exporter, a highly configurable converter | ||
that uses Jinja2 to export notebook files into different formats. | ||||
|
r9578 | """ | ||
|
r9819 | |||
|
r9578 | #----------------------------------------------------------------------------- | ||
|
r9665 | # Copyright (c) 2013, the IPython Development Team. | ||
|
r9578 | # | ||
# Distributed under the terms of the Modified BSD License. | ||||
# | ||||
# The full license is in the file COPYING.txt, distributed with this software. | ||||
#----------------------------------------------------------------------------- | ||||
#----------------------------------------------------------------------------- | ||||
# Imports | ||||
#----------------------------------------------------------------------------- | ||||
|
r10677 | |||
|
r10430 | from __future__ import print_function, absolute_import | ||
|
r9665 | |||
# Stdlib imports | ||||
import io | ||||
|
r9819 | import os | ||
|
r10631 | import inspect | ||
|
r10862 | from copy import deepcopy | ||
|
r9665 | |||
|
r11089 | # other libs/dependencies | ||
|
r11279 | from jinja2 import Environment, FileSystemLoader, ChoiceLoader | ||
|
r11089 | |||
|
r9665 | # IPython imports | ||
from IPython.config.configurable import Configurable | ||||
|
r10862 | from IPython.config import Config | ||
|
r9665 | from IPython.nbformat import current as nbformat | ||
|
r11089 | from IPython.utils.traitlets import MetaHasTraits, Unicode | ||
|
r10432 | from IPython.utils.text import indent | ||
|
r9701 | |||
|
r11089 | from IPython.nbconvert import filters | ||
from IPython.nbconvert import transformers | ||||
|
r10430 | |||
|
r10431 | #----------------------------------------------------------------------------- | ||
# Globals and constants | ||||
#----------------------------------------------------------------------------- | ||||
|
r9665 | |||
|
r10431 | #Jinja2 extensions to load. | ||
JINJA_EXTENSIONS = ['jinja2.ext.loopcontrols'] | ||||
|
r10838 | default_filters = { | ||
'indent': indent, | ||||
|
r11268 | 'markdown': filters.markdown2html, | ||
|
r11089 | 'ansi2html': filters.ansi2html, | ||
'filter_data_type': filters.DataTypeFilter, | ||||
'get_lines': filters.get_lines, | ||||
'highlight': filters.highlight, | ||||
'highlight2html': filters.highlight, | ||||
'highlight2latex': filters.highlight2latex, | ||||
'markdown2latex': filters.markdown2latex, | ||||
'markdown2rst': filters.markdown2rst, | ||||
'pycomment': filters.python_comment, | ||||
'rm_ansi': filters.remove_ansi, | ||||
'rm_dollars': filters.strip_dollars, | ||||
'rm_fake': filters.rm_fake, | ||||
|
r11302 | 'html_text' : filters.html_text, | ||
'add_anchor': filters.add_anchor, | ||||
|
r11089 | 'ansi2latex': filters.ansi2latex, | ||
'rm_math_space': filters.rm_math_space, | ||||
'wrap': filters.wrap | ||||
|
r10838 | } | ||
|
r10431 | #----------------------------------------------------------------------------- | ||
|
r10677 | # Class | ||
|
r9665 | #----------------------------------------------------------------------------- | ||
|
r10677 | |||
|
r10430 | class Exporter(Configurable): | ||
|
r10690 | """ | ||
Exports notebooks into other file formats. Uses Jinja 2 templating engine | ||||
to output new formats. Inherit from this class if you are creating a new | ||||
template type along with new filters/transformers. If the filters/ | ||||
transformers provided by default suffice, there is no need to inherit from | ||||
this class. Instead, override the template_file and file_extension | ||||
traits via a config file. | ||||
|
r10838 | {filters} | ||
""" | ||||
|
r10874 | |||
# finish the docstring | ||||
|
r10838 | __doc__ = __doc__.format(filters = '- '+'\n - '.join(default_filters.keys())) | ||
|
r10431 | template_file = Unicode( | ||
'', config=True, | ||||
help="Name of the template file to use") | ||||
|
r9768 | |||
|
r10587 | file_extension = Unicode( | ||
|
r10435 | 'txt', config=True, | ||
help="Extension of the file that should be written to disk" | ||||
) | ||||
|
r10690 | template_path = Unicode( | ||
|
r11165 | os.path.join("..", "templates"), config=True, | ||
|
r10690 | help="Path where the template files are located.") | ||
template_skeleton_path = Unicode( | ||||
|
r11165 | os.path.join("..", "templates", "skeleton"), config=True, | ||
|
r10690 | help="Path where the template skeleton files are located.") | ||
#Jinja block definitions | ||||
|
r10693 | jinja_comment_block_start = Unicode("", config=True) | ||
jinja_comment_block_end = Unicode("", config=True) | ||||
jinja_variable_block_start = Unicode("", config=True) | ||||
jinja_variable_block_end = Unicode("", config=True) | ||||
jinja_logic_block_start = Unicode("", config=True) | ||||
jinja_logic_block_end = Unicode("", config=True) | ||||
|
r10690 | |||
|
r10624 | #Extension that the template files use. | ||
|
r10690 | template_extension = Unicode(".tpl", config=True) | ||
|
r10624 | |||
|
r10431 | #Processors that process the input data prior to the export, set in the | ||
#constructor for this class. | ||||
|
r10799 | transformers = None | ||
|
r9640 | |||
|
r10588 | |||
|
r11279 | def __init__(self, transformers=None, filters=None, config=None, extra_loaders=None, **kw): | ||
|
r10690 | """ | ||
Public constructor | ||||
|
r10485 | |||
|
r10690 | Parameters | ||
---------- | ||||
transformers : list[of transformer] | ||||
Custom transformers to apply to the notebook prior to engaging | ||||
the Jinja template engine. Any transformers specified here | ||||
will override existing transformers if a naming conflict | ||||
occurs. | ||||
|
r10838 | filters : dict[of filter] | ||
filters specified here will override existing filters if a naming | ||||
conflict occurs. Filters are availlable in jinja template through | ||||
the name of the corresponding key. Cf class docstring for | ||||
availlable default filters. | ||||
|
r10690 | config : config | ||
User configuration instance. | ||||
|
r11279 | extra_loaders : list[of Jinja Loaders] | ||
ordered list of Jinja loder to find templates. Will be tried in order | ||||
before the default FileSysteme ones. | ||||
|
r10690 | """ | ||
|
r10435 | #Call the base class constructor | ||
|
r10963 | c = self.default_config | ||
if config: | ||||
c.merge(config) | ||||
super(Exporter, self).__init__(config=c, **kw) | ||||
|
r9665 | |||
|
r10480 | #Standard environment | ||
|
r11279 | self._init_environment(extra_loaders=extra_loaders) | ||
|
r9624 | |||
|
r10587 | #Add transformers | ||
self._register_transformers() | ||||
|
r9615 | |||
|
r10431 | #Add filters to the Jinja2 environment | ||
|
r10578 | self._register_filters() | ||
|
r10431 | |||
|
r10677 | #Load user transformers. Overwrite existing transformers if need be. | ||
|
r10799 | if transformers : | ||
|
r10677 | for transformer in transformers: | ||
self.register_transformer(transformer) | ||||
|
r10431 | #Load user filters. Overwrite existing filters if need be. | ||
|
r10677 | if not filters is None: | ||
for key, user_filter in filters.iteritems(): | ||||
|
r11116 | self.register_filter(key, user_filter) | ||
|
r10862 | |||
@property | ||||
def default_config(self): | ||||
|
r10963 | return Config() | ||
|
r10588 | |||
|
r10837 | def from_notebook_node(self, nb, resources=None): | ||
|
r10690 | """ | ||
Convert a notebook from a notebook node instance. | ||||
Parameters | ||||
---------- | ||||
nb : Notebook node | ||||
|
r10837 | resources : a dict of additional resources that | ||
can be accessed read/write by transformers | ||||
and filters. | ||||
|
r10690 | """ | ||
|
r10837 | if resources is None: | ||
resources = {} | ||||
nb, resources = self._preprocess(nb, resources) | ||||
|
r10588 | |||
#Load the template file. | ||||
|
r10624 | self.template = self.environment.get_template(self.template_file+self.template_extension) | ||
|
r10588 | |||
|
r9654 | return self.template.render(nb=nb, resources=resources), resources | ||
|
r9578 | |||
|
r9640 | def from_filename(self, filename): | ||
|
r10690 | """ | ||
Convert a notebook from a notebook file. | ||||
Parameters | ||||
---------- | ||||
filename : str | ||||
Full filename of the notebook file to open and convert. | ||||
""" | ||||
|
r10431 | with io.open(filename) as f: | ||
|
r10578 | return self.from_notebook_node(nbformat.read(f, 'json')) | ||
|
r10431 | |||
|
r10432 | |||
|
r10431 | def from_file(self, file_stream): | ||
|
r10690 | """ | ||
Convert a notebook from a notebook file. | ||||
Parameters | ||||
---------- | ||||
file_stream : file-like object | ||||
Notebook file-like object to convert. | ||||
""" | ||||
|
r10578 | return self.from_notebook_node(nbformat.read(file_stream, 'json')) | ||
|
r10587 | def register_transformer(self, transformer): | ||
|
r10690 | """ | ||
Register a transformer. | ||||
Transformers are classes that act upon the notebook before it is | ||||
passed into the Jinja templating engine. Transformers are also | ||||
capable of passing additional information to the Jinja | ||||
templating engine. | ||||
Parameters | ||||
---------- | ||||
transformer : transformer | ||||
""" | ||||
|
r10695 | if self.transformers is None: | ||
self.transformers = [] | ||||
|
r10690 | |||
|
r10631 | if inspect.isfunction(transformer): | ||
|
r10694 | self.transformers.append(transformer) | ||
|
r10631 | return transformer | ||
elif isinstance(transformer, MetaHasTraits): | ||||
|
r10624 | transformer_instance = transformer(config=self.config) | ||
|
r10694 | self.transformers.append(transformer_instance) | ||
|
r10624 | return transformer_instance | ||
|
r10587 | else: | ||
|
r10631 | transformer_instance = transformer() | ||
|
r10694 | self.transformers.append(transformer_instance) | ||
|
r10631 | return transformer_instance | ||
|
r10587 | |||
|
r10578 | def register_filter(self, name, filter): | ||
|
r10690 | """ | ||
Register a filter. | ||||
A filter is a function that accepts and acts on one string. | ||||
The filters are accesible within the Jinja templating engine. | ||||
Parameters | ||||
---------- | ||||
name : str | ||||
name to give the filter in the Jinja engine | ||||
filter : filter | ||||
""" | ||||
|
r10631 | if inspect.isfunction(filter): | ||
self.environment.filters[name] = filter | ||||
elif isinstance(filter, MetaHasTraits): | ||||
|
r10587 | self.environment.filters[name] = filter(config=self.config) | ||
|
r10578 | else: | ||
|
r10631 | self.environment.filters[name] = filter() | ||
|
r10588 | return self.environment.filters[name] | ||
|
r10578 | |||
|
r10588 | |||
|
r10587 | def _register_transformers(self): | ||
|
r10690 | """ | ||
Register all of the transformers needed for this exporter. | ||||
""" | ||||
|
r10694 | |||
|
r11089 | self.register_transformer(transformers.coalesce_streams) | ||
|
r10588 | |||
#Remember the figure extraction transformer so it can be enabled and | ||||
#disabled easily later. | ||||
|
r11089 | self.extract_figure_transformer = self.register_transformer(transformers.ExtractFigureTransformer) | ||
|
r10587 | |||
|
r10578 | def _register_filters(self): | ||
|
r10690 | """ | ||
Register all of the filters required for the exporter. | ||||
""" | ||||
|
r10875 | for k, v in default_filters.iteritems(): | ||
self.register_filter(k, v) | ||||
|
r10588 | |||
|
r11279 | def _init_environment(self, extra_loaders=None): | ||
|
r10690 | """ | ||
Create the Jinja templating environment. | ||||
""" | ||||
|
r11197 | here = os.path.dirname(os.path.realpath(__file__)) | ||
|
r11279 | loaders = [] | ||
if extra_loaders: | ||||
loaders.extend(extra_loaders) | ||||
loaders.append(FileSystemLoader([ | ||||
|
r11165 | os.path.join(here, self.template_path), | ||
os.path.join(here, self.template_skeleton_path), | ||||
|
r11279 | ])) | ||
self.environment = Environment( | ||||
loader= ChoiceLoader(loaders), | ||||
|
r10485 | extensions=JINJA_EXTENSIONS | ||
) | ||||
|
r10690 | |||
#Set special Jinja2 syntax that will not conflict with latex. | ||||
|
r10693 | if self.jinja_logic_block_start: | ||
|
r10690 | self.environment.block_start_string = self.jinja_logic_block_start | ||
|
r10693 | if self.jinja_logic_block_end: | ||
|
r10690 | self.environment.block_end_string = self.jinja_logic_block_end | ||
|
r10693 | if self.jinja_variable_block_start: | ||
|
r10690 | self.environment.variable_start_string = self.jinja_variable_block_start | ||
|
r10693 | if self.jinja_variable_block_end: | ||
|
r10690 | self.environment.variable_end_string = self.jinja_variable_block_end | ||
|
r10693 | if self.jinja_comment_block_start: | ||
|
r10690 | self.environment.comment_start_string = self.jinja_comment_block_start | ||
|
r10693 | if self.jinja_comment_block_end: | ||
|
r10690 | self.environment.comment_end_string = self.jinja_comment_block_end | ||
|
r10485 | |||
|
r10837 | def _preprocess(self, nb, resources): | ||
|
r10690 | """ | ||
Preprocess the notebook before passing it into the Jinja engine. | ||||
To preprocess the notebook is to apply all of the | ||||
Parameters | ||||
---------- | ||||
nb : notebook node | ||||
notebook that is being exported. | ||||
|
r10837 | resources : a dict of additional resources that | ||
can be accessed read/write by transformers | ||||
and filters. | ||||
|
r10690 | """ | ||
|
r10867 | # Do a deepcopy first, | ||
# we are never safe enough with what the transformers could do. | ||||
nbc = deepcopy(nb) | ||||
resc = deepcopy(resources) | ||||
|
r10432 | #Run each transformer on the notebook. Carry the output along | ||
#to each transformer | ||||
|
r10694 | for transformer in self.transformers: | ||
|
r10867 | nb, resources = transformer(nbc, resc) | ||
|
r10432 | return nb, resources | ||
|
r10838 | |||