Skip to content

Commit

Permalink
R Markdown visibility options are mapped to Jupyter Book ones
Browse files Browse the repository at this point in the history
By default. And a `use_runtools` option is provided to map them to the runtools options instead.
Closes #337
  • Loading branch information
mwouts committed Oct 29, 2019
1 parent e519249 commit 8caa8c7
Show file tree
Hide file tree
Showing 8 changed files with 88 additions and 49 deletions.
70 changes: 38 additions & 32 deletions jupytext/cell_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@
(('results', "'hide'"), [('hide_output', True)]),
(('results', '"hide"'), [('hide_output', True)])
]
# Alternatively, Jupytext can also map the Jupyter Book options to R Markdown
_RMARKDOWN_TO_JUPYTER_BOOK_MAP = [
(('include', 'FALSE'), 'remove_cell'),
(('echo', 'FALSE'), 'remove_input'),
(('results', "'hide'"), 'remove_output'),
(('results', '"hide"'), 'remove_output')
]

_JUPYTEXT_CELL_METADATA = [
# Pre-jupytext metadata
Expand Down Expand Up @@ -65,22 +72,25 @@ def _py_logical_values(rbool):
raise RLogicalValueError


def metadata_to_rmd_options(language, metadata):
"""
Convert language and metadata information to their rmd representation
:param language:
:param metadata:
:return:
"""
def metadata_to_rmd_options(language, metadata, use_runtools=False):
"""Convert language and metadata information to their rmd representation"""
options = (language or 'R').lower()
if 'name' in metadata:
options += ' ' + metadata['name'] + ','
del metadata['name']
for rmd_option, jupyter_options in _RMARKDOWN_TO_RUNTOOLS_OPTION_MAP:
if all([metadata.get(opt_name) == opt_value for opt_name, opt_value in jupyter_options]):
options += ' {}={},'.format(rmd_option[0], 'FALSE' if rmd_option[1] is False else rmd_option[1])
for opt_name, _ in jupyter_options:
metadata.pop(opt_name)
if use_runtools:
for rmd_option, jupyter_options in _RMARKDOWN_TO_RUNTOOLS_OPTION_MAP:
if all([metadata.get(opt_name) == opt_value for opt_name, opt_value in jupyter_options]):
options += ' {}={},'.format(rmd_option[0], 'FALSE' if rmd_option[1] is False else rmd_option[1])
for opt_name, _ in jupyter_options:
metadata.pop(opt_name)
else:
for rmd_option, tag in _RMARKDOWN_TO_JUPYTER_BOOK_MAP:
if tag in metadata.get('tags', []):
options += ' {}={},'.format(rmd_option[0], 'FALSE' if rmd_option[1] is False else rmd_option[1])
metadata['tags'] = [i for i in metadata['tags'] if i != tag]
if not metadata['tags']:
metadata.pop('tags')
for opt_name in metadata:
opt_value = metadata[opt_name]
opt_name = opt_name.strip()
Expand All @@ -100,19 +110,19 @@ def metadata_to_rmd_options(language, metadata):
return options.strip(',').strip()


def update_metadata_from_rmd_options(name, value, metadata):
"""
Update metadata using the _BOOLEAN_OPTIONS_DICTIONARY mapping
:param name: option name
:param value: option value
:param metadata:
:return:
"""
for rmd_option, jupyter_options in _RMARKDOWN_TO_RUNTOOLS_OPTION_MAP:
if name == rmd_option[0] and value == rmd_option[1]:
for opt_name, opt_value in jupyter_options:
metadata[opt_name] = opt_value
return True
def update_metadata_from_rmd_options(name, value, metadata, use_runtools=False):
"""Map the R Markdown cell visibility options to the Jupyter ones"""
if use_runtools:
for rmd_option, jupyter_options in _RMARKDOWN_TO_RUNTOOLS_OPTION_MAP:
if name == rmd_option[0] and value == rmd_option[1]:
for opt_name, opt_value in jupyter_options:
metadata[opt_name] = opt_value
return True
else:
for rmd_option, tag in _RMARKDOWN_TO_JUPYTER_BOOK_MAP:
if name == rmd_option[0] and value == rmd_option[1]:
metadata.setdefault('tags', []).append(tag)
return True
return False


Expand Down Expand Up @@ -213,12 +223,8 @@ def parse_rmd_options(line):
return result


def rmd_options_to_metadata(options):
"""
Parse rmd options and return a metadata dictionary
:param options:
:return:
"""
def rmd_options_to_metadata(options, use_runtools=False):
"""Parse rmd options and return a metadata dictionary"""
options = re.split(r'\s|,', options, 1)
if len(options) == 1:
language = options[0]
Expand All @@ -236,7 +242,7 @@ def rmd_options_to_metadata(options):
if i == 0 and name == '':
metadata['name'] = value
continue
if update_metadata_from_rmd_options(name, value, metadata):
if update_metadata_from_rmd_options(name, value, metadata, use_runtools=use_runtools):
continue
try:
metadata[name] = _py_logical_values(value)
Expand Down
5 changes: 3 additions & 2 deletions jupytext/cell_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ def __init__(self, fmt=None, default_language=None):
self.ext = fmt.get('extension')
self.default_language = default_language or _SCRIPT_EXTENSIONS.get(self.ext, {}).get('language', 'python')
self.comment_magics = fmt.get('comment_magics', self.default_comment_magics)
self.use_runtools = fmt.get('use_runtools', False)
self.format_version = fmt.get('format_version')
self.metadata = None
self.org_content = []
Expand Down Expand Up @@ -398,7 +399,7 @@ class RMarkdownCellReader(MarkdownCellReader):
default_comment_magics = True

def options_to_metadata(self, options):
return rmd_options_to_metadata(options)
return rmd_options_to_metadata(options, self.use_runtools)

def uncomment_code_and_magics(self, lines):
if self.cell_type == 'code' and self.comment_magics and is_active(self.ext, self.metadata):
Expand Down Expand Up @@ -436,7 +437,7 @@ class RScriptCellReader(ScriptCellReader):
default_comment_magics = True

def options_to_metadata(self, options):
return rmd_options_to_metadata('r ' + options)
return rmd_options_to_metadata('r ' + options, self.use_runtools)

def find_cell_end(self, lines):
"""Return position of end of cell marker, and position
Expand Down
8 changes: 4 additions & 4 deletions jupytext/cell_to_text.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,9 @@ def __init__(self, cell, default_language, fmt=None):
self.language = self.language or default_language
self.default_language = default_language
self.comment = _SCRIPT_EXTENSIONS.get(self.ext, {}).get('comment', '#')
self.comment_magics = self.fmt['comment_magics'] if 'comment_magics' in self.fmt \
else self.default_comment_magics
self.comment_magics = self.fmt.get('comment_magics', self.default_comment_magics)
self.cell_metadata_json = self.fmt.get('cell_metadata_json', False)
self.use_runtools = self.fmt.get('use_runtools', False)

# how many blank lines before next cell
self.lines_to_next_cell = cell.metadata.get('lines_to_next_cell')
Expand Down Expand Up @@ -206,7 +206,7 @@ def code_to_text(self):
lines = []
if not is_active(self.ext, self.metadata):
self.metadata['eval'] = False
options = metadata_to_rmd_options(self.language, self.metadata)
options = metadata_to_rmd_options(self.language, self.metadata, self.use_runtools)
lines.append('```{{{}}}'.format(options))
lines.extend(source)
lines.append('```')
Expand Down Expand Up @@ -373,7 +373,7 @@ def code_to_text(self):
lines = []
if not is_active(self.ext, self.metadata):
self.metadata['eval'] = False
options = metadata_to_rmd_options(None, self.metadata)
options = metadata_to_rmd_options(None, self.metadata, self.use_runtools)
if options:
lines.append('#+ {}'.format(options))
lines.extend(source)
Expand Down
2 changes: 1 addition & 1 deletion jupytext/formats.py
Original file line number Diff line number Diff line change
Expand Up @@ -534,7 +534,7 @@ def short_form_multiple_formats(jupytext_formats):


_VALID_FORMAT_INFO = ['extension', 'format_name', 'suffix', 'prefix']
_BINARY_FORMAT_OPTIONS = ['comment_magics', 'split_at_heading', 'rst2md', 'cell_metadata_json']
_BINARY_FORMAT_OPTIONS = ['comment_magics', 'split_at_heading', 'rst2md', 'cell_metadata_json', 'use_runtools']
_VALID_FORMAT_OPTIONS = _BINARY_FORMAT_OPTIONS + ['notebook_metadata_filter', 'cell_metadata_filter', 'cell_markers']


Expand Down
5 changes: 5 additions & 0 deletions jupytext/jupytext.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,11 @@ def writes(self, nb, metadata=None, **kwargs):
metadata = nb.metadata
default_language = default_language_from_metadata_and_ext(metadata, self.implementation.extension) or 'python'
self.update_fmt_with_notebook_options(nb.metadata)
if 'use_runtools' not in self.fmt:
for cell in nb.cells:
if cell.metadata.get('hide_input', False) or cell.metadata.get('hide_output', False):
self.fmt['use_runtools'] = True
break

if 'main_language' in metadata.get('jupytext', {}):
del metadata['jupytext']['main_language']
Expand Down
8 changes: 3 additions & 5 deletions tests/test_active_cells.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,22 +233,20 @@ def test_active_rmd(ext, no_jupytext_version_number):
compare(nb.cells[0], ACTIVE_RMD['.ipynb'])


ACTIVE_NOT_INCLUDE_RMD = {'.py': """# + hide_input=true hide_output=true active="Rmd"
ACTIVE_NOT_INCLUDE_RMD = {'.py': """# + tags=["remove_cell"] active="Rmd"
# # This cell is active in Rmd only
""",
'.Rmd': """```{python include=FALSE, active="Rmd"}
# This cell is active in Rmd only
```
""",
'.R': """# + hide_input=true hide_output=true active="Rmd"
'.R': """# + tags=["remove_cell"] active="Rmd"
# # This cell is active in Rmd only
""",
'.ipynb':
{'cell_type': 'raw',
'source': '# This cell is active in Rmd only',
'metadata': {'active': 'Rmd',
'hide_input': True,
'hide_output': True}}}
'metadata': {'active': 'Rmd', 'tags': ['remove_cell']}}}


@skip_if_dict_is_not_ordered
Expand Down
8 changes: 4 additions & 4 deletions tests/test_cell_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
('R', {'name': 'plot_1', 'bool': True,
'fig.path': "'fig_path/'"})),
('r echo=FALSE',
('R', {'hide_input': True})),
('R', {'tags': ['remove_input']})),
('r plot_1, echo=TRUE',
('R', {'name': 'plot_1', 'echo': True})),
('python echo=if a==5 then TRUE else FALSE',
Expand All @@ -24,10 +24,10 @@
('python active="ipynb,py"',
('python', {'active': 'ipynb,py'})),
('python include=FALSE, active="Rmd"',
('python', {'active': 'Rmd', 'hide_output': True, 'hide_input': True})),
('python', {'active': 'Rmd', 'tags': ['remove_cell']})),
('r chunk_name, include=FALSE, active="Rmd"',
('R',
{'name': 'chunk_name', 'active': 'Rmd', 'hide_output': True, 'hide_input': True})),
{'name': 'chunk_name', 'active': 'Rmd', 'tags': ['remove_cell']})),
('python tags=c("parameters")',
('python', {'tags': ['parameters']}))]

Expand Down Expand Up @@ -63,7 +63,7 @@ def test_parsing_error(options):


def test_ignore_metadata():
metadata = {'trusted': True, 'hide_input': True}
metadata = {'trusted': True, 'tags': ['remove_input']}
metadata = filter_metadata(metadata, None, _IGNORE_CELL_METADATA)
assert metadata_to_rmd_options('R', metadata) == 'r echo=FALSE'

Expand Down
31 changes: 30 additions & 1 deletion tests/test_hide_remove_input_outputs_rmarkdown.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,35 @@
from .utils import skip_if_dict_is_not_ordered


@skip_if_dict_is_not_ordered
@pytest.mark.parametrize('md,rmd', [('tags=["remove_cell"]', 'include=FALSE'),
('tags=["remove_output"]', "results='hide'"),
('tags=["remove_output"]', 'results="hide"'),
('tags=["remove_input"]', 'echo=FALSE')])
def test_jupyter_book_options_to_rmarkdown(md, rmd):
"""By default, Jupyter Book tags are mapped to R Markdown options, and vice versa #337"""
md = '```python ' + md + """
1 + 1
```
"""

rmd = '```{python ' + rmd + """}
1 + 1
```
"""

nb_md = jupytext.reads(md, 'md')
nb_rmd = jupytext.reads(rmd, 'Rmd')
compare_notebooks(nb_rmd, nb_md)

md2 = jupytext.writes(nb_rmd, 'md')
compare(md2, md)

rmd = rmd.replace('"hide"', "'hide'")
rmd2 = jupytext.writes(nb_md, 'Rmd')
compare(rmd2, rmd)


@skip_if_dict_is_not_ordered
@pytest.mark.parametrize('md,rmd', [('hide_input=true hide_output=true', 'include=FALSE'),
('hide_output=true', "results='hide'"),
Expand All @@ -23,7 +52,7 @@ def test_runtools_options_to_rmarkdown(md, rmd):
"""

nb_md = jupytext.reads(md, 'md')
nb_rmd = jupytext.reads(rmd, 'Rmd')
nb_rmd = jupytext.reads(rmd, fmt={'extension': '.Rmd', 'use_runtools': True})
compare_notebooks(nb_rmd, nb_md)

md2 = jupytext.writes(nb_rmd, 'md')
Expand Down

0 comments on commit 8caa8c7

Please sign in to comment.