3 ms·
IMO, the real killer with templates is form and input processing, so if you really want to show that this way is easier, you might want to emphasise some form p
by anthonyb 16y ago
IMO, the real killer with templates is form and input processing, so if you really want to show that this way is easier, you might want to emphasise some form processing stuff instead.
You also might be better off making your library importable as * , so your code looks more like this:
from weby_templates.templates.lib.html import *
...
with p(html()):
with p(head()):
p(title('Hello, World!'))
with p(body()):
p(h1('Hello, World!'))
p(p('Please choose from the following items:'))
I know that Python normally advocates against import *, but it looks a little more readable (to my eye, anyway), rather than call html.blah over and over again. You also don't explain what that 'p' variable is in your example - some sort of request object? It's also not clear why you need to call it to generate HTML.
Another thought: what if you used nested functions/classes instead of the with statements?
html(
head( title('Hello World!'), ... )
body( h1('Hello World!'),
p('Please choose...'),
ul( li('Option 1'),
li('Option 2'),
)
...
)
With the html() function returning the relevant HTML. That way you get your string-returning and automatic tag closing for 'free'. I think there are a few lisp/scheme libraries which use this sort of style.
- ljlolel 16y agoFirst, I'm really glad you took a look at this and thought deeply about how to improve it. It is possible to do import star, and if you really know what you're doing, you can. But, you quickly start running into issues with polluting the namespace (which is why Guido says not to do it). For example, one HTML tag is p, so now we have to use a different accumulator. I find p very natural because I think "print" every time, I used 't' for a while (months) but it just never clicked. Furthermore, HTML has other tags like a, so now you can't use either p or a as one-off variables anywhere without weird bugs or confusing reads of the code, not to mention other common names. Moreover, I even tried using import html as h to save typing. It actually made the templates less readable (I think because of less long explicitness). It can take a few minutes, but actually you quickly get used to the repeated p(html( and it just becomes a useful guidepost when reading through a template quickly. Your second suggestion of a language is nice and clean, but you lose a lot in terms of flexibility and power. For example, you cannot easily loop, or insert a readable conditional, and you definitely cannot assign a helper variable which even Django now acknowledges can be useful. I have been using these templates in production on medium-sized, complex sites for years. I have added enough functionality and simplicity so that I can do everything I need to do, plus it is obvious how to do it. A lot of toy template languages have not seen extensive use in a variety of applications with heavy load, so they are missing subtle, key elements which can be show-stopper issues in practice. Finally, languages keep snowballing massive APIs (to patch up these issues) which become so difficult to completely learn that easy things become hard.
- anthonyb 16y ago> I find p very natural because I think "print" every time ... I can do everything I need to do, plus it is obvious how to do it Probably more because you've used it for a while. I generally prefer longer variable names to help with readability, which makes the accumulator/with technique more verbose and harder to read. If you don't like import * , you can use a combination of from ...lib.html import p, h1, h2 and from ...lib import html, but as long as your namespace is clean (ie. use __all__) and you're clear that the module is designed to be imported that way, I don't see that it's a huge issue, particularly if it clears up a lot of clutter. There's also nothing in that API that prevents you looping, or assigning helper variables - you just store local variables before you generate your template or create sections of your template a bit at a time. Or use list/generator comprehensions, which would fit the functional paradigm a bit better. A couple of examples: ul( *[li(option.name) for option in options], style="dropdown", ...) def index_page(request): my_name = reqest.GET.get('name', 'Bob') head = html.head( title('Hello, %s' % my_name)) body = html.body( h1('...'), p('..')) return html.html(head, body) Of course, all of this is developed using the POOMA methodology, so there might be some hideous limitation, but I can't see any from a few minutes thought.
- DrJosiah 16y agoimport * is the devil, really don't use it. Instead, hack up this recipe: http://code.activestate.com/recipes/277940-decorator-for-bindingconstants-at-compile-time/ http://code.activestate.com/recipes/277940-decorator-for-bin... to inject the common tags into the local namespace of the function. It will have the side-effect of making the code execute faster. As for the callable variant, there are a few versions of that (nevow.stan was the first one in the style, I wrote a version a few years ago that I referenced in another post here, and I've seen a few pop up since) While it does work, indentation can get funky, looping gets tricky (you end up looping quite a bit with data-driven templates, it turns out), and closing parens aren't nearly as useful as a dedent. Being able to mix both styles may be a win.
- ljlolel 16y agoGreat comment! Agreed. I'm a little afraid of that hack though and what it might do to a query optimizer. You're right that the callable system makes loops tricky. Also, great point about the closing parens. We use python specifically to avoid them and to just use whitespace! In practice, I have done a mixed approach as you suggested. I used this iterative style with limited callable style embedded where that makes sense (everything just returns a string, so they embed cleanly).
- anthonyb 16y agoJesus, that's nasty. Hacking python bytecode is far, far worse than import *. I'd be more inclined to just use __all__ in your module to hide the bits that you don't want.