diff --git a/IPython/frontend/html/notebook/notebookmanager.py b/IPython/frontend/html/notebook/notebookmanager.py index 6aed5a4..89c0c40 100644 --- a/IPython/frontend/html/notebook/notebookmanager.py +++ b/IPython/frontend/html/notebook/notebookmanager.py @@ -118,7 +118,12 @@ class NotebookManager(LoggingConfigurable): if format not in self.allowed_formats: raise web.HTTPError(415, u'Invalid notebook format: %s' % format) last_modified, nb = self.get_notebook_object(notebook_id) - data = current.writes(nb, format) + kwargs = {} + if format == 'json': + # don't split lines for sending over the wire, because it + # should match the Python in-memory format. + kwargs['split_lines'] = False + data = current.writes(nb, format, **kwargs) name = nb.get('name','notebook') return last_modified, name, data diff --git a/IPython/nbformat/v2/nbjson.py b/IPython/nbformat/v2/nbjson.py index e67bec3..e207466 100644 --- a/IPython/nbformat/v2/nbjson.py +++ b/IPython/nbformat/v2/nbjson.py @@ -16,10 +16,14 @@ Authors: # Imports #----------------------------------------------------------------------------- -from .nbbase import from_dict -from .rwbase import NotebookReader, NotebookWriter, restore_bytes +import copy import json +from .nbbase import from_dict +from .rwbase import ( + NotebookReader, NotebookWriter, restore_bytes, rejoin_lines, split_lines +) + #----------------------------------------------------------------------------- # Code #----------------------------------------------------------------------------- @@ -40,7 +44,7 @@ class JSONReader(NotebookReader): return nb def to_notebook(self, d, **kwargs): - return restore_bytes(from_dict(d)) + return restore_bytes(rejoin_lines(from_dict(d))) class JSONWriter(NotebookWriter): @@ -49,8 +53,10 @@ class JSONWriter(NotebookWriter): kwargs['cls'] = BytesEncoder kwargs['indent'] = 4 kwargs['sort_keys'] = True + if kwargs.pop('split_lines', True): + nb = split_lines(copy.deepcopy(nb)) return json.dumps(nb, **kwargs) - + _reader = JSONReader() _writer = JSONWriter() diff --git a/IPython/nbformat/v2/rwbase.py b/IPython/nbformat/v2/rwbase.py index cbcb50a..39d54b2 100644 --- a/IPython/nbformat/v2/rwbase.py +++ b/IPython/nbformat/v2/rwbase.py @@ -41,6 +41,61 @@ def restore_bytes(nb): output.jpeg = str_to_bytes(output.jpeg, 'ascii') return nb +# output keys that are likely to have multiline values +_multiline_outputs = ['text', 'html', 'svg', 'latex', 'javascript', 'json'] + +def rejoin_lines(nb): + """rejoin multiline text into strings + + For reversing effects of ``split_lines(nb)``. + + This only rejoins lines that have been split, so if text objects were not split + they will pass through unchanged. + + Used when reading JSON files that may have been passed through split_lines. + """ + for ws in nb.worksheets: + for cell in ws.cells: + if cell.cell_type == 'code': + if 'input' in cell and isinstance(cell.input, list): + cell.input = u'\n'.join(cell.input) + for output in cell.outputs: + for key in _multiline_outputs: + item = output.get(key, None) + if isinstance(item, list): + output[key] = u'\n'.join(item) + else: # text cell + for key in ['source', 'rendered']: + item = cell.get(key, None) + if isinstance(item, list): + cell[key] = u'\n'.join(item) + return nb + + +def split_lines(nb): + """split likely multiline text into lists of strings + + For file output more friendly to line-based VCS. ``rejoin_lines(nb)`` will + reverse the effects of ``split_lines(nb)``. + + Used when writing JSON files. + """ + for ws in nb.worksheets: + for cell in ws.cells: + if cell.cell_type == 'code': + if 'input' in cell and isinstance(cell.input, basestring): + cell.input = cell.input.splitlines() + for output in cell.outputs: + for key in _multiline_outputs: + item = output.get(key, None) + if isinstance(item, basestring): + output[key] = item.splitlines() + else: # text cell + for key in ['source', 'rendered']: + item = cell.get(key, None) + if isinstance(item, basestring): + cell[key] = item.splitlines() + return nb # b64 encode/decode are never actually used, because all bytes objects in # the notebook are already b64-encoded, and we don't need/want to double-encode diff --git a/IPython/nbformat/v2/tests/test_json.py b/IPython/nbformat/v2/tests/test_json.py index 1d05fa0..d6cc5d3 100644 --- a/IPython/nbformat/v2/tests/test_json.py +++ b/IPython/nbformat/v2/tests/test_json.py @@ -16,6 +16,19 @@ class TestJSON(TestCase): # print # print s self.assertEquals(reads(s),nb0) + + def test_roundtrip_nosplit(self): + """Ensure that multiline blobs are still readable""" + # ensures that notebooks written prior to splitlines change + # are still readable. + s = writes(nb0, split_lines=False) + self.assertEquals(reads(s),nb0) + + def test_roundtrip_split(self): + """Ensure that splitting multiline blocks is safe""" + # This won't differ from test_roundtrip unless the default changes + s = writes(nb0, split_lines=True) + self.assertEquals(reads(s),nb0)