Previous implementations used python_line_transforms to transform the code. With that approach a lot of effort was required to make sure that any other transformers were executed in the proper order.
In this experiment, the markdown transformer is baked directly in the input_transform_manager
.
%reload_ext deathbeds.__Markdown_code_cells
from CommonMark import Parser
from CommonMark.render.renderer import Renderer
from textwrap import indent, dedent
from IPython.display import display, Markdown
from IPython.core.inputtransformer import InputTransformer
from importnb import Notebook
from collections import UserList
from abc import abstractmethod, ABCMeta
The commonmark renderer only catches code cells
There is an interesting discussion to be had about the role of inline cells.
class CodeRenderer(Renderer):
def code_block(self, node, entering):
while len(self.buf.splitlines()) < node.sourcepos[0][0]-1:
self.out("\n")
self.out(indent(node.literal, ' '*(
node.sourcepos[0][1])))
def __call__(self, str):
return self.render(Parser().parse(str))
def render(str):
return dedent(CodeRenderer().render(Parser().parse(str)))
class CallableTransformer(InputTransformer, UserList, metaclass=ABCMeta):
"""Define a __call__ method define a callable transformer."""
push = UserList.append
def reset(self, str = ""):
while self.data: str += self.data.pop(0) + '\n'
return self(str)
@abstractmethod
def __call__(self, str): raise NotImplemented()
Literate
Callable transformer will concatenate block code in a contigious code block. class Literate(CallableTransformer):
def __call__(self, str):
if str.split('\n', 1)[0].strip(): display(Markdown(str))
return render(str)
def load_ipython_extension(ip=None):
ip = ip or get_ipython()
if not any(isinstance(object, Literate)
for object in ip.input_transformer_manager.physical_line_transforms):
ip.input_transformer_manager.physical_line_transforms.insert(0, Literate())
def unload_ipython_extension(ip=None):
ip.input_transformer_manager.physical_line_transforms = list(
object for object in ip.input_transformer_manager.physical_line_transforms
if not isinstance(object, Literate)
)
If documents can be composed using Markdown forward syntax then they should be importable. The extension .md.ipynb
will identify markdown forward notebooks.
if __name__ == '__main__':
with MarkdownImporter(shell=False):
import Untitled1 as nb
class MarkdownImporter(Notebook):
extensions = '.md.ipynb',
def format(self, str): return super().format(render(str))
if __name__ == '__main__':
%load_ext deathbeds.__String_Node_Transformer
import deathbeds.__Markdown_code_cells
%load_ext deathbeds.__Markdown_code_cells
# with MarkdownImporter(): ...
from pathlib import Path
from pathlib import Path
import pytest
def pytest_collect_file(parent, path):
if ''.join(Path(path).suffixes) in ('.md.ipynb',):
if not parent.session.isinitpath(path):
for pat in parent.config.getini('python_files'):
if path.fnmatch(pat.rstrip('.py') + path.ext): break
else: return
return PytestModule(path, parent)
class PytestModule(pytest.Module):
def collect(self):
with MarkdownImporter(): return super().collect()
import pytest
def pytest_collect_file(parent, path):
if ''.join(Path(path).suffixes) in ('.md.ipynb',):
if not parent.session.isinitpath(path):
for pat in parent.config.getini('python_files'):
if path.fnmatch(pat.rstrip('.py') + path.ext): break
else: return
return PytestModule(path, parent)
class PytestModule(pytest.Module):
def collect(self):
with MarkdownImporter(): return super().collect()
def _import_files_with_markdown_extensions():
with MarkdownImporter(_shell=False):
import deathbeds.__Jinja2_Templating_Transformer
def _import_files_with_markdown_extensions():
with MarkdownImporter(_shell=False):
import deathbeds.__Jinja2_Templating_Transformer
- The Markdown transformer must be a physical_line_transform because they come first; as long as the class is an inputtransformer class things will work.
- This transformer should work with a template transformer.