diff -r 292b26405ee7 -r e5799432071c qmsk/web/html.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/qmsk/web/html.py Sat Jun 07 16:21:39 2014 +0300 @@ -0,0 +1,188 @@ +from qmsk.utils import flatten, merge +from html import escape + +import qmsk.web + +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, ''.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 HTMLHandler (qmsk.web.Handler): + """ + A handler that renders a full HTML page. + """ + + # HTML5 + html = html5 + + DOCTYPE = 'html' + HTML_XMLNS = None + HTML_LANG = 'en' + + # + 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 tag. + """ + + html = self.html + + return html.html( + html.head( + html.title(self.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 """\ + +{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 "), + html.pre(repr( + html.a(href="/foo")("Foo!") + )) + ), + ))