Project

General

Profile

Feature #15538

Erb indenting / unindenting

Added by kke (Kimmo Lehto) over 1 year ago. Updated over 1 year ago.

Status:
Third Party's Issue
Priority:
Normal
Assignee:
-
Target version:
-
[ruby-core:91103]

Description

In Erb, would it be possible to add a new tag that would indent the following content to match the depth of the tag? The tag could be <%~ (to resemble the <<~EOS squiggy heredoc).

Reason

Something like this would be easy to follow:

1
<%- [2, 3, 4].each do |num| -%>
  <%- unless num == 3 -%>
    <%= num %>
  <%- end -%>
<%- end -%>
5

But unfortunately it will render with "extra" indentation:

1
    2
    4
5

Currently, to avoid this, you have to write your template using either no indentation:

1
<%- [2, 3, 4].each do |num| -%>
<%- unless num == 3 -%>
<%= num %>
<%- end -%>
<%- end -%>
5

Or a weird jumpy indentation:

1
<%- [2, 3, 4].each do |num| -%>
  <%- unless num == 3 -%>
<%= num %>
  <%- end -%>
<%- end -%>
5

With the <%~ it could be written as:

1
<%~ [2, 3, 4].each do |num| -%>
  <%- unless num == 3 -%>
    <%= num %>
  <%- end -%>
<%~ end -%>
5

And it would output as desired without the "extra" indentation:

1
2
4
5

Another example:

<%= "abcd" %> <%~ [1.2.3].each do |num| -%>
                <%= num %>
              <%~ end -%>

would produce:

abcd 1
     2
     3

Using with =

It would also be handy if the ~ could be used in <%= statements, perhaps as <%~=. This would be excellent for example when templating YAML's:

<%- bars = %w(abc def)" -%>
foo:
  bar: 
    <%~= bars.map { |bar| "- #{bar}\n" } %>

Which would reindent the statement outcome to produce something like:

foo:
  bar:
    - abc
    - def

This would require these new tags:

  1. <%~ begin a code block and begin or end reindentation mode. content produced inside the block will be reindented to the depth of the < character in <%~. If the indentation mode was already active due to a previous <%~, it ends the indentation mode.
  2. <%~= like regular <%= but multiline strings will be reindented to the column of the < character

Updated by k0kubun (Takashi Kokubun) over 1 year ago

  • Status changed from Open to Feedback

It's hard to understand your proposal for me. Could you fix the following two parts first?

  1. Do not use "...." in the template. We are talking about a feature to change indentation and the indentation you imagine in "...." is not clear to me.
  2. Clarify the trim_mode you intended to use for each template, and make sure your template actually matches the result you show. At least your first template and its result does not seem to match in the default trim_mode.
$ cat a.rb
src = <<-EOS
- horse
<% ['cat', 'dog', 'goldfish'].each do |pet| %>
  <% unless pet == 'goldfish' %>
    - <%= pet %>
  <% end %>
<% end %>
EOS

puts ERB.new(src).result
$ ruby -rerb a.rb
- horse


    - cat



    - dog




Updated by jeremyevans0 (Jeremy Evans) over 1 year ago

kke (Kimmo Lehto) wrote:

In Erb, would it be possible to add a new trim mode that would indent the following content to match the depth of the tag? The tag could for example be <%| and it would be enabled using Erb.new(template, trim_mode: '|')

I think if we are going to add support for <%| and <%|= tags to ERB, it would be a better idea to use them to support output of captured blocks, as Erubi offers optional support for (https://github.com/jeremyevans/erubi#capturing). This allows you to do things like:

<%|= form do %>
  <input>
<%| end %>

and have it output:

<form>
  <input>
</form>

assuming an appropriate definition of form. This is basically what Rails does for normal <%= tags, but without attempting to parse ruby code with regexp (https://github.com/rails/rails/blob/5-2-stable/actionview/lib/action_view/template/handlers/erb/erubi.rb#L45-L61).

I'm not opposed to the idea of using a trim mode to modify indentation, but I think it should use a character other than | if support is added for it.

Updated by kke (Kimmo Lehto) over 1 year ago

  • Description updated (diff)

Clarified and added some examples

Updated by kke (Kimmo Lehto) over 1 year ago

  • Description updated (diff)

(missing closing parenthesis)

Updated by znz (Kazuhiro NISHIYAMA) over 1 year ago

How about following style?

1
<%- [2, 3, 4].each do |num| -%>
  <%- unless num == 3 -%>
<%=     num %>
  <%- end -%>
<%- end -%>
5

Updated by k0kubun (Takashi Kokubun) over 1 year ago

Thanks for the update. You didn't answer 2 but I guess you're using trim_mode: '-' or something including "-". Note that your "extra indentation" result is still not valid one since it should have 4 spaces in the real world. Do not treat your example indentation as trivial while talking about the indentation feature.

Now I'm kind of understanding what you want to achieve. But the details of what you want is not 100% clear to me, and with a real implementation your first example does not seem to work as you intended. So, let me ask questions for each feature.

<%|

For this,

1
<%| [2, 3, 4].each do |num| -%>
  <%- unless num == 3 -%>
    <%= num %>
  <%- end -%>
<%- end -%>
5
  • When does the de-indentation end?
    • When ERB finds the second end matching each do? If so, as it's super hard to implement (note that ERB is NOT aware of Ruby expressions inside "<%"s, so you should give up detecting the "matching" end), please consider changing the syntax to the following one like Erubi's capture syntax (by having second "<%|", ERB will be able to know when it should finish de-indentation):
1
<%| [2, 3, 4].each do |num| -%>
  <%- unless num == 3 -%>
    <%= num %>
  <%- end -%>
<%| end -%>
5
  • Assuming the above suggestion is accepted, what's the expected result of the following template?
- foo
  <%| ['bar', 'baz'].each do |text| -%>
    - <%= text %>
  <%| end -%>

maybe this 2 spaces, not 0 space or 4 spaces? (because the first "<%|" is indented with 2 spaces, any indentation longer than 2 is shrunk to 2 spaces until it reaches the second "<%|")

- foo
  - bar
  - bar
  • What happens when non-space is in the same line before "<%|"? Can ERB raise an error for it?
  foo <%| 3.times do |i| %>
    <%= i %>
  <%| end %>
  • Could you consider not using "<%|" for this since Erubi is using it for capturing as @jeremyevans said?

<%|=

  • Same here. Please consider using another tag literal.
  • What happens if "<%=" is placed inside "<%|" area? Indentation the same as "<%|" + extra indentation for "<%="? Can we make it an error because it's too hard to expect the behavior?

Updated by kke (Kimmo Lehto) over 1 year ago

  • Description updated (diff)

Fixed the 2 -> 4 indentation in the bad example and added the "stop indenting" closing tag |%> which would be fine by me.

<%|= inside <%| would result in:

example code:

1
<%|- (2..3).each do |num| -%>
  <%= num %>: <%|= "foo\nbar\n" |%>
<%|- end -%>
4

expected output:

1
2: foo
    bar
3: foo
    bar
4

This would be excellent when rendering partials.

The line:

  <%= num %>: <%|= "foo\nbar\n" |%>

Would generate:

  3: foo
      bar

which would be reindented by the surrounding block and end up as:

3: foo
    bar

This is bit of a problem when the <%|= is used without any leading "indentation reference point":

1
<%|- (2..3).each do |num| -%>
  <%|= num |%>
<%| end %>
4

Because reindentation works by finding the least indented row and use that as the reference point, the lines with <%|= num |%> do not contain anything less indented, and thus will be reindented by the surrounding block to the left edge, but I think the outcome is still as expected, the <%|= was just used pointlessly.

Updated by k0kubun (Takashi Kokubun) over 1 year ago

Most of "foo"-"bar" examples seem to be indented strangely. I guess you meant the following one for all like this ("f" and "b" start with the same column), right?

3: foo
   bar

Now, you introduced 4 different new tokens (do not assume "<%|-" is transparently supported when "<%|" is supported):

  • <|
  • <|-
  • <|=
  • |>

Thank you for providing real examples (it's nice), but also,

  • Could you summarize the effect of each token?
  • I'm still not understanding why 4 new tokens are needed. Can you eliminate 1 or 2 from them?

Updated by kke (Kimmo Lehto) over 1 year ago

  • Description updated (diff)

Yes, it appears I made an indentation error in my foo-bar examples. The indentation of bar was supposed to be as deep as on foo.

I added a tag summary.

Updated by k0kubun (Takashi Kokubun) over 1 year ago

  • Status changed from Feedback to Third Party's Issue

Thank you for the update.

As @jeremyevans said, the tags conflict with ones already used by Erubi. While you explained <%| as "capturing a block", I think what your <%| does in your examples seems to behave differently from Erubi's capture.

As your <%| is conflicting with a widely-used ERB-like gem Erubi, I don't think it's a good idea to introduce your suggestion as <%|. But you preferred <%| even after I said "Could you consider not using <%|". If we really need it, I think that should be achieved as an unofficial gem so that we do not break existing ecosystem globally.

So I prepared this for you https://github.com/k0kubun/erb-indent. Since your specification does not make a full sense to me, I did not implement full features of your suggestion. For example, why would we need |%> for the <%|= line? Only you can understand such kind of details, so please complete the implementation by yourself. It's already demonstrating how we could implement extra tag like <%| and only details of <%| features are missing. Thank you.

Updated by kke (Kimmo Lehto) over 1 year ago

  • Description updated (diff)
  • Subject changed from Erb indenting / unindenting trim mode to Erb indenting / unindenting

Perhaps <%~ would be good as it resembles the squiggly heredoc <<~EOB

I'll try to improve on your PoC.

It seems to only work when the <%| is in the beginning of the line, for anything else it fails. I have yet to find a way to find the index of stag, but I have a hunch it has to be done in the line scanner and yielded there.

Also, something like this should work:

<%= "abcd" %> <%~ [1.2.3].each do |num| -%>
                <%= num %>
              <%~ end -%>

which would produce:

abcd 1
     2
     3

Updated by k0kubun (Takashi Kokubun) over 1 year ago

Perhaps <%~ would be good as it resembles the squiggly heredoc <<~EOB

Thanks to rethink that part. I feel it's a good idea to make it similar to <<~.

The proposal change makes your idea closer to be added to the ERB core, but we still need working code from you because the behaviors on full edge cases are hard to understand from the given examples. Let's try implementing that in erb-indent.gem (or a gem forked from it if you like) first so that we can try/use the feature without waiting for agreements and Ruby releases.

It seems to only work when the <%| is in the beginning of the line, for anything else it fails. I have yet to find a way to find the index of stag, but I have a hunch it has to be done in the line scanner and yielded there.

Try override #make_scanner (and return a scanner inherited from existing one) if you need to change the scanner's behavior, like I did in #make_compiler.

Also available in: Atom PDF