##// END OF EJS Templates
more test fixed
Matthias BUSSONNIER -
Show More
@@ -1,333 +1,334 b''
1 from __future__ import print_function
1 from __future__ import print_function
2 from converters.utils import remove_fake_files_url
2 from converters.utils import remove_fake_files_url
3
3
4 # Stdlib
4 # Stdlib
5 import codecs
5 import codecs
6 import io
6 import io
7 import logging
7 import logging
8 import os
8 import os
9 import pprint
9 import pprint
10 import re
10 import re
11 import subprocess
11 import subprocess
12 import sys
12 import sys
13 import json
13 import json
14 import copy
14 import copy
15 from types import FunctionType
15 from types import FunctionType
16 from shutil import rmtree
16 from shutil import rmtree
17
17
18 # From IPython
18 # From IPython
19 from IPython.external import argparse
19 from IPython.external import argparse
20 from IPython.nbformat import current as nbformat
20 from IPython.nbformat import current as nbformat
21 from IPython.utils.text import indent
21 from IPython.utils.text import indent
22 from IPython.nbformat.v3.nbjson import BytesEncoder
22 from IPython.nbformat.v3.nbjson import BytesEncoder
23 from IPython.utils import path, py3compat
23 from IPython.utils import path, py3compat
24
24
25 # local
25 # local
26 from lexers import IPythonLexer
26 from lexers import IPythonLexer
27
27
28 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
29 # Class declarations
29 # Class declarations
30 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
31
31
32 class ConversionException(Exception):
32 class ConversionException(Exception):
33 pass
33 pass
34
34
35 class DocStringInheritor(type):
35 class DocStringInheritor(type):
36 """
36 """
37 This metaclass will walk the list of bases until the desired
37 This metaclass will walk the list of bases until the desired
38 superclass method is found AND if that method has a docstring and only
38 superclass method is found AND if that method has a docstring and only
39 THEN does it attach the superdocstring to the derived class method.
39 THEN does it attach the superdocstring to the derived class method.
40
40
41 Please use carefully, I just did the metaclass thing by following
41 Please use carefully, I just did the metaclass thing by following
42 Michael Foord's Metaclass tutorial
42 Michael Foord's Metaclass tutorial
43 (http://www.voidspace.org.uk/python/articles/metaclasses.shtml), I may
43 (http://www.voidspace.org.uk/python/articles/metaclasses.shtml), I may
44 have missed a step or two.
44 have missed a step or two.
45
45
46 source:
46 source:
47 http://groups.google.com/group/comp.lang.python/msg/26f7b4fcb4d66c95
47 http://groups.google.com/group/comp.lang.python/msg/26f7b4fcb4d66c95
48 by Paul McGuire
48 by Paul McGuire
49 """
49 """
50 def __new__(meta, classname, bases, classDict):
50 def __new__(meta, classname, bases, classDict):
51 newClassDict = {}
51 newClassDict = {}
52 for attributeName, attribute in classDict.items():
52 for attributeName, attribute in classDict.items():
53 if type(attribute) == FunctionType:
53 if type(attribute) == FunctionType:
54 # look through bases for matching function by name
54 # look through bases for matching function by name
55 for baseclass in bases:
55 for baseclass in bases:
56 if hasattr(baseclass, attributeName):
56 if hasattr(baseclass, attributeName):
57 basefn = getattr(baseclass,attributeName)
57 basefn = getattr(baseclass,attributeName)
58 if basefn.__doc__:
58 if basefn.__doc__:
59 attribute.__doc__ = basefn.__doc__
59 attribute.__doc__ = basefn.__doc__
60 break
60 break
61 newClassDict[attributeName] = attribute
61 newClassDict[attributeName] = attribute
62 return type.__new__(meta, classname, bases, newClassDict)
62 return type.__new__(meta, classname, bases, newClassDict)
63
63
64 class Converter(object):
64 class Converter(object):
65 __metaclass__ = DocStringInheritor
65 __metaclass__ = DocStringInheritor
66 default_encoding = 'utf-8'
66 default_encoding = 'utf-8'
67 extension = str()
67 extension = str()
68 figures_counter = 0
68 figures_counter = 0
69 infile = str()
69 infile = str()
70 infile_dir = str()
70 infile_dir = str()
71 infile_root = str()
71 infile_root = str()
72 files_dir = str()
72 files_dir = str()
73 with_preamble = True
73 with_preamble = True
74 user_preamble = None
74 user_preamble = None
75 output = str()
75 output = unicode()
76 raw_as_verbatim = False
76 raw_as_verbatim = False
77
77
78 def __init__(self, infile):
78 def __init__(self, infile):
79 self.infile = infile
79 self.infile = infile
80 self.infile_dir, infile_root = os.path.split(infile)
80 self.infile_dir, infile_root = os.path.split(infile)
81 infile_root = os.path.splitext(infile_root)[0]
81 infile_root = os.path.splitext(infile_root)[0]
82 files_dir = os.path.join(self.infile_dir, infile_root + '_files')
82 files_dir = os.path.join(self.infile_dir, infile_root + '_files')
83 if not os.path.isdir(files_dir):
83 if not os.path.isdir(files_dir):
84 os.mkdir(files_dir)
84 os.mkdir(files_dir)
85 self.infile_root = infile_root
85 self.infile_root = infile_root
86 self.files_dir = files_dir
86 self.files_dir = files_dir
87 self.outbase = os.path.join(self.infile_dir, infile_root)
87 self.outbase = os.path.join(self.infile_dir, infile_root)
88
88
89 def __del__(self):
89 def __del__(self):
90 if not os.listdir(self.files_dir):
90 if not os.listdir(self.files_dir):
91 os.rmdir(self.files_dir)
91 os.rmdir(self.files_dir)
92
92
93 def dispatch(self, cell_type):
93 def dispatch(self, cell_type):
94 """return cell_type dependent render method, for example render_code
94 """return cell_type dependent render method, for example render_code
95 """
95 """
96 return getattr(self, 'render_' + cell_type, self.render_unknown)
96 return getattr(self, 'render_' + cell_type, self.render_unknown)
97
97
98 def dispatch_display_format(self, format):
98 def dispatch_display_format(self, format):
99 """return output_type dependent render method, for example render_output_text
99 """return output_type dependent render method, for example render_output_text
100 """
100 """
101 return getattr(self, 'render_display_format_' + format, self.render_unknown_display)
101 return getattr(self, 'render_display_format_' + format, self.render_unknown_display)
102
102
103 def convert(self, cell_separator='\n'):
103 def convert(self, cell_separator='\n'):
104 """
104 """
105 Generic method to converts notebook to a string representation.
105 Generic method to converts notebook to a string representation.
106
106
107 This is accomplished by dispatching on the cell_type, so subclasses of
107 This is accomplished by dispatching on the cell_type, so subclasses of
108 Convereter class do not need to re-implement this method, but just
108 Convereter class do not need to re-implement this method, but just
109 need implementation for the methods that will be dispatched.
109 need implementation for the methods that will be dispatched.
110
110
111 Parameters
111 Parameters
112 ----------
112 ----------
113 cell_separator : string
113 cell_separator : string
114 Character or string to join cells with. Default is "\n"
114 Character or string to join cells with. Default is "\n"
115
115
116 Returns
116 Returns
117 -------
117 -------
118 out : string
118 out : string
119 """
119 """
120 lines = []
120 lines = []
121 lines.extend(self.optional_header())
121 lines.extend(self.optional_header())
122 lines.extend(self.main_body(cell_separator))
122 lines.extend(self.main_body(cell_separator))
123 lines.extend(self.optional_footer())
123 lines.extend(self.optional_footer())
124 return u'\n'.join(lines)
124 return u'\n'.join(lines)
125
125
126 def main_body(self, cell_separator='\n'):
126 def main_body(self, cell_separator='\n'):
127 converted_cells = []
127 converted_cells = []
128 for worksheet in self.nb.worksheets:
128 for worksheet in self.nb.worksheets:
129 for cell in worksheet.cells:
129 for cell in worksheet.cells:
130 #print(cell.cell_type) # dbg
130 #print(cell.cell_type) # dbg
131 conv_fn = self.dispatch(cell.cell_type)
131 conv_fn = self.dispatch(cell.cell_type)
132 if cell.cell_type in ('markdown', 'raw'):
132 if cell.cell_type in ('markdown', 'raw'):
133 remove_fake_files_url(cell)
133 remove_fake_files_url(cell)
134 converted_cells.append('\n'.join(conv_fn(cell)))
134 converted_cells.append('\n'.join(conv_fn(cell)))
135 cell_lines = cell_separator.join(converted_cells).split('\n')
135 cell_lines = cell_separator.join(converted_cells).split('\n')
136 return cell_lines
136 return cell_lines
137
137
138 def render(self):
138 def render(self):
139 "read, convert, and save self.infile"
139 "read, convert, and save self.infile"
140 if not hasattr(self, 'nb'):
140 if not hasattr(self, 'nb'):
141 self.read()
141 self.read()
142 self.output = self.convert()
142 self.output = self.convert()
143 assert(type(self.output) == unicode)
143 return self.save()
144 return self.save()
144
145
145 def read(self):
146 def read(self):
146 "read and parse notebook into NotebookNode called self.nb"
147 "read and parse notebook into NotebookNode called self.nb"
147 with open(self.infile) as f:
148 with open(self.infile) as f:
148 self.nb = nbformat.read(f, 'json')
149 self.nb = nbformat.read(f, 'json')
149
150
150 def save(self, outfile=None, encoding=None):
151 def save(self, outfile=None, encoding=None):
151 "read and parse notebook into self.nb"
152 "read and parse notebook into self.nb"
152 if outfile is None:
153 if outfile is None:
153 outfile = self.outbase + '.' + self.extension
154 outfile = self.outbase + '.' + self.extension
154 if encoding is None:
155 if encoding is None:
155 encoding = self.default_encoding
156 encoding = self.default_encoding
156 with io.open(outfile, 'w', encoding=encoding) as f:
157 with io.open(outfile, 'w', encoding=encoding) as f:
157 f.write(self.output)
158 f.write(self.output)
158 return os.path.abspath(outfile)
159 return os.path.abspath(outfile)
159
160
160 def optional_header(self):
161 def optional_header(self):
161 """
162 """
162 Optional header to insert at the top of the converted notebook
163 Optional header to insert at the top of the converted notebook
163
164
164 Returns a list
165 Returns a list
165 """
166 """
166 return []
167 return []
167
168
168 def optional_footer(self):
169 def optional_footer(self):
169 """
170 """
170 Optional footer to insert at the end of the converted notebook
171 Optional footer to insert at the end of the converted notebook
171
172
172 Returns a list
173 Returns a list
173 """
174 """
174 return []
175 return []
175
176
176 def _new_figure(self, data, fmt):
177 def _new_figure(self, data, fmt):
177 """Create a new figure file in the given format.
178 """Create a new figure file in the given format.
178
179
179 Returns a path relative to the input file.
180 Returns a path relative to the input file.
180 """
181 """
181 figname = '%s_fig_%02i.%s' % (self.infile_root,
182 figname = '%s_fig_%02i.%s' % (self.infile_root,
182 self.figures_counter, fmt)
183 self.figures_counter, fmt)
183 self.figures_counter += 1
184 self.figures_counter += 1
184 fullname = os.path.join(self.files_dir, figname)
185 fullname = os.path.join(self.files_dir, figname)
185
186
186 # Binary files are base64-encoded, SVG is already XML
187 # Binary files are base64-encoded, SVG is already XML
187 if fmt in ('png', 'jpg', 'pdf'):
188 if fmt in ('png', 'jpg', 'pdf'):
188 data = data.decode('base64')
189 data = data.decode('base64')
189 fopen = lambda fname: open(fname, 'wb')
190 fopen = lambda fname: open(fname, 'wb')
190 else:
191 else:
191 fopen = lambda fname: codecs.open(fname, 'wb', self.default_encoding)
192 fopen = lambda fname: codecs.open(fname, 'wb', self.default_encoding)
192
193
193 with fopen(fullname) as f:
194 with fopen(fullname) as f:
194 f.write(data)
195 f.write(data)
195
196
196 return fullname
197 return fullname
197
198
198 def render_heading(self, cell):
199 def render_heading(self, cell):
199 """convert a heading cell
200 """convert a heading cell
200
201
201 Returns list."""
202 Returns list."""
202 raise NotImplementedError
203 raise NotImplementedError
203
204
204 def render_code(self, cell):
205 def render_code(self, cell):
205 """Convert a code cell
206 """Convert a code cell
206
207
207 Returns list."""
208 Returns list."""
208 raise NotImplementedError
209 raise NotImplementedError
209
210
210 def render_markdown(self, cell):
211 def render_markdown(self, cell):
211 """convert a markdown cell
212 """convert a markdown cell
212
213
213 Returns list."""
214 Returns list."""
214 raise NotImplementedError
215 raise NotImplementedError
215
216
216 def _img_lines(self, img_file):
217 def _img_lines(self, img_file):
217 """Return list of lines to include an image file."""
218 """Return list of lines to include an image file."""
218 # Note: subclasses may choose to implement format-specific _FMT_lines
219 # Note: subclasses may choose to implement format-specific _FMT_lines
219 # methods if they so choose (FMT in {png, svg, jpg, pdf}).
220 # methods if they so choose (FMT in {png, svg, jpg, pdf}).
220 raise NotImplementedError
221 raise NotImplementedError
221
222
222 def render_display_data(self, output):
223 def render_display_data(self, output):
223 """convert display data from the output of a code cell
224 """convert display data from the output of a code cell
224
225
225 Returns list.
226 Returns list.
226 """
227 """
227 lines = []
228 lines = []
228
229
229 for fmt in output.keys():
230 for fmt in output.keys():
230 if fmt in ['png', 'svg', 'jpg', 'pdf']:
231 if fmt in ['png', 'svg', 'jpg', 'pdf']:
231 img_file = self._new_figure(output[fmt], fmt)
232 img_file = self._new_figure(output[fmt], fmt)
232 # Subclasses can have format-specific render functions (e.g.,
233 # Subclasses can have format-specific render functions (e.g.,
233 # latex has to auto-convert all SVG to PDF first).
234 # latex has to auto-convert all SVG to PDF first).
234 lines_fun = getattr(self, '_%s_lines' % fmt, None)
235 lines_fun = getattr(self, '_%s_lines' % fmt, None)
235 if not lines_fun:
236 if not lines_fun:
236 lines_fun = self._img_lines
237 lines_fun = self._img_lines
237 lines.extend(lines_fun(img_file))
238 lines.extend(lines_fun(img_file))
238 elif fmt != 'output_type':
239 elif fmt != 'output_type':
239 conv_fn = self.dispatch_display_format(fmt)
240 conv_fn = self.dispatch_display_format(fmt)
240 lines.extend(conv_fn(output))
241 lines.extend(conv_fn(output))
241 return lines
242 return lines
242
243
243 def render_raw(self, cell):
244 def render_raw(self, cell):
244 """convert a cell with raw text
245 """convert a cell with raw text
245
246
246 Returns list."""
247 Returns list."""
247 raise NotImplementedError
248 raise NotImplementedError
248
249
249 def render_unknown(self, cell):
250 def render_unknown(self, cell):
250 """Render cells of unkown type
251 """Render cells of unkown type
251
252
252 Returns list."""
253 Returns list."""
253 data = pprint.pformat(cell)
254 data = pprint.pformat(cell)
254 logging.warning('Unknown cell: %s' % cell.cell_type)
255 logging.warning('Unknown cell: %s' % cell.cell_type)
255 return self._unknown_lines(data)
256 return self._unknown_lines(data)
256
257
257 def render_unknown_display(self, output, type):
258 def render_unknown_display(self, output, type):
258 """Render cells of unkown type
259 """Render cells of unkown type
259
260
260 Returns list."""
261 Returns list."""
261 data = pprint.pformat(output)
262 data = pprint.pformat(output)
262 logging.warning('Unknown output: %s' % output.output_type)
263 logging.warning('Unknown output: %s' % output.output_type)
263 return self._unknown_lines(data)
264 return self._unknown_lines(data)
264
265
265 def render_stream(self, output):
266 def render_stream(self, output):
266 """render the stream part of an output
267 """render the stream part of an output
267
268
268 Returns list.
269 Returns list.
269
270
270 Identical to render_display_format_text
271 Identical to render_display_format_text
271 """
272 """
272 return self.render_display_format_text(output)
273 return self.render_display_format_text(output)
273
274
274 def render_pyout(self, output):
275 def render_pyout(self, output):
275 """convert pyout part of a code cell
276 """convert pyout part of a code cell
276
277
277 Returns list."""
278 Returns list."""
278 raise NotImplementedError
279 raise NotImplementedError
279
280
280
281
281 def render_pyerr(self, output):
282 def render_pyerr(self, output):
282 """convert pyerr part of a code cell
283 """convert pyerr part of a code cell
283
284
284 Returns list."""
285 Returns list."""
285 raise NotImplementedError
286 raise NotImplementedError
286
287
287 def _unknown_lines(self, data):
288 def _unknown_lines(self, data):
288 """Return list of lines for an unknown cell.
289 """Return list of lines for an unknown cell.
289
290
290 Parameters
291 Parameters
291 ----------
292 ----------
292 data : str
293 data : str
293 The content of the unknown data as a single string.
294 The content of the unknown data as a single string.
294 """
295 """
295 raise NotImplementedError
296 raise NotImplementedError
296
297
297 # These are the possible format types in an output node
298 # These are the possible format types in an output node
298
299
299 def render_display_format_text(self, output):
300 def render_display_format_text(self, output):
300 """render the text part of an output
301 """render the text part of an output
301
302
302 Returns list.
303 Returns list.
303 """
304 """
304 raise NotImplementedError
305 raise NotImplementedError
305
306
306 def render_display_format_html(self, output):
307 def render_display_format_html(self, output):
307 """render the html part of an output
308 """render the html part of an output
308
309
309 Returns list.
310 Returns list.
310 """
311 """
311 raise NotImplementedError
312 raise NotImplementedError
312
313
313 def render_display_format_latex(self, output):
314 def render_display_format_latex(self, output):
314 """render the latex part of an output
315 """render the latex part of an output
315
316
316 Returns list.
317 Returns list.
317 """
318 """
318 raise NotImplementedError
319 raise NotImplementedError
319
320
320 def render_display_format_json(self, output):
321 def render_display_format_json(self, output):
321 """render the json part of an output
322 """render the json part of an output
322
323
323 Returns list.
324 Returns list.
324 """
325 """
325 raise NotImplementedError
326 raise NotImplementedError
326
327
327 def render_display_format_javascript(self, output):
328 def render_display_format_javascript(self, output):
328 """render the javascript part of an output
329 """render the javascript part of an output
329
330
330 Returns list.
331 Returns list.
331 """
332 """
332 raise NotImplementedError
333 raise NotImplementedError
333
334
@@ -1,81 +1,82 b''
1 from converters.base import Converter
1 from converters.base import Converter
2 from converters.utils import cell_to_lines
2 from shutil import rmtree
3 from shutil import rmtree
3 import json
4 import json
4
5
5 class ConverterNotebook(Converter):
6 class ConverterNotebook(Converter):
6 """
7 """
7 A converter that is essentially a null-op.
8 A converter that is essentially a null-op.
8 This exists so it can be subclassed
9 This exists so it can be subclassed
9 for custom handlers of .ipynb files
10 for custom handlers of .ipynb files
10 that create new .ipynb files.
11 that create new .ipynb files.
11
12
12 What distinguishes this from JSONWriter is that
13 What distinguishes this from JSONWriter is that
13 subclasses can specify what to do with each type of cell.
14 subclasses can specify what to do with each type of cell.
14
15
15 Writes out a notebook file.
16 Writes out a notebook file.
16
17
17 """
18 """
18 extension = 'ipynb'
19 extension = 'ipynb'
19
20
20 def __init__(self, infile, outbase):
21 def __init__(self, infile, outbase):
21 Converter.__init__(self, infile)
22 Converter.__init__(self, infile)
22 self.outbase = outbase
23 self.outbase = outbase
23 rmtree(self.files_dir)
24 rmtree(self.files_dir)
24
25
25 def convert(self):
26 def convert(self):
26 return json.dumps(json.loads(Converter.convert(self, ',')), indent=1, sort_keys=True)
27 return unicode(json.dumps(json.loads(Converter.convert(self, ',')), indent=1, sort_keys=True))
27
28
28 def optional_header(self):
29 def optional_header(self):
29 s = \
30 s = \
30 """{
31 """{
31 "metadata": {
32 "metadata": {
32 "name": "%(name)s"
33 "name": "%(name)s"
33 },
34 },
34 "nbformat": 3,
35 "nbformat": 3,
35 "worksheets": [
36 "worksheets": [
36 {
37 {
37 "cells": [""" % {'name':self.outbase}
38 "cells": [""" % {'name':self.outbase}
38
39
39 return s.split('\n')
40 return s.split('\n')
40
41
41 def optional_footer(self):
42 def optional_footer(self):
42 s = \
43 s = \
43 """]
44 """]
44 }
45 }
45 ]
46 ]
46 }"""
47 }"""
47 return s.split('\n')
48 return s.split('\n')
48
49
49 def render_heading(self, cell):
50 def render_heading(self, cell):
50 return cell_to_lines(cell)
51 return cell_to_lines(cell)
51
52
52 def render_code(self, cell):
53 def render_code(self, cell):
53 return cell_to_lines(cell)
54 return cell_to_lines(cell)
54
55
55 def render_markdown(self, cell):
56 def render_markdown(self, cell):
56 return cell_to_lines(cell)
57 return cell_to_lines(cell)
57
58
58 def render_raw(self, cell):
59 def render_raw(self, cell):
59 return cell_to_lines(cell)
60 return cell_to_lines(cell)
60
61
61 def render_pyout(self, output):
62 def render_pyout(self, output):
62 return cell_to_lines(output)
63 return cell_to_lines(output)
63
64
64 def render_pyerr(self, output):
65 def render_pyerr(self, output):
65 return cell_to_lines(output)
66 return cell_to_lines(output)
66
67
67 def render_display_format_text(self, output):
68 def render_display_format_text(self, output):
68 return [output.text]
69 return [output.text]
69
70
70 def render_display_format_html(self, output):
71 def render_display_format_html(self, output):
71 return [output.html]
72 return [output.html]
72
73
73 def render_display_format_latex(self, output):
74 def render_display_format_latex(self, output):
74 return [output.latex]
75 return [output.latex]
75
76
76 def render_display_format_json(self, output):
77 def render_display_format_json(self, output):
77 return [output.json]
78 return [output.json]
78
79
79
80
80 def render_display_format_javascript(self, output):
81 def render_display_format_javascript(self, output):
81 return [output.javascript]
82 return [output.javascript]
@@ -1,327 +1,333 b''
1 from __future__ import print_function
1 from __future__ import print_function
2 from lexers import IPythonLexer
2 from lexers import IPythonLexer
3
3 import subprocess
4 import subprocess
5 import copy
6 import json
4 import re
7 import re
8
5 from IPython.utils.text import indent
9 from IPython.utils.text import indent
10 from IPython.utils import path, py3compat
11 from IPython.nbformat.v3.nbjson import BytesEncoder
6
12
7 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
8 # Utility functions
14 # Utility functions
9 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
10 def highlight(src, lang='ipython'):
16 def highlight(src, lang='ipython'):
11 """Return a syntax-highlighted version of the input source.
17 """Return a syntax-highlighted version of the input source.
12 """
18 """
13 from pygments import highlight
19 from pygments import highlight
14 from pygments.lexers import get_lexer_by_name
20 from pygments.lexers import get_lexer_by_name
15 from pygments.formatters import HtmlFormatter
21 from pygments.formatters import HtmlFormatter
16
22
17 if lang == 'ipython':
23 if lang == 'ipython':
18 lexer = IPythonLexer()
24 lexer = IPythonLexer()
19 else:
25 else:
20 lexer = get_lexer_by_name(lang, stripall=True)
26 lexer = get_lexer_by_name(lang, stripall=True)
21
27
22 return highlight(src, lexer, HtmlFormatter())
28 return highlight(src, lexer, HtmlFormatter())
23
29
24 def output_container(f):
30 def output_container(f):
25 """add a prompt-area next to an output"""
31 """add a prompt-area next to an output"""
26 def wrapped(self, output):
32 def wrapped(self, output):
27 rendered = f(self, output)
33 rendered = f(self, output)
28 if not rendered:
34 if not rendered:
29 # empty output
35 # empty output
30 return []
36 return []
31 lines = []
37 lines = []
32 lines.append('<div class="hbox output_area">')
38 lines.append('<div class="hbox output_area">')
33 lines.extend(self._out_prompt(output))
39 lines.extend(self._out_prompt(output))
34 classes = "output_subarea output_%s" % output.output_type
40 classes = "output_subarea output_%s" % output.output_type
35 if 'html' in output.keys():
41 if 'html' in output.keys():
36 classes += ' output_html rendered_html'
42 classes += ' output_html rendered_html'
37 if output.output_type == 'stream':
43 if output.output_type == 'stream':
38 classes += " output_%s" % output.stream
44 classes += " output_%s" % output.stream
39 lines.append('<div class="%s">' % classes)
45 lines.append('<div class="%s">' % classes)
40 lines.extend(rendered)
46 lines.extend(rendered)
41 lines.append('</div>') # subarea
47 lines.append('</div>') # subarea
42 lines.append('</div>') # output_area
48 lines.append('</div>') # output_area
43
49
44 return lines
50 return lines
45
51
46 return wrapped
52 return wrapped
47
53
48 def text_cell(f):
54 def text_cell(f):
49 """wrap text cells in appropriate divs"""
55 """wrap text cells in appropriate divs"""
50 def wrapped(self, cell):
56 def wrapped(self, cell):
51 rendered = f(self, cell)
57 rendered = f(self, cell)
52 classes = "text_cell_render border-box-sizing rendered_html"
58 classes = "text_cell_render border-box-sizing rendered_html"
53 lines = ['<div class="%s">' % classes] + rendered + ['</div>']
59 lines = ['<div class="%s">' % classes] + rendered + ['</div>']
54 return lines
60 return lines
55 return wrapped
61 return wrapped
56
62
57
63
58 def remove_fake_files_url(cell):
64 def remove_fake_files_url(cell):
59 """Remove from the cell source the /files/ pseudo-path we use.
65 """Remove from the cell source the /files/ pseudo-path we use.
60 """
66 """
61 src = cell.source
67 src = cell.source
62 cell.source = src.replace('/files/', '')
68 cell.source = src.replace('/files/', '')
63
69
64
70
65 # ANSI color functions:
71 # ANSI color functions:
66
72
67 def remove_ansi(src):
73 def remove_ansi(src):
68 """Strip all ANSI color escape sequences from input string.
74 """Strip all ANSI color escape sequences from input string.
69
75
70 Parameters
76 Parameters
71 ----------
77 ----------
72 src : string
78 src : string
73
79
74 Returns
80 Returns
75 -------
81 -------
76 string
82 string
77 """
83 """
78 return re.sub(r'\033\[(0|\d;\d\d)m', '', src)
84 return re.sub(r'\033\[(0|\d;\d\d)m', '', src)
79
85
80
86
81 def ansi2html(txt):
87 def ansi2html(txt):
82 """Render ANSI colors as HTML colors
88 """Render ANSI colors as HTML colors
83
89
84 This is equivalent to util.fixConsole in utils.js
90 This is equivalent to util.fixConsole in utils.js
85
91
86 Parameters
92 Parameters
87 ----------
93 ----------
88 txt : string
94 txt : string
89
95
90 Returns
96 Returns
91 -------
97 -------
92 string
98 string
93 """
99 """
94
100
95 ansi_colormap = {
101 ansi_colormap = {
96 '30': 'ansiblack',
102 '30': 'ansiblack',
97 '31': 'ansired',
103 '31': 'ansired',
98 '32': 'ansigreen',
104 '32': 'ansigreen',
99 '33': 'ansiyellow',
105 '33': 'ansiyellow',
100 '34': 'ansiblue',
106 '34': 'ansiblue',
101 '35': 'ansipurple',
107 '35': 'ansipurple',
102 '36': 'ansicyan',
108 '36': 'ansicyan',
103 '37': 'ansigrey',
109 '37': 'ansigrey',
104 '01': 'ansibold',
110 '01': 'ansibold',
105 }
111 }
106
112
107 # do ampersand first
113 # do ampersand first
108 txt = txt.replace('&', '&amp;')
114 txt = txt.replace('&', '&amp;')
109 html_escapes = {
115 html_escapes = {
110 '<': '&lt;',
116 '<': '&lt;',
111 '>': '&gt;',
117 '>': '&gt;',
112 "'": '&apos;',
118 "'": '&apos;',
113 '"': '&quot;',
119 '"': '&quot;',
114 '`': '&#96;',
120 '`': '&#96;',
115 }
121 }
116 for c, escape in html_escapes.iteritems():
122 for c, escape in html_escapes.iteritems():
117 txt = txt.replace(c, escape)
123 txt = txt.replace(c, escape)
118
124
119 ansi_re = re.compile('\x1b' + r'\[([\dA-Fa-f;]*?)m')
125 ansi_re = re.compile('\x1b' + r'\[([\dA-Fa-f;]*?)m')
120 m = ansi_re.search(txt)
126 m = ansi_re.search(txt)
121 opened = False
127 opened = False
122 cmds = []
128 cmds = []
123 opener = ''
129 opener = ''
124 closer = ''
130 closer = ''
125 while m:
131 while m:
126 cmds = m.groups()[0].split(';')
132 cmds = m.groups()[0].split(';')
127 closer = '</span>' if opened else ''
133 closer = '</span>' if opened else ''
128 opened = len(cmds) > 1 or cmds[0] != '0'*len(cmds[0]);
134 opened = len(cmds) > 1 or cmds[0] != '0'*len(cmds[0]);
129 classes = []
135 classes = []
130 for cmd in cmds:
136 for cmd in cmds:
131 if cmd in ansi_colormap:
137 if cmd in ansi_colormap:
132 classes.append(ansi_colormap.get(cmd))
138 classes.append(ansi_colormap.get(cmd))
133
139
134 if classes:
140 if classes:
135 opener = '<span class="%s">' % (' '.join(classes))
141 opener = '<span class="%s">' % (' '.join(classes))
136 else:
142 else:
137 opener = ''
143 opener = ''
138 txt = re.sub(ansi_re, closer + opener, txt, 1)
144 txt = re.sub(ansi_re, closer + opener, txt, 1)
139
145
140 m = ansi_re.search(txt)
146 m = ansi_re.search(txt)
141
147
142 if opened:
148 if opened:
143 txt += '</span>'
149 txt += '</span>'
144 return txt
150 return txt
145
151
146
152
147 # Pandoc-dependent code
153 # Pandoc-dependent code
148
154
149 def markdown2latex(src):
155 def markdown2latex(src):
150 """Convert a markdown string to LaTeX via pandoc.
156 """Convert a markdown string to LaTeX via pandoc.
151
157
152 This function will raise an error if pandoc is not installed.
158 This function will raise an error if pandoc is not installed.
153
159
154 Any error messages generated by pandoc are printed to stderr.
160 Any error messages generated by pandoc are printed to stderr.
155
161
156 Parameters
162 Parameters
157 ----------
163 ----------
158 src : string
164 src : string
159 Input string, assumed to be valid markdown.
165 Input string, assumed to be valid markdown.
160
166
161 Returns
167 Returns
162 -------
168 -------
163 out : string
169 out : string
164 Output as returned by pandoc.
170 Output as returned by pandoc.
165 """
171 """
166 p = subprocess.Popen('pandoc -f markdown -t latex'.split(),
172 p = subprocess.Popen('pandoc -f markdown -t latex'.split(),
167 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
173 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
168 out, err = p.communicate(src.encode('utf-8'))
174 out, err = p.communicate(src.encode('utf-8'))
169 if err:
175 if err:
170 print(err, file=sys.stderr)
176 print(err, file=sys.stderr)
171 #print('*'*20+'\n', out, '\n'+'*'*20) # dbg
177 #print('*'*20+'\n', out, '\n'+'*'*20) # dbg
172 return unicode(out,'utf-8')
178 return unicode(out,'utf-8')
173
179
174
180
175 def markdown2rst(src):
181 def markdown2rst(src):
176 """Convert a markdown string to LaTeX via pandoc.
182 """Convert a markdown string to LaTeX via pandoc.
177
183
178 This function will raise an error if pandoc is not installed.
184 This function will raise an error if pandoc is not installed.
179
185
180 Any error messages generated by pandoc are printed to stderr.
186 Any error messages generated by pandoc are printed to stderr.
181
187
182 Parameters
188 Parameters
183 ----------
189 ----------
184 src : string
190 src : string
185 Input string, assumed to be valid markdown.
191 Input string, assumed to be valid markdown.
186
192
187 Returns
193 Returns
188 -------
194 -------
189 out : string
195 out : string
190 Output as returned by pandoc.
196 Output as returned by pandoc.
191 """
197 """
192 p = subprocess.Popen('pandoc -f markdown -t rst'.split(),
198 p = subprocess.Popen('pandoc -f markdown -t rst'.split(),
193 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
199 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
194 out, err = p.communicate(src.encode('utf-8'))
200 out, err = p.communicate(src.encode('utf-8'))
195 if err:
201 if err:
196 print(err, file=sys.stderr)
202 print(err, file=sys.stderr)
197 #print('*'*20+'\n', out, '\n'+'*'*20) # dbg
203 #print('*'*20+'\n', out, '\n'+'*'*20) # dbg
198 return unicode(out,'utf-8')
204 return unicode(out,'utf-8')
199
205
200
206
201 def rst_directive(directive, text=''):
207 def rst_directive(directive, text=''):
202 """
208 """
203 Makes ReST directive block and indents any text passed to it.
209 Makes ReST directive block and indents any text passed to it.
204 """
210 """
205 out = [directive, '']
211 out = [directive, '']
206 if text:
212 if text:
207 out.extend([indent(text), ''])
213 out.extend([indent(text), ''])
208 return out
214 return out
209
215
210
216
211 def coalesce_streams(outputs):
217 def coalesce_streams(outputs):
212 """merge consecutive sequences of stream output into single stream
218 """merge consecutive sequences of stream output into single stream
213
219
214 to prevent extra newlines inserted at flush calls
220 to prevent extra newlines inserted at flush calls
215
221
216 TODO: handle \r deletion
222 TODO: handle \r deletion
217 """
223 """
218 new_outputs = []
224 new_outputs = []
219 last = outputs[0]
225 last = outputs[0]
220 new_outputs = [last]
226 new_outputs = [last]
221 for output in outputs[1:]:
227 for output in outputs[1:]:
222 if (output.output_type == 'stream' and
228 if (output.output_type == 'stream' and
223 last.output_type == 'stream' and
229 last.output_type == 'stream' and
224 last.stream == output.stream
230 last.stream == output.stream
225 ):
231 ):
226 last.text += output.text
232 last.text += output.text
227 else:
233 else:
228 new_outputs.append(output)
234 new_outputs.append(output)
229
235
230 return new_outputs
236 return new_outputs
231
237
232
238
233 def rst2simplehtml(infile):
239 def rst2simplehtml(infile):
234 """Convert a rst file to simplified html suitable for blogger.
240 """Convert a rst file to simplified html suitable for blogger.
235
241
236 This just runs rst2html with certain parameters to produce really simple
242 This just runs rst2html with certain parameters to produce really simple
237 html and strips the document header, so the resulting file can be easily
243 html and strips the document header, so the resulting file can be easily
238 pasted into a blogger edit window.
244 pasted into a blogger edit window.
239 """
245 """
240
246
241 # This is the template for the rst2html call that produces the cleanest,
247 # This is the template for the rst2html call that produces the cleanest,
242 # simplest html I could find. This should help in making it easier to
248 # simplest html I could find. This should help in making it easier to
243 # paste into the blogspot html window, though I'm still having problems
249 # paste into the blogspot html window, though I'm still having problems
244 # with linebreaks there...
250 # with linebreaks there...
245 cmd_template = ("rst2html --link-stylesheet --no-xml-declaration "
251 cmd_template = ("rst2html --link-stylesheet --no-xml-declaration "
246 "--no-generator --no-datestamp --no-source-link "
252 "--no-generator --no-datestamp --no-source-link "
247 "--no-toc-backlinks --no-section-numbering "
253 "--no-toc-backlinks --no-section-numbering "
248 "--strip-comments ")
254 "--strip-comments ")
249
255
250 cmd = "%s %s" % (cmd_template, infile)
256 cmd = "%s %s" % (cmd_template, infile)
251 proc = subprocess.Popen(cmd,
257 proc = subprocess.Popen(cmd,
252 stdout=subprocess.PIPE,
258 stdout=subprocess.PIPE,
253 stderr=subprocess.PIPE,
259 stderr=subprocess.PIPE,
254 shell=True)
260 shell=True)
255 html, stderr = proc.communicate()
261 html, stderr = proc.communicate()
256 if stderr:
262 if stderr:
257 raise IOError(stderr)
263 raise IOError(stderr)
258
264
259 # Make an iterator so breaking out holds state. Our implementation of
265 # Make an iterator so breaking out holds state. Our implementation of
260 # searching for the html body below is basically a trivial little state
266 # searching for the html body below is basically a trivial little state
261 # machine, so we need this.
267 # machine, so we need this.
262 walker = iter(html.splitlines())
268 walker = iter(html.splitlines())
263
269
264 # Find start of main text, break out to then print until we find end /div.
270 # Find start of main text, break out to then print until we find end /div.
265 # This may only work if there's a real title defined so we get a 'div class'
271 # This may only work if there's a real title defined so we get a 'div class'
266 # tag, I haven't really tried.
272 # tag, I haven't really tried.
267 for line in walker:
273 for line in walker:
268 if line.startswith('<body>'):
274 if line.startswith('<body>'):
269 break
275 break
270
276
271 newfname = os.path.splitext(infile)[0] + '.html'
277 newfname = os.path.splitext(infile)[0] + '.html'
272 with open(newfname, 'w') as f:
278 with open(newfname, 'w') as f:
273 for line in walker:
279 for line in walker:
274 if line.startswith('</body>'):
280 if line.startswith('</body>'):
275 break
281 break
276 f.write(line)
282 f.write(line)
277 f.write('\n')
283 f.write('\n')
278
284
279 return newfname
285 return newfname
280
286
281 #-----------------------------------------------------------------------------
287 #-----------------------------------------------------------------------------
282 # Cell-level functions -- similar to IPython.nbformat.v3.rwbase functions
288 # Cell-level functions -- similar to IPython.nbformat.v3.rwbase functions
283 # but at cell level instead of whole notebook level
289 # but at cell level instead of whole notebook level
284 #-----------------------------------------------------------------------------
290 #-----------------------------------------------------------------------------
285
291
286 def writes_cell(cell, **kwargs):
292 def writes_cell(cell, **kwargs):
287 kwargs['cls'] = BytesEncoder
293 kwargs['cls'] = BytesEncoder
288 kwargs['indent'] = 3
294 kwargs['indent'] = 3
289 kwargs['sort_keys'] = True
295 kwargs['sort_keys'] = True
290 kwargs['separators'] = (',',': ')
296 kwargs['separators'] = (',',': ')
291 if kwargs.pop('split_lines', True):
297 if kwargs.pop('split_lines', True):
292 cell = split_lines_cell(copy.deepcopy(cell))
298 cell = split_lines_cell(copy.deepcopy(cell))
293 return py3compat.str_to_unicode(json.dumps(cell, **kwargs), 'utf-8')
299 return py3compat.str_to_unicode(json.dumps(cell, **kwargs), 'utf-8')
294
300
295
301
296 _multiline_outputs = ['text', 'html', 'svg', 'latex', 'javascript', 'json']
302 _multiline_outputs = ['text', 'html', 'svg', 'latex', 'javascript', 'json']
297
303
298
304
299 def split_lines_cell(cell):
305 def split_lines_cell(cell):
300 """
306 """
301 Split lines within a cell as in
307 Split lines within a cell as in
302 IPython.nbformat.v3.rwbase.split_lines
308 IPython.nbformat.v3.rwbase.split_lines
303
309
304 """
310 """
305 if cell.cell_type == 'code':
311 if cell.cell_type == 'code':
306 if 'input' in cell and isinstance(cell.input, basestring):
312 if 'input' in cell and isinstance(cell.input, basestring):
307 cell.input = (cell.input + '\n').splitlines()
313 cell.input = (cell.input + '\n').splitlines()
308 for output in cell.outputs:
314 for output in cell.outputs:
309 for key in _multiline_outputs:
315 for key in _multiline_outputs:
310 item = output.get(key, None)
316 item = output.get(key, None)
311 if isinstance(item, basestring):
317 if isinstance(item, basestring):
312 output[key] = (item + '\n').splitlines()
318 output[key] = (item + '\n').splitlines()
313 else: # text, heading cell
319 else: # text, heading cell
314 for key in ['source', 'rendered']:
320 for key in ['source', 'rendered']:
315 item = cell.get(key, None)
321 item = cell.get(key, None)
316 if isinstance(item, basestring):
322 if isinstance(item, basestring):
317 cell[key] = (item + '\n').splitlines()
323 cell[key] = (item + '\n').splitlines()
318 return cell
324 return cell
319
325
320
326
321 def cell_to_lines(cell):
327 def cell_to_lines(cell):
322 '''
328 '''
323 Write a cell to json, returning the split lines.
329 Write a cell to json, returning the split lines.
324 '''
330 '''
325 split_lines_cell(cell)
331 split_lines_cell(cell)
326 s = writes_cell(cell).strip()
332 s = writes_cell(cell).strip()
327 return s.split('\n')
333 return s.split('\n')
General Comments 0
You need to be logged in to leave comments. Login now