slim 1.3.0 → 1.3.2

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.
Files changed (52) hide show
  1. data/.travis.yml +9 -5
  2. data/CHANGES +23 -0
  3. data/Gemfile +13 -2
  4. data/README.md +346 -105
  5. data/Rakefile +21 -9
  6. data/lib/slim.rb +3 -3
  7. data/lib/slim/boolean_attributes.rb +67 -0
  8. data/lib/slim/command.rb +23 -23
  9. data/lib/slim/control_structures.rb +57 -0
  10. data/lib/slim/embedded_engine.rb +80 -43
  11. data/lib/slim/end_inserter.rb +1 -1
  12. data/lib/slim/engine.rb +21 -15
  13. data/lib/slim/filter.rb +0 -6
  14. data/lib/slim/grammar.rb +2 -7
  15. data/lib/slim/interpolation.rb +1 -1
  16. data/lib/slim/logic_less/filter.rb +8 -8
  17. data/lib/slim/logic_less/wrapper.rb +1 -1
  18. data/lib/slim/parser.rb +51 -52
  19. data/lib/slim/splat_attributes.rb +112 -0
  20. data/lib/slim/translator.rb +13 -12
  21. data/lib/slim/version.rb +1 -1
  22. data/slim.gemspec +1 -1
  23. data/test/{slim → core}/helper.rb +3 -7
  24. data/test/{slim → core}/test_code_blocks.rb +0 -0
  25. data/test/{slim → core}/test_code_escaping.rb +4 -4
  26. data/test/{slim → core}/test_code_evaluation.rb +3 -112
  27. data/test/{slim → core}/test_code_output.rb +0 -0
  28. data/test/{slim → core}/test_code_structure.rb +0 -0
  29. data/test/{slim → core}/test_embedded_engines.rb +8 -3
  30. data/test/{slim → core}/test_encoding.rb +0 -0
  31. data/test/core/test_html_attributes.rb +218 -0
  32. data/test/{slim → core}/test_html_escaping.rb +17 -0
  33. data/test/{slim → core}/test_html_structure.rb +13 -98
  34. data/test/{slim → core}/test_parser_errors.rb +24 -15
  35. data/test/{slim → core}/test_pretty.rb +0 -0
  36. data/test/{slim → core}/test_ruby_errors.rb +7 -0
  37. data/test/{slim → core}/test_slim_template.rb +0 -0
  38. data/test/{slim → core}/test_text_interpolation.rb +2 -2
  39. data/test/core/test_thread_options.rb +18 -0
  40. data/test/{slim/logic_less → logic_less}/test_logic_less.rb +21 -0
  41. data/test/{slim/logic_less → logic_less}/test_wrapper.rb +3 -3
  42. data/test/rails/app/controllers/slim_controller.rb +4 -2
  43. data/test/rails/app/views/parents/_form.html.slim +1 -0
  44. data/test/rails/app/views/parents/edit.html.slim +2 -1
  45. data/test/rails/app/views/parents/new.html.slim +2 -1
  46. data/test/rails/app/views/slim/thread_options.html.slim +1 -0
  47. data/test/rails/test/test_slim.rb +6 -4
  48. data/test/{slim/translator → translator}/test_translator.rb +0 -0
  49. metadata +44 -82
  50. data/lib/slim/compiler.rb +0 -194
  51. data/test/rails/app/views/slim/nil.html.slim +0 -1
  52. data/test/slim/test_chain_manipulation.rb +0 -42
@@ -2,8 +2,9 @@ require 'slim'
2
2
 
3
3
  module Slim
4
4
  class Translator < Filter
5
- set_default_options :tr_mode => :dynamic,
6
- :tr_fn => '_'
5
+ define_options :tr_mode => :dynamic,
6
+ :tr_fn => '_',
7
+ :tr => false
7
8
 
8
9
  if Object.const_defined?(:I18n)
9
10
  set_default_options :tr_fn => '::Slim::Translator.i18n_text',
@@ -30,22 +31,18 @@ module Slim
30
31
  end
31
32
 
32
33
  def call(exp)
33
- if options[:tr]
34
- super
35
- else
36
- exp
37
- end
34
+ options[:tr] ? super : exp
38
35
  end
39
36
 
40
- def initialize(opts)
37
+ def initialize(opts = {})
41
38
  super
42
39
  case options[:tr_mode]
43
40
  when :static
44
- @translator = StaticTranslator.new(options)
41
+ @translator = StaticTranslator.new(:tr_fn => options[:tr_fn])
45
42
  when :dynamic
46
- @translator = DynamicTranslator.new(options)
43
+ @translator = DynamicTranslator.new(:tr_fn => options[:tr_fn])
47
44
  else
48
- raise "Invalid translator mode #{options[:tr_mode].inspect}"
45
+ raise ArgumentError, "Invalid translator mode #{options[:tr_mode].inspect}"
49
46
  end
50
47
  end
51
48
 
@@ -56,7 +53,9 @@ module Slim
56
53
  private
57
54
 
58
55
  class StaticTranslator < Filter
59
- def initialize(opts)
56
+ define_options :tr_fn
57
+
58
+ def initialize(opts = {})
60
59
  super
61
60
  @translate = eval("proc {|string| #{options[:tr_fn]}(string) }")
62
61
  end
@@ -86,6 +85,8 @@ module Slim
86
85
  end
87
86
 
88
87
  class DynamicTranslator < Filter
88
+ define_options :tr_fn
89
+
89
90
  def call(exp)
90
91
  @captures_count, @captures_var, @text = 0, unique_name, ''
91
92
 
@@ -1,5 +1,5 @@
1
1
  module Slim
2
2
  # Slim version string
3
3
  # @api public
4
- VERSION = '1.3.0'
4
+ VERSION = '1.3.2'
5
5
  end
@@ -19,7 +19,7 @@ Gem::Specification.new do |s|
19
19
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
20
  s.require_paths = %w(lib)
21
21
 
22
- s.add_runtime_dependency('temple', ['~> 0.4.1'])
22
+ s.add_runtime_dependency('temple', ['~> 0.5.0'])
23
23
  s.add_runtime_dependency('tilt', ['~> 1.3.3'])
24
24
 
25
25
  s.add_development_dependency('rake', ['>= 0.8.7'])
@@ -7,8 +7,7 @@ require 'slim/grammar'
7
7
  MiniTest::Unit.autorun
8
8
 
9
9
  Slim::Engine.after Slim::Parser, Temple::Filters::Validator, :grammar => Slim::Grammar
10
- Slim::Engine.before Slim::Compiler, Temple::Filters::Validator, :grammar => Slim::Grammar
11
- Slim::Engine.before :AttributeMerger, Temple::Filters::Validator
10
+ Slim::Engine.before :Pretty, Temple::Filters::Validator
12
11
 
13
12
  class TestSlim < MiniTest::Unit::TestCase
14
13
  def setup
@@ -16,7 +15,8 @@ class TestSlim < MiniTest::Unit::TestCase
16
15
  end
17
16
 
18
17
  def render(source, options = {}, &block)
19
- Slim::Template.new(options[:file], options) { source }.render(options[:scope] || @env, &block)
18
+ scope = options.delete(:scope)
19
+ Slim::Template.new(options[:file], options) { source }.render(scope || @env, &block)
20
20
  end
21
21
 
22
22
  def assert_html(expected, source, options = {}, &block)
@@ -133,10 +133,6 @@ class Env
133
133
  "<script>do_something_evil();</script>"
134
134
  end
135
135
 
136
- def method_which_returns_true
137
- true
138
- end
139
-
140
136
  def output_number
141
137
  1337
142
138
  end
File without changes
@@ -6,7 +6,7 @@ class TestSlimCodeEscaping < TestSlim
6
6
  p = evil_method
7
7
  }
8
8
 
9
- assert_html '<p>&lt;script&gt;do_something_evil();&lt;&#47;script&gt;</p>', source
9
+ assert_html '<p>&lt;script&gt;do_something_evil();&lt;/script&gt;</p>', source
10
10
  end
11
11
 
12
12
  def test_render_without_html_safe
@@ -14,7 +14,7 @@ p = evil_method
14
14
  p = "<strong>Hello World\\n, meet \\"Slim\\"</strong>."
15
15
  }
16
16
 
17
- assert_html "<p>&lt;strong&gt;Hello World\n, meet \&quot;Slim\&quot;&lt;&#47;strong&gt;.</p>", source
17
+ assert_html "<p>&lt;strong&gt;Hello World\n, meet \&quot;Slim\&quot;&lt;/strong&gt;.</p>", source
18
18
  end
19
19
 
20
20
  def test_render_with_html_safe_false
@@ -22,7 +22,7 @@ p = "<strong>Hello World\\n, meet \\"Slim\\"</strong>."
22
22
  p = HtmlUnsafeString.new("<strong>Hello World\\n, meet \\"Slim\\"</strong>.")
23
23
  }
24
24
 
25
- assert_html "<p>&lt;strong&gt;Hello World\n, meet \&quot;Slim\&quot;&lt;&#47;strong&gt;.</p>", source, :use_html_safe => true
25
+ assert_html "<p>&lt;strong&gt;Hello World\n, meet \&quot;Slim\&quot;&lt;/strong&gt;.</p>", source, :use_html_safe => true
26
26
  end
27
27
 
28
28
  def test_render_with_html_safe_true
@@ -39,7 +39,7 @@ p = HtmlSafeString.new("<strong>Hello World\\n, meet \\"Slim\\"</strong>.")
39
39
  == "<p>World</p>"
40
40
  }
41
41
 
42
- assert_html "&lt;p&gt;Hello&lt;&#47;p&gt;<p>World</p>", source
42
+ assert_html "&lt;p&gt;Hello&lt;/p&gt;<p>World</p>", source
43
43
  end
44
44
 
45
45
  def test_render_with_disable_escape_true
@@ -71,7 +71,7 @@ p id=@var
71
71
  form action=action_path(:page, :save) method='post'
72
72
  }
73
73
 
74
- assert_html '<form action="&#47;action-page-save" method="post"></form>', source
74
+ assert_html '<form action="/action-page-save" method="post"></form>', source
75
75
  end
76
76
 
77
77
  def test_ruby_attribute_with_unbalanced_delimiters
@@ -79,7 +79,7 @@ form action=action_path(:page, :save) method='post'
79
79
  div crazy=action_path('[') id="crazy_delimiters"
80
80
  }
81
81
 
82
- assert_html '<div crazy="&#47;action-[" id="crazy_delimiters"></div>', source
82
+ assert_html '<div crazy="/action-[" id="crazy_delimiters"></div>', source
83
83
  end
84
84
 
85
85
  def test_method_call_in_delimited_attribute_without_quotes
@@ -87,20 +87,12 @@ div crazy=action_path('[') id="crazy_delimiters"
87
87
  form(action=action_path(:page, :save) method='post')
88
88
  }
89
89
 
90
- assert_html '<form action="&#47;action-page-save" method="post"></form>', source
90
+ assert_html '<form action="/action-page-save" method="post"></form>', source
91
91
  end
92
92
 
93
93
  def test_method_call_in_delimited_attribute_without_quotes2
94
94
  source = %q{
95
95
  form(method='post' action=action_path(:page, :save))
96
- }
97
-
98
- assert_html '<form action="&#47;action-page-save" method="post"></form>', source
99
- end
100
-
101
- def test_bypassing_escape_in_attribute
102
- source = %q{
103
- form action==action_path(:page, :save) method='post'
104
96
  }
105
97
 
106
98
  assert_html '<form action="/action-page-save" method="post"></form>', source
@@ -185,105 +177,4 @@ p = output_number
185
177
 
186
178
  assert_html '<p>1337</p>', source
187
179
  end
188
-
189
- def test_ternary_operation_in_attribute
190
- source = %q{
191
- p id="#{(false ? 'notshown' : 'shown')}" = output_number
192
- }
193
-
194
- assert_html '<p id="shown">1337</p>', source
195
- end
196
-
197
- def test_ternary_operation_in_attribute_2
198
- source = %q{
199
- p id=(false ? 'notshown' : 'shown') = output_number
200
- }
201
-
202
- assert_html '<p id="shown">1337</p>', source
203
- end
204
-
205
- def test_class_attribute_merging
206
- source = %{
207
- .alpha class="beta" Test it
208
- }
209
- assert_html '<div class="alpha beta">Test it</div>', source
210
- end
211
-
212
- def test_class_attribute_merging_with_nil
213
- source = %{
214
- .alpha class="beta" class=nil class="gamma" Test it
215
- }
216
- assert_html '<div class="alpha beta gamma">Test it</div>', source
217
- end
218
-
219
- def test_id_attribute_merging
220
- source = %{
221
- #alpha id="beta" Test it
222
- }
223
- assert_html '<div id="alpha_beta">Test it</div>', source, :attr_delimiter => {'class' => ' ', 'id' => '_' }
224
- end
225
-
226
- def test_id_attribute_merging2
227
- source = %{
228
- #alpha id="beta" Test it
229
- }
230
- assert_html '<div id="alpha-beta">Test it</div>', source, :attr_delimiter => {'class' => ' ', 'id' => '-' }
231
- end
232
-
233
- def test_boolean_attribute_false
234
- source = %{
235
- option selected=false Text
236
- }
237
-
238
- assert_html '<option>Text</option>', source
239
- end
240
-
241
- def test_boolean_attribute_true
242
- source = %{
243
- option selected=true Text
244
- }
245
-
246
- assert_html '<option selected="selected">Text</option>', source
247
- end
248
-
249
- def test_boolean_attribute_dynamic
250
- source = %{
251
- option selected=method_which_returns_true Text
252
- }
253
-
254
- assert_html '<option selected="selected">Text</option>', source
255
- end
256
-
257
- def test_boolean_attribute_nil
258
- source = %{
259
- option selected=nil Text
260
- }
261
-
262
- assert_html '<option>Text</option>', source
263
- end
264
-
265
- def test_boolean_attribute_string2
266
- source = %{
267
- option selected="selected" Text
268
- }
269
-
270
- assert_html '<option selected="selected">Text</option>', source
271
- end
272
-
273
- def test_boolean_attribute_shortcut
274
- source = %{
275
- option(class="clazz" selected) Text
276
- option(selected class="clazz") Text
277
- }
278
-
279
- assert_html '<option class="clazz" selected="selected">Text</option><option class="clazz" selected="selected">Text</option>', source
280
- end
281
-
282
- def test_array_attribute
283
- source = %{
284
- .alpha class="beta" class=[:gamma, nil, :delta, [true, false]]
285
- }
286
-
287
- assert_html '<div class="alpha beta gamma delta true false"></div>', source
288
- end
289
180
  end
File without changes
@@ -27,9 +27,9 @@ markdown:
27
27
  }
28
28
  assert_html "<h1 id=\"header\">Header</h1>\n<p>Hello from Markdown!</p>\n\n<p>3</p>\n\n<ul>\n <li>one</li>\n <li>two</li>\n</ul>\n", source
29
29
 
30
- Slim::EmbeddedEngine.default_options[:markdown] = {:auto_ids => false}
31
- assert_html "<h1>Header</h1>\n<p>Hello from Markdown!</p>\n\n<p>3</p>\n\n<ul>\n <li>one</li>\n <li>two</li>\n</ul>\n", source
32
- Slim::EmbeddedEngine.default_options[:markdown] = nil
30
+ Slim::EmbeddedEngine.with_options(:markdown => {:auto_ids => false}) do
31
+ assert_html "<h1>Header</h1>\n<p>Hello from Markdown!</p>\n\n<p>3</p>\n\n<ul>\n <li>one</li>\n <li>two</li>\n</ul>\n", source
32
+ end
33
33
 
34
34
  assert_html "<h1 id=\"header\">Header</h1>\n<p>Hello from Markdown!</p>\n\n<p>3</p>\n\n<ul>\n <li>one</li>\n <li>two</li>\n</ul>\n", source
35
35
  end
@@ -114,18 +114,21 @@ scss:
114
114
  ruby:
115
115
  Embedded Ruby
116
116
  }
117
+ assert_runtime_error 'Embedded engine ruby is disabled', source, :enable_engines => [:javascript]
117
118
  assert_runtime_error 'Embedded engine ruby is disabled', source, :enable_engines => %w(javascript)
118
119
 
119
120
  source = %{
120
121
  ruby:
121
122
  Embedded Ruby
122
123
  }
124
+ assert_runtime_error 'Embedded engine ruby is disabled', source, :enable_engines => [:javascript]
123
125
  assert_runtime_error 'Embedded engine ruby is disabled', source, :enable_engines => %w(javascript)
124
126
 
125
127
  source = %{
126
128
  ruby:
127
129
  Embedded Ruby
128
130
  }
131
+ assert_runtime_error 'Embedded engine ruby is disabled', source, :disable_engines => [:ruby]
129
132
  assert_runtime_error 'Embedded engine ruby is disabled', source, :disable_engines => %w(ruby)
130
133
  end
131
134
 
@@ -134,12 +137,14 @@ ruby:
134
137
  javascript:
135
138
  $(function() {});
136
139
  }
140
+ assert_html '<script type="text/javascript">$(function() {});</script>', source, :disable_engines => [:ruby]
137
141
  assert_html '<script type="text/javascript">$(function() {});</script>', source, :disable_engines => %w(ruby)
138
142
 
139
143
  source = %q{
140
144
  javascript:
141
145
  $(function() {});
142
146
  }
147
+ assert_html '<script type="text/javascript">$(function() {});</script>', source, :enable_engines => [:javascript]
143
148
  assert_html '<script type="text/javascript">$(function() {});</script>', source, :enable_engines => %w(javascript)
144
149
  end
145
150
  end
File without changes
@@ -0,0 +1,218 @@
1
+ require 'helper'
2
+
3
+ class TestSlimHTMLAttributes < TestSlim
4
+ def test_ternary_operation_in_attribute
5
+ source = %q{
6
+ p id="#{(false ? 'notshown' : 'shown')}" = output_number
7
+ }
8
+
9
+ assert_html '<p id="shown">1337</p>', source
10
+ end
11
+
12
+ def test_ternary_operation_in_attribute_2
13
+ source = %q{
14
+ p id=(false ? 'notshown' : 'shown') = output_number
15
+ }
16
+
17
+ assert_html '<p id="shown">1337</p>', source
18
+ end
19
+
20
+ def test_class_attribute_merging
21
+ source = %{
22
+ .alpha class="beta" Test it
23
+ }
24
+ assert_html '<div class="alpha beta">Test it</div>', source
25
+ end
26
+
27
+ def test_class_attribute_merging_with_nil
28
+ source = %{
29
+ .alpha class="beta" class=nil class="gamma" Test it
30
+ }
31
+ assert_html '<div class="alpha beta gamma">Test it</div>', source
32
+ end
33
+
34
+ def test_class_attribute_merging_with_empty_static
35
+ source = %{
36
+ .alpha class="beta" class="" class="gamma" Test it
37
+ }
38
+ assert_html '<div class="alpha beta gamma">Test it</div>', source
39
+ end
40
+
41
+ def test_id_attribute_merging
42
+ source = %{
43
+ #alpha id="beta" Test it
44
+ }
45
+ assert_html '<div id="alpha_beta">Test it</div>', source, :attr_delimiter => {'class' => ' ', 'id' => '_' }
46
+ end
47
+
48
+ def test_id_attribute_merging2
49
+ source = %{
50
+ #alpha id="beta" Test it
51
+ }
52
+ assert_html '<div id="alpha-beta">Test it</div>', source, :attr_delimiter => {'class' => ' ', 'id' => '-' }
53
+ end
54
+
55
+ def test_boolean_attribute_false
56
+ source = %{
57
+ - cond=false
58
+ option selected=false Text
59
+ option selected=cond Text2
60
+ }
61
+
62
+ assert_html '<option>Text</option><option>Text2</option>', source
63
+ end
64
+
65
+ def test_boolean_attribute_true
66
+ source = %{
67
+ - cond=true
68
+ option selected=true Text
69
+ option selected=cond Text2
70
+ }
71
+
72
+ assert_html '<option selected="selected">Text</option><option selected="selected">Text2</option>', source
73
+ end
74
+
75
+ def test_boolean_attribute_nil
76
+ source = %{
77
+ - cond=nil
78
+ option selected=nil Text
79
+ option selected=cond Text2
80
+ }
81
+
82
+ assert_html '<option>Text</option><option>Text2</option>', source
83
+ end
84
+
85
+ def test_boolean_attribute_string2
86
+ source = %{
87
+ option selected="selected" Text
88
+ }
89
+
90
+ assert_html '<option selected="selected">Text</option>', source
91
+ end
92
+
93
+ def test_boolean_attribute_shortcut
94
+ source = %{
95
+ option(class="clazz" selected) Text
96
+ option(selected class="clazz") Text
97
+ }
98
+
99
+ assert_html '<option class="clazz" selected="selected">Text</option><option class="clazz" selected="selected">Text</option>', source
100
+ end
101
+
102
+ def test_array_attribute
103
+ source = %{
104
+ .alpha class="beta" class=[:gamma, nil, :delta, [true, false]]
105
+ }
106
+
107
+ assert_html '<div class="alpha beta gamma delta true false"></div>', source
108
+ end
109
+
110
+
111
+ def test_shortcut_splat
112
+ source = %q{
113
+ *hash This is my title
114
+ }
115
+
116
+ assert_html '<div a="The letter a" b="The letter b">This is my title</div>', source
117
+ end
118
+
119
+ def test_splat
120
+ source = %q{
121
+ h1 *hash This is my title
122
+ }
123
+
124
+ assert_html '<h1 a="The letter a" b="The letter b">This is my title</h1>', source
125
+ end
126
+
127
+ def test_splat_tag_name
128
+ source = %q{
129
+ *{:tag => 'h1', :id => 'title'} This is my title
130
+ }
131
+
132
+ assert_html '<h1 id="title">This is my title</h1>', source
133
+ end
134
+
135
+
136
+ def test_splat_empty_tag_name
137
+ source = %q{
138
+ *{:tag => '', :id => 'test'} This is my title
139
+ }
140
+
141
+ assert_html '<div id="test">This is my title</div>', source
142
+ end
143
+
144
+ def test_closed_splat_tag
145
+ source = %q{
146
+ *hash / This is my title
147
+ }
148
+
149
+ assert_html '<div a="The letter a" b="The letter b"/>', source
150
+ end
151
+
152
+ def test_splat_with_id_shortcut
153
+ source = %q{
154
+ #myid*hash This is my title
155
+ }
156
+
157
+ assert_html '<div a="The letter a" b="The letter b" id="myid">This is my title</div>', source
158
+ end
159
+
160
+ def test_splat_with_class_shortcut
161
+ source = %q{
162
+ .myclass*hash This is my title
163
+ }
164
+
165
+ assert_html '<div a="The letter a" b="The letter b" class="myclass">This is my title</div>', source
166
+ end
167
+
168
+ def test_splat_with_id_and_class_shortcuts
169
+ source = %q{
170
+ #myid.myclass*hash This is my title
171
+ }
172
+
173
+ assert_html '<div a="The letter a" b="The letter b" class="myclass" id="myid">This is my title</div>', source
174
+ end
175
+
176
+ def test_splat_with_class_merging
177
+ source = %q{
178
+ #myid.myclass *{:class => [:secondclass, %w(x y z)]} *hash This is my title
179
+ }
180
+
181
+ assert_html '<div a="The letter a" b="The letter b" class="myclass secondclass x y z" id="myid">This is my title</div>', source
182
+ end
183
+
184
+ def test_splat_with_boolean_attribute
185
+ source = %q{
186
+ *{:disabled => true, :empty1 => false, :nonempty => '', :empty2 => nil} This is my title
187
+ }
188
+
189
+ assert_html '<div disabled="disabled" nonempty="">This is my title</div>', source
190
+ end
191
+
192
+ def test_splat_merging_with_arrays
193
+ source = %q{
194
+ *{:a => 1, :b => 2} *[[:c, 3], [:d, 4]] *[[:e, 5], [:f, 6]] This is my title
195
+ }
196
+
197
+ assert_html '<div a="1" b="2" c="3" d="4" e="5" f="6">This is my title</div>', source
198
+ end
199
+
200
+ def test_splat_with_other_attributes
201
+ source = %q{
202
+ h1 data-id="123" *hash This is my title
203
+ }
204
+
205
+ assert_html '<h1 a="The letter a" b="The letter b" data-id="123">This is my title</h1>', source
206
+ end
207
+
208
+ def test_attribute_merging
209
+ source = %q{
210
+ a class=true class=false
211
+ a class=false *{:class=>true}
212
+ a class=true
213
+ a class=false
214
+ }
215
+
216
+ assert_html '<a class="true false"></a><a class="false true"></a><a class="class"></a><a></a>', source
217
+ end
218
+ end