hanami-helpers 0.0.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,79 @@
1
+ require 'hanami/helpers/html_helper/html_node'
2
+
3
+ module Hanami
4
+ module Helpers
5
+ module FormHelper
6
+ # HTML form node
7
+ #
8
+ # @since 0.2.0
9
+ # @api private
10
+ #
11
+ # @see Hanami::Helpers::HtmlHelper::HtmlNode
12
+ class HtmlNode < ::Hanami::Helpers::HtmlHelper::HtmlNode
13
+ # Initialize a new HTML form node
14
+ #
15
+ # @param name [Symbol,String] the name of the tag
16
+ # @param content [String,Proc,Hanami::Helpers::HtmlHelper::FormBuilder,NilClass] the optional content
17
+ # @param attributes [Hash,NilClass] the optional tag attributes
18
+ # @param options [Hash] a set of data
19
+ #
20
+ # @return [Hanami::Helpers::FormHelper::HtmlNode]
21
+ #
22
+ # @since 0.2.0
23
+ # @api private
24
+ def initialize(name, content, attributes, options)
25
+ super
26
+
27
+ @verb = options.fetch(:verb, nil)
28
+ @csrf_token = options.fetch(:csrf_token, nil)
29
+
30
+ @builder = FormBuilder.new(
31
+ options.fetch(:name),
32
+ options.fetch(:values)
33
+ )
34
+ end
35
+
36
+ private
37
+ # Resolve the (nested) content
38
+ #
39
+ # @return [String] the content
40
+ #
41
+ # @since 0.2.0
42
+ # @api private
43
+ #
44
+ # @see Hanami::Helpers::HtmlHelper::HtmlNode#content
45
+ def content
46
+ _method_override!
47
+ _csrf_protection!
48
+ super
49
+ end
50
+
51
+ # Inject a hidden field to make Method Override possible
52
+ #
53
+ # @since 0.2.0
54
+ # @api private
55
+ def _method_override!
56
+ return if @verb.nil?
57
+
58
+ verb = @verb
59
+ @builder.resolve do
60
+ input(type: :hidden, name: :_method, value: verb)
61
+ end
62
+ end
63
+
64
+ # Inject a hidden field for CSRF Protection token
65
+ #
66
+ # @since 0.2.0
67
+ # @api private
68
+ def _csrf_protection!
69
+ return if @csrf_token.nil?
70
+
71
+ _csrf_token = @csrf_token
72
+ @builder.resolve do
73
+ input(type: :hidden, name: FormHelper::CSRF_TOKEN, value: _csrf_token)
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,38 @@
1
+ require 'hanami/utils/hash'
2
+
3
+ module Hanami
4
+ module Helpers
5
+ module FormHelper
6
+ class Values
7
+ GET_SEPARATOR = '.'.freeze
8
+
9
+ def initialize(values, params)
10
+ @values = Utils::Hash.new(values).stringify!
11
+ @params = params
12
+ end
13
+
14
+ def get(key)
15
+ @params.get(key) || _get_from_values(key)
16
+ end
17
+
18
+ private
19
+ def _get_from_values(key)
20
+ initial_key, *keys = key.to_s.split(GET_SEPARATOR)
21
+ result = @values[initial_key]
22
+
23
+ Array(keys).each do |k|
24
+ break if result.nil?
25
+
26
+ result = if result.respond_to?(k)
27
+ result.public_send(k)
28
+ else
29
+ nil
30
+ end
31
+ end
32
+
33
+ result
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,207 @@
1
+ require 'hanami/helpers/html_helper/html_builder'
2
+
3
+ module Hanami
4
+ module Helpers
5
+ # HTML builder
6
+ #
7
+ # By including <tt>Hanami::Helpers::HtmlHelper</tt> it will inject one private method: <tt>html</tt>.
8
+ # This is a HTML5 markup builder.
9
+ #
10
+ # Features:
11
+ # * Support for complex markup without the need of concatenation
12
+ # * Auto closing HTML5 tags
13
+ # * Custom tags
14
+ # * Content tag auto escape (XSS protection)
15
+ # * Support for view local variables
16
+ #
17
+ # Usage:
18
+ #
19
+ # * It knows how to close tags according to HTML5 spec (1)
20
+ # * It accepts content as first argument (2)
21
+ # * It accepts another builder as first argument (3)
22
+ # * It accepts content as block which returns a string (4)
23
+ # * It accepts content as a block with nested markup builders (5)
24
+ # * It builds attributes from given hash (6)
25
+ # * It combines attributes and block (7)
26
+ #
27
+ # @since 0.1.0
28
+ #
29
+ # @see Hanami::Helpers::HtmlHelper#html
30
+ #
31
+ # @example Usage
32
+ # # 1
33
+ # html.div # => <div></div>
34
+ # html.img # => <img>
35
+ #
36
+ # # 2
37
+ # html.div('hello') # => <div>hello</div>
38
+ #
39
+ # # 3
40
+ # html.div(html.p('hello')) # => <div><p>hello</p></div>
41
+ #
42
+ # # 4
43
+ # html.div { 'hello' }
44
+ # # =>
45
+ # #<div>
46
+ # # hello
47
+ # #</div>
48
+ #
49
+ # # 5
50
+ # html.div do
51
+ # p 'hello'
52
+ # end
53
+ # # =>
54
+ # #<div>
55
+ # # <p>hello</p>
56
+ # #</div>
57
+ #
58
+ # # 6
59
+ # html.div('hello', id: 'el', 'data-x': 'y') # => <div id="el" data-x="y">hello</div>
60
+ #
61
+ # # 7
62
+ # html.div(id: 'yay') { 'hello' }
63
+ # # =>
64
+ # #<div id="yay">
65
+ # # hello
66
+ # #</div>
67
+ #
68
+ # # 8
69
+ # html do
70
+ # li 'Hello'
71
+ # li 'Hanami'
72
+ # end
73
+ # # =>
74
+ # #<li>Hello</li>
75
+ # #<li>Hanami</li>
76
+ #
77
+ #
78
+ #
79
+ # @example Complex markup
80
+ # #
81
+ # # NOTICE THE LACK OF CONCATENATION BETWEEN div AND input BLOCKS <3
82
+ # #
83
+ #
84
+ # html.form(action: '/users', method: 'POST') do
85
+ # div do
86
+ # label 'First name', for: 'user-first-name'
87
+ # input type: 'text', id: 'user-first-name', name: 'user[first_name]', value: 'L'
88
+ # end
89
+ #
90
+ # input type: 'submit', value: 'Save changes'
91
+ # end
92
+ # # =>
93
+ # #<form action="/users" method="POST" accept-charset="utf-8">
94
+ # # <div>
95
+ # # <label for="user-first-name">First name</label>
96
+ # # <input type="text" id="user-first-name" name="user[first_name]" value="L">
97
+ # # </div>
98
+ # # <input type="submit" value="Save changes">
99
+ # #</form>
100
+ #
101
+ #
102
+ #
103
+ # @example Custom tags
104
+ # html.tag(:custom, 'Foo', id: 'next') # => <custom id="next">Foo</custom>
105
+ # html.empty_tag(:xr, id: 'next') # => <xr id="next">
106
+ #
107
+ #
108
+ #
109
+ # @example Auto escape
110
+ # html.div('hello') # => <div>hello</hello>
111
+ # html.div { 'hello' } # => <div>hello</hello>
112
+ # html.div(html.p('hello')) # => <div><p>hello</p></hello>
113
+ # html.div do
114
+ # p 'hello'
115
+ # end # => <div><p>hello</p></hello>
116
+ #
117
+ #
118
+ #
119
+ # html.div("<script>alert('xss')</script>")
120
+ # # => "<div>&lt;script&gt;alert(&apos;xss&apos;)&lt;&#x2F;script&gt;</div>"
121
+ #
122
+ # html.div { "<script>alert('xss')</script>" }
123
+ # # => "<div>&lt;script&gt;alert(&apos;xss&apos;)&lt;&#x2F;script&gt;</div>"
124
+ #
125
+ # html.div(html.p("<script>alert('xss')</script>"))
126
+ # # => "<div><p>&lt;script&gt;alert(&apos;xss&apos;)&lt;&#x2F;script&gt;</p></div>"
127
+ #
128
+ # html.div do
129
+ # p "<script>alert('xss')</script>"
130
+ # end
131
+ # # => "<div><p>&lt;script&gt;alert(&apos;xss&apos;)&lt;&#x2F;script&gt;</p></div>"
132
+ #
133
+ #
134
+ # @example Basic usage
135
+ # #
136
+ # # THE VIEW CAN BE A SIMPLE RUBY OBJECT
137
+ # #
138
+ #
139
+ # require 'hanami/helpers'
140
+ #
141
+ # class MyView
142
+ # include Hanami::Helpers::HtmlHelper
143
+ #
144
+ # # Generates
145
+ # # <aside id="sidebar">
146
+ # # <div>hello</hello>
147
+ # # </aside>
148
+ # def sidebar
149
+ # html.aside(id: 'sidebar') do
150
+ # div 'hello'
151
+ # end
152
+ # end
153
+ # end
154
+ #
155
+ #
156
+ # @example View context
157
+ # #
158
+ # # LOCAL VARIABLES FROM VIEWS ARE AVAILABLE INSIDE THE NESTED BLOCKS OF HTML BUILDER
159
+ # #
160
+ #
161
+ # require 'hanami/view'
162
+ # require 'hanami/helpers'
163
+ #
164
+ # Book = Struct.new(:title)
165
+ #
166
+ # module Books
167
+ # class Show
168
+ # include Hanami::View
169
+ # include Hanami::Helpers::HtmlHelper
170
+ #
171
+ # def title_widget
172
+ # html.div do
173
+ # h1 book.title
174
+ # end
175
+ # end
176
+ # end
177
+ # end
178
+ #
179
+ # book = Book.new('The Work of Art in the Age of Mechanical Reproduction')
180
+ # rendered = Books::Show.render(format: :html, book: book)
181
+ #
182
+ # rendered
183
+ # # => <div>
184
+ # # <h1>The Work of Art in the Age of Mechanical Reproduction</h1>
185
+ # # </div>
186
+ module HtmlHelper
187
+ private
188
+ # Instantiate an HTML builder
189
+ #
190
+ # @param blk [Proc,Hanami::Helpers::HtmlHelper::HtmlBuilder,NilClass] the optional content block
191
+ #
192
+ # @return [Hanami::Helpers::HtmlHelper::HtmlBuilder] the HTML builder
193
+ #
194
+ # @since 0.1.0
195
+ #
196
+ # @see Hanami::Helpers::HtmlHelper
197
+ # @see Hanami::Helpers::HtmlHelper::HtmlBuilder
198
+ def html(&blk)
199
+ if block_given?
200
+ HtmlBuilder.new.fragment(&blk)
201
+ else
202
+ HtmlBuilder.new
203
+ end
204
+ end
205
+ end
206
+ end
207
+ end
@@ -0,0 +1,92 @@
1
+ module Hanami
2
+ module Helpers
3
+ module HtmlHelper
4
+ # Empty HTML node
5
+ #
6
+ # @since 0.1.0
7
+ # @api private
8
+ class EmptyHtmlNode
9
+ # List of attributes that get special treatment when rendering.
10
+ #
11
+ # @since 0.2.5
12
+ # @api private
13
+ #
14
+ # @see http://www.w3.org/html/wg/drafts/html/master/infrastructure.html#boolean-attribute
15
+ BOOLEAN_ATTRIBUTES = %w{allowfullscreen async autobuffer autofocus
16
+ autoplay checked compact controls declare default defaultchecked
17
+ defaultmuted defaultselected defer disabled draggable enabled
18
+ formnovalidate hidden indeterminate inert ismap itemscope loop
19
+ multiple muted nohref noresize noshade novalidate nowrap open
20
+ pauseonexit pubdate readonly required reversed scoped seamless
21
+ selected sortable spellcheck translate truespeed typemustmatch
22
+ visible
23
+ }.freeze
24
+
25
+ # Attributes separator
26
+ #
27
+ # @since 0.1.0
28
+ # @api private
29
+ ATTRIBUTES_SEPARATOR = ' '.freeze
30
+
31
+ # Initialize a new empty HTML node
32
+ #
33
+ # @param name [Symbol,String] the name of the tag
34
+ # @param attributes [Hash,NilClass] the optional tag attributes
35
+ #
36
+ # @return [Hanami::Helpers::HtmlHelper::EmptyHtmlNode]
37
+ #
38
+ # @since 0.1.0
39
+ # @api private
40
+ def initialize(name, attributes)
41
+ @name = name
42
+ @attributes = attributes
43
+ end
44
+
45
+ # Resolve and return the output
46
+ #
47
+ # @return [String] the output
48
+ #
49
+ # @since 0.1.0
50
+ # @api private
51
+ def to_s
52
+ %(<#{ @name }#{attributes}>)
53
+ end
54
+
55
+ private
56
+ # Resolve the attributes
57
+ #
58
+ # @return [String,NilClass] the tag attributes
59
+ #
60
+ # @since 0.1.0
61
+ # @api private
62
+ def attributes
63
+ return if @attributes.nil?
64
+ result = ''
65
+
66
+ @attributes.each do |attribute_name, value|
67
+ if boolean_attribute?(attribute_name)
68
+ result << boolean_attribute(attribute_name, value) if value
69
+ else
70
+ result << attribute(attribute_name, value)
71
+ end
72
+ end
73
+
74
+ result
75
+ end
76
+
77
+ def boolean_attribute?(attribute_name)
78
+ BOOLEAN_ATTRIBUTES.include?(attribute_name.to_s)
79
+ end
80
+
81
+ # Do not render boolean attributes when their value is _false_.
82
+ def boolean_attribute(attribute_name, value)
83
+ %(#{ATTRIBUTES_SEPARATOR}#{ attribute_name }="#{ attribute_name }")
84
+ end
85
+
86
+ def attribute(attribute_name, value)
87
+ %(#{ATTRIBUTES_SEPARATOR}#{ attribute_name }="#{ value }")
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,376 @@
1
+ require 'hanami/utils' # RUBY_VERSION >= '2.2'
2
+ require 'hanami/utils/class_attribute'
3
+ require 'hanami/utils/escape'
4
+ require 'hanami/helpers/html_helper/empty_html_node'
5
+ require 'hanami/helpers/html_helper/html_node'
6
+ require 'hanami/helpers/html_helper/html_fragment'
7
+ require 'hanami/helpers/html_helper/text_node'
8
+
9
+ module Hanami
10
+ module Helpers
11
+ module HtmlHelper
12
+ # HTML Builder
13
+ #
14
+ # @since 0.1.0
15
+ class HtmlBuilder
16
+ # HTML5 content tags
17
+ #
18
+ # @since 0.1.0
19
+ # @api private
20
+ #
21
+ # @see Hanami::Helpers::HtmlHelper::HtmlNode
22
+ # @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element
23
+ CONTENT_TAGS = [
24
+ 'a',
25
+ 'abbr',
26
+ 'address',
27
+ 'article',
28
+ 'aside',
29
+ 'audio',
30
+ 'b',
31
+ 'bdi',
32
+ 'bdo',
33
+ 'blockquote',
34
+ 'body',
35
+ 'button',
36
+ 'canvas',
37
+ 'caption',
38
+ 'cite',
39
+ 'code',
40
+ 'colgroup',
41
+ 'data',
42
+ 'datalist',
43
+ 'del',
44
+ 'details',
45
+ 'dfn',
46
+ 'div',
47
+ 'dl',
48
+ 'dt',
49
+ 'dd',
50
+ 'em',
51
+ 'fieldset',
52
+ 'figcaption',
53
+ 'figure',
54
+ 'footer',
55
+ 'form',
56
+ 'h1',
57
+ 'h2',
58
+ 'h3',
59
+ 'h4',
60
+ 'h5',
61
+ 'h6',
62
+ 'head',
63
+ 'header',
64
+ 'i',
65
+ 'iframe',
66
+ 'ins',
67
+ 'kbd',
68
+ 'label',
69
+ 'legend',
70
+ 'li',
71
+ 'link',
72
+ 'main',
73
+ 'map',
74
+ 'mark',
75
+ 'math',
76
+ 'menu',
77
+ 'meter',
78
+ 'nav',
79
+ 'noscript',
80
+ 'object',
81
+ 'ol',
82
+ 'optgroup',
83
+ 'option',
84
+ 'output',
85
+ 'p',
86
+ 'pre',
87
+ 'progress',
88
+ 'q',
89
+ 'rp',
90
+ 'rt',
91
+ 'ruby',
92
+ 's',
93
+ 'samp',
94
+ 'script',
95
+ 'section',
96
+ 'select',
97
+ 'small',
98
+ 'span',
99
+ 'strong',
100
+ 'style',
101
+ 'sub',
102
+ 'summary',
103
+ 'sup',
104
+ 'svg',
105
+ 'table',
106
+ 'tbody',
107
+ 'td',
108
+ 'template',
109
+ 'textarea',
110
+ 'tfoot',
111
+ 'th',
112
+ 'thead',
113
+ 'time',
114
+ 'title',
115
+ 'tr',
116
+ 'u',
117
+ 'ul',
118
+ 'video',
119
+ ].freeze
120
+
121
+ # HTML5 empty tags
122
+ #
123
+ # @since 0.1.0
124
+ # @api private
125
+ #
126
+ # @see Hanami::Helpers::HtmlHelper::EmptyHtmlNode
127
+ # @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element
128
+ EMPTY_TAGS = [
129
+ 'area',
130
+ 'base',
131
+ 'br',
132
+ 'col',
133
+ 'embed',
134
+ 'hr',
135
+ 'img',
136
+ 'input',
137
+ 'keygen',
138
+ 'link',
139
+ 'menuitem',
140
+ 'meta',
141
+ 'param',
142
+ 'source',
143
+ 'track',
144
+ 'wbr',
145
+ ].freeze
146
+
147
+ # New line separator
148
+ #
149
+ # @since 0.1.0
150
+ # @api private
151
+ NEWLINE = "\n".freeze
152
+
153
+ CONTENT_TAGS.each do |tag|
154
+ class_eval %{
155
+ def #{ tag }(content = nil, attributes = nil, &blk)
156
+ @nodes << self.class.html_node.new(:#{ tag }, blk || content, attributes || content, options)
157
+ self
158
+ end
159
+ }
160
+ end
161
+
162
+ EMPTY_TAGS.each do |tag|
163
+ class_eval %{
164
+ def #{ tag }(attributes = nil)
165
+ @nodes << EmptyHtmlNode.new(:#{ tag }, attributes)
166
+ self
167
+ end
168
+ }
169
+ end
170
+
171
+ include Utils::ClassAttribute
172
+
173
+ class_attribute :html_node
174
+ self.html_node = ::Hanami::Helpers::HtmlHelper::HtmlNode
175
+
176
+ # Initialize a new builder
177
+ #
178
+ # @return [Hanami::Helpers::HtmlHelper::HtmlBuilder] the builder
179
+ #
180
+ # @since 0.1.0
181
+ # @api private
182
+ def initialize
183
+ @nodes = []
184
+ end
185
+
186
+ def options
187
+ end
188
+
189
+ # Define a custom tag
190
+ #
191
+ # @param name [Symbol,String] the name of the tag
192
+ # @param content [String,Hanami::Helpers::HtmlHelper::HtmlBuilder,NilClass] the optional content
193
+ # @param attributes [Hash,NilClass] the optional tag attributes
194
+ # @param blk [Proc] the optional nested content espressed as a block
195
+ #
196
+ # @return [self]
197
+ #
198
+ # @since 0.1.0
199
+ # @api public
200
+ #
201
+ # @see Hanami::Helpers::HtmlHelper
202
+ #
203
+ # @example
204
+ # html.tag(:custom) # => <custom></custom>
205
+ #
206
+ # html.tag(:custom, 'foo') # => <custom>foo</custom>
207
+ #
208
+ # html.tag(:custom, html.p('hello')) # => <custom><p>hello</p></custom>
209
+ #
210
+ # html.tag(:custom) { 'foo' }
211
+ # # =>
212
+ # #<custom>
213
+ # # foo
214
+ # #</custom>
215
+ #
216
+ # html.tag(:custom) do
217
+ # p 'hello'
218
+ # end
219
+ # # =>
220
+ # #<custom>
221
+ # # <p>hello</p>
222
+ # #</custom>
223
+ #
224
+ # html.tag(:custom, 'hello', id: 'foo', 'data-xyz': 'bar') # => <custom id="foo" data-xyz="bar">hello</custom>
225
+ #
226
+ # html.tag(:custom, id: 'foo') { 'hello' }
227
+ # # =>
228
+ # #<custom id="foo">
229
+ # # hello
230
+ # #</custom>
231
+ def tag(name, content = nil, attributes = nil, &blk)
232
+ @nodes << HtmlNode.new(name, blk || content, attributes || content, options)
233
+ self
234
+ end
235
+
236
+ # Define a HTML fragment
237
+ #
238
+ # @param blk [Proc] the optional nested content espressed as a block
239
+ #
240
+ # @return [self]
241
+ #
242
+ # @since 0.2.6
243
+ # @api public
244
+ #
245
+ # @see Hanami::Helpers::HtmlHelper
246
+ #
247
+ # @example
248
+ # html.fragment('Hanami') # => Hanami
249
+ #
250
+ # html do
251
+ # p 'hello'
252
+ # p 'hanami'
253
+ # end
254
+ # # =>
255
+ # <p>hello</p>
256
+ # <p>hanami</p>
257
+ def fragment(&blk)
258
+ @nodes << HtmlFragment.new(&blk)
259
+ self
260
+ end
261
+
262
+ # Defines a custom empty tag
263
+ #
264
+ # @param name [Symbol,String] the name of the tag
265
+ # @param attributes [Hash,NilClass] the optional tag attributes
266
+ #
267
+ # @return [self]
268
+ #
269
+ # @since 0.1.0
270
+ # @api public
271
+ #
272
+ # @see Hanami::Helpers::HtmlHelper
273
+ #
274
+ # @example
275
+ # html.empty_tag(:xr) # => <xr>
276
+ #
277
+ # html.empty_tag(:xr, id: 'foo') # => <xr id="foo">
278
+ #
279
+ # html.empty_tag(:xr, id: 'foo', 'data-xyz': 'bar') # => <xr id="foo" data-xyz="bar">
280
+ def empty_tag(name, attributes = nil)
281
+ @nodes << EmptyHtmlNode.new(name, attributes)
282
+ self
283
+ end
284
+
285
+ # Defines a plain string of text. This particularly useful when you
286
+ # want to build more complex HTML.
287
+ #
288
+ # @param content [String] the text to be rendered.
289
+ #
290
+ # @return [self]
291
+ #
292
+ # @see Hanami::Helpers::HtmlHelper
293
+ # @see Hanami::Helpers::HtmlHelper::TextNode
294
+ #
295
+ # @example
296
+ #
297
+ # html.label do
298
+ # text "Option 1"
299
+ # radio_button :option, 1
300
+ # end
301
+ #
302
+ # # <label>
303
+ # # Option 1
304
+ # # <input type="radio" name="option" value="1" />
305
+ # # </label>
306
+ def text(content)
307
+ @nodes << TextNode.new(content)
308
+ self
309
+ end
310
+
311
+ # @since 0.2.5
312
+ # @api private
313
+ alias_method :+, :text
314
+
315
+ # Resolves all the nodes and generates the markup
316
+ #
317
+ # @return [Hanami::Utils::Escape::SafeString] the output
318
+ #
319
+ # @since 0.1.0
320
+ # @api private
321
+ #
322
+ # @see http://www.rubydoc.info/gems/hanami-utils/Hanami/Utils/Escape/SafeString
323
+ def to_s
324
+ Utils::Escape::SafeString.new(@nodes.map(&:to_s).join(NEWLINE))
325
+ end
326
+
327
+ # Encode the content with the given character encoding
328
+ #
329
+ # @param encoding [Encoding,String] the encoding or its string representation
330
+ #
331
+ # @return [String] the encoded string
332
+ #
333
+ # @since 0.2.5
334
+ # @api private
335
+ def encode(encoding)
336
+ to_s.encode(encoding)
337
+ end
338
+
339
+ # Check if there are nested nodes
340
+ #
341
+ # @return [TrueClass,FalseClass] the result of the check
342
+ #
343
+ # @since 0.1.0
344
+ # @api private
345
+ def nested?
346
+ @nodes.any?
347
+ end
348
+
349
+ # Resolve the context for nested contents
350
+ #
351
+ # @since 0.1.0
352
+ # @api private
353
+ if RUBY_VERSION >= '2.2' && !Utils.jruby?
354
+ def resolve(&blk)
355
+ @context = blk.binding.receiver
356
+ instance_exec(&blk)
357
+ end
358
+ else
359
+ def resolve(&blk)
360
+ @context = eval 'self', blk.binding
361
+ instance_exec(&blk)
362
+ end
363
+ end
364
+
365
+ # Forward missing methods to the current context.
366
+ # This allows to access views local variables from nested content blocks.
367
+ #
368
+ # @since 0.1.0
369
+ # @api private
370
+ def method_missing(m, *args, &blk)
371
+ @context.__send__(m, *args, &blk)
372
+ end
373
+ end
374
+ end
375
+ end
376
+ end