The pidgy
literate computing shell¶
https://ipython.readthedocs.io/en/stable/development/execution.html#execution-semantics
A powerful feature of the jupyter
ecosystem is a generalized implementation of the Shell & Kernel model for interactive computing in interfaces like the terminal and notebooks. That is to say that different programming languages can use the same interfaces, jupyter
supports over 100 languages now. The general ability to support different languages is possible because of configurable interfaces for the IPython.InteractiveShell
and ipykernel
.
import ipykernel.kernelapp, ipykernel.zmqshell, nbconvert, traitlets, pidgy, types, pluggy, IPython, jinja2
class pidgyShell(ipykernel.zmqshell.ZMQInteractiveShell):
The pidgy
shell is wrapper around the existing IPython
shell experience. It explicitly defines the tangle and weave conventions of literate programming to each interactive computing execution. Once the shell is configured, it can be reused as a jupyter
kernel or IPython
extension that supports the pidgy
[Markdown]/[IPython] metalanguage and metasyntax.
environment = traitlets.Any(nbconvert.exporters.TemplateExporter().environment)
pidgy
specification¶
@pidgy.specification(firstresult=True)
def tangle(str:str)->str:
The tangle
step operates on an input string that will become compiled source code. In a literate program, the source is written primarily in the documentation language and tangling converts it to the programming language. In pidgy
, the tangle steps target valid IPython
which is a superset of [Python], and requires further processing.
input_transformers_post = traitlets.List([pidgy.tangle.demojize])
pidgy
includes the ability the use emojis as valid python names through the existing traitlets
configuration system.
class pidgyManager(IPython.core.inputtransformer2.TransformerManager):
def transform_cell(self, cell):
shell = IPython.get_ipython()
return super(type(self), self).transform_cell(
(shell and hasattr(shell, 'manager') and shell.manager.hook.tangle)(str=cell))
input_transformer_manager = traitlets.Instance(pidgyManager, args=tuple())
ast_transformers = traitlets.List([pidgy.tangle.ExtraSyntax(), pidgy.testing.Definitions()])
Another feature of IPython
is the ability to intercept [Abstract Syntax Tree]s and change their representation or capture metadata. After these transformations are applied, IPython
compile the tree into a valid types.CodeType
.
@pidgy.specification
def post_execute(self):
...
@pidgy.specification
def post_run_cell(self, result):
The weave step happens after execution, the tangle step happens before. Weaving only occurs if the input is computationally verified. It allows different representations of the input to be displayed. pidgy
will implement templated Markdown displays of the input and formally test the contents of the input.
def _post_run_cell(self, result):
self.manager.hook.post_run_cell(result=result)
def _post_exec(self):
self.manager.hook.post_execute()
enable_html_pager = traitlets.Bool(True)
definitions = traitlets.List()
manager = traitlets.Instance('pluggy.PluginManager', args=('pidgy',))
loaders = traitlets.Dict()
weave = traitlets.Any()
@traitlets.default('weave')
def _default_weave(self): return pidgy.weave.Weave(self)
pidgy
mixes the standard IPython
configuration system and its own pluggy
specification and implementation.
Initializing the pidgy
shell¶
def init_pidgy(self):
Initialize pidgy
specific behaviors.
self.manager.add_hookspecs(pidgyShell)
for object in (
pidgy.tangle, self.weave, pidgy.testing
):
The tangle and weave implementations are discussed in other parts of this document. Here we register each of them as pluggy
hook implementations.
self.manager.register(object)
self.events.register("post_run_cell", types.MethodType(pidgyShell._post_run_cell, self))
self.events.register("post_execute", types.MethodType(pidgyShell._post_exec, self))
if pidgy.pidgyLoader not in self.loaders:
pidgy
enters a loader context allowing [Markdown] and notebook files to be used permissively as input.
self.loaders[pidgy.pidgyLoader] = pidgy.pidgyLoader().__enter__()
It also adds a few extra features to the shell.
self.user_ns["shell"] = self
self.user_ns.update({k: v for k, v in vars(IPython.display).items()
if pidgy.util.istype(v, IPython.core.display.DisplayObject)
})
and allows json syntax as valid python input.
pidgy.tangle.init_json()
pidgy.magic.load_ipython_extension(self)
def __init__(self, *args, **kwargs):
Override the initialization of the conventional IPython kernel to include the pidgy opinions.
super().__init__(*args, **kwargs)
self.init_pidgy()
pidgy
extension.¶
def load_ipython_extension(shell):
The pidgy kernel makes it easy to access the pidgy shell, but it can also be used an IPython extension.
shell.add_traits(manager=pidgyShell.manager, loaders=pidgyShell.loaders, definitions=pidgyShell.definitions, weave=pidgyShell.weave)
shell._post_run_cell = types.MethodType(pidgyShell._post_run_cell, shell)
shell._post_exec = types.MethodType(pidgyShell._post_exec, shell)
pidgyShell.init_pidgy(shell)
shell.input_transformer_manager = pidgyShell.input_transformer_manager.default_value
def unload_ipython_extension(self):
self.events.unregister("post_run_cell", self._post_run_cell)
self.events.unregister("post_run_cell", pidgy.weave.post_run_cell)
loader = self.loaders.pop(pidgy.pidgyLoader)
if loader is not None:
loader.__exit__(None, None, None)
load_ipython_extension = pidgyShell.load_ipython_extension
unload_ipython_extension = pidgyShell.unload_ipython_extension