template.py
301 lines
| 9.2 KiB
| text/x-python
|
PythonLexer
/ converters / template.py
Matthias BUSSONNIER
|
r9578 | """Base classes for the notebook conversion pipeline. | ||
This module defines Converter, from which all objects designed to implement | ||||
a conversion of IPython notebooks to some other format should inherit. | ||||
""" | ||||
#----------------------------------------------------------------------------- | ||||
# Copyright (c) 2012, 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 | ||||
Matthias BUSSONNIER
|
r9609 | import re | ||
Matthias BUSSONNIER
|
r9593 | from IPython.utils import path | ||
Matthias BUSSONNIER
|
r9578 | |||
Matthias BUSSONNIER
|
r9602 | from jinja2 import Environment, FileSystemLoader | ||
Matthias BUSSONNIER
|
r9599 | env = Environment( | ||
Matthias BUSSONNIER
|
r9616 | loader=FileSystemLoader([ | ||
'./templates/', | ||||
'./templates/skeleton/', | ||||
]), | ||||
Matthias BUSSONNIER
|
r9599 | extensions=['jinja2.ext.loopcontrols'] | ||
) | ||||
Matthias BUSSONNIER
|
r9578 | |||
Matthias BUSSONNIER
|
r9609 | texenv = Environment( | ||
Matthias BUSSONNIER
|
r9616 | loader=FileSystemLoader([ | ||
'./templates/tex/', | ||||
'./templates/skeleton/tex/', | ||||
]), | ||||
Matthias BUSSONNIER
|
r9609 | extensions=['jinja2.ext.loopcontrols'] | ||
) | ||||
Matthias BUSSONNIER
|
r9578 | # IPython imports | ||
from IPython.nbformat import current as nbformat | ||||
Matthias BUSSONNIER
|
r9602 | from IPython.config.configurable import Configurable | ||
Matthias BUSSONNIER
|
r9615 | from IPython.utils.traitlets import ( Unicode, Any, List, Bool) | ||
Matthias BUSSONNIER
|
r9578 | |||
# Our own imports | ||||
from IPython.utils.text import indent | ||||
from .utils import remove_ansi | ||||
Matthias BUSSONNIER
|
r9580 | from markdown import markdown | ||
Matthias BUSSONNIER
|
r9602 | from .utils import highlight, ansi2html | ||
Matthias BUSSONNIER
|
r9610 | from .utils import markdown2latex | ||
Matthias BUSSONNIER
|
r9578 | #----------------------------------------------------------------------------- | ||
# Class declarations | ||||
#----------------------------------------------------------------------------- | ||||
def rm_fake(strng): | ||||
return strng.replace('/files/', '') | ||||
class ConversionException(Exception): | ||||
pass | ||||
def python_comment(string): | ||||
return '# '+'\n# '.join(string.split('\n')) | ||||
Matthias BUSSONNIER
|
r9593 | |||
def header_body(): | ||||
Matthias BUSSONNIER
|
r9602 | """Return the body of the header as a list of strings.""" | ||
from pygments.formatters import HtmlFormatter | ||||
header = [] | ||||
static = os.path.join(path.get_ipython_package_dir(), | ||||
'frontend', 'html', 'notebook', 'static', | ||||
) | ||||
here = os.path.split(os.path.realpath(__file__))[0] | ||||
css = os.path.join(static, 'css') | ||||
for sheet in [ | ||||
# do we need jquery and prettify? | ||||
# os.path.join(static, 'jquery', 'css', 'themes', 'base', | ||||
# 'jquery-ui.min.css'), | ||||
# os.path.join(static, 'prettify', 'prettify.css'), | ||||
os.path.join(css, 'boilerplate.css'), | ||||
os.path.join(css, 'fbm.css'), | ||||
os.path.join(css, 'notebook.css'), | ||||
os.path.join(css, 'renderedhtml.css'), | ||||
# our overrides: | ||||
os.path.join(here, '..', 'css', 'static_html.css'), | ||||
]: | ||||
with io.open(sheet, encoding='utf-8') as f: | ||||
s = f.read() | ||||
header.append(s) | ||||
pygments_css = HtmlFormatter().get_style_defs('.highlight') | ||||
header.append(pygments_css) | ||||
return header | ||||
Matthias BUSSONNIER
|
r9614 | # todo, make the key part configurable. | ||
Matthias BUSSONNIER
|
r9613 | def _new_figure(data, fmt, count): | ||
"""Create a new figure file in the given format. | ||||
Returns a path relative to the input file. | ||||
""" | ||||
figname = '_fig_%02i.%s' % (count, fmt) | ||||
# Binary files are base64-encoded, SVG is already XML | ||||
if fmt in ('png', 'jpg', 'pdf'): | ||||
data = data.decode('base64') | ||||
return figname,data | ||||
Matthias BUSSONNIER
|
r9602 | inlining = {} | ||
Matthias BUSSONNIER
|
r9598 | inlining['css'] = header_body() | ||
Matthias BUSSONNIER
|
r9593 | |||
Matthias BUSSONNIER
|
r9609 | LATEX_SUBS = ( | ||
(re.compile(r'\\'), r'\\textbackslash'), | ||||
(re.compile(r'([{}_#%&$])'), r'\\\1'), | ||||
(re.compile(r'~'), r'\~{}'), | ||||
(re.compile(r'\^'), r'\^{}'), | ||||
(re.compile(r'"'), r"''"), | ||||
(re.compile(r'\.\.\.+'), r'\\ldots'), | ||||
) | ||||
def escape_tex(value): | ||||
newval = value | ||||
for pattern, replacement in LATEX_SUBS: | ||||
newval = pattern.sub(replacement, newval) | ||||
return newval | ||||
texenv.block_start_string = '((*' | ||||
texenv.block_end_string = '*))' | ||||
texenv.variable_start_string = '(((' | ||||
texenv.variable_end_string = ')))' | ||||
texenv.comment_start_string = '((=' | ||||
texenv.comment_end_string = '=))' | ||||
texenv.filters['escape_tex'] = escape_tex | ||||
Matthias BUSSONNIER
|
r9614 | def cell_preprocessor(function): | ||
""" wrap a function to be executed on all cells of a notebook | ||||
wrapped function parameters : | ||||
cell : the cell | ||||
other : external resources | ||||
index : index of the cell | ||||
""" | ||||
def wrappedfunc(nb,other): | ||||
for worksheet in nb.worksheets : | ||||
for index, cell in enumerate(worksheet.cells): | ||||
worksheet.cells[index],other= function(cell,other,index) | ||||
return nb,other | ||||
return wrappedfunc | ||||
@cell_preprocessor | ||||
def haspyout_transformer(cell, other, count): | ||||
""" | ||||
Add a haspyout flag to cell that have it | ||||
Easier for templating, where you can't know in advance | ||||
wether to write the out prompt | ||||
Matthias BUSSONNIER
|
r9613 | |||
Matthias BUSSONNIER
|
r9614 | """ | ||
cell.type = cell.cell_type | ||||
cell.haspyout = False | ||||
for out in cell.get('outputs', []): | ||||
if out.output_type == 'pyout': | ||||
cell.haspyout = True | ||||
break | ||||
return cell,other | ||||
@cell_preprocessor | ||||
Matthias BUSSONNIER
|
r9615 | def extract_figure_transformer(cell,other,count): | ||
Matthias BUSSONNIER
|
r9614 | for i,out in enumerate(cell.get('outputs', [])): | ||
for type in ['html', 'pdf', 'svg', 'latex', 'png', 'jpg', 'jpeg']: | ||||
if out.hasattr(type): | ||||
figname,data = _new_figure(out[type], type,count) | ||||
cell.outputs[i][type] = figname | ||||
out['key_'+type] = figname | ||||
other[figname] = data | ||||
count = count+1 | ||||
Matthias BUSSONNIER
|
r9615 | return cell,other | ||
Matthias BUSSONNIER
|
r9604 | |||
Matthias BUSSONNIER
|
r9614 | |||
Matthias BUSSONNIER
|
r9578 | class ConverterTemplate(Configurable): | ||
Matthias BUSSONNIER
|
r9607 | """ A Jinja2 base converter templates""" | ||
Matthias BUSSONNIER
|
r9578 | |||
Matthias BUSSONNIER
|
r9611 | display_data_priority = List(['html', 'pdf', 'svg', 'latex', 'png', 'jpg', 'jpeg' , 'text'], | ||
config=True, | ||||
help= """ | ||||
Matthias BUSSONNIER
|
r9619 | An ordered list of prefered output type, the firs encounterd will usually be | ||
used when converting discarding the others. | ||||
""" | ||||
) | ||||
pre_transformer_order = List([], | ||||
config=True, | ||||
help= """ An ordered list of pretransformer to apply to the ipynb file befor running through templates | ||||
Matthias BUSSONNIER
|
r9611 | """ | ||
) | ||||
Matthias BUSSONNIER
|
r9614 | |||
extract_figures = Bool(False, | ||||
config=True, | ||||
help= """ | ||||
wether to remove figure data from ipynb and store them in auxiliary | ||||
dictionnary | ||||
""" | ||||
) | ||||
Matthias BUSSONNIER
|
r9618 | |||
tex_environement = Bool(False, | ||||
config=True, | ||||
help=""" is this a tex environment or not """) | ||||
template_file = Unicode('', | ||||
config=True, | ||||
help=""" whetever """ ) | ||||
Matthias BUSSONNIER
|
r9578 | #------------------------------------------------------------------------- | ||
# Instance-level attributes that are set in the constructor for this | ||||
# class. | ||||
#------------------------------------------------------------------------- | ||||
infile = Any() | ||||
Matthias BUSSONNIER
|
r9585 | |||
Matthias BUSSONNIER
|
r9578 | infile_dir = Unicode() | ||
Matthias BUSSONNIER
|
r9611 | |||
def filter_data_type(self,output): | ||||
for fmt in self.display_data_priority: | ||||
if fmt in output: | ||||
return [fmt] | ||||
Matthias BUSSONNIER
|
r9578 | |||
Matthias BUSSONNIER
|
r9618 | def __init__(self, preprocessors=[], config=None, **kw): | ||
Matthias BUSSONNIER
|
r9603 | """ | ||
Matthias BUSSONNIER
|
r9607 | tplfile : jinja template file to process. | ||
Matthias BUSSONNIER
|
r9603 | |||
Matthias BUSSONNIER
|
r9607 | config: the Configurable confg object to pass around | ||
Matthias BUSSONNIER
|
r9603 | |||
Matthias BUSSONNIER
|
r9607 | preprocessors: list of function to run on ipynb json data before conversion | ||
to extract/inline file, | ||||
Matthias BUSSONNIER
|
r9603 | |||
""" | ||||
Matthias BUSSONNIER
|
r9615 | super(ConverterTemplate, self).__init__(config=config, **kw) | ||
Matthias BUSSONNIER
|
r9618 | self.env = texenv if self.tex_environement else env | ||
self.ext = '.tplx' if self.tex_environement else '.tpl' | ||||
Matthias BUSSONNIER
|
r9602 | self.nb = None | ||
Matthias BUSSONNIER
|
r9603 | self.preprocessors = preprocessors | ||
Matthias BUSSONNIER
|
r9604 | self.preprocessors.append(haspyout_transformer) | ||
Matthias BUSSONNIER
|
r9615 | if self.extract_figures: | ||
self.preprocessors.append(extract_figure_transformer) | ||||
Matthias BUSSONNIER
|
r9611 | self.env.filters['filter_data_type'] = self.filter_data_type | ||
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 | ||||
self.env.filters['ansi2html'] = ansi2html | ||||
self.env.filters['markdown2latex'] = markdown2latex | ||||
Matthias BUSSONNIER
|
r9618 | self.template = self.env.get_template(self.template_file+self.ext) | ||
Matthias BUSSONNIER
|
r9611 | |||
Matthias BUSSONNIER
|
r9578 | |||
Matthias BUSSONNIER
|
r9604 | |||
Matthias BUSSONNIER
|
r9578 | def process(self): | ||
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 | nb = self.nb | ||
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
|
r9615 | nb,resources = preprocessor(nb,resources) | ||
Matthias BUSSONNIER
|
r9603 | |||
Matthias BUSSONNIER
|
r9615 | return nb, resources | ||
Matthias BUSSONNIER
|
r9578 | |||
Matthias BUSSONNIER
|
r9602 | def convert(self): | ||
Matthias BUSSONNIER
|
r9601 | """ convert the ipynb file | ||
return both the converted ipynb file and a dict containing potential | ||||
other resources | ||||
""" | ||||
Matthias BUSSONNIER
|
r9615 | nb,resources = self.process() | ||
return self.template.render(nb=nb, inlining=inlining), resources | ||||
Matthias BUSSONNIER
|
r9578 | |||
def read(self, filename): | ||||
"read and parse notebook into NotebookNode called self.nb" | ||||
Matthias BUSSONNIER
|
r9589 | with io.open(filename) as f: | ||
Matthias BUSSONNIER
|
r9578 | self.nb = nbformat.read(f, 'json') | ||