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 ashello
p({'class':'jones'}, id='smith')['hello'] # my impl. style renders ashello
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: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? -- Grahamtitle>
hi there
footer
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('%s>' % 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()