The main source for the nbd jupyter application and its notebook creation tools. The nbformat namespace was renamed to simplify the creation of new notebooks. When someone is using nbd they should expect to make one python configuration file that is executed from the command line with

    jupyter nbd --config config.py
In [1]:
__all__ = 'markdown', 'notebook', 'code', 'raw', 'output', 'reads'
In [2]:
from nbconvert.nbconvertapp import NbConvertApp
from traitlets import Any  # These should be properly assigned
import typing as t

class Docs(NbConvertApp):    
    loaders = Any(default_value=tuple(), help="""""").tag(config=True)
    post = Any(default_value=None, help="""""").tag(config=True)
    report = Any(default_value=None, help="""""").tag(config=True)
                
    def convert_notebooks(self):
        super().convert_notebooks() or self.report and [
            name and NbConvertApp.convert_single_notebook(
                    self, name, io.StringIO(writes(nb))) for name, nb in self.report()]
   
    def init_single_notebook_resources(self, notebook_filename):
        resources = super().init_single_notebook_resources(notebook_filename)
        resources['name'] = resources['unique_key'] = notebook_filename
        return resources    

main = launch_new_instance = Docs.launch_instance

Continuing on attributes are appened to the Docs class as they become necessary.

Callable Exporter

In [3]:
from nbformat import reads, io, writes, NotebookNode

def identity(path, resources: dict=None, **kw) -> t.Tuple[NotebookNode, dict]:
    """callable to export ipynb files"""
    return reads(path.read_text(), 4), resources

class FuncExporter(__import__('nbconvert').exporters.html.HTMLExporter):
    callable = Any(identity)
    def from_filename(self, file_name: str, resources: dict=None, **kw) -> t.Tuple[str, dict]:
        html, resources = self.from_notebook_node(
            *self.callable(Path(file_name), resources, **kw), **kw)
        return html, resources

Update the exporter each time a new notebook is accessed.

In [4]:
def convert_single_notebook(self, name, buffer=None):    
    path = Path(name)
    for exts, exporter in reversed([*RULES, *self.loaders]):  # There is a better way to have default loaders
        if path.suffix[1:] in exts:
            self.exporter = (
                isinstance(exporter, __import__('nbconvert').exporters.base.Exporter)
                and exporter or FuncExporter(callable=exporter))
            return super(Docs, self).convert_single_notebook(name, buffer)
    else:
        self.log.warning("{} was not converted; there is no rule for the {} suffix.".format(str(path), path.suffix))

Docs.convert_single_notebook = convert_single_notebook

The Loaders.

The default loader is notebooks only.

In [112]:
def minimal_style(callable: t.Callable[[str, dict], NotebookNode]
                 ) -> t.Callable[[str, dict], t.Tuple[NotebookNode, dict]]:
    """A minimal style wrapper other file types"""
    def _return_nb(path, resources=None, **kw):
        return notebook(cells=[
            markdown("""# [{}]({})""".format(str(path), str(path)+'.html')),
            callable(path.read_text())]), resources
    return _return_nb

Notebooks are created using objects in the nbformat package; rename these objects to have shorter namespaces.

Default loaders

In [6]:
from nbformat.v4 import (
    new_code_cell     as code, 
    new_markdown_cell as markdown, 
    new_notebook      as notebook, 
    new_raw_cell      as raw, 
    new_output        as output)

RULES = [
    (('ipynb',), identity), 
    (('py', 'pyi'), minimal_style(code)), 
    (('md', 'markdown'), minimal_style(markdown)), 
    (('txt',), minimal_style(code))]

Post Processing

The standard post processor recieves just the notebook name, this one recieves the exported text, resources, and name.

In [7]:
class CallablePostProcessor(__import__('nbconvert').postprocessors.PostProcessorBase):
    """Call an arbitrary function after it has been writen to disk."""
    callable = Any().tag(config=True)    
    def postprocess(self, result: t.Tuple[str, dict, str]) -> None:
        html, resources, notebook_name = result
        self.callable(html, resources, notebook_name)
In [8]:
def init_postprocessor(self):
    if self.post: self.postprocessor = CallablePostProcessor(callable=self.post)
    else:  super(Docs, self).init_postprocessor()

Docs.init_postprocessor = init_postprocessor

Writing

The post processor above requires a patched files writer.

In [9]:
class FilesWriter(__import__('nbconvert').writers.FilesWriter):
    """Use the same name so configuration works the same.  This patch assures the parent directory exists and returns the output, resources, and name.
    """
    def write(self, output: str, resources: dict, notebook_name: str) -> t.Tuple[str, dict, str]:
        """Returns a tuple of the output html, resources, and file destination. These
        values are available to the post processor."""
        (lambda path: path.parent.exists() or path.parent.mkdir())(
            self.build_directory / Path(notebook_name))
        return output, resources, super().write(output, resources, notebook_name)

Change the default directory path to docs to be consistent with github pages.

In [10]:
FilesWriter.build_directory.default_value = 'docs'
Docs.writer_class.default_value = 'nbd.FilesWriter'

A Most Basic Index that lists the notebooks.

In [111]:
def index(data):
    def _index(html, resources, destination):
        """"""
        data.cells.append(markdown("""# [{0}]({1})\n\n---\n\n""".format(
            resources.get('name'),resources.get('name') + destination.split(resources.get('name'), 1)[-1])))
    return _index

If imports are needed in scope then don't import them.

In [ ]:
from pathlib2 import Path