{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Reasonable way to place macros in text.\n",
"\n",
"Some strings contain delimiters that identify them with a specific syntax.\n",
"\n",
"* Markdown has ticks\n",
"* Yaml has dashes\n",
"* Graphviz has graph/digraph\n",
"* Latex has Dollar signs."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# The Node Transformer\n",
"\n",
"Node transformers do what . ....\n",
"IPython accepts node transformers\n",
"\n",
"https://greentreesnakes.readthedocs.io/"
]
},
{
"cell_type": "code",
"execution_count": 27,
"metadata": {},
"outputs": [],
"source": [
" import ast, abc, doctest, types, unittest"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The NodeTransformer will takes two attributes:\n",
"\n",
"* A condition that must be satisfied to trigger the replacement.\n",
"* A formatable string that can be replaced with the desired source.\n",
"\n",
"This class is to be reused as a base class.\n",
"\n",
" >>> class NewTransformer(StrTokenTransformerMeta): ..."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
" class StrTokenTransformerMeta(ast.NodeTransformer, metaclass=abc.ABCMeta):\n",
" @abc.abstractstaticmethod\n",
" def condition(self, callable: str) -> bool:\n",
" \"\"\"A callable that tests a string condition.\"\"\"\n",
" raise NotImplemented()\n",
" \n",
" @abc.abstractproperty\n",
" def replacement(self, str) -> str:\n",
" \"\"\"A block string to replace a condition with.\"\"\"\n",
" raise NotImplemented()\n",
" \n",
" "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"> `abstractproperty` is not required, but it doesn't hurt. If this is confusing, just know we can't create a new `StrTokenTransformerMeta` class without the `condition` or `replacement` attributes existing and being `staticmethod` and `abstractproperty`, respectively."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"`StrTokenTransformer` defines import `NodeTransformer` attributes."
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
" class StrTokenTransformer(StrTokenTransformerMeta):\n",
" def generic_visit(self, node): return node\n",
" \n",
" def visit_Expr(self, node):\n",
" if isinstance(node.value, ast.Str):\n",
" str = node.value.s\n",
" if self.condition(str):\n",
" return ast.parse(self.replace(str)).body[0]\n",
" return node\n",
" \n",
" def visit_Module(self, node): return super().generic_visit(node)\n",
" \n",
" def replace(self, str):\n",
" \"\"\"Validate the source, before continuing.\"\"\"\n",
" self.validate()\n",
" quotes = '\"\"\"'\n",
" if quotes in str: quotes = \"'''\"\n",
" return self.replacement.format(\n",
" quotes + '{}' + quotes).format(str)\n",
" \n",
" def validate(self):\n",
" \"\"\"Validate that the replacement string is Python.\"\"\"\n",
" try: ast.parse(self.replacement)\n",
" except: raise StrTokenTransformerException(self.replacement)\n",
" "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"It is a good practice to define our own exceptions."
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
" class StrTokenTransformerException(BaseException): ..."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## A graphviz replacement\n",
"\n",
"When a string starts with __graph__ or __digraph__ show a [__graphviz__]() repr."
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
" class GraphViz(StrTokenTransformer):\n",
" replacement = \"\"\"__import__('graphviz').Source({})\"\"\"\n",
" condition = staticmethod(lambda str: str.startswith('graph') or str.startswith('digraph'))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"> The graphviz syntax looks extra boss with [Fira-Code]()."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## A yaml replacement"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
" class YamlDefinition(StrTokenTransformer):\n",
" replacement = \"\"\"globals().update(\n",
" __import__('collections').ChainMap(*reversed(list(\n",
" __import__('yaml').safe_load_all(\n",
" __import__('io').StringIO({}))))))\"\"\"\n",
" condition = staticmethod(lambda str: str.startswith('---\\n'))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## An Iframe replacement."
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
" class IframeDisplay(StrTokenTransformer):\n",
" replacement = \"\"\"__import__('IPython').display.display(\n",
" __import__('IPython').display.IFrame(\n",
" {}, 600, 400))\"\"\"\n",
" condition = staticmethod(lambda str: str.startswith('http:') or str.startswith('https:'))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## A tester"
]
},
{
"cell_type": "code",
"execution_count": 67,
"metadata": {},
"outputs": [],
"source": [
" class DoctestString(StrTokenTransformer):\n",
" replacement = \"\"\"__import__('doctest').testmod(\n",
" __import__('types').ModuleType('test', {}), globs=vars(__import__(__name__)))\"\"\"\n",
" condition = staticmethod(lambda str: any(line.lstrip().startswith('>>> ') for line in str.splitlines()))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## An IPython extension"
]
},
{
"cell_type": "code",
"execution_count": 68,
"metadata": {},
"outputs": [],
"source": [
" def load_ipython_extension(ip=None):\n",
" ip = ip or __import__('IPython').get_ipython()\n",
" ip.ast_transformers = [GraphViz(), YamlDefinition(), IframeDisplay(), DoctestString()]\n",
" \n",
" load = load_ipython_extension"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Usage\n",
"\n",
" >> __import__('__String_Node_Transformer').load()"
]
},
{
"cell_type": "code",
"execution_count": 69,
"metadata": {},
"outputs": [],
"source": [
" Ø = __name__ == '__main__'\n",
" Ø and load_ipython_extension()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### GraphViz Example"
]
},
{
"cell_type": "code",
"execution_count": 70,
"metadata": {},
"outputs": [
{
"data": {
"image/svg+xml": [
"\n",
"\n",
"\n",
"\n",
"\n"
],
"text/plain": [
""
]
},
"execution_count": 70,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
" \"\"\"digraph {rankdir=\"LR\" A->B->C->A}\"\"\""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Yaml Example"
]
},
{
"cell_type": "code",
"execution_count": 71,
"metadata": {},
"outputs": [],
"source": [
" \"\"\"---\n",
" foo: 42\n",
" ---\n",
" foo: 100\"\"\"\n",
" if Ø: assert foo is 100, \"\"\"The transformer is likely not loaded.\"\"\""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Doctesting"
]
},
{
"cell_type": "code",
"execution_count": 72,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"TestResults(failed=0, attempted=1)"
]
},
"execution_count": 72,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
" \"\"\">>> 1\n",
" 1\"\"\""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# What other replacements could you imagine?"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "p6",
"language": "python",
"name": "other-env"
},
"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.3"
}
},
"nbformat": 4,
"nbformat_minor": 2
}