Skip to content

Instantly share code, notes, and snippets.

@joskid
Forked from jamescasbon/template.py
Created January 17, 2012 06:19
Show Gist options
  • Save joskid/1625131 to your computer and use it in GitHub Desktop.
Save joskid/1625131 to your computer and use it in GitHub Desktop.

Revisions

  1. jamescasbon revised this gist Dec 11, 2011. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion gistfile1.py
    Original file line number Diff line number Diff line change
    @@ -42,7 +42,7 @@
    We have '$' and '#' to inline python code and variables. This to me is the
    wrong way round. I want to write python and not html. I do not want to manage
    html braces. Travis Rudd has pointed out that many of the arguments for
    html braces. Tavis Rudd has pointed out that many of the arguments for
    non python templates are unfounded [2] and I agree with him. This has been
    further enforced for me by using haml [3] and coffeekup [4], which leaves us in the
    ridiculous position that the best indent based templating DSLs for html are in
  2. jamescasbon revised this gist Dec 11, 2011. 1 changed file with 2 additions and 1 deletion.
    3 changes: 2 additions & 1 deletion gistfile1.py
    Original file line number Diff line number Diff line change
    @@ -112,6 +112,7 @@ def example_template(items):
    [4] http://coffeekup.org/
    [5] http://lxml.de/dev/api/lxml.builder.ElementMaker-class.html
    [6] http://breve.twisty-industries.com/
    [7] http://spitfire.googlecode.com/svn/trunk/tests/perf/bigtable.py
    Comments, abuse, etc to twitter @casualbon or casbon (at) gmail.com
    """
    @@ -151,7 +152,7 @@ def __exit__(self, t, v, tb):

    class Lazydec(object):
    """ Horrible decorator to allow the with statement to omit
    brackets by tracking if callable returned by doc has been called
    brackets by tracking if context returned by doc has been entered
    """

    def __init__(self, x):
  3. jamescasbon revised this gist Dec 11, 2011. 1 changed file with 21 additions and 16 deletions.
    37 changes: 21 additions & 16 deletions gistfile1.py
    Original file line number Diff line number Diff line change
    @@ -2,6 +2,23 @@
    A really stupid python template language inspired by coffeekup, markaby.
    Do not use this code, it will ruin your day. A byproduct of insomnia.
    TL;DR
    -----
    This module defines a template language that allows us to do:
    d = Doc()
    with d.html:
    with d.head:
    d.title ('example page')
    d.link (rel='stylesheet', href='/style.css', type='text/css')
    with d.body (style='foo'):
    d.a ('other stuff on another page', href='/other.html')
    d.p ('stuff on this page')
    Motivation
    ----------
    @@ -60,21 +77,8 @@
    So which way to go? Function definitions would require too much magic, so it
    seems that the best way is the _with_ statement.
    This module defines a template language that allows us to do:
    d = Doc()
    with d.html:
    with d.head:
    d.title ('example page')
    d.link (rel='stylesheet', href='/style.css', type='text/css')
    with d.body (style='foo'):
    d.a ('other stuff on another page', href='/other.html')
    d.p ('stuff on this page')
    We can wrap this in a template method to use python conditional logic based
    The result is the style shown in the TL;DR above. We can also
    wrap this in a template method to use python conditional logic based
    on supplied data:
    def example_template(items):
    @@ -93,7 +97,8 @@ def example_template(items):
    with d.li:
    d.a (str(i), href=str(i) + '.html')
    return d.to_string()
    The code below also shows template inheritance from python classes.
    This is currently based on the lxml builder, and so it is slightly slower
    than the elementree benchmarks [7] (which is pretty slow). The lazy decorator
  4. jamescasbon revised this gist Dec 11, 2011. 1 changed file with 0 additions and 1 deletion.
    1 change: 0 additions & 1 deletion gistfile1.py
    Original file line number Diff line number Diff line change
    @@ -112,7 +112,6 @@ def example_template(items):
    """
    from lxml.html import builder as E
    import lxml
    from contextlib import contextmanager


    class TagContext(object):
  5. jamescasbon created this gist Dec 11, 2011.
    273 changes: 273 additions & 0 deletions gistfile1.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,273 @@
    """
    A really stupid python template language inspired by coffeekup, markaby.
    Do not use this code, it will ruin your day. A byproduct of insomnia.
    Motivation
    ----------
    Python templating has always been a problem for me. You normally have to
    write in your target language (i.e. HTML) with some horrible syntax for
    inlining python. For example, this is a Cheetah [1] template:
    <html>
    <head><title>$title</title></head>
    <body>
    <table>
    #for $client in $clients
    <tr>
    <td>$client.surname, $client.firstname</td>
    <td><a href="mailto:$client.email">$client.email</a></td>
    </tr>
    #end for
    </table>
    </body>
    </html>
    We have '$' and '#' to inline python code and variables. This to me is the
    wrong way round. I want to write python and not html. I do not want to manage
    html braces. Travis Rudd has pointed out that many of the arguments for
    non python templates are unfounded [2] and I agree with him. This has been
    further enforced for me by using haml [3] and coffeekup [4], which leaves us in the
    ridiculous position that the best indent based templating DSLs for html are in
    non indent based languages (ruby, javascript).
    This is why we can't have nice things
    -------------------------------------
    Coffescript and ruby have some advantage over python for a DSL including the
    way anonymous blocks are defined and the ability to omit brackets and still get
    function execution. This means that the most direct python templates, such as
    lxml builder[5] and breve [6] end up with a nested structure. For example,
    this is an lxml template:
    html = E.HTML(
    E.HEAD(
    E.LINK(rel="stylesheet", href="great.css", type="text/css"),
    E.TITLE("Best Page Ever")
    ),
    E.BODY(
    E.H1(E.CLASS("heading"), "Top News"),
    E.P("World News only on this page", style="font-size: 200%"),
    "Ah, and here's some more text, by the way.",
    lxml.html.fromstring("<p> and this is a parsed fragment </p>")
    )
    )
    Now, this code is python, but logic expressed this way still requires
    management of parens and is not indent based - so is not really 'pythonic'.
    It also cannot really use conditionals and loops beyond ternarys and list comprehensions.
    So I wondered if we could hook up a bit of magic using some other method.
    So which way to go? Function definitions would require too much magic, so it
    seems that the best way is the _with_ statement.
    This module defines a template language that allows us to do:
    d = Doc()
    with d.html:
    with d.head:
    d.title ('example page')
    d.link (rel='stylesheet', href='/style.css', type='text/css')
    with d.body (style='foo'):
    d.a ('other stuff on another page', href='/other.html')
    d.p ('stuff on this page')
    We can wrap this in a template method to use python conditional logic based
    on supplied data:
    def example_template(items):
    d = Doc()
    with d.html:
    with d.head:
    d.title ('other stuff on this page')
    d.link (rel='stylesheet', href='/style.css', type='text/css')
    with d.body (style='foo'):
    d.a ('other stuff on another page', href='/other.html')
    d.p ('stuff on this page')
    with d.ul:
    for i in items:
    with d.li:
    d.a (str(i), href=str(i) + '.html')
    return d.to_string()
    This is currently based on the lxml builder, and so it is slightly slower
    than the elementree benchmarks [7] (which is pretty slow). The lazy decorator
    that allows us to miss out brackets on with statements is probably going
    to make you cry. Still this seems like a reasonable template language in
    about 40 lines of code.
    [1] http://www.cheetahtemplate.org/examples.html
    [2] https://bitbucket.org/tavisrudd/throw-out-your-templates/src/98c5afba7f35/throw_out_your_templates.py
    [3] http://haml-lang.com/
    [4] http://coffeekup.org/
    [5] http://lxml.de/dev/api/lxml.builder.ElementMaker-class.html
    [6] http://breve.twisty-industries.com/
    Comments, abuse, etc to twitter @casualbon or casbon (at) gmail.com
    """
    from lxml.html import builder as E
    import lxml
    from contextlib import contextmanager


    class TagContext(object):
    """ The context manager for an HTML tag """

    def __init__(self, tag, doc, *content, **props):
    """ create an html tag belonging to doc with given content and properties"""
    self.tag = tag
    self.doc = doc
    self.content = content
    self.props = props
    self.stack = self.doc.stack
    self.node = self.make_node()

    def make_node(self):
    """ create an lxml element and append it to the node at the top
    of the document tack """
    node = getattr(E, self.tag.upper())(*self.content, **self.props)
    if self.stack:
    self.stack[-1].append(node)
    return node

    def __enter__(self):
    """ entering the node appends it to the document stack """
    self.stack.append(self.node)

    def __exit__(self, t, v, tb):
    """ exiting the node pops it from the stack """
    if len(self.stack) > 1:
    self.stack.pop()


    class Lazydec(object):
    """ Horrible decorator to allow the with statement to omit
    brackets by tracking if callable returned by doc has been called
    """

    def __init__(self, x):
    self.x = x

    def __call__(self, *args, **kws):
    return self.x(*args, **kws)

    def __enter__(self):
    if self.x.__name__ == 'tagcallable':
    self.x = self.x()
    return self.x.__enter__()

    def __exit__(self, *args):
    return self.x.__exit__(*args)



    class Doc(object):
    """ A document manages a stack, the head of which is the current context node """

    def __init__(self, *args, **kws):
    self.stack = []

    def __getattr__(self, attr):
    """ Override getattr for quick tag access
    This means Doc.html returns a tag
    """
    # TODO: incomplete list of tags
    if attr in ['html', 'head', 'body', 'a', 'p', 'ul', 'li', 'div',
    'table', 'tr', 'td', 'link', 'title', 'span']:
    return self.make_tag(attr)
    else:
    return object.getattr(self, attr)

    def to_string(self, pretty_print=True):
    """ convert this document to string """
    return lxml.html.tostring(self.stack[0], pretty_print=pretty_print)

    def make_tag(self, name):
    """ create a tag by creating a TagContext and then decorating it with the lazy decorator"""
    def tagcallable(*content, **props):
    return TagContext(name, self, *content, **props)
    f = Lazydec(tagcallable)
    return f



    if __name__ == '__main__':

    def example_template(items):
    """ template function example"""

    d = Doc()
    with d.html:

    with d.head:
    d.title ('other stuff on this page')
    d.link (rel='stylesheet', href='/style.css', type='text/css')

    with d.body (style='foo'):
    d.a ('other stuff on another page', href='/other.html')
    d.p ('stuff on this page')
    with d.ul:
    for i in items:
    with d.li:
    d.a (str(i), href=str(i) + '.html')
    return d.to_string()


    class BaseTemplate(object):
    """ template inheritance example """

    title = 'base'

    def render(self):
    d = Doc()
    with d.html:

    with d.head:
    d.title (self.title)
    d.link (rel='stylesheet', href='/style.css', type='text/css')

    with d.body (style='foo'):
    with d.div (id='header'):
    self.header(d)
    with d.div (id='content'):
    self.content(d)
    with d.div (id='footer'):
    self.footer(d)

    return d.to_string()

    def header(self, d):
    pass

    def content(self, d):
    pass

    def footer(self, d):
    d.span('Never read this content')


    class ItemTemplate(BaseTemplate):

    title = 'item view'

    def __init__(self, items):
    self.items = items

    def header(self, d):
    d.p('view of %s items' % len(self.items))

    def content(self, d):
    with d.ul:
    for i in self.items:
    with d.li:
    d.a (str(i), href=str(i) + '.html')

    print example_template(range(10))
    print
    print ItemTemplate(range(10)).render()