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