{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Strings are savage, let's create custom outputs for them.\n", "\n", "For a long time we have been encoding rich displays into strings. Our past approaches in [2018-06-19-String-Node-Transformer.ipynb](2018-06-19-String-Node-Transformer.ipynb) are incorrect. The code of the smart strings is create better distances, this should not require any changes to the input.\n", "\n", "This notebook explores the `get_ipython().display_formatter.mimebundle_formatter` to create representations \n", "for certains strings like `\"graphviz\"`, `\"yaml\"`, `\"iframes\"`, or `\"images\"`.\n", "\n", "### [_Perlisisms_ - Epigrams in programming](http://www.cs.yale.edu/homes/perlis-alan/quotes.html) - _Strings_\n", "\n", "* __34.__ The string is a stark data structure and everywhere it is passed there is much duplication of process. It is a perfect vehicle for hiding information.\n", "* __106.__ It's difficult to extract sense from strings, but they're the only communication coin we can count on.\n", "\n", "> These epigrams suggest it is okay to do silly things with strings. We'll use them for good though.\n", "\n", "\n", "### Related posts\n", "\n", "Effectively any idea that creates an input transformer to modify a display is wrong.\n", "\n", "* [2018-08-13-Flexbox-Transformer.ipynb](2018-08-13-Flexbox-Transformer.ipynb)\n", "* [2018-08-16-HTML-Flexbox.ipynb](2018-08-16-HTML-Flexbox.ipynb)" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ " ip=get_ipython()\n", " from graphviz import Source\n", " import IPython; from IPython.display import *\n", " from mimetypes import guess_type; guess = lambda x: guess_type(x)[0]\n", " from abc import ABCMeta\n", " from IPython.utils.capture import capture_output as capture\n", " from collections import UserList\n", " import base64\n", " import vdom; from vdom import div, img; from vdom.svg import iframe\n", " __all__ = 'Row', 'Column'" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ " from pathlib import Path\n", " from toolz.curried import excepts" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`Caller` will `iter`ate over the `callable`s, the `iter`ation stops when a `not None` value is returned. " ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "class Caller(ABCMeta):\n", " callable = set()\n", " def __call__(self, object: str, result=None):\n", " for callable in self.callable:\n", " value = callable(object)\n", " if value is None: continue\n", " return value" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`StringConditions` is a `callable` object that `IPython` will use to te$t string conditions to customize their `display`s." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "class StringConditions(metaclass=Caller): ..." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Formatters" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ " ip = get_ipython()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`ip.display_formatter` contains the rules for printing rich displays in `IPython`. Individual display rules may be set on `ip.display_formatter.formatters`.\n", "In this notebook, we are going to use `ip.display_formatter.mimebundle_formatter` to compose the display data ourselves." ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ " def load_ipython_extension(ip): ip.display_formatter.mimebundle_formatter.for_type(str, StringConditions)\n", " __name__ == '__main__' and load_ipython_extension(get_ipython())" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [], "source": [ " def unload_ipython_extension(ip): ip.display_formatter.mimebundle_formatter.type_printers.pop(str)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "[`ip.display_formatter.mimebundle_formatter.for_type`](https://ipython.readthedocs.io/en/stable/config/integrating.html#formatters-for-third-party-types) provides the machinery to customize output display payloads." ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [], "source": [ " def isgraphviz(str): \n", " if str.lstrip('di').startswith('graph '): return {\n", " 'text/html': __import__('graphviz').Source(str)._repr_svg_()\n", " }, {}\n", " StringConditions.callable.add(isgraphviz)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`isembed` takes a `str\\`ing that `.startswith` `\"http\"` & shows it as an `IFrame`." ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [], "source": [ " def isembed(str): \n", " type = guess(str) or ''\n", " if excepts(OSError, Path(str).is_file)() or str.startswith('http'): \n", "\n", " if type.startswith('image') and not type.endswith('svg'):\n", " return {'text/html': img(src=str)._repr_html_()}, {}\n", "\n", " return {'text/html': \n", " iframe(src=str, style=dict(width='100%', height=\"400px\"))._repr_html_()}, {}\n", " StringConditions.callable.add(isembed)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Revisiting [FlexBox Transformers](deathbeds.github.io/deathbeds/2018-08-16-HTML-Flexbox.ipynb) with `vdom`\n", "\n", "`vdom` recently added a `vdom.VDOM._repr_html_` method for static html views." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Our flexbox view will have an `element`, `row`, and `column`. `vdom.VDOM` objects do not permit raw html, to create elements from raw html we format strings." ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [], "source": [ " element = div('%s', style={'flex': '1'})._repr_html_()\n", " row = div('%s', style={'display': 'flex', 'flex-direction': 'row'})._repr_html_()\n", " column = div('%s', style={'display': 'flex', 'flex-direction': 'column'})._repr_html_()" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [], "source": [ " class Element:\n", " def __init__(self, data=None): self.data = data\n", " def _repr_html_(self):\n", " with capture() as object: display(self.dom%self.data)\n", " if object.outputs: return self.outputs[0].data, {}\n", " raise AttributeError('_repr_html_')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`Container` provides the `Row._repr_html_ and Column._repr_html_` flexbox views." ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [], "source": [ " class Container(UserList):\n", " def _repr_html_(self, body=\"\"):\n", " for object in self:\n", " with capture() as output: display(object)\n", " output = output.outputs and output.outputs[0].data or {}\n", " for key in list(output):\n", " if key.startswith('image') and not key.endswith('xml'):\n", " encoded = output[key]\n", " if isinstance(encoded, bytes):\n", " encoded = base64.b64encode(encoded).decode('utf-8')\n", " output['text/html'] = img(src=f\"data:image/{key.split('/', 1)[1]};base64,{encoded}\")._repr_html_()\n", " body += element%output.get('text/html', output.get('text/plain', str(object))) \n", " return {Row: row, Column: column}[type(self)]%body\n", "\n", " def __iter__(self):\n", " for object in super().__iter__(): yield ({Row, Column} - {type(self)}).pop()(object) if isinstance(object, list) else object\n", "\n", " class Row(Container): \"A flexbox row\"\n", " class Column(Container): \"A flexbox column\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## `yaml` inputs" ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [], "source": [ " def yaml(str): \"\"\"Convenience function to return the yaml value\"\"\"; return __import__('yaml').safe_load(__import__('io').StringIO(str))\n", "\n", " def isyaml(str): \n", " if str.startswith('- '): return {'text/html': Row(yaml(str))._repr_html_()}, {}\n", " StringConditions.callable.add(isyaml)" ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [], "source": [ " triggers = [\n", " lambda str: str.startswith(\"- \"), \n", " lambda str: str.startswith('http') or excepts(OSError, Path(str).is_file)(),\n", " lambda str: str.lstrip('di').startswith('graph ')\n", " ]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# To Do\n", "\n", "* Add **dict options in yaml\n", "* Tests - Each transformer should be tested to assure the proper outputs." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.6" } }, "nbformat": 4, "nbformat_minor": 2 }