Skip to content

Commit

Permalink
Merge pull request #15 from mwouts/v0.3.0
Browse files Browse the repository at this point in the history
V0.3.0
  • Loading branch information
mwouts authored Jul 17, 2018
2 parents de57bfc + 9780779 commit 1808cbd
Show file tree
Hide file tree
Showing 30 changed files with 1,387 additions and 696 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ python:
# command to install dependencies
install:
- pip install codecov
- pip install pytest pytest-cov
- pip install pytest pytest-cov testfixtures
- pip install notebook
- pip install -r requirements.txt
- pip install .
Expand Down
8 changes: 8 additions & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@ Release History
dev
+++


0.3.0 (2018-07-17)
+++++++++++++++++++

**Improvements**

- Introducing support for notebooks as python `.py` or R scripts `.R`

0.2.6 (2018-07-13)
+++++++++++++++++++

Expand Down
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,12 @@ The format actually supports [many languages](https://yihui
.name/knitr/demo/engines/).

R markdown is almost like plain markdown. There are only two differences:
- R markdown has a specific syntax for active code cells, that start with
```
```{python}
```
These active cells may optionally contain cell options.
- R markdown has a specific syntax for active code cells: language, and
optional cell options are enclosed into a pair of curly brackets:

```
```{python}

- a YAML header, that describes the notebook title, author, and desired
output (HTML, slides, PDF...).

Expand Down
4 changes: 2 additions & 2 deletions nbrmd/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@
R Markdown notebooks.
"""

from .nbrmd import read, reads, readf, write, writes, writef
from .nbrmd import readf, writef, writes, reads, notebook_extensions, readme

try:
from .rmarkdownexporter import RMarkdownExporter
except ImportError as e:
RMarkdownExporter = str(e)

try:
from .cm import RmdFileContentsManager
from .contentsmanager import RmdFileContentsManager
except ImportError as e:
RmdFileContentsManager = str(e)
21 changes: 17 additions & 4 deletions nbrmd/chunk_options.py → nbrmd/cell_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"""

import ast
import json

_boolean_options_dictionary = [('hide_input', 'echo', True),
('hide_output', 'include', True)]
Expand Down Expand Up @@ -36,7 +37,7 @@ def _py_logical_values(rbool):
raise RLogicalValueError


def to_chunk_options(language, metadata):
def metadata_to_rmd_options(language, metadata):
options = language.lower()
if 'name' in metadata:
options += ' ' + metadata['name'] + ','
Expand All @@ -60,7 +61,7 @@ def to_chunk_options(language, metadata):
', '.join(['"{}"'.format(str(v)) for v in co_value])))
else:
options += ' {}={},'.format(co_name, str(co_value))
return options.strip(',')
return options.strip(',').strip()


def update_metadata_using_dictionary(name, value, metadata):
Expand Down Expand Up @@ -141,7 +142,7 @@ def parse_rmd_options(line):
if len(result) and name is '':
raise RMarkdownOptionParsingError(
'Option line "{}" has no name for '
'option value {}' .format(line, value))
'option value {}'.format(line, value))
result.append((name.strip(), value.strip()))
name = ''
value = ''
Expand All @@ -166,7 +167,7 @@ def parse_rmd_options(line):
return result


def to_metadata(options):
def rmd_options_to_metadata(options):
options = options.split(' ', 1)
if len(options) == 1:
language = options[0]
Expand Down Expand Up @@ -207,3 +208,15 @@ def to_metadata(options):
continue

return language, metadata


def json_options_to_metadata(options):
try:
return json.loads(options)
except ValueError:
return {}


def metadata_to_json_options(metadata):
return json.dumps({k: metadata[k] for k in metadata
if k not in _ignore_metadata})
198 changes: 198 additions & 0 deletions nbrmd/cells.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
from .languages import cell_language
from .cell_metadata import metadata_to_rmd_options, rmd_options_to_metadata, \
json_options_to_metadata, metadata_to_json_options
from nbformat.v4.nbbase import new_code_cell, new_markdown_cell
import re


def code_to_rmd(source, metadata, default_language):
lines = []
language = cell_language(source) or default_language
options = metadata_to_rmd_options(language, metadata)
lines.append(u'```{{{}}}'.format(options))
lines.extend(source)
lines.append(u'```')
return lines


def cell_to_text(self,
cell,
next_cell=None,
default_language='python'):
source = cell.get('source').splitlines()
metadata = cell.get('metadata', {})
skipline = True
if 'noskipline' in metadata:
skipline = not metadata['noskipline']
del metadata['noskipline']

lines = []
if cell.cell_type == 'code':
if self.ext == '.Rmd':
lines.extend(code_to_rmd(source, metadata, default_language))
else:
language = cell_language(source) or default_language
if language == default_language:
if self.ext == '.R':
options = metadata_to_rmd_options(language, metadata)[2:]
if options != '':
lines.append('#+ ' + options)
else:
options = metadata_to_json_options(metadata)
if options != '{}':
lines.append('#+ ' + options)
lines.extend(source)
else:
lines.extend(self.markdown_escape(
code_to_rmd(source, metadata, default_language)))

# Two blank lines before next code cell
if next_cell and next_cell.cell_type == 'code':
lines.append('')
else:
if source == []:
source = ['']
lines.extend(self.markdown_escape(source))

# Two blank lines between consecutive markdown cells
if self.ext == '.Rmd' and next_cell \
and next_cell.cell_type == 'markdown':
lines.append('')

if skipline and next_cell:
lines.append('')

return lines


_start_code_rmd = re.compile(r"^```\{(.*)\}\s*$")
_start_code_md = re.compile(r"^```(.*)$")
_end_code_md = re.compile(r"^```\s*$")
_option_code_rpy = re.compile(r"^#\+(.*)$")
_blank = re.compile(r"^\s*$")


def start_code_rmd(line):
return _start_code_rmd.match(line)


def start_code_rpy(line):
return _option_code_rpy.match(line)


def text_to_cell(self, lines):
if self.start_code(lines[0]):
return self.code_to_cell(lines, parse_opt=True)
elif self.prefix != '' and not lines[0].startswith(self.prefix):
return self.code_to_cell(lines, parse_opt=False)
else:
return self.markdown_to_cell(lines)


def parse_code_options(line, ext):
if ext == '.Rmd':
return rmd_options_to_metadata(_start_code_rmd.findall(line)[0])
elif ext == '.R':
return rmd_options_to_metadata(_option_code_rpy.findall(line)[0])
else: # .py
return 'python', json_options_to_metadata(_option_code_rpy.findall(
line)[0])


def code_to_cell(self, lines, parse_opt):
# Parse options
if parse_opt:
language, metadata = parse_code_options(lines[0], self.ext)
if self.ext == '.Rmd':
metadata['language'] = language
else:
metadata = {}

# Find end of cell and return
if self.ext == '.Rmd':
for pos, line in enumerate(lines):
if pos > 0 and _end_code_md.match(line):
next_line_blank = pos + 1 == len(lines) or \
_blank.match(lines[pos + 1])
if next_line_blank and pos + 2 != len(lines):
return new_code_cell(
source='\n'.join(lines[1:pos]), metadata=metadata), \
pos + 2
else:
r = new_code_cell(
source='\n'.join(lines[1:pos]),
metadata=metadata)
r.metadata['noskipline'] = True
return r, pos + 1
else:
prev_blank = False
for pos, line in enumerate(lines):
if parse_opt and pos == 0:
continue

if self.prefix != '' and line.startswith(self.prefix):
if prev_blank:
return new_code_cell(
source='\n'.join(lines[parse_opt:(pos - 1)]),
metadata=metadata), pos
else:
r = new_code_cell(
source='\n'.join(lines[parse_opt:pos]),
metadata=metadata)
r.metadata['noskipline'] = True
return r, pos

if _blank.match(line):
if prev_blank:
# Two blank lines at the end == empty code cell
return new_code_cell(
source='\n'.join(lines[parse_opt:(pos - 1)]),
metadata=metadata), min(pos + 1, len(lines) - 1)
prev_blank = True
else:
prev_blank = False

# Unterminated cell?
return new_code_cell(
source='\n'.join(lines[parse_opt:]),
metadata=metadata), len(lines)


def markdown_to_cell(self, lines):
md = []
for pos, line in enumerate(lines):
# Markdown stops with the end of comments
if line.startswith(self.prefix):
md.append(self.markdown_unescape(line))
elif _blank.match(line):
return new_markdown_cell(source='\n'.join(md)), pos + 1
else:
r = new_markdown_cell(source='\n'.join(md))
r.metadata['noskipline'] = True
return r, pos

# still here => unterminated markdown
return new_markdown_cell(source='\n'.join(md)), len(lines)


def markdown_to_cell_rmd(lines):
prev_blank = False
for pos, line in enumerate(lines):
if start_code_rmd(line):
if prev_blank and pos > 1:
return new_markdown_cell(
source='\n'.join(lines[:(pos - 1)])), pos
else:

r = new_markdown_cell(
source='\n'.join(lines[:pos]))
r.metadata['noskipline'] = True
return r, pos

if _blank.match(line) and prev_blank:
return new_markdown_cell(
source='\n'.join(lines[:(pos - 1)])), pos + 1
prev_blank = _blank.match(line)

# Unterminated cell?
return new_markdown_cell(source='\n'.join(lines)), len(lines)
Loading

0 comments on commit 1808cbd

Please sign in to comment.