Skip to content

Commit

Permalink
Empty markdown cell separates two code cells in sphinx format #97
Browse files Browse the repository at this point in the history
  • Loading branch information
mwouts committed Oct 10, 2018
1 parent ba2dd5e commit e042ee4
Show file tree
Hide file tree
Showing 19 changed files with 153 additions and 694 deletions.
2 changes: 2 additions & 0 deletions demo/World population.spx.py
Expand Up @@ -87,6 +87,7 @@

import matplotlib.pyplot as plt

""
plt.clf()
plt.figure(figsize=(10, 5), dpi=100)
plt.stackplot(population.index, population.values.T / 1e9)
Expand All @@ -108,6 +109,7 @@

offline.init_notebook_mode()

""
bars = [go.Bar(x=population.index, y=population[zone], name=zone)
for zone in zones]
fig = go.Figure(data=bars,
Expand Down
49 changes: 24 additions & 25 deletions jupytext/cell_reader.py
Expand Up @@ -32,12 +32,11 @@ def uncomment(lines, prefix='#'):
for line in lines]


def paragraph_is_fully_commented(lines, main_language):
def paragraph_is_fully_commented(lines, comment, main_language):
"""Is the paragraph fully commented?"""
for i, line in enumerate(lines):
if line.startswith('#'):
if (line.startswith(('# %', '# ?'))
and is_magic(line, main_language)):
if line.startswith(comment):
if line.startswith((comment + ' %', comment + ' ?')) and is_magic(line, main_language):
return False
continue
return i > 0 and _BLANK_LINE.match(line)
Expand Down Expand Up @@ -139,13 +138,11 @@ def metadata_and_language_from_option_line(self, line):
"""Parse code options on the given line. When a start of a code cell
is found, self.metadata is set to a dictionary."""
if self.start_code_re.match(line):
self.language, self.metadata = \
self.options_to_metadata(self.start_code_re.findall(line)[0])
self.language, self.metadata = self.options_to_metadata(self.start_code_re.findall(line)[0])

def options_to_metadata(self, options):
"""Return language (str) and metadata (dict) from the option line"""
raise NotImplementedError("Option parsing must be implemented in a "
"sub class")
raise NotImplementedError("Option parsing must be implemented in a sub class")

def find_code_cell_end(self, lines):
"""Given that this is a code cell, return position of
Expand Down Expand Up @@ -195,14 +192,12 @@ def find_code_cell_end(self, lines):
def find_cell_end(self, lines):
"""Return position of end of cell marker, and position
of first line after cell"""
raise NotImplementedError('This method must be implemented in a '
'sub class')
raise NotImplementedError('This method must be implemented in a sub class')

def find_cell_content(self, lines):
"""Parse cell till its end and set content, lines_to_next_cell.
Return the position of next cell start"""
cell_end_marker, next_cell_start, explicit_eoc = \
self.find_cell_end(lines)
cell_end_marker, next_cell_start, explicit_eoc = self.find_cell_end(lines)

# Metadata to dict
if self.metadata is None:
Expand Down Expand Up @@ -337,8 +332,7 @@ def uncomment_code_and_magics(self, lines):
lines = uncomment(lines)

if self.cell_type == 'code':
unescape_code_start(lines, self.ext, self.language or
self.default_language)
unescape_code_start(lines, self.ext, self.language or self.default_language)

if self.cell_type == 'markdown':
lines = uncomment(lines, self.markdown_prefix or self.comment)
Expand Down Expand Up @@ -396,8 +390,7 @@ def options_to_metadata(self, options):

def metadata_and_language_from_option_line(self, line):
if self.start_code_re.match(line):
self.metadata = self.options_to_metadata(
self.start_code_re.match(line).group(3))
self.metadata = self.options_to_metadata(self.start_code_re.match(line).group(3))
elif self.simple_start_code_re.match(line):
self.metadata = {}

Expand All @@ -407,8 +400,7 @@ def metadata_and_language_from_option_line(self, line):
def find_cell_end(self, lines):
"""Return position of end of cell marker, and position
of first line after cell"""
if self.metadata is None and \
paragraph_is_fully_commented(lines, 'python'):
if self.metadata is None and paragraph_is_fully_commented(lines, self.comment, self.default_language):
self.cell_type = 'markdown'
for i, line in enumerate(lines):
if _BLANK_LINE.match(line):
Expand All @@ -417,7 +409,7 @@ def find_cell_end(self, lines):

if self.metadata is not None:
end_of_cell = self.metadata.get('endofcell', '-')
self.end_code_re = re.compile('^# ' + end_of_cell + r'\s*$')
self.end_code_re = re.compile('^' + self.comment + ' ' + end_of_cell + r'\s*$')

return self.find_code_cell_end(lines)

Expand All @@ -431,20 +423,27 @@ def __init__(self, ext, comment_magics=None):
script = _SCRIPT_EXTENSIONS[ext]
self.default_language = script['language']
self.comment = script['comment']
self.start_code_re = re.compile(r"^{}\s+%%(.*)$".format(self.comment))
self.nbconvert_start_code_re = re.compile(r"^{} (<codecell>|In\[[0-9 ]*\]:?)$".format(self.comment))
self.start_code_re = re.compile(r"^{}\s*%%(%*)\s(.*)$".format(self.comment))
self.alternative_start_code_re = re.compile(r"^{}\s*(%%|<codecell>|In\[[0-9 ]*\]:?)\s*$".format(self.comment))

def metadata_and_language_from_option_line(self, line):
"""Parse code options on the given line. When a start of a code cell
is found, self.metadata is set to a dictionary."""
if self.start_code_re.match(line):
self.language, self.metadata = self.options_to_metadata(line[line.find('%%') + 2:])
elif self.alternative_start_code_re.match(line):
self.metadata = {}

def options_to_metadata(self, options):
return None, double_percent_options_to_metadata(options)

def find_cell_content(self, lines):
"""Parse cell till its end and set content, lines_to_next_cell.
Return the position of next cell start"""
cell_end_marker, next_cell_start, explicit_eoc = \
self.find_cell_end(lines)
cell_end_marker, next_cell_start, explicit_eoc = self.find_cell_end(lines)

# Metadata to dict
if self.start_code_re.match(lines[0]) or self.nbconvert_start_code_re.match(lines[0]):
if self.start_code_re.match(lines[0]) or self.alternative_start_code_re.match(lines[0]):
cell_start = 1
else:
cell_start = 0
Expand Down Expand Up @@ -478,7 +477,7 @@ def find_cell_end(self, lines):

next_cell = len(lines)
for i, line in enumerate(lines):
if i > 0 and (self.start_code_re.match(line) or self.nbconvert_start_code_re.match(line)):
if i > 0 and (self.start_code_re.match(line) or self.alternative_start_code_re.match(line)):
next_cell = i
break

Expand Down
20 changes: 18 additions & 2 deletions jupytext/jupytext.py
Expand Up @@ -56,6 +56,15 @@ def reads(self, s, **_):

set_main_and_cell_language(metadata, cells, self.format.extension)

if self.format.format_name and self.format.format_name.startswith('sphinx'):
filtered_cells = []
for i, cell in enumerate(cells):
if cell.source == '' and i > 0 and i + 1 < len(cells) \
and cells[i - 1].cell_type != 'markdown' and cells[i + 1].cell_type != 'markdown':
continue
filtered_cells.append(cell)
cells = filtered_cells

return new_notebook(cells=cells, metadata=metadata)


Expand Down Expand Up @@ -102,10 +111,17 @@ def writes(self, nb, **kwargs):

# two blank lines between markdown cells in Rmd
if self.ext in ['.Rmd', '.md'] and not cell.is_code():
if i + 1 < len(cell_exporters) and not \
cell_exporters[i + 1].is_code():
if i + 1 < len(cell_exporters) and not cell_exporters[i + 1].is_code():
lines.append('')

# "" between two consecutive code cells in sphinx
if self.format.format_name.startswith('sphinx') and cell.is_code():
if i + 1 < len(cell_exporters) and cell_exporters[i + 1].is_code():
lines.append('""')
if i + 1 == len(cell_exporters) and cell.source == ['']:
lines.append('""')


return '\n'.join(lines)


Expand Down
9 changes: 0 additions & 9 deletions tests/notebooks/ipynb_py/World population.ipynb
Expand Up @@ -1452,15 +1452,6 @@
}
],
"metadata": {
"jupytext": {
"formats": "ipynb,pct.py:percent,lgt.py:light,spx.py:sphinx,md,Rmd",
"text_representation": {
"extension": ".pct.py",
"format_name": "percent",
"format_version": "1.1",
"jupytext_version": "0.8.0"
}
},
"kernelspec": {
"display_name": "Python 3",
"language": "python",
Expand Down

0 comments on commit e042ee4

Please sign in to comment.