slim 0.9.2 → 0.9.3
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.
- data/.gitignore +18 -0
- data/.yardopts +2 -0
- data/CHANGES +147 -0
- data/Gemfile +16 -0
- data/LICENSE +21 -0
- data/README.md +31 -36
- data/benchmarks/run.rb +114 -0
- data/benchmarks/src/complex.erb +23 -0
- data/benchmarks/src/complex.haml +18 -0
- data/benchmarks/src/complex.slim +17 -0
- data/benchmarks/src/complex_view.rb +15 -0
- data/extra/slim-mode.el +409 -0
- data/extra/test.slim +49 -0
- data/lib/slim/command.rb +0 -6
- data/lib/slim/compiler.rb +40 -51
- data/lib/slim/embedded_engine.rb +69 -41
- data/lib/slim/end_inserter.rb +5 -3
- data/lib/slim/engine.rb +9 -11
- data/lib/slim/filter.rb +10 -13
- data/lib/slim/grammar.rb +19 -0
- data/lib/slim/interpolation.rb +7 -5
- data/lib/slim/parser.rb +48 -33
- data/lib/slim/sections.rb +22 -26
- data/lib/slim/template.rb +7 -7
- data/lib/slim/version.rb +3 -1
- data/lib/slim/wrapper.rb +1 -0
- data/slim.gemspec +34 -0
- data/test/helper.rb +5 -1
- data/test/slim/test_code_escaping.rb +3 -3
- data/test/slim/test_code_evaluation.rb +49 -2
- data/test/slim/test_embedded_engines.rb +3 -1
- data/test/slim/test_html_escaping.rb +8 -0
- data/test/slim/test_html_structure.rb +18 -0
- data/test/slim/test_pretty.rb +8 -3
- data/test/slim/test_ruby_errors.rb +27 -2
- metadata +38 -46
data/extra/test.slim
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
doctype html
|
2
|
+
html
|
3
|
+
head
|
4
|
+
title Slim Test
|
5
|
+
meta name="keywords" content="slim, syntax"
|
6
|
+
|
7
|
+
javascript:
|
8
|
+
$(function() {
|
9
|
+
alert('Hello World');
|
10
|
+
});
|
11
|
+
|
12
|
+
haml:
|
13
|
+
#someid.someclass{:this => 'test'} Content in haml
|
14
|
+
|
15
|
+
erb:
|
16
|
+
<%= some_method(@request) %>
|
17
|
+
|
18
|
+
body
|
19
|
+
/ comment block
|
20
|
+
with multiple lines
|
21
|
+
This is another line
|
22
|
+
h1 = @page_title
|
23
|
+
p#notice.message
|
24
|
+
| Welcome to the the syntax test.
|
25
|
+
This file is to exercise the various markup.
|
26
|
+
This is another line
|
27
|
+
- unless @users.empty?
|
28
|
+
table
|
29
|
+
- for user in users do
|
30
|
+
tr
|
31
|
+
td.user id=some_ruby('ere', @rme) data-test="some text #{with @ruby}" = @post.name
|
32
|
+
- else
|
33
|
+
p There are no users.
|
34
|
+
|
35
|
+
/ Single comment line
|
36
|
+
#content Hello #{@user.name}! Welcome to the test page!
|
37
|
+
Try out Slim!
|
38
|
+
|
39
|
+
= function_with_many_parameters(:a, @variable, :option => 1)
|
40
|
+
|
41
|
+
p.text
|
42
|
+
' Another text block
|
43
|
+
with multiple lines
|
44
|
+
|
45
|
+
= link_to('Test', @site)
|
46
|
+
|
47
|
+
.text#footer
|
48
|
+
' Footer text block
|
49
|
+
with multiple lines
|
data/lib/slim/command.rb
CHANGED
@@ -52,10 +52,6 @@ module Slim
|
|
52
52
|
@options[:pretty] = true
|
53
53
|
end
|
54
54
|
|
55
|
-
opts.on('-d', '--debug', :NONE, 'Debugging output') do
|
56
|
-
@options[:debug] = true
|
57
|
-
end
|
58
|
-
|
59
55
|
opts.on_tail('-h', '--help', 'Show this message') do
|
60
56
|
puts opts
|
61
57
|
exit
|
@@ -91,14 +87,12 @@ module Slim
|
|
91
87
|
:pretty => @options[:pretty],
|
92
88
|
:sections => @options[:sections],
|
93
89
|
:disable_capture => @options[:rails],
|
94
|
-
:debug => @options[:debug],
|
95
90
|
:generator => @options[:rails] ?
|
96
91
|
Temple::Generators::RailsOutputBuffer :
|
97
92
|
Temple::Generators::ArrayBuffer).call(@options[:input].read))
|
98
93
|
else
|
99
94
|
@options[:output].puts(Slim::Template.new(@options[:file],
|
100
95
|
:pretty => @options[:pretty],
|
101
|
-
:debug => @options[:debug],
|
102
96
|
:sections => @options[:sections]) { @options[:input].read }.render)
|
103
97
|
end
|
104
98
|
end
|
data/lib/slim/compiler.rb
CHANGED
@@ -2,7 +2,7 @@ module Slim
|
|
2
2
|
# Compiles Slim expressions into Temple::HTML expressions.
|
3
3
|
# @api private
|
4
4
|
class Compiler < Filter
|
5
|
-
set_default_options :
|
5
|
+
set_default_options :bool_attrs => %w(selected)
|
6
6
|
|
7
7
|
# Handle control expression `[:slim, :control, code, content]`
|
8
8
|
#
|
@@ -11,16 +11,17 @@ module Slim
|
|
11
11
|
# @return [Array] Compiled temple expression
|
12
12
|
def on_slim_control(code, content)
|
13
13
|
[:multi,
|
14
|
-
[:
|
14
|
+
[:code, code],
|
15
15
|
compile(content)]
|
16
16
|
end
|
17
17
|
|
18
|
-
# Handle comment expression
|
18
|
+
# Handle conditional comment expression
|
19
|
+
# `[:slim, :conditional_comment, conditional, content]`
|
19
20
|
#
|
20
21
|
# @param [Array] content Temple expression
|
21
22
|
# @return [Array] Compiled temple expression
|
22
|
-
def
|
23
|
-
[:
|
23
|
+
def on_slim_condcomment(condition, content)
|
24
|
+
[:multi, [:static, "<!--[#{condition}]>"], compile(content), [:static, '<![endif]-->']]
|
24
25
|
end
|
25
26
|
|
26
27
|
# Handle output expression `[:slim, :output, escape, code, content]`
|
@@ -31,42 +32,28 @@ module Slim
|
|
31
32
|
# @return [Array] Compiled temple expression
|
32
33
|
def on_slim_output(escape, code, content)
|
33
34
|
if empty_exp?(content)
|
34
|
-
[:multi, [:escape, escape
|
35
|
+
[:multi, [:escape, escape, [:dynamic, code]], content]
|
35
36
|
else
|
36
|
-
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
# Handle output expression `[:slim, :output, escape, code, content]`
|
41
|
-
# if content is not empty.
|
42
|
-
#
|
43
|
-
# @param [Boolean] escape Escape html
|
44
|
-
# @param [String] code Ruby code
|
45
|
-
# @param [Array] content Temple expression
|
46
|
-
# @return [Array] Compiled temple expression
|
47
|
-
def on_slim_output_block(escape, code, content)
|
48
|
-
tmp = tmp_var(:output)
|
49
|
-
|
50
|
-
[:multi,
|
51
|
-
# Capture the result of the code in a variable. We can't do
|
52
|
-
# `[:dynamic, code]` because it's probably not a complete
|
53
|
-
# expression (which is a requirement for Temple).
|
54
|
-
[:block, "#{tmp} = #{code}"],
|
37
|
+
tmp = unique_name
|
55
38
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
# Output code in the block writes directly to the output buffer then.
|
62
|
-
# Rails handles this by replacing the output buffer for helpers (with_output_buffer - braindead!).
|
63
|
-
options[:disable_capture] ? compile(content) : [:capture, tmp_var(:output), compile(content)],
|
39
|
+
[:multi,
|
40
|
+
# Capture the result of the code in a variable. We can't do
|
41
|
+
# `[:dynamic, code]` because it's probably not a complete
|
42
|
+
# expression (which is a requirement for Temple).
|
43
|
+
[:block, "#{tmp} = #{code}",
|
64
44
|
|
65
|
-
|
66
|
-
|
45
|
+
# Capture the content of a block in a separate buffer. This means
|
46
|
+
# that `yield` will not output the content to the current buffer,
|
47
|
+
# but rather return the output.
|
48
|
+
#
|
49
|
+
# The capturing can be disabled with the option :disable_capture.
|
50
|
+
# Output code in the block writes directly to the output buffer then.
|
51
|
+
# Rails handles this by replacing the output buffer for helpers.
|
52
|
+
options[:disable_capture] ? compile(content) : [:capture, unique_name, compile(content)]],
|
67
53
|
|
68
|
-
|
69
|
-
|
54
|
+
# Output the content.
|
55
|
+
[:escape, escape, [:dynamic, tmp]]]
|
56
|
+
end
|
70
57
|
end
|
71
58
|
|
72
59
|
# Handle directive expression `[:slim, :directive, type, args]`
|
@@ -82,22 +69,24 @@ module Slim
|
|
82
69
|
end
|
83
70
|
end
|
84
71
|
|
85
|
-
# Handle
|
86
|
-
#
|
87
|
-
# @param [String] name Tag name
|
88
|
-
# @param [Array] attrs Attributes
|
89
|
-
# @param [Array] content Temple expression
|
90
|
-
# @return [Array] Compiled temple expression
|
91
|
-
def on_slim_tag(name, attrs, closed, content)
|
92
|
-
[:html, :tag, name, compile(attrs), closed, compile(content)]
|
93
|
-
end
|
94
|
-
|
95
|
-
# Handle tag attributes expression `[:slim, :attrs, *attrs]`
|
72
|
+
# Handle attribute expression `[:slim, :attr, escape, code]`
|
96
73
|
#
|
97
|
-
# @param [
|
74
|
+
# @param [Boolean] escape Escape html
|
75
|
+
# @param [String] code Ruby code
|
98
76
|
# @return [Array] Compiled temple expression
|
99
|
-
def
|
100
|
-
[:
|
77
|
+
def on_slim_attr(name, escape, code)
|
78
|
+
if options[:bool_attrs].include?(name)
|
79
|
+
escape = false
|
80
|
+
value = [:dynamic, "(#{code}) ? #{name.inspect} : nil"]
|
81
|
+
elsif delimiter = options[:attr_delimiter][name]
|
82
|
+
tmp = unique_name
|
83
|
+
value = [:multi,
|
84
|
+
[:code, "#{tmp} = #{code}"],
|
85
|
+
[:dynamic, "#{tmp}.respond_to?(:join) ? #{tmp}.flatten.compact.join(#{delimiter.inspect}) : #{tmp}"]]
|
86
|
+
else
|
87
|
+
value = [:dynamic, code]
|
88
|
+
end
|
89
|
+
[:html, :attr, name, [:escape, escape, value]]
|
101
90
|
end
|
102
91
|
end
|
103
92
|
end
|
data/lib/slim/embedded_engine.rb
CHANGED
@@ -7,96 +7,124 @@ module Slim
|
|
7
7
|
class << self
|
8
8
|
attr_reader :engines
|
9
9
|
|
10
|
+
# Register embedded engine
|
11
|
+
#
|
12
|
+
# @param [String] name Name of the engine
|
13
|
+
# @param [Class] klass Engine class
|
14
|
+
# @param option_filter List of options to pass to engine.
|
15
|
+
# Last argument can be default option hash.
|
10
16
|
def register(name, klass, *option_filter)
|
11
17
|
local_options = Hash === option_filter.last ? option_filter.pop : nil
|
12
18
|
@engines[name.to_s] = [klass, option_filter, local_options]
|
13
19
|
end
|
14
20
|
end
|
15
21
|
|
22
|
+
def on_slim_embedded(name, body)
|
23
|
+
new_engine(name).on_slim_embedded(name, body)
|
24
|
+
end
|
25
|
+
|
26
|
+
protected
|
27
|
+
|
16
28
|
def new_engine(name)
|
17
29
|
name = name.to_s
|
18
30
|
raise "Embedded engine #{name} is disabled" if (options[:enable_engines] && !options[:enable_engines].include?(name)) ||
|
19
31
|
(options[:disable_engines] && options[:disable_engines].include?(name))
|
20
32
|
engine, option_filter, local_options = self.class.engines[name] || raise("Embedded engine #{name} not found")
|
21
33
|
filtered_options = Hash[*option_filter.select {|k| options.include?(k) }.map {|k| [k, options[k]] }.flatten]
|
22
|
-
engine.new(Temple::
|
34
|
+
engine.new(Temple::ImmutableHash.new(local_options, filtered_options))
|
23
35
|
end
|
24
36
|
|
25
|
-
def
|
26
|
-
|
37
|
+
def collect_text(body)
|
38
|
+
body[1..-1].inject('') do |text, exp|
|
39
|
+
exp[0] == :slim && exp[1] == :interpolate ? (text << exp[2]) : text
|
40
|
+
end
|
27
41
|
end
|
28
42
|
|
29
|
-
def
|
30
|
-
body.inject(
|
31
|
-
|
32
|
-
text
|
43
|
+
def collect_newlines(body)
|
44
|
+
body[1..-1].inject([:multi]) do |multi, exp|
|
45
|
+
exp[0] == :newline ? (multi << exp) : multi
|
33
46
|
end
|
34
47
|
end
|
35
48
|
|
49
|
+
# Basic tilt engine
|
36
50
|
class TiltEngine < EmbeddedEngine
|
37
|
-
def on_slim_embedded(engine,
|
38
|
-
text = collect_text(body)
|
51
|
+
def on_slim_embedded(engine, body)
|
39
52
|
engine = Tilt[engine] || raise("Tilt engine #{engine} is not available.")
|
40
|
-
|
53
|
+
[:multi, render(engine, collect_text(body)), collect_newlines(body)]
|
41
54
|
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Tilt-based static template (evaluated at compile-time)
|
58
|
+
class StaticTiltEngine < TiltEngine
|
59
|
+
protected
|
42
60
|
|
43
|
-
def
|
44
|
-
# Static template
|
61
|
+
def render(engine, text)
|
45
62
|
[:static, engine.new { text }.render]
|
46
63
|
end
|
47
64
|
end
|
48
65
|
|
49
|
-
|
50
|
-
|
51
|
-
|
66
|
+
# Sass engine which supports :pretty option
|
67
|
+
class SassEngine < StaticTiltEngine
|
68
|
+
protected
|
69
|
+
|
70
|
+
def render(engine, text)
|
71
|
+
text = engine.new(:style => (options[:pretty] ? :expanded : :compressed), :cache => false) { text }.render
|
52
72
|
text.chomp!
|
53
73
|
[:static, options[:pretty] ? "\n#{text}\n" : text]
|
54
74
|
end
|
55
75
|
end
|
56
76
|
|
57
|
-
|
77
|
+
# Tilt-based engine which is fully dynamically evaluated during runtime (Slow and uncached)
|
78
|
+
class DynamicTiltEngine < StaticTiltEngine
|
79
|
+
protected
|
80
|
+
|
58
81
|
# Code to collect local variables
|
59
82
|
COLLECT_LOCALS = %q{eval('{' + local_variables.select {|v| v[0] != ?_ }.map {|v| ":#{v}=>#{v}" }.join(',') + '}')}
|
60
83
|
|
61
|
-
def
|
62
|
-
# Fully dynamic evaluation of the template during runtime (Slow and uncached)
|
84
|
+
def render(engine, text)
|
63
85
|
[:dynamic, "#{engine.name}.new { #{text.inspect} }.render(self, #{COLLECT_LOCALS})"]
|
64
86
|
end
|
65
87
|
end
|
66
88
|
|
67
|
-
|
68
|
-
|
69
|
-
|
89
|
+
# Tilt-based engine which is precompiled
|
90
|
+
class PrecompiledTiltEngine < StaticTiltEngine
|
91
|
+
protected
|
92
|
+
|
93
|
+
def render(engine, text)
|
70
94
|
# WARNING: This is a bit of a hack. Tilt::Engine#precompiled is protected
|
71
|
-
|
72
|
-
[:dynamic, "proc { #{precompiled} }.call"]
|
95
|
+
[:dynamic, engine.new { text }.send(:precompiled, {}).first]
|
73
96
|
end
|
74
97
|
end
|
75
98
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
99
|
+
# Static template with interpolated ruby code
|
100
|
+
class InterpolateTiltEngine < StaticTiltEngine
|
101
|
+
protected
|
102
|
+
|
103
|
+
def render(engine, text)
|
104
|
+
[:slim, :interpolate, engine.new { text }.render]
|
80
105
|
end
|
81
106
|
end
|
82
107
|
|
108
|
+
# ERB engine (uses the Temple ERB implementation)
|
83
109
|
class ERBEngine < EmbeddedEngine
|
84
|
-
def on_slim_embedded(engine,
|
85
|
-
|
86
|
-
Temple::ERB::Parser.new(:auto_escape => true).call(text)
|
110
|
+
def on_slim_embedded(engine, body)
|
111
|
+
Temple::ERB::Parser.new.call(collect_text(body))
|
87
112
|
end
|
88
113
|
end
|
89
114
|
|
115
|
+
# Tag wrapper engine
|
116
|
+
# Generates a html tag and wraps another engine (specified via :engine option)
|
90
117
|
class TagEngine < EmbeddedEngine
|
91
|
-
def on_slim_embedded(engine,
|
92
|
-
content = options[:engine] ? options[:engine].new(options).on_slim_embedded(engine,
|
93
|
-
[:
|
118
|
+
def on_slim_embedded(engine, body)
|
119
|
+
content = options[:engine] ? options[:engine].new(options).on_slim_embedded(engine, body) : [:multi, body]
|
120
|
+
[:html, :tag, options[:tag], [:html, :attrs, *options[:attributes].map {|k, v| [:html, :attr, k, [:static, v]] }], content]
|
94
121
|
end
|
95
122
|
end
|
96
123
|
|
124
|
+
# Embeds ruby code
|
97
125
|
class RubyEngine < EmbeddedEngine
|
98
|
-
def on_slim_embedded(engine,
|
99
|
-
[:
|
126
|
+
def on_slim_embedded(engine, body)
|
127
|
+
[:code, "\n" + collect_text(body)]
|
100
128
|
end
|
101
129
|
end
|
102
130
|
|
@@ -107,10 +135,10 @@ module Slim
|
|
107
135
|
register :creole, InterpolateTiltEngine
|
108
136
|
|
109
137
|
# These engines are executed at compile time
|
110
|
-
register :coffee, TagEngine, :tag =>
|
111
|
-
register :less, TagEngine, :tag =>
|
112
|
-
register :sass, TagEngine, :pretty, :tag =>
|
113
|
-
register :scss, TagEngine, :pretty, :tag =>
|
138
|
+
register :coffee, TagEngine, :tag => :script, :attributes => { :type => 'text/javascript' }, :engine => StaticTiltEngine
|
139
|
+
register :less, TagEngine, :tag => :style, :attributes => { :type => 'text/css' }, :engine => StaticTiltEngine
|
140
|
+
register :sass, TagEngine, :pretty, :tag => :style, :attributes => { :type => 'text/css' }, :engine => SassEngine
|
141
|
+
register :scss, TagEngine, :pretty, :tag => :style, :attributes => { :type => 'text/css' }, :engine => SassEngine
|
114
142
|
|
115
143
|
# These engines are precompiled, code is embedded
|
116
144
|
register :erb, ERBEngine
|
@@ -124,8 +152,8 @@ module Slim
|
|
124
152
|
register :markaby, DynamicTiltEngine
|
125
153
|
|
126
154
|
# Embedded javascript/css
|
127
|
-
register :javascript, TagEngine, :tag =>
|
128
|
-
register :css, TagEngine, :tag =>
|
155
|
+
register :javascript, TagEngine, :tag => :script, :attributes => { :type => 'text/javascript' }
|
156
|
+
register :css, TagEngine, :tag => :style, :attributes => { :type => 'text/css' }
|
129
157
|
|
130
158
|
# Embedded ruby code
|
131
159
|
register :ruby, RubyEngine
|
data/lib/slim/end_inserter.rb
CHANGED
@@ -14,6 +14,8 @@ module Slim
|
|
14
14
|
END_REGEX = /^end\b/
|
15
15
|
|
16
16
|
# Handle multi expression `[:multi, *exps]`
|
17
|
+
#
|
18
|
+
# @return [Array] Corrected Temple expression with ends inserted
|
17
19
|
def on_multi(*exps)
|
18
20
|
result = [:multi]
|
19
21
|
# This variable is true if the previous line was
|
@@ -46,12 +48,12 @@ module Slim
|
|
46
48
|
|
47
49
|
private
|
48
50
|
|
49
|
-
# Appends an end
|
51
|
+
# Appends an end
|
50
52
|
def append_end(result)
|
51
|
-
result << [:
|
53
|
+
result << [:code, 'end']
|
52
54
|
end
|
53
55
|
|
54
|
-
# Checks if an expression is a Slim control code
|
56
|
+
# Checks if an expression is a Slim control code
|
55
57
|
def control?(exp)
|
56
58
|
exp[0] == :slim && exp[1] == :control
|
57
59
|
end
|
data/lib/slim/engine.rb
CHANGED
@@ -11,7 +11,7 @@ module Slim
|
|
11
11
|
set_default_options :pretty => false,
|
12
12
|
:attr_wrapper => '"',
|
13
13
|
:format => :html5,
|
14
|
-
:
|
14
|
+
:attr_delimiter => {'class' => ' '},
|
15
15
|
:generator => Temple::Generators::ArrayBuffer
|
16
16
|
|
17
17
|
#
|
@@ -28,12 +28,12 @@ module Slim
|
|
28
28
|
# String | :dictionary | "self" | Name of dictionary variable in sections mode
|
29
29
|
# Symbol | :dictionary_access | :wrapped | Access mode of dictionary variable (:wrapped, :symbol, :string)
|
30
30
|
# Boolean | :disable_capture | false (true in Rails) | Disable capturing in blocks (blocks write to the default buffer then)
|
31
|
-
# Boolean | :
|
32
|
-
# Boolean | :use_html_safe | false (true in Rails) | Use String#html_safe? from ActiveSupport (Works together with :
|
33
|
-
# Boolean | :debug | false | Enable debug outputs (Temple internals)
|
31
|
+
# Boolean | :disable_escape | false | Disable automatic escaping of strings
|
32
|
+
# Boolean | :use_html_safe | false (true in Rails) | Use String#html_safe? from ActiveSupport (Works together with :disable_escape)
|
34
33
|
# Symbol | :format | :html5 | HTML output format
|
35
34
|
# String | :attr_wrapper | '"' | Character to wrap attributes in html (can be ' or ")
|
36
|
-
#
|
35
|
+
# Hash | :attr_delimiter | {'class' => ' '} | Joining character used if multiple html attributes are supplied (e.g. id1_id2)
|
36
|
+
# String list | :bool_attrs | %w(selected) | List of boolean attributes
|
37
37
|
# Boolean | :pretty | false | Pretty html indenting (This is slower!)
|
38
38
|
# Class | :generator | ArrayBuffer/RailsOutputBuffer | Temple code generator (default generator generates array buffer)
|
39
39
|
#
|
@@ -58,14 +58,12 @@ module Slim
|
|
58
58
|
use Slim::Interpolation
|
59
59
|
use Slim::Sections, :sections, :dictionary, :dictionary_access
|
60
60
|
use Slim::EndInserter
|
61
|
-
use Slim::Compiler, :disable_capture, :
|
62
|
-
|
63
|
-
filter :
|
64
|
-
|
61
|
+
use Slim::Compiler, :disable_capture, :attr_delimiter, :bool_attrs
|
62
|
+
use Temple::HTML::Pretty, :format, :attr_wrapper, :attr_delimiter, :pretty
|
63
|
+
filter :Escapable, :use_html_safe, :disable_escape
|
64
|
+
filter :ControlFlow
|
65
65
|
filter :MultiFlattener
|
66
|
-
filter :StaticMerger
|
67
66
|
filter :DynamicInliner
|
68
|
-
filter :Debugger, :debug, :debug_prefix => 'Optimized code'
|
69
67
|
use(:Generator) {|exp| options[:generator].new(options).call(exp) }
|
70
68
|
end
|
71
69
|
end
|