##// END OF EJS Templates
docs
Matthias BUSSONNIER -
Show More
@@ -1,328 +1,364 b''
1 """
1 """
2 Module that regroups transformer that woudl be applied to ipynb files
3 before going through the templating machinery.
2
4
5 It exposes convenient classes to inherit from to access configurability
6 as well as decorator to simplify tasks.
3 """
7 """
4
8
5 from __future__ import print_function
9 from __future__ import print_function
6
10
7
8 from IPython.config.configurable import Configurable
11 from IPython.config.configurable import Configurable
9 from IPython.utils.traitlets import Unicode, Bool, Dict, List
12 from IPython.utils.traitlets import Unicode, Bool, Dict, List
10
13
11 class ConfigurableTransformers(Configurable):
14 class ConfigurableTransformers(Configurable):
12 """ A configurable transformer """
15 """ A configurable transformer
16
17 Inherit from this class if you wish to have configurability for your
18 transformer.
19
20 Any configurable traitlets this class exposed will be configurable in profiles
21 using c.SubClassName.atribute=value
22
23 you can overwrite cell_transform to apply a transformation independently on each cell
24 or __call__ if you prefer your own logic. See orresponding docstring for informations.
25
26
27 """
13
28
14 def __init__(self, config=None, **kw):
29 def __init__(self, config=None, **kw):
15 super(ConfigurableTransformers, self).__init__(config=config, **kw)
30 super(ConfigurableTransformers, self).__init__(config=config, **kw)
16
31
17 def __call__(self, nb, other):
32 def __call__(self, nb, other):
33 """transformation to apply on each notebook.
34
35 received a handle to the current notebook as well as a dict of resources
36 which structure depends on the transformer.
37
38 You should return modified nb, other.
39
40 If you wish to apply on each cell, you might want to overwrite cell_transform method.
41 """
18 try :
42 try :
19 for worksheet in nb.worksheets :
43 for worksheet in nb.worksheets :
20 for index, cell in enumerate(worksheet.cells):
44 for index, cell in enumerate(worksheet.cells):
21 worksheet.cells[index], other = self.cell_transform(cell, other, index)
45 worksheet.cells[index], other = self.cell_transform(cell, other, index)
22 return nb, other
46 return nb, other
23 except NotImplementedError:
47 except NotImplementedError:
24 raise NotImplementedError('should be implemented by subclass')
48 raise NotImplementedError('should be implemented by subclass')
25
49
26 def cell_transform(self, cell, other, index):
50 def cell_transform(self, cell, other, index):
27 """
51 """
28 Overwrite if you want to apply a transformation on each cell
52 Overwrite if you want to apply a transformation on each cell,
53
54 receive the current cell, the resource dict and the index of current cell as parameter.
55
56 You should return modified cell and resource dict.
29 """
57 """
30 raise NotImplementedError('should be implemented by subclass')
58 raise NotImplementedError('should be implemented by subclass')
59 return cell, other
31
60
32
61
33 class ActivatableTransformer(ConfigurableTransformers):
62 class ActivatableTransformer(ConfigurableTransformers):
63 """A simple ConfigurableTransformers that have an enabled flag
64
65 Inherit from that if you just want to have a transformer which is
66 no-op by default but can be activated in profiles with
67
68 c.YourTransformerName.enabled = True
69 """
34
70
35 enabled = Bool(False, config=True)
71 enabled = Bool(False, config=True)
36
72
37 def __call__(self, nb, other):
73 def __call__(self, nb, other):
38 if not self.enabled :
74 if not self.enabled :
39 return nb, other
75 return nb, other
40 else :
76 else :
41 return super(ActivatableTransformer, self).__call__(nb, other)
77 return super(ActivatableTransformer, self).__call__(nb, other)
42
78
43
79
44 def cell_preprocessor(function):
80 def cell_preprocessor(function):
45 """ wrap a function to be executed on all cells of a notebook
81 """ wrap a function to be executed on all cells of a notebook
46
82
47 wrapped function parameters :
83 wrapped function parameters :
48 cell : the cell
84 cell : the cell
49 other : external resources
85 other : external resources
50 index : index of the cell
86 index : index of the cell
51 """
87 """
52 def wrappedfunc(nb, other):
88 def wrappedfunc(nb, other):
53 for worksheet in nb.worksheets :
89 for worksheet in nb.worksheets :
54 for index, cell in enumerate(worksheet.cells):
90 for index, cell in enumerate(worksheet.cells):
55 worksheet.cells[index], other = function(cell, other, index)
91 worksheet.cells[index], other = function(cell, other, index)
56 return nb, other
92 return nb, other
57 return wrappedfunc
93 return wrappedfunc
58
94
59
95
60 @cell_preprocessor
96 @cell_preprocessor
61 def haspyout_transformer(cell, other, count):
97 def haspyout_transformer(cell, other, count):
62 """
98 """
63 Add a haspyout flag to cell that have it
99 Add a haspyout flag to cell that have it
64
100
65 Easier for templating, where you can't know in advance
101 Easier for templating, where you can't know in advance
66 wether to write the out prompt
102 wether to write the out prompt
67
103
68 """
104 """
69 cell.type = cell.cell_type
105 cell.type = cell.cell_type
70 cell.haspyout = False
106 cell.haspyout = False
71 for out in cell.get('outputs', []):
107 for out in cell.get('outputs', []):
72 if out.output_type == 'pyout':
108 if out.output_type == 'pyout':
73 cell.haspyout = True
109 cell.haspyout = True
74 break
110 break
75 return cell, other
111 return cell, other
76
112
77 @cell_preprocessor
113 @cell_preprocessor
78 def coalesce_streams(cell, other, count):
114 def coalesce_streams(cell, other, count):
79 """merge consecutive sequences of stream output into single stream
115 """merge consecutive sequences of stream output into single stream
80
116
81 to prevent extra newlines inserted at flush calls
117 to prevent extra newlines inserted at flush calls
82
118
83 TODO: handle \r deletion
119 TODO: handle \r deletion
84 """
120 """
85 outputs = cell.get('outputs', [])
121 outputs = cell.get('outputs', [])
86 if not outputs:
122 if not outputs:
87 return cell, other
123 return cell, other
88 new_outputs = []
124 new_outputs = []
89 last = outputs[0]
125 last = outputs[0]
90 new_outputs = [last]
126 new_outputs = [last]
91 for output in outputs[1:]:
127 for output in outputs[1:]:
92 if (output.output_type == 'stream' and
128 if (output.output_type == 'stream' and
93 last.output_type == 'stream' and
129 last.output_type == 'stream' and
94 last.stream == output.stream
130 last.stream == output.stream
95 ):
131 ):
96 last.text += output.text
132 last.text += output.text
97 else:
133 else:
98 new_outputs.append(output)
134 new_outputs.append(output)
99
135
100 cell.outputs = new_outputs
136 cell.outputs = new_outputs
101 return cell, other
137 return cell, other
102
138
103
104
105 class ExtractFigureTransformer(ActivatableTransformer):
139 class ExtractFigureTransformer(ActivatableTransformer):
106
140
141
107 extra_ext_map = Dict({},
142 extra_ext_map = Dict({},
108 config=True,
143 config=True,
109 help="""extra map to override extension based on type.
144 help="""extra map to override extension based on type.
110 Usefull for latex where svg will be converted to pdf before inclusion
145 Usefull for latex where svg will be converted to pdf before inclusion
111 """
146 """
112 )
147 )
113 display_data_priority = List(['html', 'pdf', 'svg', 'latex', 'png', 'jpg', 'jpeg' , 'text'],
148 display_data_priority = List(['html', 'pdf', 'svg', 'latex', 'png', 'jpg', 'jpeg' , 'text'],
114 config=True,
149 config=True,
115 help= """
150 help= """
116 An ordered list of prefered output type, the first
151 An ordered list of prefered output type, the first
117 encounterd will usually be used when converting discarding
152 encounterd will usually be used when converting discarding
118 the others.
153 the others.
119 """
154 """
120 )
155 )
121
156
122 key_format_map = Dict({},
157 key_format_map = Dict({},
123 config=True,
158 config=True,
124 )
159 )
125
160
126 figname_format_map = Dict({},
161 figname_format_map = Dict({},
127 config=True,
162 config=True,
128 )
163 )
129
164
165
130 #to do change this to .format {} syntax
166 #to do change this to .format {} syntax
131 default_key_tpl = Unicode('_fig_{count:02d}.{ext}', config=True)
167 default_key_tpl = Unicode('_fig_{count:02d}.{ext}', config=True)
132
168
133 def _get_ext(self, ext):
169 def _get_ext(self, ext):
134 if ext in self.extra_ext_map :
170 if ext in self.extra_ext_map :
135 return self.extra_ext_map[ext]
171 return self.extra_ext_map[ext]
136 return ext
172 return ext
137
173
138 def _new_figure(self, data, fmt, count):
174 def _new_figure(self, data, fmt, count):
139 """Create a new figure file in the given format.
175 """Create a new figure file in the given format.
140
176
141 """
177 """
142 tplf = self.figname_format_map.get(fmt,self.default_key_tpl)
178 tplf = self.figname_format_map.get(fmt,self.default_key_tpl)
143 tplk = self.key_format_map.get(fmt,self.default_key_tpl)
179 tplk = self.key_format_map.get(fmt,self.default_key_tpl)
144
180
145 # option to pass the hash as data ?
181 # option to pass the hash as data ?
146 figname = tplf.format(count=count, ext=self._get_ext(fmt))
182 figname = tplf.format(count=count, ext=self._get_ext(fmt))
147 key = tplk.format(count=count, ext=self._get_ext(fmt))
183 key = tplk.format(count=count, ext=self._get_ext(fmt))
148
184
149 # Binary files are base64-encoded, SVG is already XML
185 # Binary files are base64-encoded, SVG is already XML
150 if fmt in ('png', 'jpg', 'pdf'):
186 if fmt in ('png', 'jpg', 'pdf'):
151 data = data.decode('base64')
187 data = data.decode('base64')
152
188
153 return figname, key, data
189 return figname, key, data
154
190
155
191
156 def cell_transform(self, cell, other, count):
192 def cell_transform(self, cell, other, count):
157 if other.get('figures', None) is None :
193 if other.get('figures', None) is None :
158 other['figures'] = {}
194 other['figures'] = {}
159 for out in cell.get('outputs', []):
195 for out in cell.get('outputs', []):
160 for out_type in self.display_data_priority:
196 for out_type in self.display_data_priority:
161 if out.hasattr(out_type):
197 if out.hasattr(out_type):
162 figname, key, data = self._new_figure(out[out_type], out_type, count)
198 figname, key, data = self._new_figure(out[out_type], out_type, count)
163 out['key_'+out_type] = figname
199 out['key_'+out_type] = figname
164 other['figures'][key] = data
200 other['figures'][key] = data
165 count = count+1
201 count = count+1
166 return cell, other
202 return cell, other
167
203
168 class RevealHelpTransformer(ConfigurableTransformers):
204 class RevealHelpTransformer(ConfigurableTransformers):
169
205
170 section_open = False
206 section_open = False
171 subsection_open = False
207 subsection_open = False
172 fragment_open = False
208 fragment_open = False
173
209
174 def open_subsection(self):
210 def open_subsection(self):
175 self.subsection_open = True
211 self.subsection_open = True
176 return True
212 return True
177
213
178 def open_section(self):
214 def open_section(self):
179 self.section_open = True
215 self.section_open = True
180 return True
216 return True
181
217
182 def open_fragment(self):
218 def open_fragment(self):
183 self.fragment_open = True
219 self.fragment_open = True
184 return True
220 return True
185
221
186 # could probaly write those maybe_close/open
222 # could probaly write those maybe_close/open
187 # with a function functor
223 # with a function functor
188 def maybe_close_section(self):
224 def maybe_close_section(self):
189 """return True is already open, false otherwise
225 """return True is already open, false otherwise
190 and change state to close
226 and change state to close
191 """
227 """
192 if self.section_open :
228 if self.section_open :
193 self.section_open = False
229 self.section_open = False
194 return True
230 return True
195 else :
231 else :
196 return False
232 return False
197
233
198 def maybe_open_section(self):
234 def maybe_open_section(self):
199 """return True is already open, false otherwise
235 """return True is already open, false otherwise
200 and change state to close
236 and change state to close
201 """
237 """
202 if not self.section_open :
238 if not self.section_open :
203 self.section_open = True
239 self.section_open = True
204 return True
240 return True
205 else :
241 else :
206 return False
242 return False
207
243
208 def maybe_open_subsection(self):
244 def maybe_open_subsection(self):
209 """return True is already open, false otherwise
245 """return True is already open, false otherwise
210 and change state to close
246 and change state to close
211 """
247 """
212 if not self.subsection_open :
248 if not self.subsection_open :
213 self.subsection_open = True
249 self.subsection_open = True
214 return True
250 return True
215 else :
251 else :
216 return False
252 return False
217
253
218 def maybe_close_subsection(self):
254 def maybe_close_subsection(self):
219 """return True is already open, false otherwise
255 """return True is already open, false otherwise
220 and change state to close
256 and change state to close
221 """
257 """
222 if self.subsection_open :
258 if self.subsection_open :
223 self.subsection_open = False
259 self.subsection_open = False
224 return True
260 return True
225 else :
261 else :
226 return False
262 return False
227
263
228 def maybe_close_fragment(self):
264 def maybe_close_fragment(self):
229 """return True is already open, false otherwise
265 """return True is already open, false otherwise
230 and change state to close
266 and change state to close
231 """
267 """
232 if self.fragment_open :
268 if self.fragment_open :
233 self.fragment_open = False
269 self.fragment_open = False
234 return True
270 return True
235 else :
271 else :
236 return False
272 return False
237
273
238 def cell_transform(self, cell, other, count):
274 def cell_transform(self, cell, other, count):
239 ctype = cell.metadata.get('slideshow', {}).get('slide_type', None)
275 ctype = cell.metadata.get('slideshow', {}).get('slide_type', None)
240 if ctype in [None, '-'] :
276 if ctype in [None, '-'] :
241 cell.metadata.slideshow = {}
277 cell.metadata.slideshow = {}
242 cell.metadata.slideshow['slide_type'] = None
278 cell.metadata.slideshow['slide_type'] = None
243 elif ctype == 'fragment':
279 elif ctype == 'fragment':
244 cell.metadata.slideshow.close_fragment = self.maybe_close_fragment()
280 cell.metadata.slideshow.close_fragment = self.maybe_close_fragment()
245 cell.metadata.slideshow.close_subsection = False
281 cell.metadata.slideshow.close_subsection = False
246 cell.metadata.slideshow.close_section = False
282 cell.metadata.slideshow.close_section = False
247
283
248 cell.metadata.slideshow.open_section = self.maybe_open_section()
284 cell.metadata.slideshow.open_section = self.maybe_open_section()
249 cell.metadata.slideshow.open_subsection = self.maybe_open_subsection()
285 cell.metadata.slideshow.open_subsection = self.maybe_open_subsection()
250 cell.metadata.slideshow.open_fragment = self.open_fragment()
286 cell.metadata.slideshow.open_fragment = self.open_fragment()
251
287
252 elif ctype == 'subslide':
288 elif ctype == 'subslide':
253 cell.metadata.slideshow.close_fragment = self.maybe_close_fragment()
289 cell.metadata.slideshow.close_fragment = self.maybe_close_fragment()
254 cell.metadata.slideshow.close_subsection = self.maybe_close_subsection()
290 cell.metadata.slideshow.close_subsection = self.maybe_close_subsection()
255 cell.metadata.slideshow.close_section = False
291 cell.metadata.slideshow.close_section = False
256
292
257 cell.metadata.slideshow.open_section = self.maybe_open_section()
293 cell.metadata.slideshow.open_section = self.maybe_open_section()
258 cell.metadata.slideshow.open_subsection = self.open_subsection()
294 cell.metadata.slideshow.open_subsection = self.open_subsection()
259 cell.metadata.slideshow.open_fragment = False
295 cell.metadata.slideshow.open_fragment = False
260 elif ctype == 'slide':
296 elif ctype == 'slide':
261 cell.metadata.slideshow.close_fragment = self.maybe_close_fragment()
297 cell.metadata.slideshow.close_fragment = self.maybe_close_fragment()
262 cell.metadata.slideshow.close_subsection = self.maybe_close_subsection()
298 cell.metadata.slideshow.close_subsection = self.maybe_close_subsection()
263 cell.metadata.slideshow.close_section = self.maybe_close_section()
299 cell.metadata.slideshow.close_section = self.maybe_close_section()
264
300
265 cell.metadata.slideshow.open_section = self.open_section()
301 cell.metadata.slideshow.open_section = self.open_section()
266 cell.metadata.slideshow.open_subsection = self.open_subsection()
302 cell.metadata.slideshow.open_subsection = self.open_subsection()
267 cell.metadata.slideshow.open_fragment = False
303 cell.metadata.slideshow.open_fragment = False
268 return cell, other
304 return cell, other
269
305
270
306
271 class CSSHtmlHeaderTransformer(ActivatableTransformer):
307 class CSSHtmlHeaderTransformer(ActivatableTransformer):
272
308
273 def __call__(self, nb, resources):
309 def __call__(self, nb, resources):
274 """Fetch and add css to the resource dict
310 """Fetch and add css to the resource dict
275
311
276 Fetch css from IPython adn Pygment to add at the beginning
312 Fetch css from IPython adn Pygment to add at the beginning
277 of the html files.
313 of the html files.
278
314
279 Add this css in resources in the "inlining.css" key
315 Add this css in resources in the "inlining.css" key
280 """
316 """
281 resources['inlining'] = {}
317 resources['inlining'] = {}
282 resources['inlining']['css'] = self.header
318 resources['inlining']['css'] = self.header
283 return nb, resources
319 return nb, resources
284
320
285 header = []
321 header = []
286
322
287 def __init__(self, config=None, **kw):
323 def __init__(self, config=None, **kw):
288 super(CSSHtmlHeaderTransformer, self).__init__(config=config, **kw)
324 super(CSSHtmlHeaderTransformer, self).__init__(config=config, **kw)
289 if self.enabled :
325 if self.enabled :
290 self.regen_header()
326 self.regen_header()
291
327
292 def regen_header(self):
328 def regen_header(self):
293 ## lazy load asa this might not be use in many transformers
329 ## lazy load asa this might not be use in many transformers
294 import os
330 import os
295 from IPython.utils import path
331 from IPython.utils import path
296 import io
332 import io
297 from pygments.formatters import HtmlFormatter
333 from pygments.formatters import HtmlFormatter
298 header = []
334 header = []
299 static = os.path.join(path.get_ipython_package_dir(),
335 static = os.path.join(path.get_ipython_package_dir(),
300 'frontend', 'html', 'notebook', 'static',
336 'frontend', 'html', 'notebook', 'static',
301 )
337 )
302 here = os.path.split(os.path.realpath(__file__))[0]
338 here = os.path.split(os.path.realpath(__file__))[0]
303 css = os.path.join(static, 'css')
339 css = os.path.join(static, 'css')
304 for sheet in [
340 for sheet in [
305 # do we need jquery and prettify?
341 # do we need jquery and prettify?
306 # os.path.join(static, 'jquery', 'css', 'themes', 'base',
342 # os.path.join(static, 'jquery', 'css', 'themes', 'base',
307 # 'jquery-ui.min.css'),
343 # 'jquery-ui.min.css'),
308 # os.path.join(static, 'prettify', 'prettify.css'),
344 # os.path.join(static, 'prettify', 'prettify.css'),
309 os.path.join(css, 'boilerplate.css'),
345 os.path.join(css, 'boilerplate.css'),
310 os.path.join(css, 'fbm.css'),
346 os.path.join(css, 'fbm.css'),
311 os.path.join(css, 'notebook.css'),
347 os.path.join(css, 'notebook.css'),
312 os.path.join(css, 'renderedhtml.css'),
348 os.path.join(css, 'renderedhtml.css'),
313 os.path.join(css, 'style.min.css'),
349 os.path.join(css, 'style.min.css'),
314 # our overrides:
350 # our overrides:
315 os.path.join(here, '..', 'css', 'static_html.css'),
351 os.path.join(here, '..', 'css', 'static_html.css'),
316 ]:
352 ]:
317 try:
353 try:
318 with io.open(sheet, encoding='utf-8') as f:
354 with io.open(sheet, encoding='utf-8') as f:
319 s = f.read()
355 s = f.read()
320 header.append(s)
356 header.append(s)
321 except IOError:
357 except IOError:
322 # new version of ipython with style.min.css, pass
358 # new version of ipython with style.min.css, pass
323 pass
359 pass
324
360
325 pygments_css = HtmlFormatter().get_style_defs('.highlight')
361 pygments_css = HtmlFormatter().get_style_defs('.highlight')
326 header.append(pygments_css)
362 header.append(pygments_css)
327 self.header = header
363 self.header = header
328
364
General Comments 0
You need to be logged in to leave comments. Login now