Skip to content

Commit

Permalink
Support for code cell metadata in Markdown format
Browse files Browse the repository at this point in the history
  • Loading branch information
mwouts committed Mar 24, 2019
1 parent 7049426 commit ece5b22
Show file tree
Hide file tree
Showing 13 changed files with 173 additions and 142 deletions.
62 changes: 46 additions & 16 deletions jupytext/cell_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,27 +248,57 @@ def rmd_options_to_metadata(options):
return metadata.get('language') or language, metadata


def metadata_to_md_options(metadata):
"""Encode {'class':None, 'key':'value'} into 'class key="value"' """

return ' '.join(["{}={}".format(key, json.dumps(metadata[key]))
if metadata[key] is not None else key for key in metadata])


def parse_md_code_options(options):
"""Parse 'python class key="value"' into [('python', None), ('class', None), ('key', 'value')]"""

metadata = []
while options:
name_and_value = re.split(r'[\s=]+', options, maxsplit=1)
name = name_and_value[0]

# Equal sign in between name and what's next?
if len(name_and_value) == 2:
sep = options[len(name):-len(name_and_value[1])]
has_value = sep.find('=') >= 0
options = name_and_value[1]
else:
has_value = False
options = ''

if not has_value:
metadata.append((name, None))
continue

try:
value = json.loads(options)
options = ''
except json.JSONDecodeError as err:
value = json.loads(options[:(err.colno - 1)])
options = options[(err.colno - 1):]

metadata.append((name, value))

return metadata


def md_options_to_metadata(options):
"""Parse markdown options and return language and metadata (cell name)"""
language = None
name = None

options = [opt for opt in options.split(' ') if opt != '']
if len(options) >= 2:
language, name = options[:2]
elif options:
language = options[0]
"""Parse markdown options and return language and metadata"""
metadata = parse_md_code_options(options)

if language:
if metadata:
language = metadata[0][0]
for lang in _JUPYTER_LANGUAGES + ['julia', 'scheme', 'c++']:
if language.lower() == lang.lower():
if name:
return lang, {'name': name}
return lang, {}

return None, {'name': language}
return lang, dict(metadata[1:])

return None, {}
return None, dict(metadata)


def try_eval_metadata(metadata, name):
Expand Down
10 changes: 7 additions & 3 deletions jupytext/cell_to_text.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
from copy import copy
from .languages import cell_language, comment_lines
from .cell_metadata import is_active, _IGNORE_CELL_METADATA
from .cell_metadata import metadata_to_rmd_options, metadata_to_json_options, metadata_to_double_percent_options
from .cell_metadata import metadata_to_md_options, metadata_to_rmd_options
from .cell_metadata import metadata_to_json_options, metadata_to_double_percent_options
from .metadata_filter import filter_metadata
from .magics import comment_magic, escape_code_start
from .cell_reader import LightScriptCellReader
Expand Down Expand Up @@ -119,8 +120,11 @@ def code_to_text(self):
options = []
if self.cell_type == 'code' and self.language:
options.append(self.language)
if 'name' in self.metadata:
options.append(self.metadata['name'])

filtered_metadata = {key: self.metadata[key] for key in self.metadata
if key not in ['active', 'language']}
if filtered_metadata:
options.append(metadata_to_md_options(filtered_metadata))

return ['```{}'.format(' '.join(options))] + source + ['```']

Expand Down
2 changes: 1 addition & 1 deletion jupytext/compare.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ def compare_notebooks(notebook_expected,
# Expected differences
allow_filtered_cell_metadata = allow_expected_differences
allow_splitted_markdown_cells = allow_expected_differences and ext in ['.md', '.Rmd']
allow_missing_code_cell_metadata = allow_expected_differences and (ext in ['.md'] or format_name == 'sphinx')
allow_missing_code_cell_metadata = allow_expected_differences and format_name == 'sphinx'
allow_missing_markdown_cell_metadata = allow_expected_differences and (ext in ['.md', '.Rmd']
or format_name in ['sphinx', 'spin'])
allow_removed_final_blank_line = allow_expected_differences
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ jupyter:
A markdown cell
And below, the cell for function f has non trivial cell metadata. And the next cell as well.

```python
```python attributes={"classes": [], "id": "", "n": "10"}
def f(x):
return x
```

```python
```python attributes={"classes": [], "id": "", "n": "10"}
f(5)
```

Expand Down
29 changes: 29 additions & 0 deletions tests/notebooks/mirror/ipynb_to_md/Notebook_with_R_magic.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
jupyter:
kernelspec:
display_name: Python 2
language: python
name: python2
---

# 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()
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
jupyter:
kernelspec:
display_name: Python 3
language: python
name: python3
---

```python
%load_ext rpy2.ipython
import pandas as pd

df = pd.DataFrame(
{
"Letter": ["a", "a", "a", "b", "b", "b", "c", "c", "c"],
"X": [4, 3, 5, 2, 1, 7, 7, 5, 9],
"Y": [0, 4, 3, 6, 7, 10, 11, 9, 13],
"Z": [1, 2, 3, 1, 2, 3, 1, 2, 3],
}
)
```

```R magic_args="-i df"
library("ggplot2")
ggplot(data = df) + geom_point(aes(x = X, y = Y, color = Letter, size = Z))
```
112 changes: 0 additions & 112 deletions tests/notebooks/mirror/ipynb_to_md/World population.md

This file was deleted.

17 changes: 17 additions & 0 deletions tests/notebooks/mirror/ipynb_to_md/frozen_cell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
jupyter:
kernelspec:
display_name: Python 3
language: python
name: python3
---

```python
# This is an unfrozen cell. Works as usual.
print("I'm a regular cell so I run and print!")
```

```python deletable=false editable=false run_control={"frozen": true}
# This is an frozen cell
print("I'm frozen so Im not executed :(")
```
8 changes: 4 additions & 4 deletions tests/notebooks/mirror/ipynb_to_md/nteract_with_parameter.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,21 @@ jupyter:
name: python3
---

```python
```python outputHidden=false inputHidden=false tags=["parameters"]
param = 4
```

```python
```python outputHidden=false inputHidden=false
import pandas as pd
```

```python
```python outputHidden=false inputHidden=false
df = pd.DataFrame({'A': [1, 2], 'B': [3 + param, 4]},
index=pd.Index(['x0', 'x1'], name='x'))
df
```

```python
```python outputHidden=false inputHidden=false
%matplotlib inline
df.plot(kind='bar')
```
15 changes: 15 additions & 0 deletions tests/notebooks/mirror/ipynb_to_md/sample_rise_notebook_66.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
jupyter:
kernelspec:
display_name: Python 3
language: python
name: python3
---

A markdown cell

```python slideshow={"slide_type": ""}
1+1
```

Markdown cell two
3 changes: 1 addition & 2 deletions tests/test_cell_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,7 @@ def test_parse_wrong_json():

def test_parse_md_options():
assert md_options_to_metadata('python') == ('python', {})
assert md_options_to_metadata('not_a_language') == (None, {
'name': 'not_a_language'})
assert md_options_to_metadata('not_a_language') == (None, {'not_a_language': None})


def test_write_parse_json():
Expand Down
5 changes: 3 additions & 2 deletions tests/test_mirror.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ def assert_conversion_same_as_mirror(nb_file, fmt, mirror_name, compare_notebook

if ext == '.md':
for cell in notebook.cells:
cell.metadata = {}
if cell.cell_type == 'markdown':
cell.metadata = {}

compare_notebooks(notebook, nb_mirror, ext)

Expand Down Expand Up @@ -253,6 +254,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|magic)'))
@pytest.mark.parametrize('nb_file', list_notebooks('ipynb'))
def test_ipynb_to_md(nb_file):
assert_conversion_same_as_mirror(nb_file, 'md', 'ipynb_to_md')
Loading

0 comments on commit ece5b22

Please sign in to comment.