Skip to content

Commit

Permalink
Arguments to %%R now supported in Rmd and py formats #111
Browse files Browse the repository at this point in the history
  • Loading branch information
mwouts committed Oct 25, 2018
1 parent d110433 commit c291395
Show file tree
Hide file tree
Showing 11 changed files with 250 additions and 32 deletions.
2 changes: 1 addition & 1 deletion HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Release History

- Notebook metadata is filtered - only the most common metadata are stored in the text representation (#105)
- New config option ``additional_metadata_on_text_files`` on the content manager. Defaults to ``True``. Change its value to ``False`` to avoid creating a YAML header or cell metadata if there was none initially (#110)
- Language magic arguments are preserved (#111)
- Language magic arguments are preserved in R Markdown, and also supported in ``light`` and ``percent`` scripts (#111)
- First markdown cell exported as a docstring when using the Sphinx format (#107)

0.8.3 (2018-10-19)
Expand Down
9 changes: 4 additions & 5 deletions jupytext/cell_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,9 +236,6 @@ def rmd_options_to_metadata(options):
else:
if update_metadata_from_rmd_options(name, value, metadata):
continue
if name == 'active':
metadata[name] = value.replace('"', '').replace("'", '')
continue
try:
metadata[name] = _py_logical_values(value)
continue
Expand All @@ -251,7 +248,7 @@ def rmd_options_to_metadata(options):
if ('active' in metadata or metadata.get('run_control', {}).get('frozen') is True) and 'eval' in metadata:
del metadata['eval']

return language, metadata
return metadata.get('language') or language, metadata


def md_options_to_metadata(options):
Expand Down Expand Up @@ -282,7 +279,9 @@ def try_eval_metadata(metadata, name):
value = metadata[name]
if not isinstance(value, (str, unicode)):
return
if value.startswith('"') or value.startswith("'"):
if (value.startswith('"') and value.endswith('"')) or (value.startswith("'") and value.endswith("'")):
if name in ['active', 'magic_args', 'language']:
metadata[name] = value[1:-1]
return
if value.startswith('c(') and value.endswith(')'):
value = '[' + value[2:-1] + ']'
Expand Down
13 changes: 8 additions & 5 deletions jupytext/cell_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ class BaseCellReader(object):

cell_type = None
language = None
default_language = 'python'
default_comment_magics = None
metadata = None
content = []
Expand All @@ -95,6 +94,7 @@ class BaseCellReader(object):
def __init__(self, ext, comment_magics=None):
"""Create a cell reader with empty content"""
self.ext = ext
self.default_language = _SCRIPT_EXTENSIONS.get(ext, {}).get('language', 'python')
self.comment_magics = comment_magics if comment_magics is not None else self.default_comment_magics

def read(self, lines):
Expand All @@ -106,8 +106,7 @@ def read(self, lines):
self.metadata_and_language_from_option_line(lines[0])

if self.metadata and 'language' in self.metadata:
self.language = self.metadata['language']
del self.metadata['language']
self.language = self.metadata.pop('language')

# Parse cell till its end and set content, lines_to_next_cell
pos_next_cell = self.find_cell_content(lines)
Expand Down Expand Up @@ -202,10 +201,14 @@ def find_cell_content(self, lines):
# Cell content
source = lines[cell_start:cell_end_marker]

self.content = self.uncomment_code_and_magics(source)
if not is_active(self.ext, self.metadata) or \
('active' not in self.metadata and self.language and self.language != self.default_language):
self.content = uncomment(source, self.comment if self.ext != '.R' else '#')
else:
self.content = self.uncomment_code_and_magics(source)

# Exactly two empty lines at the end of cell (caused by PEP8)?
if (self.ext == '.py' and explicit_eoc and last_two_lines_blank(source)):
if self.ext == '.py' and explicit_eoc and last_two_lines_blank(source):
self.content = source[:-2]
self.metadata['lines_to_end_of_cell_marker'] = 2

Expand Down
28 changes: 17 additions & 11 deletions jupytext/cell_to_text.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,21 +31,28 @@ def comment_lines(lines, prefix):
class BaseCellExporter(object):
"""A class that represent a notebook cell as text"""
default_comment_magics = None
parse_cell_language = True

def __init__(self, cell, default_language, ext, comment_magics=None, cell_metadata_filter=None):
self.ext = ext
self.cell_type = cell.cell_type
self.source = cell_source(cell)
self.unfiltered_metadata = cell.metadata
self.metadata = filter_metadata(copy(cell.metadata), cell_metadata_filter, _IGNORE_CELL_METADATA)
self.language, magic_args = cell_language(self.source)
if magic_args:
if ext.endswith('.Rmd'):
if "'" in magic_args:
magic_args = '"' + magic_args + '"'
else:
magic_args = "'" + magic_args + "'"
self.metadata['magic_args'] = magic_args
self.language, magic_args = cell_language(self.source) if self.parse_cell_language else (None, None)

if self.language:
if magic_args:
if ext.endswith('.Rmd'):
if "'" in magic_args:
magic_args = '"' + magic_args + '"'
else:
magic_args = "'" + magic_args + "'"
self.metadata['magic_args'] = magic_args

if not ext.endswith('.Rmd'):
self.metadata['language'] = self.language

self.language = self.language or default_language
self.default_language = default_language
self.comment = _SCRIPT_EXTENSIONS.get(ext, {}).get('comment', '#')
Expand Down Expand Up @@ -184,10 +191,8 @@ def is_code(self):
def code_to_text(self):
"""Return the text representation of a code cell"""
active = is_active(self.ext, self.metadata)
if active and self.language != self.default_language:
if self.language != self.default_language and 'active' not in self.metadata:
active = False
self.metadata['active'] = 'ipynb'
self.metadata['language'] = self.language

source = copy(self.source)
escape_code_start(source, self.ext, self.language)
Expand Down Expand Up @@ -282,6 +287,7 @@ class DoublePercentCellExporter(BaseCellExporter):
"""A class that can represent a notebook cell as an
Hydrogen/Spyder/VScode script (#59)"""
default_comment_magics = False
parse_cell_language = False

def code_to_text(self):
"""Not used"""
Expand Down
3 changes: 0 additions & 3 deletions jupytext/languages.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,6 @@ def set_main_and_cell_language(metadata, cells, ext):
if language != main_language and language in _JUPYTER_LANGUAGES:
if 'magic_args' in cell['metadata']:
magic_args = cell['metadata'].pop('magic_args')
if (magic_args.startswith('"') and magic_args.endswith('"')) or \
(magic_args.startswith("'") and magic_args.endswith("'")):
magic_args = magic_args[1:-1]
cell['source'] = u'%%{} {}\n'.format(language, magic_args) + cell['source']
else:
cell['source'] = u'%%{}\n'.format(language) + cell['source']
Expand Down
96 changes: 96 additions & 0 deletions tests/notebooks/ipynb_py/Notebook_with_R_magic.ipynb

Large diffs are not rendered by default.

39 changes: 39 additions & 0 deletions tests/notebooks/mirror/ipynb_to_Rmd/Notebook_with_R_magic.Rmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
---
jupyter:
kernelspec:
display_name: Python 2
language: python
name: python2
language_info:
codemirror_mode:
name: ipython
version: 2
file_extension: .py
mimetype: text/x-python
name: python
nbconvert_exporter: python
pygments_lexer: ipython2
version: 2.7.11
---

# A notebook with R cells

This notebook shows the use of R cells to generate plots

```{python}
# %load_ext rpy2.ipython
```

```{r}
suppressMessages(require(tidyverse))
```

```{r}
ggplot(iris, aes(x = Sepal.Length, y = Petal.Length, color=Species)) + geom_point()
```

The default plot dimensions are not good for us, so we use the -w and -h parameters in %%R magic to set the plot size

```{r magic_args='-w 400 -h 240'}
ggplot(iris, aes(x = Sepal.Length, y = Petal.Length, color=Species)) + geom_point()
```
40 changes: 40 additions & 0 deletions tests/notebooks/mirror/ipynb_to_percent/Notebook_with_R_magic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# ---
# jupyter:
# kernelspec:
# display_name: Python 2
# language: python
# name: python2
# language_info:
# codemirror_mode:
# name: ipython
# version: 2
# file_extension: .py
# mimetype: text/x-python
# name: python
# nbconvert_exporter: python
# pygments_lexer: ipython2
# version: 2.7.11
# ---

# %% [markdown]
# # A notebook with R cells
#
# This notebook shows the use of R cells to generate plots

# %%
%load_ext rpy2.ipython

# %%
%%R
suppressMessages(require(tidyverse))

# %%
%%R
ggplot(iris, aes(x = Sepal.Length, y = Petal.Length, color=Species)) + geom_point()

# %% [markdown]
# The default plot dimensions are not good for us, so we use the -w and -h parameters in %%R magic to set the plot size

# %%
%%R -w 400 -h 240
ggplot(iris, aes(x = Sepal.Length, y = Petal.Length, color=Species)) + geom_point()
35 changes: 35 additions & 0 deletions tests/notebooks/mirror/ipynb_to_script/Notebook_with_R_magic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# ---
# jupyter:
# kernelspec:
# display_name: Python 2
# language: python
# name: python2
# language_info:
# codemirror_mode:
# name: ipython
# version: 2
# file_extension: .py
# mimetype: text/x-python
# name: python
# nbconvert_exporter: python
# pygments_lexer: ipython2
# version: 2.7.11
# ---

# # A notebook with R cells
#
# This notebook shows the use of R cells to generate plots

# %load_ext rpy2.ipython

# + {"language": "R"}
# suppressMessages(require(tidyverse))

# + {"language": "R"}
# ggplot(iris, aes(x = Sepal.Length, y = Petal.Length, color=Species)) + geom_point()
# -

# The default plot dimensions are not good for us, so we use the -w and -h parameters in %%R magic to set the plot size

# + {"magic_args": "-w 400 -h 240", "language": "R"}
# ggplot(iris, aes(x = Sepal.Length, y = Petal.Length, color=Species)) + geom_point()
13 changes: 8 additions & 5 deletions tests/test_contentsmanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -545,25 +545,28 @@ def test_metadata_filter_is_effective(nb_file, tmpdir):
cm.root_dir = str(tmpdir)

# save notebook to tmpdir
cm.save(model=dict(type='notebook', content=nb), path=tmp_ipynb)
with mock.patch('jupytext.header.INSERT_AND_CHECK_VERSION_NUMBER', True):
cm.save(model=dict(type='notebook', content=nb), path=tmp_ipynb)

# set config
cm.default_jupytext_formats = 'ipynb, py'
cm.default_notebook_metadata_filter = 'jupytext,-all'
cm.default_notebook_metadata_filter = 'language_info,jupytext,-all'
cm.default_cell_metadata_filter = '-all'

# load notebook
nb = cm.get(tmp_ipynb)['content']
with mock.patch('jupytext.header.INSERT_AND_CHECK_VERSION_NUMBER', True):
nb = cm.get(tmp_ipynb)['content']

# save notebook again
with mock.patch('jupytext.header.INSERT_AND_CHECK_VERSION_NUMBER', True):
cm.save(model=dict(type='notebook', content=nb), path=tmp_ipynb)

# read text version
nb2 = jupytext.readf(str(tmpdir.join(tmp_script)))
with mock.patch('jupytext.header.INSERT_AND_CHECK_VERSION_NUMBER', True):
nb2 = jupytext.readf(str(tmpdir.join(tmp_script)))

# test no metadata
assert set(nb2.metadata.keys()) == {'jupytext'}
assert set(nb2.metadata.keys()) <= {'language_info', 'jupytext'}
for cell in nb2.cells:
assert not cell.metadata

Expand Down
4 changes: 2 additions & 2 deletions tests/test_mirror.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ def test_percent_to_ipynb(nb_file):
assert_conversion_same_as_mirror(nb_file, '.ipynb', 'script_to_ipynb', format_name='percent')


@pytest.mark.parametrize('nb_file', list_notebooks('ipynb_py', skip='(raw|hash|frozen)'))
@pytest.mark.parametrize('nb_file', list_notebooks('ipynb_py', skip='(raw|hash|frozen|magic)'))
def test_ipynb_to_python_sphinx(nb_file):
assert_conversion_same_as_mirror(nb_file, '.py', 'ipynb_to_sphinx', format_name='sphinx')

Expand All @@ -164,6 +164,6 @@ def test_ipynb_to_Rmd(nb_file):
assert_conversion_same_as_mirror(nb_file, '.Rmd', 'ipynb_to_Rmd')


@pytest.mark.parametrize('nb_file', list_notebooks('ipynb', skip='(66|frozen)'))
@pytest.mark.parametrize('nb_file', list_notebooks('ipynb', skip='(66|frozen|magic)'))
def test_ipynb_to_md(nb_file):
assert_conversion_same_as_mirror(nb_file, '.md', 'ipynb_to_md')

0 comments on commit c291395

Please sign in to comment.