exporter.py
279 lines
| 9.4 KiB
| text/x-python
|
PythonLexer
Jonathan Frederic
|
r12505 | """This module defines Exporter, a highly configurable converter | ||
Jonathan Frederic
|
r12502 | that uses Jinja2 to export notebook files into different formats. | ||
""" | ||||
#----------------------------------------------------------------------------- | ||||
# 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 __future__ import print_function, absolute_import | ||||
# Stdlib imports | ||||
import io | ||||
import os | ||||
import copy | ||||
import collections | ||||
import datetime | ||||
# IPython imports | ||||
from IPython.config.configurable import LoggingConfigurable | ||||
from IPython.config import Config | ||||
from IPython.nbformat import current as nbformat | ||||
from IPython.utils.traitlets import MetaHasTraits, Unicode, List | ||||
from IPython.utils.importstring import import_item | ||||
Jonathan Frederic
|
r12505 | from IPython.utils import text, py3compat | ||
Jonathan Frederic
|
r12502 | |||
Jonathan Frederic
|
r12505 | from IPython.nbconvert import preprocessors as nbpreprocessors | ||
Jonathan Frederic
|
r12502 | |||
#----------------------------------------------------------------------------- | ||||
# Class | ||||
#----------------------------------------------------------------------------- | ||||
class ResourcesDict(collections.defaultdict): | ||||
def __missing__(self, key): | ||||
return '' | ||||
Jonathan Frederic
|
r12505 | class Exporter(LoggingConfigurable): | ||
Jonathan Frederic
|
r12502 | """ | ||
Jonathan Frederic
|
r12518 | Class containing methods that sequentially run a list of preprocessors on a | ||
NotebookNode object and then return the modified NotebookNode object and | ||||
accompanying resources dict. | ||||
Jonathan Frederic
|
r12502 | """ | ||
file_extension = Unicode( | ||||
'txt', config=True, | ||||
help="Extension of the file that should be written to disk" | ||||
) | ||||
Jonathan Frederic
|
r12505 | #Configurability, allows the user to easily add filters and preprocessors. | ||
preprocessors = List(config=True, | ||||
help="""List of preprocessors, by name or namespace, to enable.""") | ||||
Jonathan Frederic
|
r12502 | |||
Jonathan Frederic
|
r12505 | _preprocessors = None | ||
Jonathan Frederic
|
r12502 | |||
Jonathan Frederic
|
r12505 | default_preprocessors = List([nbpreprocessors.coalesce_streams, | ||
nbpreprocessors.SVG2PDFPreprocessor, | ||||
nbpreprocessors.ExtractOutputPreprocessor, | ||||
nbpreprocessors.CSSHTMLHeaderPreprocessor, | ||||
nbpreprocessors.RevealHelpPreprocessor, | ||||
nbpreprocessors.LatexPreprocessor, | ||||
nbpreprocessors.SphinxPreprocessor], | ||||
Jonathan Frederic
|
r12502 | config=True, | ||
Jonathan Frederic
|
r12505 | help="""List of preprocessors available by default, by name, namespace, | ||
Jonathan Frederic
|
r12502 | instance, or type.""") | ||
def __init__(self, config=None, **kw): | ||||
""" | ||||
Public constructor | ||||
Parameters | ||||
---------- | ||||
config : config | ||||
User configuration instance. | ||||
""" | ||||
if not config: | ||||
config = self.default_config | ||||
Jonathan Frederic
|
r12505 | super(Exporter, self).__init__(config=config, **kw) | ||
Jonathan Frederic
|
r12502 | |||
#Init | ||||
Jonathan Frederic
|
r12505 | self._init_preprocessors() | ||
Jonathan Frederic
|
r12502 | |||
@property | ||||
def default_config(self): | ||||
return Config() | ||||
def _config_changed(self, name, old, new): | ||||
"""When setting config, make sure to start with our default_config""" | ||||
c = self.default_config | ||||
if new: | ||||
c.merge(new) | ||||
if c != old: | ||||
self.config = c | ||||
Jonathan Frederic
|
r12505 | super(Exporter, self)._config_changed(name, old, c) | ||
Jonathan Frederic
|
r12502 | |||
Jonathan Frederic
|
r12518 | def from_notebook_node(self, nb, resources=None, **kw): | ||
Jonathan Frederic
|
r12502 | """ | ||
Convert a notebook from a notebook node instance. | ||||
Parameters | ||||
---------- | ||||
nb : Notebook node | ||||
resources : dict (**kw) | ||||
of additional resources that can be accessed read/write by | ||||
Jonathan Frederic
|
r12505 | preprocessors. | ||
Jonathan Frederic
|
r12502 | """ | ||
nb_copy = copy.deepcopy(nb) | ||||
resources = self._init_resources(resources) | ||||
# Preprocess | ||||
Jonathan Frederic
|
r12518 | nb_copy, resources = self._preprocess(nb_copy, resources) | ||
Jonathan Frederic
|
r12502 | |||
return nb_copy, resources | ||||
def from_filename(self, filename, resources=None, **kw): | ||||
""" | ||||
Convert a notebook from a notebook file. | ||||
Parameters | ||||
---------- | ||||
filename : str | ||||
Full filename of the notebook file to open and convert. | ||||
""" | ||||
Jonathan Frederic
|
r12505 | # Pull the metadata from the filesystem. | ||
Jonathan Frederic
|
r12502 | if resources is None: | ||
resources = ResourcesDict() | ||||
if not 'metadata' in resources or resources['metadata'] == '': | ||||
resources['metadata'] = ResourcesDict() | ||||
basename = os.path.basename(filename) | ||||
notebook_name = basename[:basename.rfind('.')] | ||||
resources['metadata']['name'] = notebook_name | ||||
modified_date = datetime.datetime.fromtimestamp(os.path.getmtime(filename)) | ||||
Jonathan Frederic
|
r12505 | resources['metadata']['modified_date'] = modified_date.strftime(text.date_format) | ||
Jonathan Frederic
|
r12502 | |||
with io.open(filename) as f: | ||||
return self.from_notebook_node(nbformat.read(f, 'json'), resources=resources, **kw) | ||||
def from_file(self, file_stream, resources=None, **kw): | ||||
""" | ||||
Convert a notebook from a notebook file. | ||||
Parameters | ||||
---------- | ||||
file_stream : file-like object | ||||
Notebook file-like object to convert. | ||||
""" | ||||
return self.from_notebook_node(nbformat.read(file_stream, 'json'), resources=resources, **kw) | ||||
Jonathan Frederic
|
r12505 | def register_preprocessor(self, preprocessor, enabled=False): | ||
Jonathan Frederic
|
r12502 | """ | ||
Jonathan Frederic
|
r12505 | Register a preprocessor. | ||
Jonathan Frederic
|
r12518 | Preprocessors are classes that act upon the notebook before it is | ||
Jonathan Frederic
|
r12505 | passed into the Jinja templating engine. preprocessors are also | ||
Jonathan Frederic
|
r12502 | capable of passing additional information to the Jinja | ||
templating engine. | ||||
Parameters | ||||
---------- | ||||
Jonathan Frederic
|
r12505 | preprocessor : preprocessor | ||
Jonathan Frederic
|
r12502 | """ | ||
Jonathan Frederic
|
r12505 | if preprocessor is None: | ||
raise TypeError('preprocessor') | ||||
isclass = isinstance(preprocessor, type) | ||||
Jonathan Frederic
|
r12502 | constructed = not isclass | ||
Jonathan Frederic
|
r12505 | #Handle preprocessor's registration based on it's type | ||
if constructed and isinstance(preprocessor, py3compat.string_types): | ||||
#preprocessor is a string, import the namespace and recursively call | ||||
#this register_preprocessor method | ||||
preprocessor_cls = import_item(preprocessor) | ||||
return self.register_preprocessor(preprocessor_cls, enabled) | ||||
Jonathan Frederic
|
r12502 | |||
Jonathan Frederic
|
r12505 | if constructed and hasattr(preprocessor, '__call__'): | ||
#preprocessor is a function, no need to construct it. | ||||
#Register and return the preprocessor. | ||||
Jonathan Frederic
|
r12502 | if enabled: | ||
Jonathan Frederic
|
r12505 | preprocessor.enabled = True | ||
self._preprocessors.append(preprocessor) | ||||
return preprocessor | ||||
Jonathan Frederic
|
r12502 | |||
Jonathan Frederic
|
r12505 | elif isclass and isinstance(preprocessor, MetaHasTraits): | ||
#preprocessor is configurable. Make sure to pass in new default for | ||||
Jonathan Frederic
|
r12502 | #the enabled flag if one was specified. | ||
Jonathan Frederic
|
r12505 | self.register_preprocessor(preprocessor(parent=self), enabled) | ||
Jonathan Frederic
|
r12502 | |||
elif isclass: | ||||
Jonathan Frederic
|
r12505 | #preprocessor is not configurable, construct it | ||
self.register_preprocessor(preprocessor(), enabled) | ||||
Jonathan Frederic
|
r12502 | |||
else: | ||||
Jonathan Frederic
|
r12505 | #preprocessor is an instance of something without a __call__ | ||
Jonathan Frederic
|
r12502 | #attribute. | ||
Jonathan Frederic
|
r12505 | raise TypeError('preprocessor') | ||
Jonathan Frederic
|
r12502 | |||
Jonathan Frederic
|
r12505 | def _init_preprocessors(self): | ||
Jonathan Frederic
|
r12502 | """ | ||
Jonathan Frederic
|
r12505 | Register all of the preprocessors needed for this exporter, disabled | ||
Jonathan Frederic
|
r12502 | unless specified explicitly. | ||
""" | ||||
Jonathan Frederic
|
r12505 | if self._preprocessors is None: | ||
self._preprocessors = [] | ||||
Jonathan Frederic
|
r12502 | |||
Jonathan Frederic
|
r12505 | #Load default preprocessors (not necessarly enabled by default). | ||
if self.default_preprocessors: | ||||
for preprocessor in self.default_preprocessors: | ||||
self.register_preprocessor(preprocessor) | ||||
Jonathan Frederic
|
r12502 | |||
Jonathan Frederic
|
r12505 | #Load user preprocessors. Enable by default. | ||
if self.preprocessors: | ||||
for preprocessor in self.preprocessors: | ||||
self.register_preprocessor(preprocessor, enabled=True) | ||||
Jonathan Frederic
|
r12502 | |||
def _init_resources(self, resources): | ||||
#Make sure the resources dict is of ResourcesDict type. | ||||
if resources is None: | ||||
resources = ResourcesDict() | ||||
if not isinstance(resources, ResourcesDict): | ||||
new_resources = ResourcesDict() | ||||
new_resources.update(resources) | ||||
resources = new_resources | ||||
#Make sure the metadata extension exists in resources | ||||
if 'metadata' in resources: | ||||
if not isinstance(resources['metadata'], ResourcesDict): | ||||
resources['metadata'] = ResourcesDict(resources['metadata']) | ||||
else: | ||||
resources['metadata'] = ResourcesDict() | ||||
if not resources['metadata']['name']: | ||||
resources['metadata']['name'] = 'Notebook' | ||||
#Set the output extension | ||||
resources['output_extension'] = self.file_extension | ||||
return resources | ||||
Jonathan Frederic
|
r12518 | def _preprocess(self, nb, resources): | ||
Jonathan Frederic
|
r12502 | """ | ||
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. | ||||
resources : a dict of additional resources that | ||||
Jonathan Frederic
|
r12505 | can be accessed read/write by preprocessors | ||
Jonathan Frederic
|
r12502 | """ | ||
# Do a copy.deepcopy first, | ||||
Jonathan Frederic
|
r12505 | # we are never safe enough with what the preprocessors could do. | ||
Jonathan Frederic
|
r12502 | nbc = copy.deepcopy(nb) | ||
resc = copy.deepcopy(resources) | ||||
Jonathan Frederic
|
r12505 | #Run each preprocessor on the notebook. Carry the output along | ||
#to each preprocessor | ||||
for preprocessor in self._preprocessors: | ||||
nbc, resc = preprocessor(nbc, resc) | ||||
Jonathan Frederic
|
r12502 | return nbc, resc | ||