qmsk/web/html.py
changeset 92 e5799432071c
child 102 611787305686
--- /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, '</{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 HTMLHandler (qmsk.web.Handler):
+    """
+        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
+
+        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 """\
+<!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!")
+            ))
+        ),
+    ))