Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Translate R Markdown visibility options #366

Merged
merged 6 commits into from
Oct 29, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Release History
- The light format uses ``# + [markdown]`` rather than the previous ``cell_type`` metadata to identify markdown cells with metadata (#356)
- Explicit Markdown cells in the light format ``# + [markdown]`` can use triple quotes (#356)
- Cell metadata can be encoded as either key=value (the new default) or in JSON. An automatic option ``cell_metadata_json`` should help minimize the impact on existing files (#344)
- R Markdown hidden inputs, outputs, or cells are now mapped to the corresponding Jupyter Book tags by default (#337)
- The Jupyter Notebook extension for Jupytext is compatible with Jupyter Notebook 6.0 (#346)
- ``jupytext notebook.py --to ipynb`` updates the timestamp of ``notebook.py`` so that the paired notebook still works in Jupyter (#335, #254)
- Added support for Rust/Evxcr, by Jonas Bushart (#351)
Expand Down
83 changes: 46 additions & 37 deletions jupytext/cell_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,23 @@
except NameError:
unicode = str # Python 3

# Map R Markdown's "echo" and "include" to "hide_input" and "hide_output", that are understood by the `runtools`
# extension for Jupyter notebook, and by nbconvert (use the `hide_input_output.tpl` template).
# Map R Markdown's "echo", "results" and "include" to "hide_input" and "hide_output", that are understood by the
# `runtools` extension for Jupyter notebook, and by nbconvert (use the `hide_input_output.tpl` template).
# See http://jupyter-contrib-nbextensions.readthedocs.io/en/latest/nbextensions/runtools/readme.html
_BOOLEAN_OPTIONS_DICTIONARY = [('hide_input', 'echo', True),
('hide_output', 'include', True)]
_RMARKDOWN_TO_RUNTOOLS_OPTION_MAP = [
(('include', 'FALSE'), [('hide_input', True), ('hide_output', True)]),
(('echo', 'FALSE'), [('hide_input', True)]),
(('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
'skipline', 'noskipline',
Expand Down Expand Up @@ -60,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 jupyter_option, rmd_option, rev in _BOOLEAN_OPTIONS_DICTIONARY:
if jupyter_option in metadata:
options += ' {}={},'.format(
rmd_option, _r_logical_values(metadata[jupyter_option] != rev))
del metadata[jupyter_option]
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 @@ -95,21 +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 jupyter_option, rmd_option, rev in _BOOLEAN_OPTIONS_DICTIONARY:
if name == rmd_option:
try:
metadata[jupyter_option] = _py_logical_values(value) != rev
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
except RLogicalValueError:
pass
return False


Expand Down Expand Up @@ -210,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 @@ -233,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
2 changes: 1 addition & 1 deletion tests/notebooks/mirror/Rmd_to_ipynb/R_sample.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,4 @@
},
"nbformat": 4,
"nbformat_minor": 2
}
}
16 changes: 10 additions & 6 deletions tests/notebooks/mirror/Rmd_to_ipynb/chunk_options.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@
"cell_type": "code",
"execution_count": null,
"metadata": {
"hide_output": true,
"lines_to_next_cell": 2,
"name": "knitr_setup"
"name": "knitr_setup",
"tags": [
"remove_cell"
]
},
"outputs": [],
"source": [
Expand All @@ -29,7 +31,7 @@
"cell_type": "code",
"execution_count": null,
"metadata": {
"hide_input": false
"echo": true
},
"outputs": [],
"source": [
Expand All @@ -43,9 +45,11 @@
"metadata": {
"fig.height": 5,
"fig.width": 8,
"hide_input": true,
"lines_to_next_cell": 0,
"name": "bar_plot"
"name": "bar_plot",
"tags": [
"remove_input"
]
},
"outputs": [],
"source": [
Expand All @@ -67,4 +71,4 @@
},
"nbformat": 4,
"nbformat_minor": 2
}
}
12 changes: 7 additions & 5 deletions tests/notebooks/mirror/Rmd_to_ipynb/ioslides.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
"cell_type": "code",
"execution_count": null,
"metadata": {
"hide_input": false
"echo": true
},
"outputs": [],
"source": [
Expand All @@ -70,8 +70,10 @@
"metadata": {
"fig.height": 5,
"fig.width": 8,
"hide_input": true,
"lines_to_next_cell": 0
"lines_to_next_cell": 0,
"tags": [
"remove_input"
]
},
"outputs": [],
"source": [
Expand All @@ -88,11 +90,11 @@
],
"metadata": {
"jupytext": {
"cell_metadata_filter": "fig.width,hide_input,fig.height,-all",
"cell_metadata_filter": "echo,tags,fig.width,fig.height,-all",
"main_language": "python",
"notebook_metadata_filter": "-all"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
}
10 changes: 6 additions & 4 deletions tests/notebooks/mirror/Rmd_to_ipynb/knitr-spin.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@
"cell_type": "code",
"execution_count": null,
"metadata": {
"hide_output": true,
"name": "setup"
"name": "setup",
"tags": [
"remove_cell"
]
},
"outputs": [],
"source": [
Expand Down Expand Up @@ -173,11 +175,11 @@
],
"metadata": {
"jupytext": {
"cell_metadata_filter": "name,fig.width,cache,hide_output,fig.height,-all",
"cell_metadata_filter": "tags,name,fig.width,cache,fig.height,-all",
"main_language": "R",
"notebook_metadata_filter": "-all"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
}
2 changes: 1 addition & 1 deletion tests/notebooks/mirror/Rmd_to_ipynb/markdown.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,4 @@
},
"nbformat": 4,
"nbformat_minor": 2
}
}
5 changes: 3 additions & 2 deletions tests/notebooks/mirror/script_to_ipynb/knitr-spin.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"cell_type": "code",
"execution_count": null,
"metadata": {
"hide_input": true,
"hide_output": true,
"name": "setup"
},
Expand Down Expand Up @@ -173,11 +174,11 @@
],
"metadata": {
"jupytext": {
"cell_metadata_filter": "name,fig.width,cache,hide_output,fig.height,-all",
"cell_metadata_filter": "name,hide_input,fig.width,fig.height,cache,hide_output,-all",
"main_language": "R",
"notebook_metadata_filter": "-all"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
}
7 changes: 3 additions & 4 deletions tests/test_active_cells.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,21 +233,20 @@ def test_active_rmd(ext, no_jupytext_version_number):
compare(nb.cells[0], ACTIVE_RMD['.ipynb'])


ACTIVE_NOT_INCLUDE_RMD = {'.py': """# + 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_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_output': True}}}
'metadata': {'active': 'Rmd', 'tags': ['remove_cell']}}}


@skip_if_dict_is_not_ordered
Expand Down
Loading