Repository: mwouts/jupytext

Issue:
empty line in notebook metadata section prevent it from openning
jupytext seems remove the indent space in empty lines when syncing the metadata from ipynb to python file. And empty line in YAML, while legal, prevent jupytext from working with error message:

```
Unreadable Notebook: c.py ScannerError('while scanning a quoted scalar', <yaml.error.Mark object at 0x7f9f90243190>, 'found unexpected end of stream', <yaml.error.Mark object at 0x7f9f90535be0>)
```

reproduce step:

1. create a new python file with following content:

```python
# ---
# jupyter:
#   jupytext:
#     cell_markers: '"""
#       
#       ,
#       
#       """'
#     formats: .notebook//ipynb,py:percent
#     notebook_metadata_filter: -all,jupytext
#     text_representation:
#       extension: .py
#       format_name: percent
# ---
```
it configs the jupytext to use percent format and """\n \n""" as the markdown seperator.
note that there are **indent space in the two empty lines**

2. open the python file in jupyter notebook mode, it will open it successfully
3. press the save button to save the file, in order to trigger jupytext to save the metadata back to python file
4. you will notice that the **indent space were removed in the two empty lines by jupytext**
5. refresh the notebook, or reopen it again, there's an error
```
Unreadable Notebook: c.py ScannerError('while scanning a quoted scalar', <yaml.error.Mark object at 0x7f9f90243190>, 'found unexpected end of stream', <yaml.error.Mark object at 0x7f9f90535be0>)
```

Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/mwouts/jupytext/pull/405?src=pr&el=h1) Report
> Merging [#405](https://codecov.io/gh/mwouts/jupytext/pull/405?src=pr&el=desc) into [master](https://codecov.io/gh/mwouts/jupytext/commit/8cc0d8348f6cf989a1f4f354bda4bf2beafdd90d?src=pr&el=desc) will **decrease** coverage by `11.06%`.
> The diff coverage is `100%`.

[![Impacted file tree graph](https://codecov.io/gh/mwouts/jupytext/pull/405/graphs/tree.svg?width=650&token=a1pNjsiILi&height=150&src=pr)](https://codecov.io/gh/mwouts/jupytext/pull/405?src=pr&el=tree)

```diff
@@             Coverage Diff             @@
##           master     #405       +/-   ##
===========================================
- Coverage   99.05%   87.99%   -11.07%     
===========================================
  Files          75       75               
  Lines        7538     7378      -160     
===========================================
- Hits         7467     6492      -975     
- Misses         71      886      +815
```


| [Impacted Files](https://codecov.io/gh/mwouts/jupytext/pull/405?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [jupytext/header.py](https://codecov.io/gh/mwouts/jupytext/pull/405/diff?src=pr&el=tree#diff-anVweXRleHQvaGVhZGVyLnB5) | `99.23% <100%> (ø)` | :arrow_up: |
| [tests/test\_hide\_remove\_input\_outputs\_rmarkdown.py](https://codecov.io/gh/mwouts/jupytext/pull/405/diff?src=pr&el=tree#diff-dGVzdHMvdGVzdF9oaWRlX3JlbW92ZV9pbnB1dF9vdXRwdXRzX3JtYXJrZG93bi5weQ==) | `28.57% <0%> (-71.43%)` | :arrow_down: |
| [tests/test\_black.py](https://codecov.io/gh/mwouts/jupytext/pull/405/diff?src=pr&el=tree#diff-dGVzdHMvdGVzdF9ibGFjay5weQ==) | `31.53% <0%> (-68.47%)` | :arrow_down: |
| [tests/test\_mirror.py](https://codecov.io/gh/mwouts/jupytext/pull/405/diff?src=pr&el=tree#diff-dGVzdHMvdGVzdF9taXJyb3IucHk=) | `36.84% <0%> (-63.16%)` | :arrow_down: |
| [tests/test\_cell\_metadata.py](https://codecov.io/gh/mwouts/jupytext/pull/405/diff?src=pr&el=tree#diff-dGVzdHMvdGVzdF9jZWxsX21ldGFkYXRhLnB5) | `40.5% <0%> (-59.5%)` | :arrow_down: |
| [tests/test\_read\_simple\_pandoc.py](https://codecov.io/gh/mwouts/jupytext/pull/405/diff?src=pr&el=tree#diff-dGVzdHMvdGVzdF9yZWFkX3NpbXBsZV9wYW5kb2MucHk=) | `44.82% <0%> (-55.18%)` | :arrow_down: |
| [jupytext/pandoc.py](https://codecov.io/gh/mwouts/jupytext/pull/405/diff?src=pr&el=tree#diff-anVweXRleHQvcGFuZG9jLnB5) | `40.81% <0%> (-55.11%)` | :arrow_down: |
| [tests/test\_knitr\_spin.py](https://codecov.io/gh/mwouts/jupytext/pull/405/diff?src=pr&el=tree#diff-dGVzdHMvdGVzdF9rbml0cl9zcGluLnB5) | `45.45% <0%> (-54.55%)` | :arrow_down: |
| [tests/test\_active\_cells.py](https://codecov.io/gh/mwouts/jupytext/pull/405/diff?src=pr&el=tree#diff-dGVzdHMvdGVzdF9hY3RpdmVfY2VsbHMucHk=) | `52.54% <0%> (-47.46%)` | :arrow_down: |
| [tests/test\_contentsmanager.py](https://codecov.io/gh/mwouts/jupytext/pull/405/diff?src=pr&el=tree#diff-dGVzdHMvdGVzdF9jb250ZW50c21hbmFnZXIucHk=) | `69.18% <0%> (-30.82%)` | :arrow_down: |
| ... and [31 more](https://codecov.io/gh/mwouts/jupytext/pull/405/diff?src=pr&el=tree-more) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/mwouts/jupytext/pull/405?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/mwouts/jupytext/pull/405?src=pr&el=footer). Last update [8cc0d83...2aee348](https://codecov.io/gh/mwouts/jupytext/pull/405?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 9c10543..42f41c6 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -37,14 +37,13 @@ A pull request for which you do not need to contact us in advance is the additio
 Most of Jupytext's code is written in Python. To develop the Python part of Jupytext, you should clone Jupytext, then create a dedicated Python env:
 ```
 cd jupytext
-conda create -n jupytext-dev jupyterlab pyyaml
+conda env create --file environment.yml
 conda activate jupytext-dev
-pip install -r requirements-dev.txt
+pip install -e .
 ```
 
-Tests are executed with `pytest`. Install `pytest-xdist` to allow running the tests in parallel:
+Tests are executed with `pytest`. You can run them in parallel with for instance
 ```
-pip install pytest-xdist
 pytest -n 5
 ``` 
 
diff --git a/environment.yml b/environment.yml
new file mode 100644
index 0000000..5d3cff2
--- /dev/null
+++ b/environment.yml
@@ -0,0 +1,21 @@
+name: jupytext-dev
+channels:
+  - default
+  - conda-forge
+dependencies:
+  - python==3.7
+  - jupyter
+  - pyyaml
+  - nbconvert
+  - jupyter_contrib_nbextensions
+  - pytest
+  - pytest-xdist
+  - pylint
+  - flake8
+  - black
+  - autopep8
+  - sphinx-gallery
+  - nodejs
+  - pip
+  - setuptools
+  - pandoc
diff --git a/jupytext/header.py b/jupytext/header.py
index b477b55..c23ff72 100644
--- a/jupytext/header.py
+++ b/jupytext/header.py
@@ -173,7 +173,7 @@ def header_to_metadata_and_cell(lines, header_prefix, ext=None):
 
         if _JUPYTER_RE.match(line):
             injupyter = True
-        elif not _LEFTSPACE_RE.match(line):
+        elif line and not _LEFTSPACE_RE.match(line):
             injupyter = False
 
         if injupyter:


Test patch:
diff --git a/tests/test_header.py b/tests/test_header.py
index 762456d..9f9c86d 100644
--- a/tests/test_header.py
+++ b/tests/test_header.py
@@ -99,3 +99,24 @@ def test_notebook_from_plain_script_has_metadata_filter(script="""print('Hello w
     script2 = jupytext.writes(nb, '.py')
 
     compare(script2, script)
+
+
+def test_multiline_metadata(
+        no_jupytext_version_number,
+        notebook=new_notebook(metadata={'multiline': """A multiline string
+
+with a blank line""", 'jupytext': {'notebook_metadata_filter': 'all'}}),
+        markdown="""---
+jupyter:
+  jupytext:
+    notebook_metadata_filter: all
+  multiline: 'A multiline string
+
+
+    with a blank line'
+---
+"""):
+    actual = jupytext.writes(notebook, '.md')
+    compare(actual, markdown)
+    nb2 = jupytext.reads(markdown, '.md')
+    compare(nb2, notebook)