Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Jinja keep indentation on include or macro

I am wondering if there is any way to keep the indentation with jinja when adding a include or macro inside a file. I would like to use jinja to generating a code file. An example would be

File: class.html

class MyClass:
     def someOp():
         pass

     {% include "someOp.html" %}

File: someOp.html

def someOp2():
    pass

The result of the template should be:

class MyClass:
     def someOp():
         pass

     def someOp2():
         pass

If there any way to make jinja prepend the indent before the include tag for each line in the included file? Or is there any way to customize jinja to do this?

like image 438
Razvi Avatar asked May 30 '12 17:05

Razvi


People also ask

What is include in Jinja?

'Include' statement allows you to break large templates into smaller logical units that can then be assembled in the final template. When you use include you refer to another template and tell Jinja to render the referenced template. Jinja then inserts rendered text into the current template.

What is the difference between Jinja and Jinja2?

Jinja, also commonly referred to as "Jinja2" to specify the newest release version, is a Python template engine used to create HTML, XML or other markup formats that are returned to the user via an HTTP response.

What is a macro in Jinja?

Macros are similar to functions in many programming languages. We use them to encapsulate logic used to perform repeatable actions. Macros can take arguments or be used without them. Inside of macros we can use any of the Jinja features and constructs. Result of running macro is some text.


3 Answers

One way is to wrap the include in a macro, then because the macro is a function, its output can be passed through the indent filter:

class MyClass:     def someOp():         pass      {% macro someop() %}{% include "someOp.html" %}{% endmacro %}     {{ someop()|indent }} 

By default 'indent' indents 4 spaces and does not indent the first line, you can use e.g. 'indent(8)' to indent further, see http://jinja.pocoo.org/docs/templates/#list-of-builtin-filters for more details.

If what you're including is defined as a macro to begin with then the further wrapper macro is not needed, and you can jump straight to using the indent filter.

like image 92
AmirS Avatar answered Sep 24 '22 02:09

AmirS


I was looking in Jinja2 to achieve the same and got to conclusion aligning multi-line block indentation with the originating Jinja statement is not possible currently.

I've posted a small PR to Jinja to add a new syntax {%* ... %} and {{* ... }} that does exactly this. See the PR for details:

https://github.com/pallets/jinja/pull/919

like image 31
wigwam Avatar answered Sep 24 '22 02:09

wigwam


It would be easier if Jinja provided the facility. It looks like some work was done on this but the issue is currently closed (20 Nov 2019) and the pull request hasn't yet been merged. It could be because things get tricky quite quickly with indents (think of tabs and spaces, for example.)

The following is a simple solution I've found effective for generating Python code which, of course, needs to handle indenting well. It copes with files that use spaces for indentation.

auto_indent() detects the indent level of a variable in a host template, then applies that indent to a piece of text.

import os
import itertools
import jinja2


def indent_lines(text_lines: list, indent: int):
    return [' ' * indent + line for line in text_lines]


def matching_line(s, substring):
    lineno = s[:s.index(substring)].count('\n')
    return s.splitlines()[lineno]


def is_space(c):
    return c == ' '


def indentation(line: str) -> int:
    initial_spaces = ''.join(itertools.takewhile(is_space, line))
    return len(initial_spaces)


def auto_indent(template: str, placeholder: str, content_to_indent: str):
    placeholder_line = matching_line(template, '{{ ' + placeholder + ' }}')
    indent_width = indentation(placeholder_line)
    lines = content_to_indent.splitlines()
    first_line = [lines[0]]  # first line uses placeholder indent-- no added indent
    rest = indent_lines(lines[1:], indent_width)
    return os.linesep.join(first_line + rest)

Example:

action_class = """\
class Actions:

    def __init__(self):
        pass
    
    def prequel(self):
        pass
    
    {{ methods }}
    
    def sequel(self):
        pass
"""

inserted_methods = """\
def create_branch():
    pass

def merge_branch():
    pass
"""


if __name__ == '__main__':
    indented_methods = auto_indent(action_class, 'methods', inserted_methods)
    print(jinja2.Template(action_class).render(methods=indented_methods))

Example output:

>>> python indent.py
class Actions:

    def __init__(self):
        pass
    
    def prequel(self):
        pass
    
    def create_branch():
        pass
    
    def merge_branch():
        pass
    
    def sequel(self):
        pass
like image 24
Nick Avatar answered Sep 24 '22 02:09

Nick