Skip to content

Commit

Permalink
implementing feedback from @rossant
Browse files Browse the repository at this point in the history
  • Loading branch information
bollwyvl committed Jul 13, 2015
1 parent 76586d9 commit 2869ac2
Show file tree
Hide file tree
Showing 9 changed files with 84 additions and 67 deletions.
43 changes: 28 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
---
celltoolbar: Slideshow
---

---
slideshow:
slide_type: slide
...

[![Build Status](https://travis-ci.org/rossant/ipymd.svg?branch=travis)](https://travis-ci.org/rossant/ipymd)
[![Coverage Status](https://coveralls.io/repos/rossant/ipymd/badge.svg)](https://coveralls.io/r/rossant/ipymd)

Expand Down Expand Up @@ -45,7 +54,7 @@ with:

The JSON `.ipynb` are removed from the equation, and the conversion happens on the fly. The IPython Notebook becomes an interactive Markdown text editor!

A drawback is that you prompt numbers and images (for now).
A drawback is that you lose prompt numbers and images (for now).

This is useful when you write technical documents, blog posts, books, etc.

Expand Down Expand Up @@ -214,13 +223,11 @@ You can convert from any supported format to any supported format. This works by

An **ipymd cell** is a Python dictionary with the following fields:

* `cell_type`: `markdown` or `code`
* `cell_type`: `markdown`, `code` or `notebok_metadata` (if implemented)
* `input`: a string with the code input (code cell only)
* `output`: a string with the text output and stdout (code cell only)
* `source`: a string containing Markdown markup (markdown cell only)
* `metadata`: a dictionary containing cell (or notebook) metadata
* `is_notebook`: a boolean telling whether this cell's metadata is the notebook
metadata

### Customize the Markdown format

Expand All @@ -241,7 +248,8 @@ You can also implement your own format by following these instructions:

* To activate this format, call this at Notebook launch time (not in a kernel!), perhaps in your `ipython_notebook_config.py`:

```python
```python
from ipymd import format_manager
format_manager().register(
name='my_format',
Expand All @@ -250,26 +258,28 @@ You can also implement your own format by following these instructions:
file_extension='.md', # or anything else
file_type='text', # or JSON
)
```
```

* Now you can convert contents: `ipymd.convert(contents, from_='notebook', to='my_format')` or any other combination.

### Contributing a new ipymd format
* To further integrate your format in ipymd, create a `ipymd/formats/my_format.py` file.
* Put your reader and writer class in there, as well as a top-level variable:

```python
```python
MY_FORMAT = dict(
reader=MyFormatReader,
writer=MyFormatWriter,
file_extension='.md',
file_type='text',
)
```
```

* In `setup.py`, add this to `entry_points`:

```python
```python
...
entry_points={
'ipymd.format': [
Expand All @@ -278,10 +288,10 @@ You can also implement your own format by following these instructions:
...
]
}
```
```

> Note that the `entry_point` name will be used by default. you may override
it, if you like, but Don't Repeat Yourself.
it, if you like, but Don't Repeat Yourself.
* Add some unit tests in `ipymd/formats/tests`.
* Propose a PR!
Expand All @@ -293,25 +303,28 @@ Look at the existing format implementations for more details.
* If you want to be able to redistribute your format without adding it to ipymd proper (i.e. in-house or experimental), implement all your code in a real python module.
* Someplace easy to import, e.g. `myformat.py` or `myformat/__init__.py`, add:
```python
```python
MY_FORMAT = dict(
reader=MyFormatReader,
writer=MyFormatWriter,
file_extension='.md', # or anything else
file_type='text', # or JSON
)
```
```
and this to your `setup.py`:
```python
```python
...
entry_points={
'ipymd.format': [
'my_format=myformat:MY_FORMAT',
],
},
...
```
```
* Publish on pypi!
* Your users will now be able to `pip install myformat`, then configure their Notebook to use your format with the name `my_format`.
6 changes: 3 additions & 3 deletions examples/ex4.notebook.ipynb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"nbformat": 4,
"cells": [
{
"cell_type": "markdown",
Expand Down Expand Up @@ -66,9 +67,8 @@
}
}
],
"nbformat_minor": 0,
"nbformat": 4,
"metadata": {
"title": "A Slideshow"
}
},
"nbformat_minor": 0
}
2 changes: 1 addition & 1 deletion examples/ex4.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# List of ipymd cells expected for this example.
output = [

{'is_notebook': True,
{'cell_type': 'notebook_metadata',
'metadata': {'title': 'A Slideshow'}},

{'cell_type': 'markdown',
Expand Down
10 changes: 5 additions & 5 deletions ipymd/core/contents_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,13 @@ def _file_extension(os_path):
class IPymdContentsManager(FileContentsManager, Configurable):
format = Unicode('markdown', config=True)

# The name of the default kernel: if left blank, assume native (pythonX)
# won't store kernelspec/language_info unless forced
# this will be passed to the FormatManager, overwriting any config there
# The name of the default kernel: if left blank, assume native (pythonX),
# won't store kernelspec/language_info unless forced with verbose_metadata.
# This will be passed to the FormatManager, overwriting any config there.
default_kernel_name = Unicode(config=True)

# don't strip any metadata
# this will be passed to the FormatManager, overwriting any config there
# Don't strip any metadata.
# This will be passed to the FormatManager, overwriting any config there.
verbose_metadata = Bool(False, config=True)

def __init__(self, *args, **kwargs):
Expand Down
28 changes: 18 additions & 10 deletions ipymd/core/format_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,16 @@ class FormatManager(LoggingConfigurable):
# The name of the setup_tools entry point group to use in setup.py
entry_point_group = "ipymd.format"

# The name of the default kernel: if left blank, assume native (pythonX)
# The name of the default kernel: if left blank, assume native (pythonX),
# won't store kernelspec/language_info unless forced
# TODO: where does this get set but by the ContentsManager?
default_kernel_name = Unicode(config=True)

# don't strip any metadata
# Don't strip any metadata
# TODO: where does this get set but by the ContentsManager?
verbose_metadata = Bool(False, config=True)

# the singleton. there can be only one.
# The singleton. There can be only one.
_instance = None

def __init__(self, *args, **kwargs):
Expand Down Expand Up @@ -254,16 +254,24 @@ def convert(self,
# a list of ipymd cells.
cells = contents

notebook_metadata = [cell for cell in cells
if cell["cell_type"] == "notebook_metadata"]

if writer is not None:
if notebook_metadata:
notebook_metadata = notebook_metadata[0]["metadata"]
if hasattr(writer, "write_notebook_metadata"):
writer.write_notebook_metadata(notebook_metadata)
else:
print("{} does not support notebook metadata, "
"dropping metadata: {}".format(
writer,
notebook_metadata))

# Convert from ipymd cells to the target format.
supports_nb_metadata = hasattr(writer, "write_notebook_metadata")
for cell in cells:
writer.write(cell)

for i, cell in enumerate(cells):
if cell.get("is_notebook", None) and supports_nb_metadata:
writer.write_notebook_metadata(
self.clean_meta(cell["metadata"]))
else:
writer.write(cell)
return writer.contents
else:
# If no writer is specified, the output is supposed to be
Expand Down
45 changes: 20 additions & 25 deletions ipymd/formats/markdown.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,6 @@
from ..core.prompt import create_prompt


CLOSE_NOTEBOOK_METADATA = "---"
CLOSE_CELL_METADATA = "..."


#------------------------------------------------------------------------------
# Base Markdown
Expand Down Expand Up @@ -70,9 +67,9 @@ def _meta(self, source, is_notebook=False):
"""Turn a YAML string into ipynb cell/notebook metadata
"""
if is_notebook:
return {'is_notebook': True,
return {'cell_type': 'notebook_metadata',
'metadata': source}
return {'is_meta': True,
return {'cell_type': 'cell_metadata',
'metadata': source}

def _markdown_cell_from_regex(self, m):
Expand All @@ -96,17 +93,17 @@ def _meta_from_regex(self, m):
Both must be followed by at least one blank line (\n\n).
"""
body = m.group("body")
is_notebook = m.group("sep_close") == CLOSE_NOTEBOOK_METADATA
body = m.group('body')
is_notebook = m.group('sep_close') == '---'

if is_notebook:
# make it into a valid YAML object
body = body.strip()[:-3] + CLOSE_CELL_METADATA
# make it into a valid YAML object by stripping ---
body = body.strip()[:-3] + '...'
try:
if body:
return self._meta(yaml.safe_load(m.group("body")), is_notebook)
return self._meta(yaml.safe_load(m.group('body')), is_notebook)
else:
return self._meta({"ipymd": {"empty_meta": True}}, is_notebook)
return self._meta({'ipymd': {'empty_meta': True}}, is_notebook)
except Exception as err:
raise Exception(body, err)

Expand All @@ -124,7 +121,7 @@ def meta(self, source, is_notebook=False):
if source is None:
return ''

if source.get("ipymd", {}).get("empty_meta", None):
if source.get('ipymd', {}).get('empty_meta', None):
return '---\n\n'

if not source:
Expand All @@ -138,7 +135,8 @@ def meta(self, source, is_notebook=False):
default_flow_style=False))

if is_notebook:
meta = meta[:-5] + "---\n\n"
# Replace the trailing `...\n\n`
meta = meta[:-5] + '---\n\n'

return meta

Expand Down Expand Up @@ -181,23 +179,20 @@ class MarkdownReader(BaseMarkdownReader):
def __init__(self, prompt=None):
super(MarkdownReader, self).__init__()
self._prompt = create_prompt(prompt)
self._notebook_metadata = {}

def read(self, text, rules=None):
cells_and_meta = super(MarkdownReader, self).read(text, rules)
raw_cells = super(MarkdownReader, self).read(text, rules)
cells = []

for i, cell_or_meta in enumerate(cells_and_meta):
if cell_or_meta.get("is_notebook", None):
if cells:
raise ValueError("Notebook metadata must appear first")
cells.append(cell_or_meta)
elif (
cell_or_meta.get("is_meta", None) and cell_or_meta["metadata"]
):
cells_and_meta[i + 1].update(
metadata=cell_or_meta["metadata"])
last_index = len(raw_cells) - 1

for i, cell in enumerate(raw_cells):
if cell['cell_type'] == 'cell_metadata':
if i + 1 <= last_index:
raw_cells[i + 1].update(metadata=cell['metadata'])
else:
cells.append(cell_or_meta)
cells.append(cell)

return cells

Expand Down
13 changes: 9 additions & 4 deletions ipymd/formats/notebook.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,14 @@ class NotebookReader(object):

ignore_meta = ["collapsed", "trusted"]

def __init__(self):
self._notebook_metadata = {}

def read(self, nb):
assert nb['nbformat'] >= 4
yield {
"is_notebook": True,
"metadata": nb["metadata"]
}

self._notebook_metadata = nb['metadata']

for cell in nb['cells']:
ipymd_cell = {}
metadata = self.clean_meta(cell)
Expand All @@ -88,6 +90,9 @@ def read(self, nb):
continue
yield ipymd_cell

def read_notebook_metadata(self):
return self._notebook_metadata

def clean_meta(self, cell):
metadata = cell.get('metadata', {})
for key in self.ignore_meta:
Expand Down
3 changes: 0 additions & 3 deletions ipymd/formats/tests/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,6 @@ def _test_reader(basename, format, ignore_notebook_meta=True):
"""Return converted and expected ipymd cells of a given example."""
contents = _read_test_file(basename, format)
converted = convert(contents, from_=format)
if ignore_notebook_meta:
converted = [cell for cell in converted
if not cell.get("is_notebook", None)]
expected = _exec_test_file(basename)
return converted, expected

Expand Down
1 change: 0 additions & 1 deletion ipymd/lib/markdown.py
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,6 @@ def parse_text(self, m):
self.renderer.text(text)

def parse_meta(self, m):
# raise Exception(m.groupdict())
if not m.group("alias") and not m.group("body"):
self.renderer.text("META SPLIT")
elif m.group("alias") and not m.group("body"):
Expand Down

0 comments on commit 2869ac2

Please sign in to comment.