-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Preserving whitespace prefix in multiline strings #178
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:
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:
is rendered now :
|
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 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...
...because I need to count the "8" and "12" in each and every template where I emit code. In comparison, in StringTemplate...
|
Fixes pallets#178 Blocks now support a new syntax '{%* ... %}' that alings the indentation of multiline string with the block statement itself. This is especially useful with YAML or other languages where indentation matter. Example: labels.j2: ``` tla: webtool env: {{ env }} ``` deployment.yaml.j2: ``` apiVersion: extensions/v1beta1 kind: Deployment metadata: labels: {% include 'labels.j2' %} name: webtool spec: selector: matchLabels: {% include 'labels.j2' %} strategy: type: Recreate ``` ...renders to broken YAML: ``` apiVersion: extensions/v1beta1 kind: Deployment metadata: labels: tla: webtool env: qa name: webtool spec: selector: matchLabels: tla: webtool env: qa strategy: type: Recreate ``` deployment_new_syntax.yaml.j2: ``` apiVersion: extensions/v1beta1 kind: Deployment metadata: labels: {%* include 'labels.j2' %} name: webtool spec: selector: matchLabels: {%* include 'labels.j2' %} strategy: type: Recreate ``` ...renders correctly: ``` apiVersion: extensions/v1beta1 kind: Deployment metadata: labels: tla: webtool env: qa name: webtool spec: selector: matchLabels: tla: webtool env: qa strategy: type: Recreate ```
Fixes pallets#178 Blocks now support a new syntax '{%* ... %}' that alings the indentation of multiline string with the block statement itself. This is especially useful with YAML or other languages where indentation matter. Example: labels.j2: ``` tla: webtool env: {{ env }} ``` deployment.yaml.j2: ``` apiVersion: extensions/v1beta1 kind: Deployment metadata: labels: {% include 'labels.j2' %} name: webtool spec: selector: matchLabels: {% include 'labels.j2' %} strategy: type: Recreate ``` ...renders to broken YAML: ``` apiVersion: extensions/v1beta1 kind: Deployment metadata: labels: tla: webtool env: qa name: webtool spec: selector: matchLabels: tla: webtool env: qa strategy: type: Recreate ``` deployment_new_syntax.yaml.j2: ``` apiVersion: extensions/v1beta1 kind: Deployment metadata: labels: {%* include 'labels.j2' %} name: webtool spec: selector: matchLabels: {%* include 'labels.j2' %} strategy: type: Recreate ``` ...renders correctly: ``` apiVersion: extensions/v1beta1 kind: Deployment metadata: labels: tla: webtool env: qa name: webtool spec: selector: matchLabels: tla: webtool env: qa strategy: type: Recreate ```
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
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
If you, later, need to include this block with some specific indentation, all you need is to call it like |
This feature would be super useful for folks using jinja to template config files which nowadays almost always involves yaml somewhere. Why not use yaml-specific tooling you ask? A lot of tools aren't using just yaml but a mix of different config formats. For instance butane the config format for Fedora CoreOS uses yaml itself but needs to play with other tools like systemd that use other file formats for their configuration. Using jinja to template this out and assemble the file using |
For anyone interested I've written an extension that works around this long standing issue so that I may use |
+1 should be optional, similar to the probably this should throw error on mixed indent (tabs vs spaces), similar to python related issue on stackoverflow: Jinja2 correctly indent included block
|
@davidism Do you have any comment on #1456 which helps with this? This issue is 10 years old and #919 was closed. Can #178 (comment) be included as the fix? |
@rightaway #178 (comment) would only work for include blocks though. Getting this to work for extends would also be useful and I‘ve tinkered a bit with this already but it isn’t pretty. This is to say that fixing the underlying problem of |
I think that the solution that I've started working on some time ago in #1456 would be an elegant way of solving this issue, essentially only requiring adding the I only had a weekend to work on it though and I'm not very familiar with the inner workings of everything, nonetheless, if someone could finish that work or provide some input on the PR properly that would be awesome. |
I believe I found an elegant way to work around this: by calling The details can be found in this demo: import jinja2
def j2_gen(template, context, key_for_extras="__EXTRAS"):
"""Call `template.generate` with extras."""
extras = context.get(key_for_extras, {})
extras.update({
"current_column": 0
})
newline_sequence = template.environment.newline_sequence
for chunk in template.generate(
{**context, key_for_extras: extras}
):
yield chunk
index = chunk.rfind(newline_sequence)
if index == -1:
extras["current_column"] += len(chunk)
else:
extras["current_column"] = (
len(chunk) - len(newline_sequence) - index
)
t = jinja2.Template(
"""\
{% macro render_lines() -%}
ABC
XYZ
{%- endmacro -%}
{{" " * indent}}{{render_lines() | indent(__EXTRAS.current_column)}}
"""
)
for i in range(3):
print("---+" * 4)
print("".join(list(j2_gen(t, {"indent": 4 * i})))) It prints the following text:
I think whether to introduce a similar mechanism to Jinja is open to discuss, but adding some tests to ensure this always works seems to be a good idea. |
btw, this works in nickel
def concat(str_array, log=false):
res = []
for s in str_array:
if log:
print("log:", s)
res.append(s)
return res |
I was in a similar situation as @ttsiodras and after some hours of searching and experimenting I finally understood the problem and the solution became evident. The requirement is to write several lines all having the same indentation. A template engine takes data and a pattern as input and produces a string as output. If the data is a list and one wants to use the same pattern with each element of the list, the solution is to iterate over the list! Data: linesGlobal = [
'int i;',
'i=1;'
]
linesLocal = [
'int j=i;',
'j++;'
] Template: void foo() {
if (someRuntimeFlag) {
{%- for line in linesGlobal %}
{{line}}
{%- endfor %}
if (anotherRuntimeFlag) {
{%- for line in linesLocal %}
{{line}}
{%- endfor %}
}
}
} Generated string: void foo() {
if (someRuntimeFlag) {
int i;
i=1;
if (anotherRuntimeFlag) {
int j=i;
j++;
}
}
} |
@mardukbp Point is data is not a list. It's a multiline block of text that has possibly been generated by the application of another template. |
@maxime-esa That is precisely the situation that brought me here. The multiline string can be converted into data (e.g. with splitlines) and after removing its original indentation it can be used in a template with any level of indentation. What we see here is that templates represented as strings are not easy to compose. But data is. That is why programming languages with macros are better suited for code generation (e.g. Clojure or Elixir). |
In the StringTemplate engine - which I've used in some projects to emit C code - whitespace prefixes are automatically added in the output lines:
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.:
I am new to Jinja2 - and tried to replicate this:
...but saw it produce this:
...which is not what I want. I managed to make the output emit proper whitespace prefixes with this:
...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?
The text was updated successfully, but these errors were encountered: