##// END OF EJS Templates
preserve trailing newlines in ipynb...
MinRK -
Show More
@@ -1,174 +1,174 b''
1 """Base classes and utilities for readers and writers.
1 """Base classes and utilities for readers and writers.
2
2
3 Authors:
3 Authors:
4
4
5 * Brian Granger
5 * Brian Granger
6 """
6 """
7
7
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2008-2011 The IPython Development Team
9 # Copyright (C) 2008-2011 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 from base64 import encodestring, decodestring
19 from base64 import encodestring, decodestring
20 import pprint
20 import pprint
21
21
22 from IPython.utils import py3compat
22 from IPython.utils import py3compat
23
23
24 str_to_bytes = py3compat.str_to_bytes
24 str_to_bytes = py3compat.str_to_bytes
25
25
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27 # Code
27 # Code
28 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
29
29
30 def restore_bytes(nb):
30 def restore_bytes(nb):
31 """Restore bytes of image data from unicode-only formats.
31 """Restore bytes of image data from unicode-only formats.
32
32
33 Base64 encoding is handled elsewhere. Bytes objects in the notebook are
33 Base64 encoding is handled elsewhere. Bytes objects in the notebook are
34 always b64-encoded. We DO NOT encode/decode around file formats.
34 always b64-encoded. We DO NOT encode/decode around file formats.
35 """
35 """
36 for ws in nb.worksheets:
36 for ws in nb.worksheets:
37 for cell in ws.cells:
37 for cell in ws.cells:
38 if cell.cell_type == 'code':
38 if cell.cell_type == 'code':
39 for output in cell.outputs:
39 for output in cell.outputs:
40 if 'png' in output:
40 if 'png' in output:
41 output.png = str_to_bytes(output.png, 'ascii')
41 output.png = str_to_bytes(output.png, 'ascii')
42 if 'jpeg' in output:
42 if 'jpeg' in output:
43 output.jpeg = str_to_bytes(output.jpeg, 'ascii')
43 output.jpeg = str_to_bytes(output.jpeg, 'ascii')
44 return nb
44 return nb
45
45
46 # output keys that are likely to have multiline values
46 # output keys that are likely to have multiline values
47 _multiline_outputs = ['text', 'html', 'svg', 'latex', 'javascript', 'json']
47 _multiline_outputs = ['text', 'html', 'svg', 'latex', 'javascript', 'json']
48
48
49 def rejoin_lines(nb):
49 def rejoin_lines(nb):
50 """rejoin multiline text into strings
50 """rejoin multiline text into strings
51
51
52 For reversing effects of ``split_lines(nb)``.
52 For reversing effects of ``split_lines(nb)``.
53
53
54 This only rejoins lines that have been split, so if text objects were not split
54 This only rejoins lines that have been split, so if text objects were not split
55 they will pass through unchanged.
55 they will pass through unchanged.
56
56
57 Used when reading JSON files that may have been passed through split_lines.
57 Used when reading JSON files that may have been passed through split_lines.
58 """
58 """
59 for ws in nb.worksheets:
59 for ws in nb.worksheets:
60 for cell in ws.cells:
60 for cell in ws.cells:
61 if cell.cell_type == 'code':
61 if cell.cell_type == 'code':
62 if 'input' in cell and isinstance(cell.input, list):
62 if 'input' in cell and isinstance(cell.input, list):
63 cell.input = u'\n'.join(cell.input)
63 cell.input = u'\n'.join(cell.input)
64 for output in cell.outputs:
64 for output in cell.outputs:
65 for key in _multiline_outputs:
65 for key in _multiline_outputs:
66 item = output.get(key, None)
66 item = output.get(key, None)
67 if isinstance(item, list):
67 if isinstance(item, list):
68 output[key] = u'\n'.join(item)
68 output[key] = u'\n'.join(item)
69 else: # text, heading cell
69 else: # text, heading cell
70 for key in ['source', 'rendered']:
70 for key in ['source', 'rendered']:
71 item = cell.get(key, None)
71 item = cell.get(key, None)
72 if isinstance(item, list):
72 if isinstance(item, list):
73 cell[key] = u'\n'.join(item)
73 cell[key] = u'\n'.join(item)
74 return nb
74 return nb
75
75
76
76
77 def split_lines(nb):
77 def split_lines(nb):
78 """split likely multiline text into lists of strings
78 """split likely multiline text into lists of strings
79
79
80 For file output more friendly to line-based VCS. ``rejoin_lines(nb)`` will
80 For file output more friendly to line-based VCS. ``rejoin_lines(nb)`` will
81 reverse the effects of ``split_lines(nb)``.
81 reverse the effects of ``split_lines(nb)``.
82
82
83 Used when writing JSON files.
83 Used when writing JSON files.
84 """
84 """
85 for ws in nb.worksheets:
85 for ws in nb.worksheets:
86 for cell in ws.cells:
86 for cell in ws.cells:
87 if cell.cell_type == 'code':
87 if cell.cell_type == 'code':
88 if 'input' in cell and isinstance(cell.input, basestring):
88 if 'input' in cell and isinstance(cell.input, basestring):
89 cell.input = cell.input.splitlines()
89 cell.input = (cell.input + '\n').splitlines()
90 for output in cell.outputs:
90 for output in cell.outputs:
91 for key in _multiline_outputs:
91 for key in _multiline_outputs:
92 item = output.get(key, None)
92 item = output.get(key, None)
93 if isinstance(item, basestring):
93 if isinstance(item, basestring):
94 output[key] = item.splitlines()
94 output[key] = (item + '\n').splitlines()
95 else: # text, heading cell
95 else: # text, heading cell
96 for key in ['source', 'rendered']:
96 for key in ['source', 'rendered']:
97 item = cell.get(key, None)
97 item = cell.get(key, None)
98 if isinstance(item, basestring):
98 if isinstance(item, basestring):
99 cell[key] = item.splitlines()
99 cell[key] = (item + '\n').splitlines()
100 return nb
100 return nb
101
101
102 # b64 encode/decode are never actually used, because all bytes objects in
102 # b64 encode/decode are never actually used, because all bytes objects in
103 # the notebook are already b64-encoded, and we don't need/want to double-encode
103 # the notebook are already b64-encoded, and we don't need/want to double-encode
104
104
105 def base64_decode(nb):
105 def base64_decode(nb):
106 """Restore all bytes objects in the notebook from base64-encoded strings.
106 """Restore all bytes objects in the notebook from base64-encoded strings.
107
107
108 Note: This is never used
108 Note: This is never used
109 """
109 """
110 for ws in nb.worksheets:
110 for ws in nb.worksheets:
111 for cell in ws.cells:
111 for cell in ws.cells:
112 if cell.cell_type == 'code':
112 if cell.cell_type == 'code':
113 for output in cell.outputs:
113 for output in cell.outputs:
114 if 'png' in output:
114 if 'png' in output:
115 if isinstance(output.png, unicode):
115 if isinstance(output.png, unicode):
116 output.png = output.png.encode('ascii')
116 output.png = output.png.encode('ascii')
117 output.png = decodestring(output.png)
117 output.png = decodestring(output.png)
118 if 'jpeg' in output:
118 if 'jpeg' in output:
119 if isinstance(output.jpeg, unicode):
119 if isinstance(output.jpeg, unicode):
120 output.jpeg = output.jpeg.encode('ascii')
120 output.jpeg = output.jpeg.encode('ascii')
121 output.jpeg = decodestring(output.jpeg)
121 output.jpeg = decodestring(output.jpeg)
122 return nb
122 return nb
123
123
124
124
125 def base64_encode(nb):
125 def base64_encode(nb):
126 """Base64 encode all bytes objects in the notebook.
126 """Base64 encode all bytes objects in the notebook.
127
127
128 These will be b64-encoded unicode strings
128 These will be b64-encoded unicode strings
129
129
130 Note: This is never used
130 Note: This is never used
131 """
131 """
132 for ws in nb.worksheets:
132 for ws in nb.worksheets:
133 for cell in ws.cells:
133 for cell in ws.cells:
134 if cell.cell_type == 'code':
134 if cell.cell_type == 'code':
135 for output in cell.outputs:
135 for output in cell.outputs:
136 if 'png' in output:
136 if 'png' in output:
137 output.png = encodestring(output.png).decode('ascii')
137 output.png = encodestring(output.png).decode('ascii')
138 if 'jpeg' in output:
138 if 'jpeg' in output:
139 output.jpeg = encodestring(output.jpeg).decode('ascii')
139 output.jpeg = encodestring(output.jpeg).decode('ascii')
140 return nb
140 return nb
141
141
142
142
143 class NotebookReader(object):
143 class NotebookReader(object):
144 """A class for reading notebooks."""
144 """A class for reading notebooks."""
145
145
146 def reads(self, s, **kwargs):
146 def reads(self, s, **kwargs):
147 """Read a notebook from a string."""
147 """Read a notebook from a string."""
148 raise NotImplementedError("loads must be implemented in a subclass")
148 raise NotImplementedError("loads must be implemented in a subclass")
149
149
150 def read(self, fp, **kwargs):
150 def read(self, fp, **kwargs):
151 """Read a notebook from a file like object"""
151 """Read a notebook from a file like object"""
152 nbs = fp.read()
152 nbs = fp.read()
153 if not py3compat.PY3 and not isinstance(nbs, unicode):
153 if not py3compat.PY3 and not isinstance(nbs, unicode):
154 nbs = py3compat.str_to_unicode(nbs)
154 nbs = py3compat.str_to_unicode(nbs)
155 return self.reads(nbs, **kwargs)
155 return self.reads(nbs, **kwargs)
156
156
157
157
158 class NotebookWriter(object):
158 class NotebookWriter(object):
159 """A class for writing notebooks."""
159 """A class for writing notebooks."""
160
160
161 def writes(self, nb, **kwargs):
161 def writes(self, nb, **kwargs):
162 """Write a notebook to a string."""
162 """Write a notebook to a string."""
163 raise NotImplementedError("loads must be implemented in a subclass")
163 raise NotImplementedError("loads must be implemented in a subclass")
164
164
165 def write(self, nb, fp, **kwargs):
165 def write(self, nb, fp, **kwargs):
166 """Write a notebook to a file like object"""
166 """Write a notebook to a file like object"""
167 nbs = self.writes(nb,**kwargs)
167 nbs = self.writes(nb,**kwargs)
168 if not py3compat.PY3 and not isinstance(nbs, unicode):
168 if not py3compat.PY3 and not isinstance(nbs, unicode):
169 # this branch is likely only taken for JSON on Python 2
169 # this branch is likely only taken for JSON on Python 2
170 nbs = py3compat.str_to_unicode(nbs)
170 nbs = py3compat.str_to_unicode(nbs)
171 return fp.write(nbs)
171 return fp.write(nbs)
172
172
173
173
174
174
@@ -1,129 +1,147 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 import os
3 import os
4 from base64 import encodestring
4 from base64 import encodestring
5
5
6 from ..nbbase import (
6 from ..nbbase import (
7 NotebookNode,
7 NotebookNode,
8 new_code_cell, new_text_cell, new_worksheet, new_notebook, new_output,
8 new_code_cell, new_text_cell, new_worksheet, new_notebook, new_output,
9 new_metadata, new_author, new_heading_cell, nbformat
9 new_metadata, new_author, new_heading_cell, nbformat
10 )
10 )
11
11
12 # some random base64-encoded *bytes*
12 # some random base64-encoded *bytes*
13 png = encodestring(os.urandom(5))
13 png = encodestring(os.urandom(5))
14 jpeg = encodestring(os.urandom(6))
14 jpeg = encodestring(os.urandom(6))
15
15
16 ws = new_worksheet(name='worksheet1')
16 ws = new_worksheet(name='worksheet1')
17
17
18 ws.cells.append(new_text_cell(
18 ws.cells.append(new_text_cell(
19 u'html',
19 u'html',
20 source='Some NumPy Examples',
20 source='Some NumPy Examples',
21 rendered='Some NumPy Examples'
21 rendered='Some NumPy Examples'
22 ))
22 ))
23
23
24
24
25 ws.cells.append(new_code_cell(
25 ws.cells.append(new_code_cell(
26 input='import numpy',
26 input='import numpy',
27 prompt_number=1,
27 prompt_number=1,
28 collapsed=False
28 collapsed=False
29 ))
29 ))
30
30
31 ws.cells.append(new_text_cell(
31 ws.cells.append(new_text_cell(
32 u'markdown',
32 u'markdown',
33 source='A random array',
33 source='A random array',
34 rendered='A random array'
34 rendered='A random array'
35 ))
35 ))
36
36
37 ws.cells.append(new_text_cell(
37 ws.cells.append(new_text_cell(
38 u'plaintext',
38 u'plaintext',
39 source='A random array',
39 source='A random array',
40 ))
40 ))
41
41
42 ws.cells.append(new_heading_cell(
42 ws.cells.append(new_heading_cell(
43 u'My Heading',
43 u'My Heading',
44 level=2
44 level=2
45 ))
45 ))
46
46
47 ws.cells.append(new_code_cell(
47 ws.cells.append(new_code_cell(
48 input='a = numpy.random.rand(100)',
48 input='a = numpy.random.rand(100)',
49 prompt_number=2,
49 prompt_number=2,
50 collapsed=True
50 collapsed=True
51 ))
51 ))
52 ws.cells.append(new_code_cell(
53 input='a = 10\nb = 5\n',
54 prompt_number=3,
55 ))
56 ws.cells.append(new_code_cell(
57 input='a = 10\nb = 5',
58 prompt_number=4,
59 ))
52
60
53 ws.cells.append(new_code_cell(
61 ws.cells.append(new_code_cell(
54 input=u'print "ünîcødé"',
62 input=u'print "ünîcødé"',
55 prompt_number=3,
63 prompt_number=3,
56 collapsed=False,
64 collapsed=False,
57 outputs=[new_output(
65 outputs=[new_output(
58 output_type=u'pyout',
66 output_type=u'pyout',
59 output_text=u'<array a>',
67 output_text=u'<array a>',
60 output_html=u'The HTML rep',
68 output_html=u'The HTML rep',
61 output_latex=u'$a$',
69 output_latex=u'$a$',
62 output_png=png,
70 output_png=png,
63 output_jpeg=jpeg,
71 output_jpeg=jpeg,
64 output_svg=u'<svg>',
72 output_svg=u'<svg>',
65 output_json=u'json data',
73 output_json=u'json data',
66 output_javascript=u'var i=0;',
74 output_javascript=u'var i=0;',
67 prompt_number=3
75 prompt_number=3
68 ),new_output(
76 ),new_output(
69 output_type=u'display_data',
77 output_type=u'display_data',
70 output_text=u'<array a>',
78 output_text=u'<array a>',
71 output_html=u'The HTML rep',
79 output_html=u'The HTML rep',
72 output_latex=u'$a$',
80 output_latex=u'$a$',
73 output_png=png,
81 output_png=png,
74 output_jpeg=jpeg,
82 output_jpeg=jpeg,
75 output_svg=u'<svg>',
83 output_svg=u'<svg>',
76 output_json=u'json data',
84 output_json=u'json data',
77 output_javascript=u'var i=0;'
85 output_javascript=u'var i=0;'
78 ),new_output(
86 ),new_output(
79 output_type=u'pyerr',
87 output_type=u'pyerr',
80 etype=u'NameError',
88 etype=u'NameError',
81 evalue=u'NameError was here',
89 evalue=u'NameError was here',
82 traceback=[u'frame 0', u'frame 1', u'frame 2']
90 traceback=[u'frame 0', u'frame 1', u'frame 2']
83 )]
91 )]
84 ))
92 ))
85
93
86 authors = [new_author(name='Bart Simpson',email='bsimpson@fox.com',
94 authors = [new_author(name='Bart Simpson',email='bsimpson@fox.com',
87 affiliation=u'Fox',url=u'http://www.fox.com')]
95 affiliation=u'Fox',url=u'http://www.fox.com')]
88 md = new_metadata(name=u'My Notebook',license=u'BSD',created=u'8601_goes_here',
96 md = new_metadata(name=u'My Notebook',license=u'BSD',created=u'8601_goes_here',
89 modified=u'8601_goes_here',gistid=u'21341231',authors=authors)
97 modified=u'8601_goes_here',gistid=u'21341231',authors=authors)
90
98
91 nb0 = new_notebook(
99 nb0 = new_notebook(
92 worksheets=[ws, new_worksheet(name='worksheet2')],
100 worksheets=[ws, new_worksheet(name='worksheet2')],
93 metadata=md
101 metadata=md
94 )
102 )
95
103
96 nb0_py = u"""# -*- coding: utf-8 -*-
104 nb0_py = u"""# -*- coding: utf-8 -*-
97 # <nbformat>%i</nbformat>
105 # <nbformat>%i</nbformat>
98
106
99 # <htmlcell>
107 # <htmlcell>
100
108
101 # Some NumPy Examples
109 # Some NumPy Examples
102
110
103 # <codecell>
111 # <codecell>
104
112
105 import numpy
113 import numpy
106
114
107 # <markdowncell>
115 # <markdowncell>
108
116
109 # A random array
117 # A random array
110
118
111 # <plaintextcell>
119 # <plaintextcell>
112
120
113 # A random array
121 # A random array
114
122
115 # <headingcell level=2>
123 # <headingcell level=2>
116
124
117 # My Heading
125 # My Heading
118
126
119 # <codecell>
127 # <codecell>
120
128
121 a = numpy.random.rand(100)
129 a = numpy.random.rand(100)
122
130
123 # <codecell>
131 # <codecell>
124
132
133 a = 10
134 b = 5
135
136 # <codecell>
137
138 a = 10
139 b = 5
140
141 # <codecell>
142
125 print "ünîcødé"
143 print "ünîcødé"
126
144
127 """ % nbformat
145 """ % nbformat
128
146
129
147
@@ -1,38 +1,43 b''
1 # -*- coding: utf8 -*-
1 # -*- coding: utf8 -*-
2 from . import formattest
2 from . import formattest
3
3
4 from .. import nbpy
4 from .. import nbpy
5 from .nbexamples import nb0, nb0_py
5 from .nbexamples import nb0, nb0_py
6
6
7
7
8 class TestPy(formattest.NBFormatTestCase):
8 class TestPy(formattest.NBFormatTestCase):
9
9
10 nb0_ref = nb0_py
10 nb0_ref = nb0_py
11 ext = 'py'
11 ext = 'py'
12 mod = nbpy
12 mod = nbpy
13 ignored_keys = ['collapsed', 'outputs', 'prompt_number', 'metadata']
13 ignored_keys = ['collapsed', 'outputs', 'prompt_number', 'metadata']
14
14
15 def assertSubset(self, da, db):
15 def assertSubset(self, da, db):
16 """assert that da is a subset of db, ignoring self.ignored_keys.
16 """assert that da is a subset of db, ignoring self.ignored_keys.
17
17
18 Called recursively on containers, ultimately comparing individual
18 Called recursively on containers, ultimately comparing individual
19 elements.
19 elements.
20 """
20 """
21 if isinstance(da, dict):
21 if isinstance(da, dict):
22 for k,v in da.iteritems():
22 for k,v in da.iteritems():
23 if k in self.ignored_keys:
23 if k in self.ignored_keys:
24 continue
24 continue
25 self.assertTrue(k in db)
25 self.assertTrue(k in db)
26 self.assertSubset(v, db[k])
26 self.assertSubset(v, db[k])
27 elif isinstance(da, list):
27 elif isinstance(da, list):
28 for a,b in zip(da, db):
28 for a,b in zip(da, db):
29 self.assertSubset(a,b)
29 self.assertSubset(a,b)
30 else:
30 else:
31 if isinstance(da, basestring) and isinstance(db, basestring):
32 # pyfile is not sensitive to preserving leading/trailing
33 # newlines in blocks through roundtrip
34 da = da.strip('\n')
35 db = db.strip('\n')
31 self.assertEquals(da, db)
36 self.assertEquals(da, db)
32 return True
37 return True
33
38
34 def assertNBEquals(self, nba, nbb):
39 def assertNBEquals(self, nba, nbb):
35 # since roundtrip is lossy, only compare keys that are preserved
40 # since roundtrip is lossy, only compare keys that are preserved
36 # assumes nba is read from my file format
41 # assumes nba is read from my file format
37 return self.assertSubset(nba, nbb)
42 return self.assertSubset(nba, nbb)
38
43
General Comments 0
You need to be logged in to leave comments. Login now