Skip to content

Commit 3083eba

Browse files
committed
short form for dynamic attributes
1 parent 1851d5c commit 3083eba

File tree

3 files changed

+54
-4
lines changed

3 files changed

+54
-4
lines changed

README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,34 @@ is equivalent to:
4646
> [!TIP]
4747
> Computed attributes work with partials as well as standard HTML tags.
4848
49+
#### Short form
50+
51+
If value of a dynamic attribute is the same as its name, you can omit the value.
52+
53+
For example
54+
```html
55+
<div style%>Text</div>
56+
```
57+
is equivalent to:
58+
```erb
59+
<div style%="style">Text</div>
60+
```
61+
which in turn is equivalent to:
62+
```erb
63+
<div style="<%= style %>">Text</div>
64+
```
65+
66+
Since `class` is a Ruby keyword, it's treated specially:
67+
```html
68+
<div class%>Text</div>
69+
```
70+
is equivalent to:
71+
```erb
72+
<div class="<%= binding.local_variable_get('class') %>">Text</div>
73+
```
74+
75+
> [!TIP]
76+
> Short form is especially useful when you want to apply a `class` and `style` attribute to a partial root.
4977
5078
### Partials
5179

lib/theo-rails/theo.rb

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
module Theo
22
module Rails
3-
ATTRIBUTE_NAME = /[\w\-:@]+/
3+
ATTRIBUTE_NAME = /(?<name>[a-z][\w\-:@]*)/
44
ATTRIBUTE_VALUE = /(?:(?:"(?<value>[^"]*)")|(?:'(?<value>[^']*)'))/
5-
ATTRIBUTE = /(?:(?:(?<name>#{ATTRIBUTE_NAME.source})\s*=\s*#{ATTRIBUTE_VALUE.source})|(?<name>#{ATTRIBUTE_NAME.source}))/
6-
DYNAMIC_ATTRIBUTE = /(?:(?<name>#{ATTRIBUTE_NAME.source})\s*%=\s*#{ATTRIBUTE_VALUE.source})/
5+
ATTRIBUTE = /(?:(?:#{ATTRIBUTE_NAME.source}\s*=\s*#{ATTRIBUTE_VALUE.source})|#{ATTRIBUTE_NAME.source})/
6+
DYNAMIC_ATTRIBUTE = /(?:(?:#{ATTRIBUTE_NAME.source}\s*%=\s*#{ATTRIBUTE_VALUE.source})|(?:#{ATTRIBUTE_NAME.source}%))/
7+
RESERVED_ATTRIBUTE_NAME = %w[alias and begin break case class def do else elsif end ensure false for if in module next nil not or redo rescue retry return self super then true undef unless until when while yield].to_set
78
ATTRIBUTES = /(?<attrs>(?:\s+#{ATTRIBUTE.source})*)/
89
LITERAL_ATTRIBUTES = %i[path as yields collection].freeze
910
PARTIAL_TAG = /(?<partial>_\w+)/
@@ -16,7 +17,16 @@ module Rails
1617
class Theo
1718
def process(source)
1819
# Attributes
19-
source = source.gsub(DYNAMIC_ATTRIBUTE, '\k<name>="<%= \k<value> %>"')
20+
source = source.gsub(DYNAMIC_ATTRIBUTE) do |_|
21+
match = Regexp.last_match
22+
23+
name = match[:name]
24+
25+
# See https://island94.org/2024/06/rails-strict-locals-local_assigns-and-reserved-keywords for more info
26+
value = match[:value] || (RESERVED_ATTRIBUTE_NAME.include?(name) ? "binding.local_variable_get('#{name}')" : name)
27+
28+
"#{name}=\"<%= #{value} %>\""
29+
end
2030

2131
# Partials
2232
source.gsub(TEMPLATE) do |_|

spec/theo-rails/theo_spec.rb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,18 @@
1717
include_examples 'theo to erb', 'evaluates dynamic attribute',
1818
%(<a href%="2 % 2 == 0 ? '/even' : '/odd'">Link</a>),
1919
%(<a href="<%= 2 % 2 == 0 ? '/even' : '/odd' %>">Link</a>)
20+
21+
include_examples 'theo to erb', 'evaluates shortened dynamic attribute',
22+
%(<a href%>Link</a>),
23+
%(<a href="<%= href %>">Link</a>)
24+
25+
include_examples 'theo to erb', 'evaluates shortened reserved dynamic attribute',
26+
%(<div class%>Content</div>),
27+
%(<div class="<%= binding.local_variable_get('class') %>">Content</div>)
28+
29+
include_examples 'theo to erb', 'ignores trim symbols',
30+
%(<%- variable -%>),
31+
%(<%- variable -%>)
2032
end
2133

2234
context 'partial' do

0 commit comments

Comments
 (0)