diff --git a/nbrmd/cells.py b/nbrmd/cells.py index 8aa2e74f6..4777d9443 100644 --- a/nbrmd/cells.py +++ b/nbrmd/cells.py @@ -54,10 +54,26 @@ def code_to_text(self, if options != '': lines.append('#+ ' + options) else: + # Issue #31: does the cell ends with a blank line? + # Do we find two blank lines in the cell? In that case + # we add an end-of-cell marker + if (len(source) and _BLANK_LINE.match(source[-1])) or \ + code_to_cell(self, source, False)[1] != len(source): + cellend = '-' + while True: + cellend_re = re.compile('^#( )' + cellend + '\s*$') + if len(list(filter(cellend_re.match, source))): + cellend = cellend + '-' + else: + break + metadata['cellend'] = cellend + options = metadata_to_json_options(metadata) if options != '{}': lines.append('# + ' + options) lines.extend(source) + if 'cellend' in metadata: + lines.append('# ' + metadata['cellend']) else: lines.extend(self.markdown_escape( code_to_rmd(source, metadata, default_language))) @@ -232,10 +248,18 @@ def code_to_cell(self, lines, parse_opt): else: metadata = {} - # Find end of cell and return if self.ext == '.Rmd': + end_cell_re = _END_CODE_MD + elif 'cellend' in metadata: + end_cell_re = re.compile('^#( )' + metadata['cellend'] + '\s*$') + del metadata['cellend'] + else: + end_cell_re = None + + # Find end of cell and return + if end_cell_re: for pos, line in enumerate(lines): - if pos > 0 and _END_CODE_MD.match(line): + if pos > 0 and end_cell_re.match(line): next_line_blank = pos + 1 == len(lines) or \ _BLANK_LINE.match(lines[pos + 1]) if next_line_blank and pos + 2 != len(lines): diff --git a/tests/python_notebook_sample.py b/tests/python_notebook_sample.py index 6bce6cefc..c7138fa7c 100755 --- a/tests/python_notebook_sample.py +++ b/tests/python_notebook_sample.py @@ -21,17 +21,24 @@ # - two blank lines if followed by an other code cell # - one blank line if followed by a markdown cell -# Code cells can have blank lines, but no two consecutive blank lines (that's -# a cell break!). Below we have a cell with multiple instructions: +# Code cells can have blank lines, but no two consecutive blank lines (unless +# a specific cell end is specified in the cell options, see below). +# Now we have a cell with multiple instructions a = 3 a + 1 -# ## Metadata in code cells +# And a cell with an arbitrary count of blank lines. This last example +# is also an instance of a cell with metadata information in json format, +# escaped with '#+' or '# +' -# In case a code cell has metadata information, it -# is represented in json format, escaped with '#+' or '# +' +# + {"endcell": "-"} +def f(x): + return x + 1 -# + {"scrolled": true} -a + 2 + +def g(x): + return x + 2 + +# - diff --git a/tests/test_read_simple_python.py b/tests/test_read_simple_python.py index a527018d8..b5aab39dc 100644 --- a/tests/test_read_simple_python.py +++ b/tests/test_read_simple_python.py @@ -104,6 +104,30 @@ def f(x): compare(pynb, pynb2) +def test_read_cell_two_blank_lines(pynb="""# --- +# title: cell with two consecutive blank lines +# --- + +# + {"cellend": "-"} +a = 1 + + +a + 2 +# - +"""): + nb = nbrmd.reads(pynb, ext='.py') + + assert len(nb.cells) == 2 + assert nb.cells[0].cell_type == 'raw' + assert nb.cells[0].source == '---\ntitle: cell with two ' \ + 'consecutive blank lines\n---' + assert nb.cells[1].cell_type == 'code' + assert nb.cells[1].source == 'a = 1\n\n\na + 2' + + pynb2 = nbrmd.writes(nb, ext='.py') + compare(pynb, pynb2) + + def test_read_multiline_comment(pynb="""'''This is a multiline comment with "quotes", 'single quotes' # and comments