hanami-helpers 0.0.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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