durusmail: quixote-users: A toy Nevow implementation
A toy Nevow implementation
2004-01-15
2004-01-15
2004-01-15
2004-01-15
Debug syntax errors in PTL (was: A toy Nevow implementation)
2004-01-15
Debug syntax errors in PTL (was: A toy Nevowimplementation)
2004-01-15
Debug syntax errors in PTL (was: A toy Nevowimplementation)
2004-01-16
Jason E. Sibre (2 parts)
Debug syntax errors in PTL
2004-01-22
Debug syntax errors in PTL
2004-01-18
2004-01-18
2004-01-15
Re: A toy Nevow implementation
2004-01-16
2004-01-19
Re: A toy Nevow implementation
2004-01-19
Re: A toy Nevow implementation
2004-01-19
2004-01-19
2004-01-19
2004-01-19
2004-01-19
2004-01-19
2004-01-19
2004-01-19
2004-01-20
2004-01-20
2004-01-20
2004-01-20
2004-01-21
2004-01-20
A toy Nevow implementation
Graham Fawcett
2004-01-15
Here's a little toy for your amusement. On comp.lang.python today, I discovered
that Twisted is building a next-generation templating system, called Nevow (a
wordplay on "Woven", their earlier system, and "nouveau").

This is what Nevow template code looks like. It's valid Python, though it looks
more like an S-expression:

     document = html[
         body[
             form(action="")[ input(type="text", name="name") ],
         ]
     ]

Sneaky tricks with __call__ and __getitem__ are used to pull off this feat of
syntactic sugaring. More (and better) examples can be seen at

     http://stewstuff.com/doc/nevow.xhtml


I wrote a little Nevow implementation, just so I could see what the system felt
like. It's a toy, of course, but I thought you might enjoy playing along.

Attached are three files: server.py (a driver script employing the proposed
quixote.server.medua patch), nevow.py (the nevow impl.) and ui.ptl (a sample
PTL file). Of course, server.py is easily rewritten for your own server.

Nevow uses keyword arguments to set tag attributes. I don't know how they are
handling the 'class' attribute and its keyword conflict. As a workaround, I
implemented tag attributes to allow a dict as an optional non-keyword argument,
like so:

     p(id='smith')['hello']                       # Nevow style
renders as
     

hello

p({'class':'jones'}, id='smith')['hello'] # my impl. style renders as

hello

Nevow also allows callables to be included within the 'list' section of a tag. For example: def hello_function(): return p ['hi there'] print div[ h1['title'], hello_function, div['footer'] ] would render as:

title>

hi there

footer

Callables are lazy-evaluated (at render-time). Nevow passes a "request context" as a parameter to the callables; my toy implementation does not. They also use interfaces and component adaptation to adapt various Python types to Nevow "renderables", a very Twisted thing to do. (I used if..elif..else and some isinstance() checking instead, a very cheap thing to do. ;-) Not sure how they implemented the html/body/etc. "keywords" in the real Nevow, but I used a prototype-based approach. html is an object; html['foo'] creates a copy of html, adds 'foo' to the copy's content, and returns the copy. You can use this yourself to build your own prototypes, like this Anchor derivative: biglink = a(style='font-size: 200%') print biglink(href='foo/')['Visit the Foo Site'] displays: Visit the Foo Site A last note: my implementation renders expressions into strings, not htmltext. * * * * Whether you try the code or not, it would be interesting to hear other people's opinions of the Nevow templating system: is it black magic to be shunned, the next best thing, or just YATS? -- Graham
from nevow import *

_q_exports = ['ptl']

def template(doctitle, docbody):
    """
    A page template. The stylesheet is there as a visual check
    that class and id attributes are set properly.
    """
    return html [
        head [
            title[doctitle],
            style(type='text/css')[
                'body { background-color: lightblue; } ',
                '.section { border: blue 3px solid; padding: 6px; } ',
                '#mainbody { background-color: white; } '
            ],
        ],
        body [
            h1 [doctitle],
            div({'class':'section'}, id='mainbody')[docbody],
            hr
        ]
    ]

def _q_index (req):
    docbody = [
        p['This is a test of a Nevow-like templating system.'],
        p[
            'For more information, see ',
            a(href='http://stewstuff.com/doc/nevow.xhtml')[
                'this Nevow document'],
            '.'
        ],
        p['And here is a ',
            a(href='ptl')['PTL-style method using Nevow'], '.']
    ]

    return template('Nevow Test', docbody)

# This is a "PTL Style" usage. Note that you must either use
# a [plain] declaration or render as htmltext. I added an
# .htmltext() method to the Tag types to facilitate the latter.

def ptl [html] (req):
    html[
        body[
            h1['hi there'],
            p[a(href='..')['Return']]
        ]
    ].htmltext()
"""
Basic Nevow-style tags for Quixote (or any other purpose).

Nevow is the next generation templating system for Twisted.Web.
See http://stewstuff.com/doc/nevow.xhtml for details.

This is a clean-room, toy implementation. Not sure about the
real Nevow, but this code uses a prototype-based approach.
All tag "classes" are really objects, and return modified copies
of themselves under certain conditions.

Graham Fawcett
2004.01.14 -- first revision.
"""

from quixote.html import htmltext

class Tag:

    def __init__(self, name, content='', attribs=None):
        self.name = name
        self.content = content
        if attribs is None:
            attribs = {}
        self.attribs = attribs

    def __call__(self, *args, **kwargs):
        if len(args):
            kwargs.update(args[0])
        d = self.attribs.copy()
        d.update(kwargs)
        dup = Tag(self.name, None, attribs=d)
        return dup

    def __getitem__(self, content):
        return Tag(self.name, wrap(content), attribs=self.attribs)

    def render(self):
        x = []
        x.append('<%s' % self.name)
        if self.attribs:
            for k, v in self.attribs.items():
                x.append(' %s="%s"' % (k, v))
        if self.content:
            x.append('>')
            x.append(self.content.render())
            x.append('' % self.name)
        else:
            x.append(' />')
        return ''.join(x)

    def __repr__(self):
        return '<%s:%s>' %  (self.__class__.__name__, self.name)

    def __str__(self):
        return self.render()

    def htmltext(self):
        return htmltext(str(self))

class ListWrap:
    def __init__(self, lst):
        self.lst = [wrap(x) for x in lst]
    def render(self):
        x = []
        for element in self.lst:
            x.append(element.render())
        return ''.join(x)

class StringWrap:
    def __init__(self, data):
        self.data = str(data)
    def render(self):
        return self.data

class CallWrap:
    def __init__(self, func):
        self.func = func
    def render(self):
        return wrap(self.func()).render()

def wrap(content):
    w = None
    if isinstance(content, Tag):
        #print 'wrap returns %s' % content.name
        w = content
    elif callable(content):
        return CallWrap(content)
    elif type(content) in (tuple, list):
        #print 'listwrap for %s' % repr(content)
        w = ListWrap(content)
    else:
        w = StringWrap(content)
    return w


tagnames = (
    'html head meta script title style link body p h1 h2 h3 h4 h5 h6 '
    'div table tr td tbody ol ul li a hr img object embed '
)

for tagname in tagnames.split(' '):
    cmd = '%s = Tag("%s")' % (tagname, tagname)
    exec cmd
from quixote.server.medusa import Server

s = Server('ui', port=8080)
s.run()
reply