rux 1.1.1 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +16 -0
- data/Gemfile +1 -1
- data/README.md +91 -1
- data/bin/ruxc +21 -4
- data/lib/rux/ast/attr_node.rb +22 -0
- data/lib/rux/ast/attrs_node.rb +30 -0
- data/lib/rux/ast/fragment_node.rb +15 -0
- data/lib/rux/ast/root_node.rb +19 -0
- data/lib/rux/ast/ruby_attr_node.rb +28 -0
- data/lib/rux/ast/string_node.rb +4 -2
- data/lib/rux/ast/tag_node.rb +10 -1
- data/lib/rux/ast/text_node.rb +2 -1
- data/lib/rux/ast.rb +10 -5
- data/lib/rux/buffer.rb +21 -3
- data/lib/rux/default_tag_builder.rb +15 -6
- data/lib/rux/default_visitor.rb +93 -19
- data/lib/rux/lex/states.csv +22 -11
- data/lib/rux/parser.rb +102 -24
- data/lib/rux/ruby_lexer.rb +1 -0
- data/lib/rux/rux_lexer.rb +15 -3
- data/lib/rux/version.rb +1 -1
- data/lib/rux/visitor.rb +16 -0
- data/spec/parser/attributes_spec.rb +97 -0
- data/spec/parser/fragment_spec.rb +47 -0
- data/spec/parser/html_safety_spec.rb +13 -0
- data/spec/parser/slot_spec.rb +40 -0
- data/spec/parser/tag_spec.rb +174 -0
- data/spec/parser_spec.rb +17 -242
- data/spec/render_spec.rb +89 -6
- data/spec/spec_helper.rb +19 -22
- data/spec/support/components/args_component.rb +14 -0
- data/spec/support/components/data_component.rb +11 -0
- data/spec/support/components/table_component.rb +23 -0
- data/spec/support/components/test_component.rb +7 -0
- data/spec/support/view_component/base.rb +59 -0
- metadata +17 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fba8846934ea39d67f4705ce5a0ec7a477e65eccc5fc5b63a6ff1f6a2233e9ca
|
4
|
+
data.tar.gz: a27256e117f07fe67fe286bc0730b265bccd91e98e18c6944aa968862b8c0e4b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 62c36cfb6e05f6379f659e89c6bc3bea79144ff6eae9de80491597686b967daabe6c84b5e55dbf24ef104b9e7267aa186c370c9fae18ba8c1fcf038a3805db20
|
7
|
+
data.tar.gz: 3c7c4a4b5f7ac00a344358df1e02f7bf5850aa76b0974f466b6759973f6b8400700cfc2050e6c9e1debff42ac8ad56eba9e5b018261bfa4e6dfd4b344dbbca70
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,19 @@
|
|
1
|
+
# 1.2.0
|
2
|
+
* Improve output safety.
|
3
|
+
- HTML tags are now automatically escaped when they come from Ruby code.
|
4
|
+
* Add fragment support.
|
5
|
+
- Analogous to JSX fragments, eg. `<>foo</>`.
|
6
|
+
* Add keyword argument support in HTML attributes.
|
7
|
+
- Eg. `<div {**kwargs} bar="baz">boo</div>`.
|
8
|
+
* Add ViewComponent slot support.
|
9
|
+
- Works via pseudo components that begin with `With`, eg. `<MySlotComponent><WithItem>Item</WithItem></MySlotComponent>`.
|
10
|
+
* Allow printing `ruxc` results to STDOUT.
|
11
|
+
* Support for unquoted attributes.
|
12
|
+
* Drop explicit support for Ruby versions < 3.
|
13
|
+
|
14
|
+
# 1.1.2
|
15
|
+
* Don't slugify HTML attributes in the tag builder either.
|
16
|
+
|
1
17
|
# 1.1.1
|
2
18
|
* Don't slugify HTML attributes.
|
3
19
|
- Previously rux would emit `<div data-foo="bar">` as `<div data_foo="bar">` because it treated HTML attributes as if they were being passed as Ruby arguments, which don't allow dashes. If these arguments are passed to a component initializer, then they must be slugified, but HTML attributes shouldn't be affected.
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -148,6 +148,94 @@ end
|
|
148
148
|
|
149
149
|
Notice we were able to embed Ruby within rux within Ruby within rux. Within Ruby. The rux parser supports unlimited levels of nesting, although you'll probably not want to go _too_ crazy.
|
150
150
|
|
151
|
+
## Slots
|
152
|
+
|
153
|
+
Rux fully supports the view_component gem's [slots feature](https://viewcomponent.org/guide/slots.html), which allows a component to expose specific points in the rendered output where the caller can provide their own content. Let's look at a table component that exposes rows and columns via slots:
|
154
|
+
|
155
|
+
```ruby
|
156
|
+
class TableComponent < ViewComponent::Base
|
157
|
+
renders_many :rows, RowComponent
|
158
|
+
|
159
|
+
def call
|
160
|
+
<table>
|
161
|
+
{rows.each do |row|
|
162
|
+
<>{row}</>
|
163
|
+
end}
|
164
|
+
</table>
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
class RowComponent < ViewComponent::Base
|
169
|
+
renders_many :columns, ColumnComponent
|
170
|
+
|
171
|
+
def call
|
172
|
+
<tr>
|
173
|
+
{columns.each do |column|
|
174
|
+
<>{column}</>
|
175
|
+
end}
|
176
|
+
</tr>
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
class ColumnComponent < ViewComponent::Base
|
181
|
+
def call
|
182
|
+
<td>{content}</td>
|
183
|
+
end
|
184
|
+
end
|
185
|
+
```
|
186
|
+
|
187
|
+
Notice the use of rux fragments (analogous to JSX fragments) via the `<></>` syntax. This allows emitting a slot by dropping back to ruby via rux.
|
188
|
+
|
189
|
+
The `TableComponent` might be rendered in an ERB template like so:
|
190
|
+
|
191
|
+
```erb
|
192
|
+
<%= render(TableComponent.new) do |table| %>
|
193
|
+
<% table.with_row do |row| %>
|
194
|
+
<% row.with_column { "Row 1, Col 1" } %>
|
195
|
+
<% row.with_column { "Row 1, Col 2" } %>
|
196
|
+
<% end %>
|
197
|
+
<% table.with_row do |row| %>
|
198
|
+
<% row.with_column { "Row 2, Col 1" } %>
|
199
|
+
<% row.with_column { "Row 2, Col 2" } %>
|
200
|
+
<% end %>
|
201
|
+
<% end %>
|
202
|
+
```
|
203
|
+
|
204
|
+
Notice the slots are "filled in" using the `#with_row` and `#with_column` methods. In rux, these methods become components:
|
205
|
+
|
206
|
+
```ruby
|
207
|
+
<TableComponent>
|
208
|
+
<WithRow>
|
209
|
+
<WithColumn>Row 1, Col 1</WithColumn>
|
210
|
+
<WithColumn>Row 1, Col 2</WithColumn>
|
211
|
+
</WithRow>
|
212
|
+
<WithRow>
|
213
|
+
<WithColumn>Row 2, Col 1</WithColumn>
|
214
|
+
<WithColumn>Row 2, Col 2</WithColumn>
|
215
|
+
</WithRow>
|
216
|
+
</TableComponent>
|
217
|
+
```
|
218
|
+
|
219
|
+
## The `as:` argument
|
220
|
+
|
221
|
+
In ViewComponent, component instances are yielded to the block on `#render`, eg:
|
222
|
+
|
223
|
+
```erb
|
224
|
+
<%= render(MyComponent.new) do |component| %>
|
225
|
+
<%# 'component' is the instance of MyComponent passed to #render above %>
|
226
|
+
<% end %>
|
227
|
+
```
|
228
|
+
|
229
|
+
Most of the time in rux, a reference to the component instance isn't necessary (see the section on slots above). Occasionally however it can be useful to, for example, call methods on the component instance to query its state, etc. Use the `as:` argument to assign the component instance to a local variable that's available inside the tag body:
|
230
|
+
|
231
|
+
```ruby
|
232
|
+
<TableComponent something={value} as={table}>
|
233
|
+
{if table.something
|
234
|
+
# your code here
|
235
|
+
end}
|
236
|
+
</TableComponent>
|
237
|
+
```
|
238
|
+
|
151
239
|
## Keyword Arguments Only
|
152
240
|
|
153
241
|
Any view component that will be rendered by rux must _only_ accept keyword arguments in its constructor. For example:
|
@@ -170,7 +258,7 @@ end
|
|
170
258
|
|
171
259
|
In other words, positional arguments are not allowed. This is because there's no such thing as a positional HTML attribute - all HTML attributes are key/value pairs. So, in order to match up with HTML, rux components are written with keyword arguments.
|
172
260
|
|
173
|
-
Note also that the rux parser will replace dashes with underscores in
|
261
|
+
Note also that the rux parser will replace dashes with underscores in component tag attributes to adhere to both HTML and Ruby syntax conventions, since HTML attributes use dashes while Ruby keyword arguments use underscores. For example, here's how to write a rux tag for `MyComponent` above:
|
174
262
|
|
175
263
|
```ruby
|
176
264
|
<MyComponent first-name="Homer" last-name="Simpson" />
|
@@ -178,6 +266,8 @@ Note also that the rux parser will replace dashes with underscores in rux tag at
|
|
178
266
|
|
179
267
|
Notice that the rux attribute "first-name" is passed to `MyComponent#initialize` as "first_name".
|
180
268
|
|
269
|
+
Attributes on regular tags, i.e. non-component tags like `<div>` and `<span>`, are not modified. In other words, `<div data-foo="foo">` does _not_ become `<div data_foo="foo">` because that would be very annoying.
|
270
|
+
|
181
271
|
## How it Works
|
182
272
|
|
183
273
|
Translating rux code (Ruby + HTML tags) into Ruby code happens in three phases: lexing, parsing, and emitting. The lexer phase is implemented as a wrapper around the lexer from the [Parser gem](https://github.com/whitequark/parser) that looks for specific patterns in the token stream. When it finds an opening HTML tag, it hands off lexing to the rux lexer. When the tag ends, the lexer continues emitting Ruby tokens, and so on.
|
data/bin/ruxc
CHANGED
@@ -14,8 +14,8 @@ class RuxCLI
|
|
14
14
|
end
|
15
15
|
|
16
16
|
options = {
|
17
|
-
|
18
|
-
|
17
|
+
pretty: true,
|
18
|
+
stdout: false
|
19
19
|
}
|
20
20
|
|
21
21
|
if argv.first != '-h' && argv.first != '--help'
|
@@ -33,6 +33,14 @@ class RuxCLI
|
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
36
|
+
oneline(<<~DESC).tap do |desc|
|
37
|
+
Print results to STDOUT instead of writing files to disk.
|
38
|
+
DESC
|
39
|
+
opts.on('-o', '--stdout', desc) do |stdout|
|
40
|
+
options[:stdout] = stdout
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
36
44
|
opts.on('-h', '--help', 'Prints this help info') do
|
37
45
|
puts opts
|
38
46
|
exit
|
@@ -79,6 +87,10 @@ class RuxCLI
|
|
79
87
|
@options[:pretty]
|
80
88
|
end
|
81
89
|
|
90
|
+
def write_to_stdout?
|
91
|
+
@options[:stdout]
|
92
|
+
end
|
93
|
+
|
82
94
|
private
|
83
95
|
|
84
96
|
def directory?
|
@@ -91,6 +103,11 @@ cli.validate
|
|
91
103
|
|
92
104
|
cli.each_file do |in_file, out_file, rbi_file|
|
93
105
|
rux_file = Rux::File.new(in_file)
|
94
|
-
|
95
|
-
|
106
|
+
|
107
|
+
if cli.write_to_stdout?
|
108
|
+
puts rux_file.to_ruby(pretty: cli.pretty?)
|
109
|
+
else
|
110
|
+
rux_file.write(out_file, pretty: cli.pretty?)
|
111
|
+
puts "Wrote #{out_file}"
|
112
|
+
end
|
96
113
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Rux
|
2
|
+
module AST
|
3
|
+
class AttrNode
|
4
|
+
attr_reader :name, :value, :name_pos
|
5
|
+
attr_accessor :tag_node
|
6
|
+
|
7
|
+
def initialize(name, value, name_pos)
|
8
|
+
@name = name
|
9
|
+
@value = value
|
10
|
+
@name_pos = name_pos
|
11
|
+
end
|
12
|
+
|
13
|
+
def accept(visitor)
|
14
|
+
visitor.visit_attr(self)
|
15
|
+
end
|
16
|
+
|
17
|
+
def ruby_code?
|
18
|
+
false
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Rux
|
2
|
+
module AST
|
3
|
+
class AttrsNode
|
4
|
+
include Enumerable
|
5
|
+
|
6
|
+
attr_reader :attrs, :pos
|
7
|
+
|
8
|
+
def initialize(attrs, pos)
|
9
|
+
@attrs = attrs
|
10
|
+
@pos = pos
|
11
|
+
end
|
12
|
+
|
13
|
+
def accept(visitor)
|
14
|
+
visitor.visit_attrs(self)
|
15
|
+
end
|
16
|
+
|
17
|
+
def each(&block)
|
18
|
+
attrs.each(&block)
|
19
|
+
end
|
20
|
+
|
21
|
+
def empty?
|
22
|
+
attrs.empty?
|
23
|
+
end
|
24
|
+
|
25
|
+
def get(name)
|
26
|
+
find { |attr| attr.name == name }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Rux
|
2
|
+
module AST
|
3
|
+
class RubyAttrNode
|
4
|
+
attr_reader :ruby_node
|
5
|
+
attr_accessor :tag_node
|
6
|
+
|
7
|
+
def initialize(ruby_node)
|
8
|
+
@ruby_node = ruby_node
|
9
|
+
end
|
10
|
+
|
11
|
+
def code
|
12
|
+
ruby_node.code
|
13
|
+
end
|
14
|
+
|
15
|
+
def accept(visitor)
|
16
|
+
visitor.visit_attr(self)
|
17
|
+
end
|
18
|
+
|
19
|
+
def ruby_code?
|
20
|
+
true
|
21
|
+
end
|
22
|
+
|
23
|
+
def name
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/rux/ast/string_node.rb
CHANGED
data/lib/rux/ast/tag_node.rb
CHANGED
@@ -3,9 +3,10 @@ module Rux
|
|
3
3
|
class TagNode
|
4
4
|
attr_reader :name, :attrs, :children
|
5
5
|
|
6
|
-
def initialize(name, attrs)
|
6
|
+
def initialize(name, attrs, pos)
|
7
7
|
@name = name
|
8
8
|
@attrs = attrs
|
9
|
+
@pos = pos
|
9
10
|
@children = []
|
10
11
|
end
|
11
12
|
|
@@ -16,6 +17,14 @@ module Rux
|
|
16
17
|
def component?
|
17
18
|
name.start_with?(/[A-Z]/)
|
18
19
|
end
|
20
|
+
|
21
|
+
def slot_component?
|
22
|
+
name.start_with?("With")
|
23
|
+
end
|
24
|
+
|
25
|
+
def slot_method
|
26
|
+
@slot_method ||= name.gsub(/(?<!^)([A-Z])/) { |x| "_#{x}" }.downcase
|
27
|
+
end
|
19
28
|
end
|
20
29
|
end
|
21
30
|
end
|
data/lib/rux/ast/text_node.rb
CHANGED
data/lib/rux/ast.rb
CHANGED
@@ -1,9 +1,14 @@
|
|
1
1
|
module Rux
|
2
2
|
module AST
|
3
|
-
autoload :
|
4
|
-
autoload :
|
5
|
-
autoload :
|
6
|
-
autoload :
|
7
|
-
autoload :
|
3
|
+
autoload :AttrNode, 'rux/ast/attr_node'
|
4
|
+
autoload :AttrsNode, 'rux/ast/attrs_node'
|
5
|
+
autoload :FragmentNode, 'rux/ast/fragment_node'
|
6
|
+
autoload :ListNode, 'rux/ast/list_node'
|
7
|
+
autoload :RootNode, 'rux/ast/root_node'
|
8
|
+
autoload :RubyAttrNode, 'rux/ast/ruby_attr_node'
|
9
|
+
autoload :RubyNode, 'rux/ast/ruby_node'
|
10
|
+
autoload :StringNode, 'rux/ast/string_node'
|
11
|
+
autoload :TagNode, 'rux/ast/tag_node'
|
12
|
+
autoload :TextNode, 'rux/ast/text_node'
|
8
13
|
end
|
9
14
|
end
|
data/lib/rux/buffer.rb
CHANGED
@@ -1,15 +1,33 @@
|
|
1
1
|
module Rux
|
2
|
+
class SafeString < String
|
3
|
+
def html_safe?
|
4
|
+
true
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
2
8
|
class Buffer
|
3
9
|
def initialize(init_str = '')
|
4
10
|
@string = init_str.dup
|
5
11
|
end
|
6
12
|
|
7
|
-
def
|
8
|
-
|
13
|
+
def append(obj)
|
14
|
+
Array(obj).each do |o|
|
15
|
+
@string << if o.respond_to?(:html_safe?) && o.html_safe?
|
16
|
+
o.to_s
|
17
|
+
else
|
18
|
+
CGI.escapeHTML(o.to_s)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def safe_append(obj)
|
24
|
+
Array(obj).each { |o| @string << o.to_s }
|
9
25
|
end
|
10
26
|
|
11
27
|
def to_s
|
12
|
-
@string
|
28
|
+
SafeString.new(@string)
|
13
29
|
end
|
30
|
+
|
31
|
+
alias html_safe to_s
|
14
32
|
end
|
15
33
|
end
|
@@ -1,19 +1,28 @@
|
|
1
1
|
module Rux
|
2
2
|
class DefaultTagBuilder
|
3
|
-
def call(tag_name, attributes = {})
|
4
|
-
|
5
|
-
"<#{tag_name}#{attr_str}>" <<
|
6
|
-
(block_given? ? Array(yield) : []).join <<
|
7
|
-
"</#{tag_name}>"
|
3
|
+
def call(tag_name, attributes = {}, &block)
|
4
|
+
SafeString.new(build(tag_name, attributes, &block))
|
8
5
|
end
|
9
6
|
|
10
7
|
private
|
11
8
|
|
9
|
+
def build(tag_name, attributes = {})
|
10
|
+
attr_str = attributes.empty? ? '' : " #{serialize_attrs(attributes)}"
|
11
|
+
|
12
|
+
"<#{tag_name}#{attr_str}>".tap do |result|
|
13
|
+
if block_given?
|
14
|
+
Array(yield).each { |body| result << body }
|
15
|
+
end
|
16
|
+
|
17
|
+
result << "</#{tag_name}>"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
12
21
|
def serialize_attrs(attributes)
|
13
22
|
''.tap do |result|
|
14
23
|
attributes.each_pair.with_index do |(k, v), idx|
|
15
24
|
result << ' ' unless idx == 0
|
16
|
-
result << "#{k.to_s.gsub('
|
25
|
+
result << "#{k}=\"#{v.to_s.gsub('"', """)}\""
|
17
26
|
end
|
18
27
|
end
|
19
28
|
end
|
data/lib/rux/default_visitor.rb
CHANGED
@@ -2,8 +2,18 @@ require 'cgi'
|
|
2
2
|
|
3
3
|
module Rux
|
4
4
|
class DefaultVisitor < Visitor
|
5
|
+
def initialize
|
6
|
+
@render_stack = []
|
7
|
+
end
|
8
|
+
|
9
|
+
def visit_root(node)
|
10
|
+
visit_list(node.list)
|
11
|
+
end
|
12
|
+
|
5
13
|
def visit_list(node)
|
6
|
-
|
14
|
+
''.tap do |result|
|
15
|
+
node.children.each { |child| result << visit(child) }
|
16
|
+
end
|
7
17
|
end
|
8
18
|
|
9
19
|
def visit_ruby(node)
|
@@ -11,57 +21,121 @@ module Rux
|
|
11
21
|
end
|
12
22
|
|
13
23
|
def visit_string(node)
|
14
|
-
node.
|
24
|
+
case node.quote_type
|
25
|
+
when :single
|
26
|
+
"'#{node.str}'"
|
27
|
+
else
|
28
|
+
"\"#{node.str}\""
|
29
|
+
end
|
15
30
|
end
|
16
31
|
|
17
32
|
def visit_tag(node)
|
18
33
|
''.tap do |result|
|
19
|
-
block_arg = if (as = node.attrs
|
34
|
+
block_arg = if (as = node.attrs.get('as'))
|
20
35
|
visit(as)
|
21
36
|
end
|
22
37
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
38
|
+
block_arg ||= "rux_block_arg#{@render_stack.size}"
|
39
|
+
|
40
|
+
if node.slot_component?
|
41
|
+
result << "(#{parent_render[:block_arg]}.#{node.slot_method}"
|
27
42
|
|
28
|
-
|
43
|
+
unless node.attrs.empty?
|
44
|
+
result << "(#{visit(node.attrs)})"
|
45
|
+
end
|
46
|
+
elsif node.component?
|
29
47
|
result << "render(#{node.name}.new"
|
30
48
|
|
31
49
|
unless node.attrs.empty?
|
32
|
-
result << "(#{
|
50
|
+
result << "(#{visit(node.attrs)})"
|
33
51
|
end
|
52
|
+
|
53
|
+
result << ')'
|
34
54
|
else
|
35
55
|
result << "Rux.tag('#{node.name}'"
|
36
56
|
|
37
57
|
unless node.attrs.empty?
|
38
|
-
result << ", { #{
|
58
|
+
result << ", { #{visit(node.attrs)} }"
|
39
59
|
end
|
60
|
+
|
61
|
+
result << ')'
|
40
62
|
end
|
41
63
|
|
42
|
-
|
64
|
+
@render_stack.push({
|
65
|
+
component_name: node.name,
|
66
|
+
block_arg: block_arg
|
67
|
+
})
|
43
68
|
|
44
|
-
if node.children.size >
|
69
|
+
if node.children.size > 0
|
45
70
|
result << " { "
|
46
|
-
result << "|#{block_arg}| " if block_arg
|
71
|
+
result << "|#{block_arg}| " if block_arg && node.component?
|
47
72
|
result << "Rux.create_buffer.tap { |_rux_buf_| "
|
48
73
|
|
49
74
|
node.children.each do |child|
|
50
|
-
result <<
|
75
|
+
result << append_statement_for(child)
|
51
76
|
end
|
52
77
|
|
53
78
|
result << " }.to_s }"
|
54
|
-
elsif node.children.size == 1
|
55
|
-
result << ' { '
|
56
|
-
result << "|#{block_arg}| " if block_arg
|
57
|
-
result << visit(node.children.first).strip
|
58
|
-
result << ' }'
|
59
79
|
end
|
80
|
+
|
81
|
+
# don't pass instances of ViewComponent::Slot to _rux_buf_#<< by wrapping
|
82
|
+
# the slot setter return value in (retval; nil)
|
83
|
+
if node.slot_component?
|
84
|
+
result << "; nil)"
|
85
|
+
end
|
86
|
+
|
87
|
+
@render_stack.pop
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def append_statement_for(node)
|
92
|
+
if node.is_a?(AST::TextNode)
|
93
|
+
"_rux_buf_.safe_append(#{visit(node).strip});"
|
94
|
+
else
|
95
|
+
"_rux_buf_.append(#{visit(node).strip});"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def visit_attrs(node)
|
100
|
+
visited_attrs = node.attrs.each_with_object([]) do |attr, memo|
|
101
|
+
memo << visit(attr) unless attr.name == "as"
|
102
|
+
end
|
103
|
+
|
104
|
+
visited_attrs.join(", ")
|
105
|
+
end
|
106
|
+
|
107
|
+
def visit_attr(node)
|
108
|
+
if node.ruby_code?
|
109
|
+
node.code
|
110
|
+
else
|
111
|
+
Utils.attr_to_hash_elem(
|
112
|
+
node.name,
|
113
|
+
visit(node.value),
|
114
|
+
slugify: node.tag_node.component?
|
115
|
+
)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def visit_fragment(node)
|
120
|
+
''.tap do |result|
|
121
|
+
result << "Rux.create_buffer.tap { |_rux_buf_| "
|
122
|
+
|
123
|
+
node.children.each do |child|
|
124
|
+
result << append_statement_for(child)
|
125
|
+
end
|
126
|
+
|
127
|
+
result << " }.to_s;"
|
60
128
|
end
|
61
129
|
end
|
62
130
|
|
63
131
|
def visit_text(node)
|
64
132
|
"\"#{CGI.escape_html(node.text)}\""
|
65
133
|
end
|
134
|
+
|
135
|
+
private
|
136
|
+
|
137
|
+
def parent_render
|
138
|
+
@render_stack.last
|
139
|
+
end
|
66
140
|
end
|
67
141
|
end
|