views:

20

answers:

1

Hi I've been trying to create an extension for jinja2 that would join multiple items with a separator, while skipping items (template fragments) that evaluate to whitespace.

There are several of those fragments and you never know in advance which ones will be non-empty and which ones will.

Sounds like a trivial task, but I had real hard time making this to work in jinja2. Maybe part of the reason is that jinja does not allow to define custom template nodes.

Would you have any suggestions? Below is a snippet that will do the parsing job, but it's missing the evaluation part.

class JoinerExtension(Extension):
    """Template tag that joins non-whitespace (string) items
    with a specified separator

    Usage syntax:

    {% joinitems separator='|' %}
    ....
    {% separator %}
    ....
    {% separator %}
    ....
    {% endjoinitems %}

    where value of "separator" within the joinitems tag
    can be an expression, not necessarily a sting
    """

    tags = set(['joinitems'])

    def parse(self, parser):
        """parse function for the 
        joinitems template tag
        """
        lineno = next(parser.stream).lineno

        #1) read separator
        separator = None
        while parser.stream.current.type != 'block_end':
            name = parser.stream.expect('name')
            if name.value != 'separator':
                parser.fail('found %r, "separator" expected' %
                            name.value, name.lineno,
                            exc=TemplateAssertionError)

            # expressions
            if parser.stream.current.type == 'assign':
                next(parser.stream)
                separator = parser.parse_expression()
            else:
                var = parser.stream.current
                parser.fail('assignment expected after the separator' %
                            var.value, var.lineno,
                            exc=TemplateAssertionError)

        #2) read the items
        items = list()
        end_tags = ['name:separator', 'name:endjoinitems']
        while True:
            item = parser.parse_statements(end_tags)
            items.append(item)
            if parser.stream.current.test('name:separator'):
                next(parser.stream)
            else:
                next(parser.stream)
                break
+2  A: 

Would the built-in joiner class potentially work? Here is a simple example from the documentation.

{% set pipe = joiner("|") %}
{% if categories %} {{ pipe() }}
    Categories: {{ categories|join(", ") }}
{% endif %}
{% if author %} {{ pipe() }}
    Author: {{ author() }}
{% endif %}
{% if can_edit %} {{ pipe() }}
    <a href="?action=edit">Edit</a>
{% endif %}

You mentioned that it is not known ahead of time which fragments will be empty; perhaps it is possible to store the value of each fragment in a variable before "displaying" it so that you can determine which fragments are indeed empty. For example:

{% set pipe = joiner("|") %}
{% set fragment = gen_fragment1() %}
{% if fragment|trim is not "" %} 
    {{ pipe() }} {{ fragment }}
{% endif %}
...

You could even encapsulate the above pattern in a macro to reduce repetition:

{% set pipe = joiner("|") %}
{{ print_if_notblank(pipe, gen_fragment1()) }}
{{ print_if_notblank(pipe, gen_fragment2()) }}
...

where print_if_notblank is a macro defined as:

{% macro print_if_notblank(separator, content) %}
    {% if content|trim is not "" %}
        {{ separator() }} {{ content }}
    {% endif %}
{% endmacro %}
crewbum
that will work, using the builtin for now with strategically placed {{pipe()}} calls.
Evgeny