diff --git a/CHANGELOG.md b/CHANGELOG.md index 832e40b60..9febf36d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ - `jupytext --test textfile.ext` now really compares the text file to its round trip (rather than the corresponding notebook) (#339) - Markdown cells that contain code are now preserved in a round trip through the Markdown and R Markdown formats (#361) - Code cells with a `%%python3` cell magic are now preserved in a round trip through the Markdown format (#365) +- Strings in the metadata of code cells are quoted in the Rmd representation. And we escape R code in chunk options with `#R_CODE#` (#383) 1.2.4 (2019-09-19) diff --git a/jupytext/cell_metadata.py b/jupytext/cell_metadata.py index c2463858b..1bbd3e79a 100644 --- a/jupytext/cell_metadata.py +++ b/jupytext/cell_metadata.py @@ -103,6 +103,13 @@ def metadata_to_rmd_options(language, metadata, use_runtools=False): options += ' {}={},'.format( opt_name, 'c({})'.format( ', '.join(['"{}"'.format(str(v)) for v in opt_value]))) + elif isinstance(opt_value, (str, unicode)): + if opt_value.startswith('#R_CODE#'): + options += ' {}={},'.format(opt_name, opt_value[8:]) + elif '"' not in opt_value: + options += ' {}="{}",'.format(opt_name, opt_value) + else: + options += " {}='{}',".format(opt_name, opt_value) else: options += ' {}={},'.format(opt_name, str(opt_value)) if not language: @@ -267,13 +274,12 @@ def metadata_to_md_options(metadata): def try_eval_metadata(metadata, name): - """Evaluate given metadata to a python object, if possible""" + """Evaluate the metadata to a python object, if possible""" value = metadata[name] if not isinstance(value, (str, unicode)): return if (value.startswith('"') and value.endswith('"')) or (value.startswith("'") and value.endswith("'")): - if name in ['active', 'magic_args', 'language']: - metadata[name] = value[1:-1] + metadata[name] = value[1:-1] return if value.startswith('c(') and value.endswith(')'): value = '[' + value[2:-1] + ']' @@ -282,6 +288,8 @@ def try_eval_metadata(metadata, name): try: metadata[name] = ast.literal_eval(value) except (SyntaxError, ValueError): + if name != 'name': + metadata[name] = '#R_CODE#' + value return diff --git a/jupytext/cell_to_text.py b/jupytext/cell_to_text.py index e979f11b1..0cf1e5239 100644 --- a/jupytext/cell_to_text.py +++ b/jupytext/cell_to_text.py @@ -41,9 +41,6 @@ def __init__(self, cell, default_language, fmt=None): if self.language: if magic_args: - if self.ext.endswith('.Rmd'): - quote = '"' if "'" in magic_args else "'" - magic_args = quote + magic_args + quote self.metadata['magic_args'] = magic_args if not self.ext.endswith('.Rmd'): diff --git a/tests/notebooks/mirror/ipynb_to_Rmd/Notebook with metadata and long cells.Rmd b/tests/notebooks/mirror/ipynb_to_Rmd/Notebook with metadata and long cells.Rmd index 5b89327c9..b982aab5a 100644 --- a/tests/notebooks/mirror/ipynb_to_Rmd/Notebook with metadata and long cells.Rmd +++ b/tests/notebooks/mirror/ipynb_to_Rmd/Notebook with metadata and long cells.Rmd @@ -49,6 +49,6 @@ This is a markdown cell with cell metadata `{"key": "value"}` """This is a code cell with metadata `{"tags":["parameters"], ".class":null}`""" ``` -```{python key=value, active="", eval=FALSE} +```{python key="value", active="", eval=FALSE} This is a raw cell with cell metadata `{"key": "value"}` ``` diff --git a/tests/notebooks/mirror/ipynb_to_Rmd/Notebook_with_R_magic.Rmd b/tests/notebooks/mirror/ipynb_to_Rmd/Notebook_with_R_magic.Rmd index 5edc7e8bb..fce1c338a 100644 --- a/tests/notebooks/mirror/ipynb_to_Rmd/Notebook_with_R_magic.Rmd +++ b/tests/notebooks/mirror/ipynb_to_Rmd/Notebook_with_R_magic.Rmd @@ -24,6 +24,6 @@ ggplot(iris, aes(x = Sepal.Length, y = Petal.Length, color=Species)) + geom_poin 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'} +```{r magic_args="-w 400 -h 240"} ggplot(iris, aes(x = Sepal.Length, y = Petal.Length, color=Species)) + geom_point() ``` diff --git a/tests/notebooks/mirror/ipynb_to_Rmd/Notebook_with_more_R_magic_111.Rmd b/tests/notebooks/mirror/ipynb_to_Rmd/Notebook_with_more_R_magic_111.Rmd index 5491cda84..439dd09af 100644 --- a/tests/notebooks/mirror/ipynb_to_Rmd/Notebook_with_more_R_magic_111.Rmd +++ b/tests/notebooks/mirror/ipynb_to_Rmd/Notebook_with_more_R_magic_111.Rmd @@ -20,7 +20,7 @@ df = pd.DataFrame( ) ``` -```{r magic_args='-i df'} +```{r magic_args="-i df"} library("ggplot2") ggplot(data = df) + geom_point(aes(x = X, y = Y, color = Letter, size = Z)) ``` diff --git a/tests/notebooks/mirror/ipynb_to_Rmd/The flavors of raw cells.Rmd b/tests/notebooks/mirror/ipynb_to_Rmd/The flavors of raw cells.Rmd index 767380a3d..297393774 100644 --- a/tests/notebooks/mirror/ipynb_to_Rmd/The flavors of raw cells.Rmd +++ b/tests/notebooks/mirror/ipynb_to_Rmd/The flavors of raw cells.Rmd @@ -6,23 +6,23 @@ jupyter: name: python3 --- -```{python raw_mimetype=text/latex, active="", eval=FALSE} +```{python raw_mimetype="text/latex", active="", eval=FALSE} $1+1$ ``` -```{python raw_mimetype=text/restructuredtext, active="", eval=FALSE} +```{python raw_mimetype="text/restructuredtext", active="", eval=FALSE} :math:`1+1` ``` -```{python raw_mimetype=text/html, active="", eval=FALSE} +```{python raw_mimetype="text/html", active="", eval=FALSE} Bold text ``` -```{python raw_mimetype=text/markdown, active="", eval=FALSE} +```{python raw_mimetype="text/markdown", active="", eval=FALSE} **Bold text** ``` -```{python raw_mimetype=text/x-python, active="", eval=FALSE} +```{python raw_mimetype="text/x-python", active="", eval=FALSE} 1 + 1 ``` diff --git a/tests/test_cell_metadata.py b/tests/test_cell_metadata.py index 0245eff5a..0a86ce26d 100644 --- a/tests/test_cell_metadata.py +++ b/tests/test_cell_metadata.py @@ -8,19 +8,19 @@ SAMPLES = [('r', ('R', {})), ('r plot_1, dpi=72, fig.path="fig_path/"', - ('R', {'name': 'plot_1', 'dpi': 72, 'fig.path': '"fig_path/"'})), - ("r plot_1, bool=TRUE, fig.path='fig_path/'", + ('R', {'name': 'plot_1', 'dpi': 72, 'fig.path': 'fig_path/'})), + ('r plot_1, bool=TRUE, fig.path="fig_path/"', ('R', {'name': 'plot_1', 'bool': True, - 'fig.path': "'fig_path/'"})), + 'fig.path': 'fig_path/'})), ('r echo=FALSE', ('R', {'tags': ['remove_input']})), ('r plot_1, echo=TRUE', ('R', {'name': 'plot_1', 'echo': True})), ('python echo=if a==5 then TRUE else FALSE', - ('python', {'echo': 'if a==5 then TRUE else FALSE'})), + ('python', {'echo': '#R_CODE#if a==5 then TRUE else FALSE'})), ('python noname, tags=c("a", "b", "c"), echo={sum(a+c(1,2))>1}', ('python', {'name': 'noname', 'tags': ['a', 'b', 'c'], - 'echo': '{sum(a+c(1,2))>1}'})), + 'echo': '#R_CODE#{sum(a+c(1,2))>1}'})), ('python active="ipynb,py"', ('python', {'active': 'ipynb,py'})), ('python include=FALSE, active="Rmd"', diff --git a/tests/test_read_simple_rmd.py b/tests/test_read_simple_rmd.py index 26ed0f031..2de1c925c 100644 --- a/tests/test_read_simple_rmd.py +++ b/tests/test_read_simple_rmd.py @@ -24,7 +24,7 @@ def test_read_mostly_py_rmd_file(rmd="""--- ls() ``` -```{r, results='asis', magic_args='-i x'} +```{r, results="asis", magic_args="-i x"} cat(stringi::stri_rand_lipsum(3), sep='\n\n') ``` """): @@ -49,7 +49,7 @@ def test_read_mostly_py_rmd_file(rmd="""--- 'source': '%%R\nls()', 'outputs': []}, {'cell_type': 'code', - 'metadata': {'results': "'asis'"}, + 'metadata': {'results': 'asis'}, 'execution_count': None, 'source': "%%R -i x\ncat(stringi::" "stri_rand_lipsum(3), sep='\n\n')", @@ -99,3 +99,23 @@ def test_tags_in_rmd(rmd='''--- ''', nb=new_notebook(cells=[new_code_cell('p = 1', metadata={'tags': ['parameters']})])): nb2 = jupytext.reads(rmd, 'Rmd') compare_notebooks(nb2, nb) + + +def round_trip_cell_metadata(cell_metadata): + nb = new_notebook(metadata={'jupytext': {'main_language': 'python'}}, + cells=[new_code_cell('1 + 1', metadata=cell_metadata)]) + text = jupytext.writes(nb, 'Rmd') + nb2 = jupytext.reads(text, 'Rmd') + compare_notebooks(nb2, nb) + + +def test_comma_in_metadata(cell_metadata={'a': 'b, c'}): + round_trip_cell_metadata(cell_metadata) + + +def test_dict_in_metadata(cell_metadata={'a': {'b': 'c'}}): + round_trip_cell_metadata(cell_metadata) + + +def test_list_in_metadata(cell_metadata={'d': ['e']}): + round_trip_cell_metadata(cell_metadata)