Skip to content

Instantly share code, notes, and snippets.

@un-def
Created December 2, 2017 20:14
Show Gist options
  • Save un-def/f7f083e37b57dca6c3d231a3153ccafd to your computer and use it in GitHub Desktop.
Save un-def/f7f083e37b57dca6c3d231a3153ccafd to your computer and use it in GitHub Desktop.
Ugly Python-based XML/HTML DSL
#!/usr/bin/env python3
from abc import ABC, ABCMeta, abstractmethod
from types import FunctionType
from collections import OrderedDict
# flake8: noqa
class UnresolvedFreeVarsError(Exception):
def __init__(self, freevars):
if isinstance(freevars, FreeVarWrapper):
freevars = (freevars,)
self.freevars = freevars
def __str__(self):
names = ', '.join(fv.name for fv in self.freevars)
return "Unresolved freevar(s): {names}".format(names=names)
class BaseNode(ABC):
def __str__(self):
return self.render()
@abstractmethod
def render(self, indent=0):
pass
def _resolve_slice_object(self, slice_obj):
name = slice_obj.start
value = slice_obj.stop
if isinstance(value, FreeVarWrapper):
raise UnresolvedFreeVarError(value)
if isinstance(name, FreeVarWrapper):
name = name.name
return str(name), value
class TextNode(BaseNode):
def __init__(self, value):
self.value = str(value)
def render(self, indent=0):
return ' ' * indent + self.value
class ElementNode(BaseNode):
def __init__(self, name):
self.name = name
self.children = []
self.attrs = OrderedDict()
def __getitem__(self, items):
if not isinstance(items, tuple):
items = (items,)
for item in items:
if isinstance(item, slice):
self._set_attr(item)
else:
if isinstance(item, FreeVarWrapper):
item = item.to_element_node()
elif not isinstance(item, (ElementNode, ComponentNode)):
item = TextNode(item)
self.children.append(item)
return self
def _set_attr(self, slice_obj):
name, value = self._resolve_slice_object(slice_obj)
if isinstance(value, (list, tuple)):
unresolved_freevars = list(
filter(lambda e: isinstance(e, FreeVarWrapper), value)
)
if unresolved_freevars:
raise UnresolvedFreeVarsError(unresolved_freevars)
value = ' '.join(str(e) for e in value)
self.attrs[name] = value
def render(self, indent=0):
if self.attrs:
attrs = ' ' + ' '.join('{}="{}"'.format(k, v)
for k, v in self.attrs.items())
else:
attrs = ''
indentation = ' ' * indent
if self.children:
strings = []
strings.append('{}<{}{}>'.format(indentation, self.name, attrs))
strings.extend(c.render(indent=indent+2) for c in self.children)
strings.append('{}</{}>'.format(indentation, self.name))
return '\n'.join(strings)
else:
return '{}<{}{}/>'.format(indentation, self.name, attrs)
class ComponentNode(BaseNode):
def __init__(self, component_class):
self.component_class = component_class
self.component_params = {}
def __getitem__(self, items):
if not isinstance(items, tuple):
items = (items,)
for item in items:
if not isinstance(item, slice):
raise ValueError('Component should be used with '
'parameter:value syntax')
name, value = self._resolve_slice_object(item)
self.component_params[name] = value
return self
def render(self, indent=0):
component = self.component_class(**self.component_params)
return component.render().render(indent=indent)
class FreeVarWrapper:
element_node_class = ElementNode
def __init__(self, name):
name = name.strip('_')
self.name = name
def __str__(self):
return '<FreeVar: {name}>'.format(self.name)
def __getitem__(self, items):
return self.to_element_node()[items]
def __sub__(self, other):
return self.__class__('{}-{}'.format(self.name, other.name))
def to_element_node(self):
return self.element_node_class(self.name)
class RenderEnv(dict):
def __init__(self, render_globals):
self.available_components = {}
for name, obj in render_globals.items():
if isinstance(obj, BaseComponentMeta) and obj is not BaseComponent:
self.available_components[name] = obj
self.render_globals = render_globals
def __missing__(self, key):
if key in self.available_components:
return ComponentNode(self.available_components[key])
return FreeVarWrapper(key)
class BaseComponentMeta(ABCMeta):
def __new__(meta, name, bases, dct):
if bases and 'render' in dct:
render = dct['render']
dct['render'] = FunctionType(render.__code__,
RenderEnv(render.__globals__))
return super().__new__(meta, name, bases, dct)
class BaseComponent(metaclass=BaseComponentMeta):
def __str__(self):
return str(self.render())
@abstractmethod
def render(self):
pass
class Alert(BaseComponent):
def __init__(self, style, text):
self.style = style
self.text = text
def render(self):
return (
div[class_:('alert', 'alert-'+self.style),
self.text
]
)
class Content(BaseComponent):
def __init__(self, image):
self.image = image
def get_link(self, location):
return '/link/to/' + location
def render(self):
danger = 'This is a danger alert'
info = 'This is a info alert'
return (
div[class_:'toolbar', id:'#id123', data-initial-value:'foo',
a['class':'link', href:self.get_link('nowhere'),
'Click me!',
img[src:self.image],
'Please!'
],
div[id:'alert-wrapper',
Alert[style:'danger', text:danger]
],
eval_[data-value:14/88],
Alert[style:'info', text:info]
]
)
class PageComponent(BaseComponent):
def __init__(self, title):
self.title = title
def render(self):
return (
html[
head[
title[self.title]
],
body[
Content[image:'http://deaddrop.ftp.sh/QvystY0EkXu6.jpg']
],
]
)
component = PageComponent(title='It works!')
print(component)
<html>
<head>
<title>
It works!
</title>
</head>
<body>
<div class="toolbar" id="#id123" data-initial-value="foo">
<a class="link" href="/link/to/nowhere">
Click me!
<img src="http://deaddrop.ftp.sh/QvystY0EkXu6.jpg"/>
Please!
</a>
<div id="alert-wrapper">
<div class="alert alert-danger">
This is a danger alert
</div>
</div>
<eval data-value="0.1590909090909091"/>
<div class="alert alert-info">
This is a info alert
</div>
</div>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment