transformers.py
328 lines
| 10.5 KiB
| text/x-python
|
PythonLexer
/ converters / transformers.py
Matthias BUSSONNIER
|
r9302 | """ | ||
""" | ||||
Matthias BUSSONNIER
|
r9490 | from __future__ import print_function | ||
Matthias BUSSONNIER
|
r9307 | |||
from IPython.config.configurable import Configurable | ||||
Matthias BUSSONNIER
|
r9312 | from IPython.utils.traitlets import Unicode, Bool, Dict, List | ||
Matthias BUSSONNIER
|
r9307 | |||
Matthias BUSSONNIER
|
r9490 | class ConfigurableTransformers(Configurable): | ||
Matthias BUSSONNIER
|
r9307 | """ A configurable transformer """ | ||
def __init__(self, config=None, **kw): | ||||
super(ConfigurableTransformers, self).__init__(config=config, **kw) | ||||
def __call__(self, nb, other): | ||||
try : | ||||
for worksheet in nb.worksheets : | ||||
for index, cell in enumerate(worksheet.cells): | ||||
worksheet.cells[index], other = self.cell_transform(cell, other, index) | ||||
return nb, other | ||||
Matthias BUSSONNIER
|
r9491 | except NotImplementedError: | ||
Matthias BUSSONNIER
|
r9307 | raise NotImplementedError('should be implemented by subclass') | ||
def cell_transform(self, cell, other, index): | ||||
""" | ||||
Overwrite if you want to apply a transformation on each cell | ||||
""" | ||||
raise NotImplementedError('should be implemented by subclass') | ||||
Matthias BUSSONNIER
|
r9420 | class ActivatableTransformer(ConfigurableTransformers): | ||
Matthias BUSSONNIER
|
r9307 | |||
Matthias BUSSONNIER
|
r9421 | enabled = Bool(False, config=True) | ||
Matthias BUSSONNIER
|
r9307 | |||
Matthias BUSSONNIER
|
r9420 | def __call__(self, nb, other): | ||
Matthias BUSSONNIER
|
r9421 | if not self.enabled : | ||
Matthias BUSSONNIER
|
r9491 | return nb, other | ||
Matthias BUSSONNIER
|
r9420 | else : | ||
Matthias BUSSONNIER
|
r9491 | return super(ActivatableTransformer, self).__call__(nb, other) | ||
Matthias BUSSONNIER
|
r9307 | |||
Matthias BUSSONNIER
|
r9302 | 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 | ||||
""" | ||||
Matthias BUSSONNIER
|
r9303 | def wrappedfunc(nb, other): | ||
Matthias BUSSONNIER
|
r9302 | for worksheet in nb.worksheets : | ||
for index, cell in enumerate(worksheet.cells): | ||||
Matthias BUSSONNIER
|
r9303 | worksheet.cells[index], other = function(cell, other, index) | ||
return nb, other | ||||
Matthias BUSSONNIER
|
r9302 | return wrappedfunc | ||
@cell_preprocessor | ||||
def haspyout_transformer(cell, other, count): | ||||
""" | ||||
Add a haspyout flag to cell that have it | ||||
Matthias BUSSONNIER
|
r9307 | |||
Matthias BUSSONNIER
|
r9302 | Easier for templating, where you can't know in advance | ||
wether to write the out prompt | ||||
""" | ||||
cell.type = cell.cell_type | ||||
cell.haspyout = False | ||||
for out in cell.get('outputs', []): | ||||
if out.output_type == 'pyout': | ||||
cell.haspyout = True | ||||
break | ||||
Matthias BUSSONNIER
|
r9303 | return cell, other | ||
Matthias BUSSONNIER
|
r9302 | |||
Matthias BUSSONNIER
|
r9492 | @cell_preprocessor | ||
def coalesce_streams(cell, other, count): | ||||
"""merge consecutive sequences of stream output into single stream | ||||
to prevent extra newlines inserted at flush calls | ||||
TODO: handle \r deletion | ||||
""" | ||||
Matthias BUSSONNIER
|
r9494 | outputs = cell.get('outputs', []) | ||
Matthias BUSSONNIER
|
r9492 | if not outputs: | ||
return cell, other | ||||
new_outputs = [] | ||||
last = outputs[0] | ||||
new_outputs = [last] | ||||
for output in outputs[1:]: | ||||
if (output.output_type == 'stream' and | ||||
last.output_type == 'stream' and | ||||
last.stream == output.stream | ||||
): | ||||
last.text += output.text | ||||
else: | ||||
new_outputs.append(output) | ||||
cell.outputs = new_outputs | ||||
return cell, other | ||||
Matthias BUSSONNIER
|
r9302 | |||
Matthias BUSSONNIER
|
r9303 | |||
Matthias BUSSONNIER
|
r9420 | class ExtractFigureTransformer(ActivatableTransformer): | ||
Matthias BUSSONNIER
|
r9303 | |||
Matthias BUSSONNIER
|
r9307 | extra_ext_map = Dict({}, | ||
config=True, | ||||
help="""extra map to override extension based on type. | ||||
Usefull for latex where svg will be converted to pdf before inclusion | ||||
""" | ||||
) | ||||
Matthias BUSSONNIER
|
r9490 | display_data_priority = List(['html', 'pdf', 'svg', 'latex', 'png', 'jpg', 'jpeg' , 'text'], | ||
config=True, | ||||
help= """ | ||||
An ordered list of prefered output type, the first | ||||
encounterd will usually be used when converting discarding | ||||
the others. | ||||
""" | ||||
) | ||||
Matthias BUSSONNIER
|
r9493 | key_format_map = Dict({}, | ||
config=True, | ||||
) | ||||
Matthias BUSSONNIER
|
r9494 | figname_format_map = Dict({}, | ||
config=True, | ||||
) | ||||
Matthias BUSSONNIER
|
r9303 | |||
Matthias BUSSONNIER
|
r9307 | #to do change this to .format {} syntax | ||
Matthias BUSSONNIER
|
r9494 | default_key_tpl = Unicode('_fig_{count:02d}.{ext}', config=True) | ||
Matthias BUSSONNIER
|
r9307 | |||
def _get_ext(self, ext): | ||||
if ext in self.extra_ext_map : | ||||
return self.extra_ext_map[ext] | ||||
return ext | ||||
def _new_figure(self, data, fmt, count): | ||||
"""Create a new figure file in the given format. | ||||
""" | ||||
Matthias BUSSONNIER
|
r9494 | tplf = self.figname_format_map.get(fmt,self.default_key_tpl) | ||
tplk = self.key_format_map.get(fmt,self.default_key_tpl) | ||||
# option to pass the hash as data ? | ||||
figname = tplf.format(count=count, ext=self._get_ext(fmt)) | ||||
key = tplk.format(count=count, ext=self._get_ext(fmt)) | ||||
Matthias BUSSONNIER
|
r9307 | |||
# Binary files are base64-encoded, SVG is already XML | ||||
if fmt in ('png', 'jpg', 'pdf'): | ||||
data = data.decode('base64') | ||||
return figname, key, data | ||||
def cell_transform(self, cell, other, count): | ||||
Matthias BUSSONNIER
|
r9491 | if other.get('figures', None) is None : | ||
other['figures'] = {} | ||||
for out in cell.get('outputs', []): | ||||
for out_type in self.display_data_priority: | ||||
if out.hasattr(out_type): | ||||
figname, key, data = self._new_figure(out[out_type], out_type, count) | ||||
out['key_'+out_type] = figname | ||||
Matthias BUSSONNIER
|
r9331 | other['figures'][key] = data | ||
Matthias BUSSONNIER
|
r9307 | count = count+1 | ||
return cell, other | ||||
Matthias BUSSONNIER
|
r9302 | |||
Matthias BUSSONNIER
|
r9401 | class RevealHelpTransformer(ConfigurableTransformers): | ||
section_open = False | ||||
subsection_open = False | ||||
fragment_open = False | ||||
def open_subsection(self): | ||||
self.subsection_open = True | ||||
return True | ||||
def open_section(self): | ||||
self.section_open = True | ||||
return True | ||||
def open_fragment(self): | ||||
self.fragment_open = True | ||||
return True | ||||
Matthias BUSSONNIER
|
r9407 | # could probaly write those maybe_close/open | ||
# with a function functor | ||||
Matthias BUSSONNIER
|
r9401 | def maybe_close_section(self): | ||
"""return True is already open, false otherwise | ||||
and change state to close | ||||
""" | ||||
if self.section_open : | ||||
self.section_open = False | ||||
return True | ||||
else : | ||||
return False | ||||
def maybe_open_section(self): | ||||
"""return True is already open, false otherwise | ||||
and change state to close | ||||
""" | ||||
if not self.section_open : | ||||
self.section_open = True | ||||
return True | ||||
else : | ||||
return False | ||||
def maybe_open_subsection(self): | ||||
"""return True is already open, false otherwise | ||||
and change state to close | ||||
""" | ||||
if not self.subsection_open : | ||||
self.subsection_open = True | ||||
return True | ||||
else : | ||||
return False | ||||
def maybe_close_subsection(self): | ||||
"""return True is already open, false otherwise | ||||
and change state to close | ||||
""" | ||||
if self.subsection_open : | ||||
self.subsection_open = False | ||||
return True | ||||
else : | ||||
return False | ||||
def maybe_close_fragment(self): | ||||
"""return True is already open, false otherwise | ||||
and change state to close | ||||
""" | ||||
if self.fragment_open : | ||||
self.fragment_open = False | ||||
return True | ||||
else : | ||||
return False | ||||
Matthias BUSSONNIER
|
r9491 | def cell_transform(self, cell, other, count): | ||
ctype = cell.metadata.get('slideshow', {}).get('slide_type', None) | ||||
Matthias BUSSONNIER
|
r9419 | if ctype in [None, '-'] : | ||
Matthias BUSSONNIER
|
r9401 | cell.metadata.slideshow = {} | ||
cell.metadata.slideshow['slide_type'] = None | ||||
Matthias BUSSONNIER
|
r9407 | elif ctype == 'fragment': | ||
Matthias BUSSONNIER
|
r9401 | cell.metadata.slideshow.close_fragment = self.maybe_close_fragment() | ||
cell.metadata.slideshow.close_subsection = False | ||||
cell.metadata.slideshow.close_section = False | ||||
Matthias BUSSONNIER
|
r9407 | |||
Matthias BUSSONNIER
|
r9401 | cell.metadata.slideshow.open_section = self.maybe_open_section() | ||
cell.metadata.slideshow.open_subsection = self.maybe_open_subsection() | ||||
cell.metadata.slideshow.open_fragment = self.open_fragment() | ||||
elif ctype == 'subslide': | ||||
cell.metadata.slideshow.close_fragment = self.maybe_close_fragment() | ||||
cell.metadata.slideshow.close_subsection = self.maybe_close_subsection() | ||||
cell.metadata.slideshow.close_section = False | ||||
Matthias BUSSONNIER
|
r9407 | |||
Matthias BUSSONNIER
|
r9401 | cell.metadata.slideshow.open_section = self.maybe_open_section() | ||
cell.metadata.slideshow.open_subsection = self.open_subsection() | ||||
cell.metadata.slideshow.open_fragment = False | ||||
elif ctype == 'slide': | ||||
cell.metadata.slideshow.close_fragment = self.maybe_close_fragment() | ||||
cell.metadata.slideshow.close_subsection = self.maybe_close_subsection() | ||||
cell.metadata.slideshow.close_section = self.maybe_close_section() | ||||
Matthias BUSSONNIER
|
r9407 | |||
Matthias BUSSONNIER
|
r9401 | cell.metadata.slideshow.open_section = self.open_section() | ||
cell.metadata.slideshow.open_subsection = self.open_subsection() | ||||
cell.metadata.slideshow.open_fragment = False | ||||
Matthias BUSSONNIER
|
r9491 | return cell, other | ||
Matthias BUSSONNIER
|
r9401 | |||
Matthias BUSSONNIER
|
r9490 | |||
class CSSHtmlHeaderTransformer(ActivatableTransformer): | ||||
Matthias BUSSONNIER
|
r9492 | |||
Matthias BUSSONNIER
|
r9490 | def __call__(self, nb, resources): | ||
"""Fetch and add css to the resource dict | ||||
Matthias BUSSONNIER
|
r9492 | Fetch css from IPython adn Pygment to add at the beginning | ||
of the html files. | ||||
Matthias BUSSONNIER
|
r9490 | |||
Add this css in resources in the "inlining.css" key | ||||
""" | ||||
Matthias BUSSONNIER
|
r9491 | resources['inlining'] = {} | ||
Matthias BUSSONNIER
|
r9490 | resources['inlining']['css'] = self.header | ||
return nb, resources | ||||
Matthias BUSSONNIER
|
r9491 | header = [] | ||
Matthias BUSSONNIER
|
r9490 | |||
def __init__(self, config=None, **kw): | ||||
Matthias BUSSONNIER
|
r9491 | super(CSSHtmlHeaderTransformer, self).__init__(config=config, **kw) | ||
Matthias BUSSONNIER
|
r9490 | if self.enabled : | ||
self.regen_header() | ||||
def regen_header(self): | ||||
## lazy load asa this might not be use in many transformers | ||||
import os | ||||
from IPython.utils import path | ||||
import io | ||||
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'), | ||||
os.path.join(css, 'style.min.css'), | ||||
# our overrides: | ||||
os.path.join(here, '..', 'css', 'static_html.css'), | ||||
]: | ||||
try: | ||||
with io.open(sheet, encoding='utf-8') as f: | ||||
s = f.read() | ||||
header.append(s) | ||||
except IOError: | ||||
# new version of ipython with style.min.css, pass | ||||
pass | ||||
pygments_css = HtmlFormatter().get_style_defs('.highlight') | ||||
header.append(pygments_css) | ||||
self.header = header | ||||