Jinja: Preserving whitespace prefix in multiline strings

Created on 16 Feb 2013  ·  12Comments  ·  Source: pallets/jinja

In the StringTemplate engine - which I've used in some projects to emit C code - whitespace prefixes are automatically added in the output lines:

PrintCFunction(linesGlobal, linesLocal) ::= <<
void foo() {
    if (someRuntimeFlag) {
        <linesGlobal>
        if (anotherRuntimeFlag) {
            <linesLocal>
        }
    }
}
>>

When this template is rendered in StringTemplate, the whitespace prefixing the multilined linesGlobal and linesLocal strings, is copied for all the lines emitted. The generated C code is e.g.:

void foo() {
    if (someRuntimeFlag) {
        int i;
        i=1;   // <=== whitespace prefix copied in 2nd
        i++;   // <=== and 3rd line
        if (anotherRuntimeFlag) {
            int j=i;
            j++; //  <=== ditto
        }
    }
}

I am new to Jinja2 - and tried to replicate this:

#!/usr/bin/env python
from jinja2 import Template

linesGlobal='\n'.join(['int i;', 'i=1;'])
linesLocal='\n'.join(['int j=i;', 'j++;'])

tmpl = Template(u'''\
void foo() {
    if (someRuntimeFlag) {
        {{linesGlobal}}
        if (anotherRuntimeFlag) {
            {{linesLocal}}
        }
    }
}
''')

print tmpl.render(
    linesGlobal=linesGlobal,
    linesLocal=linesLocal)

...but saw it produce this:

void foo() {
    if (someRuntimeFlag) {
        int i;
i=1;
        if (anotherRuntimeFlag) {
            int j=i;
j++;
        }
    }
}

...which is not what I want. I managed to make the output emit proper whitespace prefixes with this:

...
if (someRuntimeFlag) {
    {{linesGlobal|indent(8)}}
    if (anotherRuntimeFlag) {
        {{linesLocal|indent(12)}}
    }
}

...but this is arguably bad, since I need to manually count whitespace for every string I emit...

Is there a better way that I am missing?

Most helpful comment

When emitting YAML or Python, this becomes pretty crucial.

All 12 comments

I am interested in basically the same thing. The issue has also come up at stackoverflow: http://stackoverflow.com/questions/10821539/jinja-keep-indentation-on-include-or-macro

+1

Also true when using the form:

    {{linesGlobal|join('\n')}}

This form does not behave as one would expect - since jinja2 is the one emmitting the newlines, it should make sure that they remain aligned with the last indentation level.

This would be very nice to have! It would lead to much nicer templates and rendered output at the same time.

Why not create another whitespace option similar to {%+ and {%- that prepends the current indentation on whatever it evaluates to? Could be {%= or {%|

+1

+1

needed here for templating API blueprint documentation:

{% macro entity_one() -%}
{
    "one": 1,
    "two": 2
}
{% endmacro -%}

+ Response 200 (application/json):

        {
            "entity": [
                {{ entity_one() }}
            ]
        }

is rendered now :

+ Response 200 (application/json):

        {
            "entity": [
                {
    "one": 1,
    "two": 2

}

            ]
        }

When emitting YAML or Python, this becomes pretty crucial.

Ran into the same problem.

Is there a workaround for now other than defining a macro for every included tempkate and manually entering the indentation?

Sorry for reviving this old issue, I just came across the same problem and googling brought me here. After some more looking around I found that by now there is a nice way to achieve this through the indent filter

@kaikuchn Thank you, dude! It works.

@kaikuchn , @Cigizmoond-Vyhuholev Guys, I am not sure I follow... as you can see in my original report at the top, I do mention a workaround with the indent filter - but also clearly state that it doesn't address the issue in the simple and powerful way that e.g. StringTemplate does, because it forces you to count indentation spaces every time you need to emit a block of lines... If you have to do that and you are generating code of any form (C, Python, whatever) you'll very quickly just abandon the process altogether...

Then again, I may have misunderstood what you meant... Can you share exactly how you'd implement my original requirement shown at the top? i.e. generate the same kind of output with Jinja2 syntax? This is what I don't like...

if (someRuntimeFlag) {
    {{linesGlobal|indent(8)}}
    if (anotherRuntimeFlag) {
        {{linesLocal|indent(12)}}
    }
}

...because I need to count the "8" and "12" in each and every template where I emit code. In comparison, in StringTemplate...

PrintCFunction(linesGlobal, linesLocal) ::= <<
void foo() {
    if (someRuntimeFlag) {
        <linesGlobal>
        if (anotherRuntimeFlag) {
            <linesLocal>
        }
    }
}
>>

I noticed that #919 was closed due to a code change which would apparently require some major refactoring of the PR. However I would really like to see this feature implemented in my favourite templating engine.

If this is still something the core devs would like to see implemented (the community sure wants it as it is the PR and open issue with the most thumbs up) I would be eager to help and maybe even try to implement this on my own.

Would also love to see this feature. I'll note that the indent workaround can't be used when the indentation level isn't known. e.g.:

{{field.type}} {{field.name}}; {{field.comment|indent(??)}}

Where the indentation level to be preserved depends on the length of the first two value.

I have one hypothesis. What about left-trimming 1st level of indentation in block declaration?

Example:

{%- macro some_yaml_block(sub_block) ~%}
  label indented with how many spaces: 2
  amount of spaces that will be trimmed due to that "~" char above: 2
  {%- if sub_block ~%}
    "yay! we can indent ifs!": true
    the minimal indent in this if block is 2, so:
      this extra-indented value is still indented properly
  {%- endif %}
{%- endmacro -%}

now we invoke that here:
  {{ some_yaml_block(true) }}

The rendering would be:

now we invoke that here:
  label indented with how many spaces: 2
  amount of spaces that will be trimmed due to that "~" char above: 2
  "yay! we can indent ifs!": true
  the minimal indent in this if block is 2, so:
    this extra-indented value is still indented properly

Basically, when finishing a block with ~%}, Jinja would:

  1. Remove the 1st characters if it is a EOL sequence, as indicated in the newline_sequence Environment parameter.
  2. Render the content internally.
  3. Count how many common whitespace characters prefix those rendered lines.
  4. Strip them from the beginning of each line.

If you, later, need to include this block with some specific indentation, all you need is to call it like some_yaml_block|indent. Since the indent was normalized at block declaration, you can later specify it without problem. And the block would behave consistently across calls.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

glasserc picture glasserc  ·  4Comments

harobed picture harobed  ·  6Comments

Xion picture Xion  ·  5Comments

hvnsweeting picture hvnsweeting  ·  4Comments

The-Compiler picture The-Compiler  ·  4Comments