from qmsk.utils import flatten, merge
from html import escape
import qmsk.web.application
class Tag :
def __init__ (self, _name=None, *_contents,
_selfclosing=True,
_whitespace_sensitive=False,
**_attrs) :
self.name = _name
self.contents = _contents or [ ]
self.attrs = _attrs or { }
# options
self.selfclosing = _selfclosing
self.whitespace_sensitive = _whitespace_sensitive
def __call__ (self, *_contents,
_selfclosing=None,
_whitespace_sensitive=None,
**_attrs):
return type(self)(self.name, *tuple(flatten(self.contents, _contents)),
_selfclosing = self.selfclosing if _selfclosing is None else _selfclosing,
_whitespace_sensitive = self.whitespace_sensitive if _whitespace_sensitive is None else _whitespace_sensitive,
**merge(self.attrs, _attrs)
)
def render_attrs (self):
for name, value in self.attrs.items():
name = name.strip('_').replace('_', '-')
if value is True:
value = name
elif value is None:
continue
else:
value = str(value)
yield '{name}="{value}"'.format(name=name, value=escape(value, quote=True))
def render (self, indent):
"""
Iterate over lines of output HTML.
"""
open = (self.contents or not self.selfclosing)
if self.name and self.attrs and open:
yield indent, '<{name} {attrs}>'.format(name=self.name, attrs=' '.join(self.render_attrs()))
elif self.name and self.attrs:
yield indent, '<{name} {attrs} />'.format(name=self.name, attrs=' '.join(self.render_attrs()))
elif self.name and open:
yield indent, '<{name}>'.format(name=self.name)
elif self.name:
yield indent, '<{name} />'.format(name=self.name)
for item in self.contents:
if isinstance(item, Tag):
yield from item.render(indent=indent+1)
elif self.whitespace_sensitive:
yield 0, str(item)
else:
yield indent + 1, str(item)
if self.name and open:
yield indent, '</{name}>'.format(name=self.name)
def __str__ (self):
"""
Render as HTML.
"""
return '\n'.join('\t'*indent+line for indent, line in self.render(indent=0))
def __repr__ (self):
return '{name}({args})'.format(
name = self.__class__.__name__,
args = ', '.join(
[repr(self.name)]
+ [repr(item) for item in self.contents]
+ ['{name}={value!r}'.format(name=name, value=value) for name, value in self.attrs.items()]
),
)
class HTML:
pre = Tag('pre', _whitespace_sensitive=True)
def __getattr__ (self, name):
"""
Get an empty Tag object.
"""
return Tag(name)
def __call__ (self, *values):
"""
Raw HTML.
"""
return Tag(None, *values)
class HTML5 (HTML):
span = Tag('span', _selfclosing=False)
script = Tag('script', _selfclosing=False)
html5 = HTML5()
class HTMLMixin:
"""
A handler that renders a full HTML page.
"""
# HTML5
html = html5
DOCTYPE = 'html'
HTML_XMLNS = None
HTML_LANG = 'en'
# <head>
TITLE = None
STYLE = None
SCRIPT = None
CSS = (
)
JS = (
)
HEAD = None
def title (self):
return self.TITLE
def render (self):
raise NotImplementedError()
def render_html (self) :
"""
Render HTML <html> tag.
"""
html = self.html
title = self.title()
assert title
return html.html(
html.head(
html.title(title),
(
html.link(rel='Stylesheet', type="text/css", href=src) for src in self.CSS
),
(
html.script(src=src, type='text/javascript', _selfclosing=False) for src in self.JS
),
html.style(type='text/css')(self.STYLE) if self.STYLE else None,
html.script(type='text/javascript')(self.SCRIPT) if self.SCRIPT else None,
self.HEAD,
),
html.body(
self.render(),
),
xmlns=self.HTML_XMLNS, lang=self.HTML_LANG)
def render_response (self):
"""
Render entire HTML response.
"""
return """\
<!DOCTYPE {doctype}>
{html}\
""".format(doctype=self.DOCTYPE, html=self.render_html())
if __name__ == '__main__':
html = HTML5()
print(html.html(
html.head(
html.title("Testing")
),
html.body(
html.h1("Testing"),
html.p("Just testing this..."),
html("Raw HTML <tags>"),
html.pre(repr(
html.a(href="/foo")("Foo!")
))
),
))
class HTMLHandler (HTMLMixin, qmsk.web.application.Handler):
pass