Jan92007

Python template engines - why reinvent PHP?

Filed under: breve template xml python stan dsl 

When I first published TurboStan, I admit I was a bit taken aback by the apparent lack of interest from the majority of TurboGears users. Stan's beauty and clarity seemed so obvious to me that it baffled me that people would choose what I perceived as a lesser PHP over something as clearly elegant as Stan. I tallied the possible reasons for TurboStan's lack of fanfare and came up with the following:

  1. Not the default engine for TurboGears. While not a good technical reason, there are reasons for sticking close to the mainstream, especially with an unstable target like TurboGears was in the early days.
  2. Dependencies on Twisted, Nevow and zope.interfaces. At least one user mentioned that he was unable to find a version of zope.interfaces that would both install and was compatible with the other two dependencies. I figured that if one person mentioned this problem, then there were probably a dozen more who gave up in silence.
  3. Lack of documentation. This plagued me in my early days using Nevow and I didn't improve the situation for others any by extending Stan with even more features that were only documented briefly in my blog (and I changed blogs about as often as my socks in those days).
  4. Not really seeing the elegance of Stan.

Since the release of Breve, I've seen a bit more interest than I did with TurboStan (I even received a patch from someone, something that TurboStan never did), but still the warm reception isn't quite what I'd like it to be.

Now I admit that when I first decided to try Nevow (way back before TurboGears was a glimmer in Kevin's eye), I took a look at the two options for templating (Nevow's XML engine and Stan) and went with the XML option. Stan looked... odd. I didn't really see it for what it was. Plus I had some odd sense that XML was a better choice because it was more "standard". I'm not sure when the lights came on and I was able to appreciate the elegance of Stan, but at some point it happened and I'm now loathe to touch the angle brackets on my keyboard. I became so attached to Stan that I refused to try Django, Pylons, CleverHarold or any other framework that I couldn't use Stan with. The main reason I used TurboGears was so I could use TurboStan. Now, I'm sure at least part of this love came from the fact that I was playing with my own creation (not Stan, but the extensions I added) and my ability to direct its future. There's always a lot of fun in that and I can't deny that is part of it.

Still, as I watch TurboGears (and a few other frameworks) adopt Genshi as their default engine, I can't help but wonder at the attraction people find to these sorts of engines. I can't help but notice with a bit of smugness that there's a good chunk of helper functions in Pylons (and Rails) to alleviate the need to spell out HTML, which seems to confirm my belief that their default template engines don't really help as much as they might when it comes to HTML generation (an odd weakness for a web framework to have). When I was first starting on the web (and Python web frameworks were pretty ugly), I was forced to program in PHP/Smarty at my job. Once I got over the novelty of programming amongst the web's various vagaries and oddnesses, I bemoaned these tools and wished for a Python framework that would allow me to escape the ugliness I perceived in them. So what's my point? Well my point is that template engines such as Genshi, Kid, Myghty, et al all smell of PHP to me. Is some of that just cosmetic? You bet. In fact, probably most of it is just cosmetic. There's no doubt in my mind that Genshi is a powerful too and probably full of beautiful Python code under the hood. That doesn't change how it looks to me from the outside. It isn't Python. I find it mildly ironic that Pythonistas are renowned for their loathing of Perl's typographical perversions and then turn around and create equally ugly things to describe web pages with.

Anyway, this isn't so much about how much I dislike those types of engines as it is curiosity as to what reasoning (whether it be founded in technical or taste) other Python programmers have for selecting their preferred template engine. Going along with that, I'm curious why DSL's like Stan, Breve, XIST and others don't generate more enthusiasm amongst Pythonistas.

So if you've got an opinion on this, I'd be interested in hearing it. I'm not looking for an argument, just enlightenment (right now I feel like I'm the enlightened one looking out on the unwashed masses, but there's always the remote possibility I just don't get it, and if that's the case, someone should be kind enough to show me the light) . Also I'm interested to see if there is an actual perceived shortcoming in Breve so that I can perhaps address it.



13 comments Leave a comment


Jul312006

The slippery slope of presentation logic

Filed under: turbostan template 

I've been asked, and have previously considered, whether to add logic tags to TurboStan in order to facilitate "presentation logic". I'm still undecided whether this is a good idea or not.

Despite being firmly in the MVC camp, I am also a firm believer that there is such a thing as "presentation logic" that really belongs in the View (template).

For example, in a blog application, a template designer may want to specify that only 10 recent articles are displayed in a sidebar in one situation, and 20 in another. I don't expect a template designer to delve into the controller and add a one-off method to support this. I think the ability to specify things like this is a valid reason to have logic in a template.

On the other hand, once logic flow is added to templates, as it turns out, that is often the easiest place to do logic and it gets abused. Take a look at any moderately sophisticated Smarty template and you'll see what I mean. It quickly becomes a mess and it appears beyond the discipline of programmers and designers alike to avoid it.

So what's the solution? At the moment I've taken to allowing passing extra arguments to renderers in TurboStan, such as:

div ( id = 'sidebar',
      render = render.sequence,
      data = vars.model.Article.data.articles,
      selectby = { 'published': True, 'limit': 10 } )

Of course the controller has to accept these arguments, but that's easily doable.

The question is, is this enough? Is it inherently better than the alternatives? Should I add an "if" tag to TurboStan? You can already (ab)use list comprehensions and any other Python expression within TurboStan, but I wonder if it's wise to open this can of worms.

If you think I should add some sort of logic tag(s) ( I'm talking to you PacoPablo ), maybe a concrete example of something that can't easily be implemented without them would be convincing. An example that either a) cannot be done or b) is just too damn ugly to talk about would be best.

Personally I've found that my desire for such things has dropped pretty much proportionately to how much I've put aside what I'm used to and swallowed the Nevow pill of render and data callbacks, but I'm always willing to at least entertain other's ideas.



2 comments Leave a comment


Jan82007

Web 2.0 still in beta

Filed under: breve template xml xhtml 

My work on Breve has revealed more dark corners of the web than I thought I ever wanted to know.

Breve is, at its heart, an XML-generation engine. The fact that it can output HTML and that doing so is its primary purpose is almost incidental, really. What this means is that Breve happily outputs things like <div /> which is logically sound, but technically incorrect.

Now, in a previous, happier life I was blissfully unaware that not all elements can be self-closing in XHTML, especially since such tags will even validate in the W3C's own validator. I ran across the issue when Breve output something similar to the following:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0
           Strict//EN" "DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
  </head>
  <body>
    <div />
    <div>test</div>
    <div />
  </body>
</html>

Now on the surface, this looks fine. However, this is how Firefox rendered it:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0
           Strict//EN"  "DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
  </head>
  <body>
    <div>
      <div>test</div>
      <div></div>
    </div>
  </body>
</html>

I spent about an hour trying to figure out what I was doing wrong, changing doctypes, adding meta tags, etc. As it turns out, I was missing a key element: the HTTP header "text/xml". Now, I didn't think I was missing it, since I'd tried setting that via a meta tag to no avail. Luckily someone on #firefox pointed me to this bug report.

The short story is that there is no practical way to specify that an XML document is, in fact, XML from within the document itself. You must configure the web server to output the proper headers.

The longer story is that I modified Breve to only output self-closing tags for a small subset of XHTML and all is well (except for my faith in people who write RFC's).



0 comments Leave a comment


Jan52007

Breve 1.0 beta 13

Filed under: breve template pylons turbogears buffet 

Fixed a major issue with how Breve dealt with template paths. I'm not 100% happy with the solution, but it's an issue that the frameworks (Pylons and TurboGears, in this case) tell the template engine nothing about the root path to the templates.

This is an issue for Breve because all Breve paths are relative to a static root path. I've found that this makes it far easier to have fragments in subdirectories that can still inherit from templates in higher-level directories that might then include templates from subdirectories. I tried using things like "inherits ( '../index.stan' )" with TurboStan and it turned into an unmanageable mess real quick. However, TurboStan was only really meant to support TurboGears so I cheated and called out the the TG config file to get a root directory. Supporting both Pylons and TurboGears makes that solution unusable.

Anyway, like it or not, it's been tested with Pylons 0.94 and seems to work. Note that you must specify:

breve_opts = { 'root': 'myproject.templates' }

in your config/middleware.py file.



0 comments Leave a comment


Aug92006

Using inheritance to eliminate template logic

Filed under: turbostan template 

I recently added a logic tag to TurboStan with some mixed feelings. One of the things I love about using TurboStan is that I'm constantly discovering new ways to utilize its features.

I'm working on a couple of sites that will have one-off pages, that is, pages that don't fit the standard template for the site. In this case, the only difference was that the home page would not have a sidebar, but the other pages all would. In all cases, index.stan defined the overall site layout, and public.stan filled in slots in this template.

The quick and easy solution seemed to be to use the new "case" tag something like this:

#index.stan
html [
    body [
        div ( id = 'left-sidebar' ) [ slot ( 'left-sidebar' ) ],
        div ( id = 'content' ) [ slot ( 'content' ) ]
    ]
]

#public.stan
inherits ( 'index' ) [
    override ( 'left-sidebar' ) [
        case ( vars.page != '/home' ) [
            xml ( 'left sidebar stuff!' )
        ]
    ],
    override ( 'content' ) [
        div ( render = vars.content, data = vars.page )
    ]
]

This worked fine, except I had a problem: even though the left-sidebar was now empty on the /home page, the CSS attached to it was still in effect. "Easy", I thought, "I'll move the div inside the case tag. So now I ended up with something like:

#index.stan
html [
    body [
        slot ( 'left-sidebar' ),
        slot ( 'content' )
    ]
]

#public.stan
inherits ( 'index' ) [
    override ( 'left-sidebar' ) [
        case ( vars.page != '/home' ) [
            div ( id = 'left-sidebar' ) [
                xml ( 'left sidebar stuff!' )
            ]
        ]
    ],
    override ( 'content' ) [
        div ( id='content', render = vars.content, data = vars.page )
    ]
]

This was looking better. I could see how this small alteration gave me a bit more flexibility. However, I still had a problem: in order to make the left-column and content divs sit side-by-side, I was forced to give them both fixed widths in the CSS. If I removed the widths, the content div ended up underneath the left sidebar. The problem was that the /home page needed to be wider than this.

Anyway, I tossed out several ideas that seemed like kludges until it finally dawned on me: inheritance was the answer. I added a new template home.stan, a new controller that utilized that template, and ended up with the following:

#index.stan
html [
    body [
        slot ( 'left-sidebar' ),
        slot ( 'content' )
    ]
]

#public.stan
inherits ( 'index' ) [
    override ( 'left-sidebar' ) [
        div ( id = 'left-sidebar' ) [
            xml ( 'left sidebar stuff!' )
        ]
    ],
    override ( 'content' ) [
        div ( id='content', render = vars.content, data = vars.page )
    ]
]

#home.stan
inherits ( 'public' ) [
    override ( 'left-sidebar' ),
    override ( 'content' ) [
        div ( id = 'home', render = vars.content, data = vars.page )
    ]
]

Much better! Now not only had I removed the need for actual logic in the template, but I was able to have a separate id for the home page which allowed me to set the width for content div to a different value than the rest of the site.



0 comments Leave a comment


Copyright © 2007, Cliff Wells