slim 1.3.0 → 1.3.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +9 -5
- data/CHANGES +23 -0
- data/Gemfile +13 -2
- data/README.md +346 -105
- data/Rakefile +21 -9
- data/lib/slim.rb +3 -3
- data/lib/slim/boolean_attributes.rb +67 -0
- data/lib/slim/command.rb +23 -23
- data/lib/slim/control_structures.rb +57 -0
- data/lib/slim/embedded_engine.rb +80 -43
- data/lib/slim/end_inserter.rb +1 -1
- data/lib/slim/engine.rb +21 -15
- data/lib/slim/filter.rb +0 -6
- data/lib/slim/grammar.rb +2 -7
- data/lib/slim/interpolation.rb +1 -1
- data/lib/slim/logic_less/filter.rb +8 -8
- data/lib/slim/logic_less/wrapper.rb +1 -1
- data/lib/slim/parser.rb +51 -52
- data/lib/slim/splat_attributes.rb +112 -0
- data/lib/slim/translator.rb +13 -12
- data/lib/slim/version.rb +1 -1
- data/slim.gemspec +1 -1
- data/test/{slim → core}/helper.rb +3 -7
- data/test/{slim → core}/test_code_blocks.rb +0 -0
- data/test/{slim → core}/test_code_escaping.rb +4 -4
- data/test/{slim → core}/test_code_evaluation.rb +3 -112
- data/test/{slim → core}/test_code_output.rb +0 -0
- data/test/{slim → core}/test_code_structure.rb +0 -0
- data/test/{slim → core}/test_embedded_engines.rb +8 -3
- data/test/{slim → core}/test_encoding.rb +0 -0
- data/test/core/test_html_attributes.rb +218 -0
- data/test/{slim → core}/test_html_escaping.rb +17 -0
- data/test/{slim → core}/test_html_structure.rb +13 -98
- data/test/{slim → core}/test_parser_errors.rb +24 -15
- data/test/{slim → core}/test_pretty.rb +0 -0
- data/test/{slim → core}/test_ruby_errors.rb +7 -0
- data/test/{slim → core}/test_slim_template.rb +0 -0
- data/test/{slim → core}/test_text_interpolation.rb +2 -2
- data/test/core/test_thread_options.rb +18 -0
- data/test/{slim/logic_less → logic_less}/test_logic_less.rb +21 -0
- data/test/{slim/logic_less → logic_less}/test_wrapper.rb +3 -3
- data/test/rails/app/controllers/slim_controller.rb +4 -2
- data/test/rails/app/views/parents/_form.html.slim +1 -0
- data/test/rails/app/views/parents/edit.html.slim +2 -1
- data/test/rails/app/views/parents/new.html.slim +2 -1
- data/test/rails/app/views/slim/thread_options.html.slim +1 -0
- data/test/rails/test/test_slim.rb +6 -4
- data/test/{slim/translator → translator}/test_translator.rb +0 -0
- metadata +44 -82
- data/lib/slim/compiler.rb +0 -194
- data/test/rails/app/views/slim/nil.html.slim +0 -1
- data/test/slim/test_chain_manipulation.rb +0 -42
data/lib/slim/end_inserter.rb
CHANGED
@@ -24,7 +24,7 @@ module Slim
|
|
24
24
|
|
25
25
|
exps.each do |exp|
|
26
26
|
if control?(exp)
|
27
|
-
raise 'Explicit end statements are forbidden' if exp[2] =~ END_REGEX
|
27
|
+
raise(Temple::FilterError, 'Explicit end statements are forbidden') if exp[2] =~ END_REGEX
|
28
28
|
|
29
29
|
# Two control code in a row. If this one is *not*
|
30
30
|
# an else block, we should close the previous one.
|
data/lib/slim/engine.rb
CHANGED
@@ -4,29 +4,35 @@ module Slim
|
|
4
4
|
class Engine < Temple::Engine
|
5
5
|
# This overwrites some Temple default options or sets default options for Slim specific filters.
|
6
6
|
# It is recommended to set the default settings only once in the code and avoid duplication. Only use
|
7
|
-
# `
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
:default_tag => 'div'
|
7
|
+
# `define_options` when you have to override some default settings.
|
8
|
+
define_options :pretty => false,
|
9
|
+
:sort_attrs => true,
|
10
|
+
:attr_wrapper => '"',
|
11
|
+
:attr_delimiter => {'class' => ' '},
|
12
|
+
:generator => Temple::Generators::ArrayBuffer,
|
13
|
+
:default_tag => 'div'
|
15
14
|
|
16
|
-
|
15
|
+
# TODO: Remove these options in 1.4.0
|
16
|
+
define_deprecated_options :remove_empty_attrs, :chain
|
17
|
+
|
18
|
+
use Slim::Parser, :file, :tabsize, :encoding, :shortcut, :default_tag, :escape_quoted_attrs
|
17
19
|
use Slim::EmbeddedEngine, :enable_engines, :disable_engines, :pretty
|
18
20
|
use Slim::Interpolation
|
19
21
|
use Slim::EndInserter
|
20
|
-
use Slim::
|
21
|
-
|
22
|
+
use Slim::ControlStructures, :disable_capture
|
23
|
+
use Slim::SplatAttributes, :attr_delimiter, :attr_wrapper, :sort_attrs, :default_tag
|
22
24
|
html :AttributeSorter, :sort_attrs
|
23
|
-
html :
|
25
|
+
html :AttributeMerger, :attr_delimiter
|
26
|
+
use Slim::BooleanAttributes, :attr_delimiter
|
24
27
|
html :Pretty, :format, :attr_wrapper, :pretty, :indent
|
25
28
|
filter :Escapable, :use_html_safe, :disable_escape
|
26
29
|
filter :ControlFlow
|
27
30
|
filter :MultiFlattener
|
28
|
-
use
|
29
|
-
|
30
|
-
|
31
|
+
use :Optimizer do
|
32
|
+
(options[:streaming] ? Temple::Filters::StaticMerger : Temple::Filters::DynamicInliner).new
|
33
|
+
end
|
34
|
+
use :Generator do
|
35
|
+
options[:generator].new(options.to_hash.reject {|k,v| !options[:generator].default_options.valid_keys.include?(k) })
|
36
|
+
end
|
31
37
|
end
|
32
38
|
end
|
data/lib/slim/filter.rb
CHANGED
@@ -31,11 +31,5 @@ module Slim
|
|
31
31
|
def on_slim_attrs(*attrs)
|
32
32
|
[:slim, :attrs, *attrs.map {|a| compile(a) }]
|
33
33
|
end
|
34
|
-
|
35
|
-
# Pass-through handler
|
36
|
-
def on_slim_tag(name, attrs, content = nil)
|
37
|
-
tag = [:slim, :tag, name, compile(attrs)]
|
38
|
-
content ? (tag << compile(content)) : tag
|
39
|
-
end
|
40
34
|
end
|
41
35
|
end
|
data/lib/slim/grammar.rb
CHANGED
@@ -10,14 +10,9 @@ module Slim
|
|
10
10
|
[:slim, :interpolate, String] |
|
11
11
|
[:slim, :embedded, String, Expression] |
|
12
12
|
[:slim, :text, Expression] |
|
13
|
-
[:slim, :
|
13
|
+
[:slim, :attrvalue, Bool, String]
|
14
14
|
|
15
|
-
|
16
|
-
[:slim, :attrs, 'SlimAttr*']
|
17
|
-
|
18
|
-
SlimAttr <<
|
19
|
-
HTMLAttr |
|
20
|
-
[:slim, :attr, String, Bool, String] |
|
15
|
+
HTMLAttr <<
|
21
16
|
[:slim, :splat, String]
|
22
17
|
end
|
23
18
|
end
|
data/lib/slim/interpolation.rb
CHANGED
@@ -1,16 +1,16 @@
|
|
1
1
|
module Slim
|
2
|
-
# Handle logic
|
2
|
+
# Handle logic less mode
|
3
3
|
# This filter can be activated with the option "logic_less"
|
4
4
|
# @api private
|
5
5
|
class LogicLess < Filter
|
6
|
-
|
7
|
-
|
8
|
-
|
6
|
+
define_options :logic_less => true,
|
7
|
+
:dictionary => 'self',
|
8
|
+
:dictionary_access => :wrapped # :symbol, :string, :wrapped
|
9
9
|
|
10
10
|
def initialize(opts = {})
|
11
11
|
super
|
12
12
|
unless [:string, :symbol, :wrapped].include?(options[:dictionary_access])
|
13
|
-
raise "Invalid dictionary access #{options[:dictionary_access].inspect}"
|
13
|
+
raise ArgumentError, "Invalid dictionary access #{options[:dictionary_access].inspect}"
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
@@ -37,7 +37,7 @@ module Slim
|
|
37
37
|
end
|
38
38
|
|
39
39
|
def on_slim_output(escape, name, content)
|
40
|
-
raise 'Output statements with content are forbidden in logic less mode' if !empty_exp?(content)
|
40
|
+
raise(Temple::FilterError, 'Output statements with content are forbidden in logic less mode') if !empty_exp?(content)
|
41
41
|
[:slim, :output, escape, access(name), content]
|
42
42
|
end
|
43
43
|
|
@@ -50,11 +50,11 @@ module Slim
|
|
50
50
|
end
|
51
51
|
|
52
52
|
def on_dynamic(code)
|
53
|
-
raise 'Embedded code is forbidden in logic less mode'
|
53
|
+
raise Temple::FilterError, 'Embedded code is forbidden in logic less mode'
|
54
54
|
end
|
55
55
|
|
56
56
|
def on_code(code)
|
57
|
-
raise 'Embedded code is forbidden in logic less mode'
|
57
|
+
raise Temple::FilterError, 'Embedded code is forbidden in logic less mode'
|
58
58
|
end
|
59
59
|
|
60
60
|
protected
|
data/lib/slim/parser.rb
CHANGED
@@ -1,15 +1,16 @@
|
|
1
1
|
module Slim
|
2
2
|
# Parses Slim code and transforms it to a Temple expression
|
3
3
|
# @api private
|
4
|
-
class Parser
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
4
|
+
class Parser < Temple::Parser
|
5
|
+
define_options :file,
|
6
|
+
:default_tag,
|
7
|
+
:escape_quoted_attrs => false,
|
8
|
+
:tabsize => 4,
|
9
|
+
:encoding => 'utf-8',
|
10
|
+
:shortcut => {
|
11
|
+
'#' => 'id',
|
12
|
+
'.' => 'class'
|
13
|
+
}
|
13
14
|
|
14
15
|
class SyntaxError < StandardError
|
15
16
|
attr_reader :error, :file, :line, :lineno, :column
|
@@ -26,22 +27,22 @@ module Slim
|
|
26
27
|
line = @line.strip
|
27
28
|
column = @column + line.size - @line.size
|
28
29
|
%{#{error}
|
29
|
-
#{file}, Line #{lineno}
|
30
|
+
#{file}, Line #{lineno}, Column #{@column}
|
30
31
|
#{line}
|
31
32
|
#{' ' * column}^
|
32
33
|
}
|
33
34
|
end
|
34
35
|
end
|
35
36
|
|
36
|
-
def initialize(
|
37
|
+
def initialize(opts = {})
|
37
38
|
super
|
38
|
-
@tab = ' ' *
|
39
|
+
@tab = ' ' * options[:tabsize]
|
39
40
|
@shortcut = {}
|
40
|
-
|
41
|
+
options[:shortcut].each do |k,v|
|
41
42
|
@shortcut[k] = if v =~ /\A([^\s]+)\s+([^\s]+)\Z/
|
42
43
|
[$1, $2]
|
43
44
|
else
|
44
|
-
[
|
45
|
+
[options[:default_tag], v]
|
45
46
|
end
|
46
47
|
end
|
47
48
|
shortcut = "[#{Regexp.escape @shortcut.keys.join}]"
|
@@ -72,7 +73,7 @@ module Slim
|
|
72
73
|
result
|
73
74
|
end
|
74
75
|
|
75
|
-
|
76
|
+
protected
|
76
77
|
|
77
78
|
DELIMITERS = {
|
78
79
|
'(' => ')',
|
@@ -82,8 +83,8 @@ module Slim
|
|
82
83
|
|
83
84
|
DELIMITER_REGEX = /\A[#{Regexp.escape DELIMITERS.keys.join}]/
|
84
85
|
ATTR_NAME = '\A\s*(\w[:\w-]*)'
|
85
|
-
QUOTED_ATTR_REGEX = /#{ATTR_NAME}=("|')/
|
86
|
-
CODE_ATTR_REGEX = /#{ATTR_NAME}
|
86
|
+
QUOTED_ATTR_REGEX = /#{ATTR_NAME}=(=?)("|')/
|
87
|
+
CODE_ATTR_REGEX = /#{ATTR_NAME}=(=?)/
|
87
88
|
|
88
89
|
def reset(lines = nil, stacks = nil)
|
89
90
|
# Since you can indent however you like in Slim, we need to keep a list
|
@@ -176,25 +177,28 @@ module Slim
|
|
176
177
|
|
177
178
|
def parse_line_indicators
|
178
179
|
case @line
|
180
|
+
when /\A\/!( ?)/
|
181
|
+
# HTML comment
|
182
|
+
@stacks.last << [:html, :comment, [:slim, :text, parse_text_block($', @indents.last + $1.size + 2)]]
|
183
|
+
when /\A\/\[\s*(.*?)\s*\]\s*\Z/
|
184
|
+
# HTML conditional comment
|
185
|
+
block = [:multi]
|
186
|
+
@stacks.last << [:html, :condcomment, $1, block]
|
187
|
+
@stacks << block
|
179
188
|
when /\A\//
|
180
|
-
#
|
181
|
-
|
182
|
-
|
183
|
-
@stacks.last << [:html, :comment, [:slim, :text, parse_text_block($2, @indents.last + $1.size + 2)]]
|
184
|
-
elsif @line =~ %r{\A/\[\s*(.*?)\s*\]\s*\Z}
|
185
|
-
# HTML conditional comment
|
186
|
-
block = [:multi]
|
187
|
-
@stacks.last << [:html, :condcomment, $1, block]
|
188
|
-
@stacks << block
|
189
|
-
else
|
190
|
-
# Slim comment
|
191
|
-
parse_comment_block
|
192
|
-
end
|
193
|
-
when /\A([\|'])( ?)(.*)\Z/
|
189
|
+
# Slim comment
|
190
|
+
parse_comment_block
|
191
|
+
when /\A([\|'])( ?)/
|
194
192
|
# Found a text block.
|
195
193
|
trailing_ws = $1 == "'"
|
196
|
-
@stacks.last << [:slim, :text, parse_text_block($
|
194
|
+
@stacks.last << [:slim, :text, parse_text_block($', @indents.last + $2.size + 1)]
|
197
195
|
@stacks.last << [:static, ' '] if trailing_ws
|
196
|
+
when /\A</
|
197
|
+
# Inline html
|
198
|
+
# @stacks.last << parse_text_block(@line, @indents.last + 1)
|
199
|
+
block = [:multi]
|
200
|
+
@stacks.last << [:multi, [:slim, :interpolate, @line], block]
|
201
|
+
@stacks << block
|
198
202
|
when /\A-/
|
199
203
|
# Found a code block.
|
200
204
|
# We expect the line to be broken or the next line to be indented.
|
@@ -289,7 +293,7 @@ module Slim
|
|
289
293
|
end
|
290
294
|
|
291
295
|
def parse_tag(tag)
|
292
|
-
tag = [:
|
296
|
+
tag = [:html, :tag, @shortcut[tag] ? @shortcut[tag][0] : tag, parse_attributes]
|
293
297
|
@stacks.last << tag
|
294
298
|
|
295
299
|
case @line
|
@@ -325,7 +329,7 @@ module Slim
|
|
325
329
|
end
|
326
330
|
|
327
331
|
def parse_attributes
|
328
|
-
attributes = [:
|
332
|
+
attributes = [:html, :attrs]
|
329
333
|
|
330
334
|
# Find any shortcut attributes
|
331
335
|
while @line =~ @shortcut_regex
|
@@ -356,20 +360,21 @@ module Slim
|
|
356
360
|
when QUOTED_ATTR_REGEX
|
357
361
|
# Value is quoted (static)
|
358
362
|
@line = $'
|
359
|
-
attributes << [:html, :attr, $1,
|
363
|
+
attributes << [:html, :attr, $1,
|
364
|
+
[:escape, options[:escape_quoted_attrs] && $2.empty?,
|
365
|
+
[:slim, :interpolate, parse_quoted_attribute($3)]]]
|
360
366
|
when CODE_ATTR_REGEX
|
361
367
|
# Value is ruby code
|
362
368
|
@line = $'
|
363
|
-
escape = @line[0] != ?=
|
364
|
-
@line.slice!(0) unless escape
|
365
369
|
name = $1
|
370
|
+
escape = $2.empty?
|
366
371
|
value = parse_ruby_code(delimiter)
|
367
372
|
# Remove attribute wrapper which doesn't belong to the ruby code
|
368
373
|
# e.g id=[hash[:a] + hash[:b]]
|
369
374
|
value = value[1..-2] if value =~ DELIMITER_REGEX &&
|
370
375
|
DELIMITERS[$&] == value[-1, 1]
|
371
376
|
syntax_error!('Invalid empty attribute') if value.empty?
|
372
|
-
attributes << [:
|
377
|
+
attributes << [:html, :attr, name, [:slim, :attrvalue, escape, value]]
|
373
378
|
else
|
374
379
|
break unless delimiter
|
375
380
|
|
@@ -377,7 +382,7 @@ module Slim
|
|
377
382
|
when boolean_attr_regex
|
378
383
|
# Boolean attribute
|
379
384
|
@line = $'
|
380
|
-
attributes << [:
|
385
|
+
attributes << [:html, :attr, $1, [:slim, :attrvalue, false, 'true']]
|
381
386
|
when end_regex
|
382
387
|
# Find ending delimiter
|
383
388
|
@line = $'
|
@@ -389,11 +394,8 @@ module Slim
|
|
389
394
|
|
390
395
|
# Attributes span multiple lines
|
391
396
|
@stacks.last << [:newline]
|
392
|
-
|
393
|
-
next_line
|
394
|
-
:orig_line => orig_line,
|
395
|
-
:lineno => lineno,
|
396
|
-
:column => orig_line.size)
|
397
|
+
syntax_error!("Expected closing delimiter #{delimiter}") if @lines.empty?
|
398
|
+
next_line
|
397
399
|
end
|
398
400
|
end
|
399
401
|
end
|
@@ -442,19 +444,16 @@ module Slim
|
|
442
444
|
end
|
443
445
|
|
444
446
|
syntax_error!("Expected closing brace }") if count != 0
|
447
|
+
syntax_error!("Expected closing quote #{quote}") if @line[0] != quote[0]
|
445
448
|
@line.slice!(0)
|
449
|
+
|
446
450
|
value
|
447
451
|
end
|
448
452
|
|
449
453
|
# Helper for raising exceptions
|
450
|
-
def syntax_error!(message
|
451
|
-
|
452
|
-
|
453
|
-
args[:lineno] ||= @lineno
|
454
|
-
args[:column] ||= args[:orig_line] && args[:line] ?
|
455
|
-
args[:orig_line].size - args[:line].size : 0
|
456
|
-
raise SyntaxError.new(message, options[:file],
|
457
|
-
args[:orig_line], args[:lineno], args[:column])
|
454
|
+
def syntax_error!(message)
|
455
|
+
raise SyntaxError.new(message, options[:file], @orig_line, @lineno,
|
456
|
+
@orig_line && @line ? @orig_line.size - @line.size : 0)
|
458
457
|
end
|
459
458
|
end
|
460
459
|
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
module Slim
|
2
|
+
# @api private
|
3
|
+
class SplatAttributes < Filter
|
4
|
+
define_options :attr_delimiter, :attr_wrapper, :sort_attrs, :default_tag
|
5
|
+
|
6
|
+
def call(exp)
|
7
|
+
@attr_delimiter, @splat_used = unique_name, false
|
8
|
+
exp = compile(exp)
|
9
|
+
if @splat_used
|
10
|
+
[:multi, [:code, "#{@attr_delimiter} = #{@options[:attr_delimiter].inspect}"], exp]
|
11
|
+
else
|
12
|
+
exp
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# Handle tag expression `[:html, :tag, name, attrs, content]`
|
17
|
+
#
|
18
|
+
# @param [String] name Tag name
|
19
|
+
# @param [Array] attrs Temple expression
|
20
|
+
# @param [Array] content Temple expression
|
21
|
+
# @return [Array] Compiled temple expression
|
22
|
+
def on_html_tag(name, attrs, content = nil)
|
23
|
+
return super if name != '*'
|
24
|
+
hash, merger, formatter = splat_attributes(attrs[2..-1])
|
25
|
+
tmp = unique_name
|
26
|
+
tag = [:multi,
|
27
|
+
merger,
|
28
|
+
[:code, "#{tmp} = #{hash}.delete('tag').to_s"],
|
29
|
+
[:if, "#{tmp}.empty?",
|
30
|
+
[:code, "#{tmp} = #{@options[:default_tag].inspect}"]],
|
31
|
+
[:static, '<'],
|
32
|
+
[:dynamic, "#{tmp}"],
|
33
|
+
formatter]
|
34
|
+
tag << if content
|
35
|
+
[:multi,
|
36
|
+
[:static, '>'],
|
37
|
+
compile(content),
|
38
|
+
[:static, '</'],
|
39
|
+
[:dynamic, "#{tmp}"],
|
40
|
+
[:static, '>']]
|
41
|
+
else
|
42
|
+
[:static, '/>']
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Handle attributes expression `[:html, :attrs, *attrs]`
|
47
|
+
#
|
48
|
+
# @param [Array] attrs Array of temple expressions
|
49
|
+
# @return [Array] Compiled temple expression
|
50
|
+
def on_html_attrs(*attrs)
|
51
|
+
return super if attrs.all? {|attr| attr[1] != :splat}
|
52
|
+
hash, merger, formatter = splat_attributes(attrs)
|
53
|
+
[:multi, merger, formatter]
|
54
|
+
end
|
55
|
+
|
56
|
+
protected
|
57
|
+
|
58
|
+
def splat_attributes(attrs)
|
59
|
+
@splat_used = true
|
60
|
+
|
61
|
+
hash, name, value, tmp = unique_name, unique_name, unique_name, unique_name
|
62
|
+
|
63
|
+
merger = [:multi, [:code, "#{hash} = {}"]]
|
64
|
+
attrs.each do |attr|
|
65
|
+
merger << if attr[0] == :html && attr[1] == :attr
|
66
|
+
[:multi,
|
67
|
+
[:capture, tmp, compile(attr[3])],
|
68
|
+
[:code, "(#{hash}[#{attr[2].inspect}] ||= []) << #{tmp}"]]
|
69
|
+
elsif attr[0] == :slim
|
70
|
+
if attr[1] == :attr
|
71
|
+
[:code, "(#{hash}[#{attr[2].inspect}] ||= []) << (#{attr[4]})"]
|
72
|
+
elsif attr[1] == :splat
|
73
|
+
[:code, "(#{attr[2]}).each {|#{name},#{value}| (#{hash}[#{name}.to_s] ||= []) << (#{value}) }"]
|
74
|
+
else
|
75
|
+
attr
|
76
|
+
end
|
77
|
+
else
|
78
|
+
attr
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
merger << [:block, "#{hash}.each do |#{name},#{value}|",
|
83
|
+
[:multi,
|
84
|
+
[:code, "#{value}.flatten!"],
|
85
|
+
[:if, "#{value}.size > 1 && !#{@attr_delimiter}[#{name}]",
|
86
|
+
[:code, %{raise("Multiple #\{#{name}\} attributes specified")}]],
|
87
|
+
[:code, "#{value}.compact!"],
|
88
|
+
[:case, "#{value}.size",
|
89
|
+
['0',
|
90
|
+
[:code, "#{hash}[#{name}] = nil"]],
|
91
|
+
['1',
|
92
|
+
[:case, "#{value}.first",
|
93
|
+
['true', [:code, "#{hash}[#{name}] = #{name}"]],
|
94
|
+
['false, nil', [:code, "#{hash}[#{name}] = nil"]],
|
95
|
+
[:else, [:code, "#{hash}[#{name}] = #{value}.first"]]]],
|
96
|
+
[:else,
|
97
|
+
[:code, "#{hash}[#{name}] = #{value}.join(#{@attr_delimiter}[#{name}].to_s)"]]]]]
|
98
|
+
|
99
|
+
attr = [:if, value,
|
100
|
+
[:multi,
|
101
|
+
[:static, ' '],
|
102
|
+
[:dynamic, name],
|
103
|
+
[:static, "=#{options[:attr_wrapper]}"],
|
104
|
+
[:escape, true, [:dynamic, value]],
|
105
|
+
[:static, options[:attr_wrapper]]]]
|
106
|
+
enumerator = options[:sort_attrs] ? "#{hash}.sort_by {|#{name},#{value}| #{name} }" : hash
|
107
|
+
formatter = [:block, "#{enumerator}.each do |#{name},#{value}|", attr]
|
108
|
+
|
109
|
+
return hash, merger, formatter
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|