template.py
252 lines
| 8.7 KiB
| text/x-python
|
PythonLexer
/ converters / template.py
Matthias BUSSONNIER
|
r9578 | """Base classes for the notebook conversion pipeline. | ||
Matthias BUSSONNIER
|
r9665 | This module defines ConverterTemplate, a highly configurable converter | ||
that uses Jinja2 to convert notebook files into different format. | ||||
You can register both pre-transformers that will act on the notebook format | ||||
befor conversion and jinja filter that would then be availlable in the templates | ||||
Matthias BUSSONNIER
|
r9578 | """ | ||
Matthias BUSSONNIER
|
r9819 | |||
from __future__ import absolute_import | ||||
Matthias BUSSONNIER
|
r9578 | #----------------------------------------------------------------------------- | ||
Matthias BUSSONNIER
|
r9665 | # Copyright (c) 2013, the IPython Development Team. | ||
Matthias BUSSONNIER
|
r9578 | # | ||
# Distributed under the terms of the Modified BSD License. | ||||
# | ||||
# The full license is in the file COPYING.txt, distributed with this software. | ||||
#----------------------------------------------------------------------------- | ||||
#----------------------------------------------------------------------------- | ||||
# Imports | ||||
#----------------------------------------------------------------------------- | ||||
Matthias BUSSONNIER
|
r9819 | from __future__ import print_function | ||
from __future__ import absolute_import | ||||
Matthias BUSSONNIER
|
r9665 | |||
# Stdlib imports | ||||
import io | ||||
Matthias BUSSONNIER
|
r9819 | import os | ||
Matthias BUSSONNIER
|
r9665 | |||
# IPython imports | ||||
from IPython.utils.traitlets import MetaHasTraits | ||||
from IPython.utils.traitlets import (Unicode, List, Bool) | ||||
from IPython.config.configurable import Configurable | ||||
from IPython.nbformat import current as nbformat | ||||
Matthias BUSSONNIER
|
r9701 | |||
Matthias BUSSONNIER
|
r9665 | # other libs/dependencies | ||
from jinja2 import Environment, FileSystemLoader | ||||
# local import (pre-transformers) | ||||
Matthias BUSSONNIER
|
r9819 | from . import transformers as trans | ||
Matthias BUSSONNIER
|
r9822 | try: | ||
from .sphinx_transformer import (SphinxTransformer) | ||||
except ImportError: | ||||
SphinxTransformer = None | ||||
Matthias BUSSONNIER
|
r9819 | from .latex_transformer import (LatexTransformer) | ||
Matthias BUSSONNIER
|
r9665 | |||
# some jinja filters | ||||
Matthias BUSSONNIER
|
r9819 | from .jinja_filters import (python_comment, indent, | ||
Jonathan Frederic
|
r9780 | rm_fake, remove_ansi, markdown, highlight, highlight2latex, | ||
ansi2html, markdown2latex, get_lines, escape_tex, FilterDataType) | ||||
Matthias BUSSONNIER
|
r9621 | |||
Matthias BUSSONNIER
|
r9819 | from .utils import markdown2rst | ||
Matthias BUSSONNIER
|
r9578 | |||
Matthias BUSSONNIER
|
r9701 | import textwrap | ||
def wrap(text, width=100): | ||||
""" try to detect and wrap paragraph""" | ||||
splitt = text.split('\n') | ||||
wrp = map(lambda x:textwrap.wrap(x,width),splitt) | ||||
wrpd = map('\n'.join, wrp) | ||||
return '\n'.join(wrpd) | ||||
Matthias BUSSONNIER
|
r9624 | |||
Matthias BUSSONNIER
|
r9654 | |||
Matthias BUSSONNIER
|
r9665 | # define differents environemnt with different | ||
# delimiters not to conflict with languages inside | ||||
Matthias BUSSONNIER
|
r9578 | |||
Matthias BUSSONNIER
|
r9599 | env = Environment( | ||
Matthias BUSSONNIER
|
r9616 | loader=FileSystemLoader([ | ||
Matthias BUSSONNIER
|
r9819 | os.path.dirname(os.path.realpath(__file__))+'/../templates/', | ||
os.path.dirname(os.path.realpath(__file__))+'/../templates/skeleton/', | ||||
Matthias BUSSONNIER
|
r9616 | ]), | ||
Matthias BUSSONNIER
|
r9599 | extensions=['jinja2.ext.loopcontrols'] | ||
) | ||||
Matthias BUSSONNIER
|
r9578 | |||
Matthias BUSSONNIER
|
r9819 | print(os.path.dirname(os.path.realpath(__file__))+'/../templates/') | ||
Matthias BUSSONNIER
|
r9609 | texenv = Environment( | ||
Matthias BUSSONNIER
|
r9616 | loader=FileSystemLoader([ | ||
Matthias BUSSONNIER
|
r9819 | os.path.dirname(os.path.realpath(__file__))+'/../templates/tex/', | ||
os.path.dirname(os.path.realpath(__file__))+'/../templates/skeleton/tex/', | ||||
Matthias BUSSONNIER
|
r9616 | ]), | ||
Matthias BUSSONNIER
|
r9609 | extensions=['jinja2.ext.loopcontrols'] | ||
) | ||||
texenv.block_start_string = '((*' | ||||
texenv.block_end_string = '*))' | ||||
Matthias BUSSONNIER
|
r9650 | |||
Matthias BUSSONNIER
|
r9609 | texenv.variable_start_string = '(((' | ||
texenv.variable_end_string = ')))' | ||||
Matthias BUSSONNIER
|
r9650 | |||
Matthias BUSSONNIER
|
r9609 | texenv.comment_start_string = '((=' | ||
texenv.comment_end_string = '=))' | ||||
Matthias BUSSONNIER
|
r9650 | |||
Matthias BUSSONNIER
|
r9609 | texenv.filters['escape_tex'] = escape_tex | ||
Matthias BUSSONNIER
|
r9665 | #----------------------------------------------------------------------------- | ||
# Class declarations | ||||
#----------------------------------------------------------------------------- | ||||
class ConversionException(Exception): | ||||
pass | ||||
Matthias BUSSONNIER
|
r9614 | |||
Matthias BUSSONNIER
|
r9578 | class ConverterTemplate(Configurable): | ||
Matthias BUSSONNIER
|
r9640 | """ A Jinja2 base converter templates | ||
Preprocess the ipynb files, feed it throug jinja templates, | ||||
and spit an converted files and a data object with other data | ||||
Jonathan Frederic
|
r9771 | should be mostly configurable | ||
Matthias BUSSONNIER
|
r9640 | """ | ||
Matthias BUSSONNIER
|
r9578 | |||
Matthias BUSSONNIER
|
r9640 | pre_transformer_order = List(['haspyout_transformer'], | ||
Matthias BUSSONNIER
|
r9619 | config=True, | ||
Matthias BUSSONNIER
|
r9623 | help= """ | ||
An ordered list of pre transformer to apply to the ipynb | ||||
Jonathan Frederic
|
r9770 | file before running through templates | ||
Matthias BUSSONNIER
|
r9611 | """ | ||
) | ||||
Matthias BUSSONNIER
|
r9614 | |||
Matthias BUSSONNIER
|
r9618 | tex_environement = Bool(False, | ||
config=True, | ||||
help=""" is this a tex environment or not """) | ||||
template_file = Unicode('', | ||||
config=True, | ||||
Matthias BUSSONNIER
|
r9640 | help=""" Name of the template file to use """ ) | ||
Matthias BUSSONNIER
|
r9578 | #------------------------------------------------------------------------- | ||
# Instance-level attributes that are set in the constructor for this | ||||
# class. | ||||
#------------------------------------------------------------------------- | ||||
Jonathan Frederic
|
r9768 | |||
Matthias BUSSONNIER
|
r9640 | preprocessors = [] | ||
def __init__(self, preprocessors={}, jinja_filters={}, config=None, **kw): | ||||
Jonathan Frederic
|
r9769 | """ Init a new converter. | ||
config: the Configurable config object to pass around. | ||||
Matthias BUSSONNIER
|
r9603 | |||
Matthias BUSSONNIER
|
r9650 | preprocessors: dict of **availlable** key/value function to run on | ||
Matthias BUSSONNIER
|
r9665 | ipynb json data before conversion to extract/inline file. | ||
See `transformer.py` and `ConfigurableTransformers` | ||||
Matthias BUSSONNIER
|
r9650 | |||
Matthias BUSSONNIER
|
r9665 | set the order in which the transformers should apply | ||
with the `pre_transformer_order` trait of this class | ||||
Matthias BUSSONNIER
|
r9603 | |||
Matthias BUSSONNIER
|
r9665 | transformers registerd by this key will take precedence on | ||
default one. | ||||
jinja_filters: dict of supplementary jinja filter that should be made | ||||
availlable in template. If those are of Configurable Class type, | ||||
they will be instanciated with the config object as argument. | ||||
user defined filter will overwrite the one availlable by default. | ||||
Matthias BUSSONNIER
|
r9603 | """ | ||
Matthias BUSSONNIER
|
r9615 | super(ConverterTemplate, self).__init__(config=config, **kw) | ||
Matthias BUSSONNIER
|
r9665 | |||
# variable parameters depending on the pype of jinja environement | ||||
Matthias BUSSONNIER
|
r9618 | self.env = texenv if self.tex_environement else env | ||
self.ext = '.tplx' if self.tex_environement else '.tpl' | ||||
Matthias BUSSONNIER
|
r9624 | |||
Matthias BUSSONNIER
|
r9621 | for name in self.pre_transformer_order: | ||
Matthias BUSSONNIER
|
r9665 | # get the user-defined transformer first | ||
Jonathan Frederic
|
r9773 | transformer = preprocessors.get(name, getattr(trans, name, None)) | ||
Matthias BUSSONNIER
|
r9640 | if isinstance(transformer, MetaHasTraits): | ||
transformer = transformer(config=config) | ||||
self.preprocessors.append(transformer) | ||||
## for compat, remove later | ||||
Matthias BUSSONNIER
|
r9656 | self.preprocessors.append(trans.coalesce_streams) | ||
Matthias BUSSONNIER
|
r9650 | self.preprocessors.append(trans.ExtractFigureTransformer(config=config)) | ||
Matthias BUSSONNIER
|
r9644 | self.preprocessors.append(trans.RevealHelpTransformer(config=config)) | ||
Matthias BUSSONNIER
|
r9654 | self.preprocessors.append(trans.CSSHtmlHeaderTransformer(config=config)) | ||
Matthias BUSSONNIER
|
r9822 | if SphinxTransformer: | ||
self.preprocessors.append(SphinxTransformer(config=config)) | ||||
Jonathan Frederic
|
r9803 | self.preprocessors.append(LatexTransformer(config=config)) | ||
Matthias BUSSONNIER
|
r9615 | |||
Matthias BUSSONNIER
|
r9640 | ## | ||
Matthias BUSSONNIER
|
r9650 | self.env.filters['filter_data_type'] = FilterDataType(config=config) | ||
Matthias BUSSONNIER
|
r9615 | self.env.filters['pycomment'] = python_comment | ||
self.env.filters['indent'] = indent | ||||
self.env.filters['rm_fake'] = rm_fake | ||||
self.env.filters['rm_ansi'] = remove_ansi | ||||
self.env.filters['markdown'] = markdown | ||||
self.env.filters['highlight'] = highlight | ||||
Jonathan Frederic
|
r9780 | self.env.filters['highlight2latex'] = highlight2latex | ||
Matthias BUSSONNIER
|
r9615 | self.env.filters['ansi2html'] = ansi2html | ||
self.env.filters['markdown2latex'] = markdown2latex | ||||
Matthias BUSSONNIER
|
r9641 | self.env.filters['markdown2rst'] = markdown2rst | ||
Jonathan Frederic
|
r9780 | self.env.filters['get_lines'] = get_lines | ||
Matthias BUSSONNIER
|
r9701 | self.env.filters['wrap'] = wrap | ||
Matthias BUSSONNIER
|
r9665 | |||
## user filter will overwrite | ||||
Matthias BUSSONNIER
|
r9650 | for key, filtr in jinja_filters.iteritems(): | ||
if isinstance(filtr, MetaHasTraits): | ||||
self.env.filters[key] = filtr(config=config) | ||||
else : | ||||
self.env.filters[key] = filtr | ||||
Matthias BUSSONNIER
|
r9615 | |||
Matthias BUSSONNIER
|
r9618 | self.template = self.env.get_template(self.template_file+self.ext) | ||
Matthias BUSSONNIER
|
r9611 | |||
Matthias BUSSONNIER
|
r9578 | |||
Matthias BUSSONNIER
|
r9640 | def process(self, nb): | ||
Matthias BUSSONNIER
|
r9607 | """ | ||
Matthias BUSSONNIER
|
r9614 | preprocess the notebook json for easier use with the templates. | ||
will call all the `preprocessor`s in order before returning it. | ||||
Matthias BUSSONNIER
|
r9607 | """ | ||
Matthias BUSSONNIER
|
r9603 | |||
Matthias BUSSONNIER
|
r9615 | # dict of 'resources' that could be made by the preprocessors | ||
# like key/value data to extract files from ipynb like in latex conversion | ||||
resources = {} | ||||
Matthias BUSSONNIER
|
r9603 | for preprocessor in self.preprocessors: | ||
Matthias BUSSONNIER
|
r9622 | nb, resources = preprocessor(nb, resources) | ||
Matthias BUSSONNIER
|
r9603 | |||
Matthias BUSSONNIER
|
r9615 | return nb, resources | ||
Matthias BUSSONNIER
|
r9578 | |||
Matthias BUSSONNIER
|
r9640 | def convert(self, nb): | ||
Matthias BUSSONNIER
|
r9601 | """ convert the ipynb file | ||
return both the converted ipynb file and a dict containing potential | ||||
other resources | ||||
""" | ||||
Matthias BUSSONNIER
|
r9640 | nb, resources = self.process(nb) | ||
Matthias BUSSONNIER
|
r9654 | return self.template.render(nb=nb, resources=resources), resources | ||
Matthias BUSSONNIER
|
r9578 | |||
Matthias BUSSONNIER
|
r9640 | def from_filename(self, filename): | ||
Matthias BUSSONNIER
|
r9666 | """read and convert a notebook from a file name""" | ||
Matthias BUSSONNIER
|
r9589 | with io.open(filename) as f: | ||
Matthias BUSSONNIER
|
r9640 | return self.convert(nbformat.read(f, 'json')) | ||
Matthias BUSSONNIER
|
r9624 | |||
Matthias BUSSONNIER
|
r9666 | def from_file(self, filelike): | ||
"""read and convert a notebook from a filelike object | ||||
Matthias BUSSONNIER
|
r9650 | |||
Matthias BUSSONNIER
|
r9666 | filelike object will just be "read" and should be json format.. | ||
""" | ||||
return self.convert(nbformat.read(filelike, 'json')) | ||||
def from_json(self, json): | ||||
""" not implemented | ||||
Should convert from a json object | ||||
""" | ||||
raise NotImplementedError('not implemented (yet?)') | ||||