temple 0.1.3 → 0.1.4
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 +3 -0
- data/README.md +17 -18
- data/Rakefile +17 -12
- data/lib/temple.rb +19 -14
- data/lib/temple/engine.rb +13 -18
- data/lib/temple/erb/engine.rb +13 -0
- data/lib/temple/erb/parser.rb +38 -0
- data/lib/temple/erb/template.rb +7 -0
- data/lib/temple/erb/trimming.rb +33 -0
- data/lib/temple/filter.rb +7 -0
- data/lib/temple/filters/debugger.rb +15 -0
- data/lib/temple/filters/dynamic_inliner.rb +11 -19
- data/lib/temple/filters/escape_html.rb +27 -0
- data/lib/temple/filters/multi_flattener.rb +5 -13
- data/lib/temple/filters/static_merger.rb +6 -14
- data/lib/temple/{core.rb → generators.rb} +78 -37
- data/lib/temple/html/fast.rb +79 -127
- data/lib/temple/html/pretty.rb +75 -0
- data/lib/temple/mixins.rb +66 -0
- data/lib/temple/template.rb +35 -0
- data/lib/temple/utils.rb +51 -15
- data/lib/temple/version.rb +3 -0
- data/temple.gemspec +8 -13
- data/test/filters/test_dynamic_inliner.rb +42 -58
- data/test/filters/test_escape_html.rb +32 -0
- data/test/filters/test_multi_flattener.rb +33 -0
- data/test/filters/test_static_merger.rb +21 -23
- data/test/helper.rb +11 -17
- data/test/html/test_fast.rb +153 -0
- data/test/test_erb.rb +68 -0
- data/test/test_generator.rb +69 -76
- data/test/test_utils.rb +28 -0
- metadata +45 -16
- data/lib/temple/engines/erb.rb +0 -93
- data/lib/temple/generator.rb +0 -76
- data/lib/temple/parsers/erb.rb +0 -83
- data/test/engines/hello.erb +0 -4
- data/test/engines/test_erb.rb +0 -495
- data/test/engines/test_erb_m17n.rb +0 -132
- data/test/test_temple.rb +0 -28
@@ -0,0 +1,27 @@
|
|
1
|
+
module Temple
|
2
|
+
module Filters
|
3
|
+
class EscapeHTML < Filter
|
4
|
+
temple_dispatch :escape, :html
|
5
|
+
|
6
|
+
def on_escape_static(value)
|
7
|
+
[:static, options[:user_html_safe] ? escape_html_safe(value) : escape_html(value)]
|
8
|
+
end
|
9
|
+
|
10
|
+
def on_escape_dynamic(value)
|
11
|
+
[:dynamic, "Temple::Utils.escape_html#{options[:use_html_safe] ? '_safe' : ''}((#{value}))"]
|
12
|
+
end
|
13
|
+
|
14
|
+
def on_html_staticattrs(*attrs)
|
15
|
+
[:html, :staticattrs, *attrs.map {|k,v| [k, compile!(v)] }]
|
16
|
+
end
|
17
|
+
|
18
|
+
def on_html_comment(content)
|
19
|
+
[:html, :comment, compile!(content)]
|
20
|
+
end
|
21
|
+
|
22
|
+
def on_html_tag(name, attrs, closed, content)
|
23
|
+
[:html, :tag, name, compile!(attrs), closed, compile!(content)]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -1,28 +1,20 @@
|
|
1
1
|
module Temple
|
2
2
|
module Filters
|
3
|
-
class MultiFlattener
|
4
|
-
def initialize(options = {})
|
5
|
-
@options = {}
|
6
|
-
end
|
7
|
-
|
8
|
-
def compile(exp)
|
9
|
-
exp.first == :multi ? on_multi(*exp[1..-1]) : exp
|
10
|
-
end
|
11
|
-
|
3
|
+
class MultiFlattener < Filter
|
12
4
|
def on_multi(*exps)
|
13
5
|
# If the multi contains a single element, just return the element
|
14
|
-
return compile(exps.first) if exps.length == 1
|
6
|
+
return compile!(exps.first) if exps.length == 1
|
15
7
|
result = [:multi]
|
16
|
-
|
8
|
+
|
17
9
|
exps.each do |exp|
|
18
|
-
exp = compile(exp)
|
10
|
+
exp = compile!(exp)
|
19
11
|
if exp.first == :multi
|
20
12
|
result.concat(exp[1..-1])
|
21
13
|
else
|
22
14
|
result << exp
|
23
15
|
end
|
24
16
|
end
|
25
|
-
|
17
|
+
|
26
18
|
result
|
27
19
|
end
|
28
20
|
end
|
@@ -7,23 +7,15 @@ module Temple
|
|
7
7
|
# [:static, "World!"]]
|
8
8
|
#
|
9
9
|
# Compiles to:
|
10
|
-
#
|
10
|
+
#
|
11
11
|
# [:multi,
|
12
12
|
# [:static, "Hello World!"]]
|
13
|
-
class StaticMerger
|
14
|
-
def initialize(options = {})
|
15
|
-
@options = {}
|
16
|
-
end
|
17
|
-
|
18
|
-
def compile(exp)
|
19
|
-
exp.first == :multi ? on_multi(*exp[1..-1]) : exp
|
20
|
-
end
|
21
|
-
|
13
|
+
class StaticMerger < Filter
|
22
14
|
def on_multi(*exps)
|
23
15
|
res = [:multi]
|
24
16
|
curr = nil
|
25
17
|
state = :looking
|
26
|
-
|
18
|
+
|
27
19
|
exps.each do |exp|
|
28
20
|
if exp.first == :static
|
29
21
|
if state == :looking
|
@@ -33,13 +25,13 @@ module Temple
|
|
33
25
|
curr << exp[1]
|
34
26
|
end
|
35
27
|
else
|
36
|
-
res << compile(exp)
|
28
|
+
res << compile!(exp)
|
37
29
|
state = :looking unless exp.first == :newline
|
38
30
|
end
|
39
31
|
end
|
40
|
-
|
32
|
+
|
41
33
|
res
|
42
34
|
end
|
43
35
|
end
|
44
36
|
end
|
45
|
-
end
|
37
|
+
end
|
@@ -1,65 +1,65 @@
|
|
1
1
|
module Temple
|
2
2
|
# == The Core Abstraction
|
3
|
-
#
|
3
|
+
#
|
4
4
|
# The core abstraction is what every template evetually should be compiled
|
5
5
|
# to. Currently it consists of four essential and two convenient types:
|
6
6
|
# multi, static, dynamic, block, newline and capture.
|
7
|
-
#
|
7
|
+
#
|
8
8
|
# When compiling, there's two different strings we'll have to think about.
|
9
9
|
# First we have the generated code. This is what your engine (from Temple's
|
10
10
|
# point of view) spits out. If you construct this carefully enough, you can
|
11
11
|
# make exceptions report correct line numbers, which is very convenient.
|
12
|
-
#
|
12
|
+
#
|
13
13
|
# Then there's the result. This is what your engine (from the user's point
|
14
14
|
# of view) spits out. It's what happens if you evaluate the generated code.
|
15
|
-
#
|
15
|
+
#
|
16
16
|
# === [:multi, *sexp]
|
17
|
-
#
|
17
|
+
#
|
18
18
|
# Multi is what glues everything together. It's simply a sexp which combines
|
19
19
|
# several others sexps:
|
20
|
-
#
|
20
|
+
#
|
21
21
|
# [:multi,
|
22
22
|
# [:static, "Hello "],
|
23
23
|
# [:dynamic, "@world"]]
|
24
|
-
#
|
24
|
+
#
|
25
25
|
# === [:static, string]
|
26
|
-
#
|
26
|
+
#
|
27
27
|
# Static indicates that the given string should be appended to the result.
|
28
|
-
#
|
28
|
+
#
|
29
29
|
# Example:
|
30
|
-
#
|
30
|
+
#
|
31
31
|
# [:static, "Hello World"]
|
32
32
|
# # is the same as:
|
33
33
|
# _buf << "Hello World"
|
34
|
-
#
|
34
|
+
#
|
35
35
|
# [:static, "Hello \n World"]
|
36
36
|
# # is the same as
|
37
37
|
# _buf << "Hello\nWorld"
|
38
|
-
#
|
38
|
+
#
|
39
39
|
# === [:dynamic, ruby]
|
40
|
-
#
|
40
|
+
#
|
41
41
|
# Dynamic indicates that the given Ruby code should be evaluated and then
|
42
42
|
# appended to the result.
|
43
|
-
#
|
43
|
+
#
|
44
44
|
# The Ruby code must be a complete expression in the sense that you can pass
|
45
45
|
# it to eval() and it would not raise SyntaxError.
|
46
|
-
#
|
46
|
+
#
|
47
47
|
# === [:block, ruby]
|
48
|
-
#
|
48
|
+
#
|
49
49
|
# Block indicates that the given Ruby code should be evaluated, and may
|
50
50
|
# change the control flow. Any \n causes a newline in the generated code.
|
51
|
-
#
|
51
|
+
#
|
52
52
|
# === [:newline]
|
53
|
-
#
|
53
|
+
#
|
54
54
|
# Newline causes a newline in the generated code, but not in the result.
|
55
|
-
#
|
55
|
+
#
|
56
56
|
# === [:capture, variable_name, sexp]
|
57
|
-
#
|
57
|
+
#
|
58
58
|
# Evaluates the Sexp using the rules above, but instead of appending to the
|
59
59
|
# result, it sets the content to the variable given.
|
60
|
-
#
|
60
|
+
#
|
61
61
|
# Example:
|
62
|
-
#
|
62
|
+
#
|
63
63
|
# [:multi,
|
64
64
|
# [:static, "Some content"],
|
65
65
|
# [:capture, "foo", [:static, "More content"]],
|
@@ -68,9 +68,51 @@ module Temple
|
|
68
68
|
# _buf << "Some content"
|
69
69
|
# foo = "More content"
|
70
70
|
# _buf << foo.downcase
|
71
|
-
|
71
|
+
class Generator
|
72
|
+
include Mixins::Options
|
73
|
+
|
74
|
+
default_options[:buffer] = '_buf'
|
75
|
+
|
76
|
+
def compile(exp)
|
77
|
+
[preamble, compile!(exp), postamble].join(' ; ')
|
78
|
+
end
|
79
|
+
|
80
|
+
def compile!(exp)
|
81
|
+
type, *args = exp
|
82
|
+
send("on_#{type}", *args)
|
83
|
+
end
|
84
|
+
|
85
|
+
def on_multi(*exp)
|
86
|
+
exp.map { |e| compile!(e) }.join(' ; ')
|
87
|
+
end
|
88
|
+
|
89
|
+
def on_newline
|
90
|
+
"\n"
|
91
|
+
end
|
92
|
+
|
93
|
+
def on_capture(name, block)
|
94
|
+
capture_generator.new(:buffer => name).compile(block)
|
95
|
+
end
|
96
|
+
|
97
|
+
protected
|
98
|
+
|
99
|
+
def capture_generator
|
100
|
+
@capture_generator ||=
|
101
|
+
options[:capture_generator] || Temple::Generators::StringBuffer
|
102
|
+
end
|
103
|
+
|
104
|
+
def buffer
|
105
|
+
options[:buffer]
|
106
|
+
end
|
107
|
+
|
108
|
+
def concat(str)
|
109
|
+
"#{buffer} << (#{str})"
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
module Generators
|
72
114
|
# Implements an array buffer.
|
73
|
-
#
|
115
|
+
#
|
74
116
|
# _buf = []
|
75
117
|
# _buf << "static"
|
76
118
|
# _buf << dynamic
|
@@ -79,29 +121,29 @@ module Temple
|
|
79
121
|
# end
|
80
122
|
# _buf.join
|
81
123
|
class ArrayBuffer < Generator
|
82
|
-
def preamble; buffer
|
83
|
-
def postamble; buffer
|
84
|
-
|
124
|
+
def preamble; "#{buffer} = []" end
|
125
|
+
def postamble; "#{buffer} = #{buffer}.join" end
|
126
|
+
|
85
127
|
def on_static(text)
|
86
|
-
concat(
|
128
|
+
concat(text.inspect)
|
87
129
|
end
|
88
|
-
|
130
|
+
|
89
131
|
def on_dynamic(code)
|
90
132
|
concat(code)
|
91
133
|
end
|
92
|
-
|
134
|
+
|
93
135
|
def on_block(code)
|
94
136
|
code
|
95
137
|
end
|
96
138
|
end
|
97
|
-
|
139
|
+
|
98
140
|
# Just like ArrayBuffer, but doesn't call #join on the array.
|
99
141
|
class Array < ArrayBuffer
|
100
142
|
def postamble; buffer; end
|
101
143
|
end
|
102
|
-
|
144
|
+
|
103
145
|
# Implements a string buffer.
|
104
|
-
#
|
146
|
+
#
|
105
147
|
# _buf = ''
|
106
148
|
# _buf << "static"
|
107
149
|
# _buf << dynamic.to_s
|
@@ -109,10 +151,9 @@ module Temple
|
|
109
151
|
# _buf << "more static"
|
110
152
|
# end
|
111
153
|
# _buf
|
112
|
-
class StringBuffer <
|
113
|
-
def preamble;
|
114
|
-
|
115
|
-
|
154
|
+
class StringBuffer < Array
|
155
|
+
def preamble; "#{buffer} = ''" end
|
156
|
+
|
116
157
|
def on_dynamic(code)
|
117
158
|
concat(code) + '.to_s'
|
118
159
|
end
|
data/lib/temple/html/fast.rb
CHANGED
@@ -1,163 +1,115 @@
|
|
1
1
|
module Temple
|
2
2
|
module HTML
|
3
|
-
class Fast
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
3
|
+
class Fast < Filter
|
4
|
+
temple_dispatch :html
|
5
|
+
|
6
|
+
XHTML_DOCTYPES = {
|
7
|
+
'1.1' => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">',
|
8
|
+
'5' => '<!DOCTYPE html>',
|
9
|
+
'strict' => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">',
|
10
|
+
'frameset' => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">',
|
11
|
+
'mobile' => '<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">',
|
12
|
+
'basic' => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">',
|
13
|
+
'transitional' => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">',
|
14
|
+
}.freeze
|
15
|
+
|
16
|
+
HTML4_DOCTYPES = {
|
17
|
+
'strict' => '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">',
|
18
|
+
'frameset' => '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">',
|
19
|
+
'transitional' => '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">',
|
20
|
+
}.freeze
|
21
|
+
|
22
|
+
set_default_options :format => :xhtml,
|
23
|
+
:attr_wrapper => "'",
|
24
|
+
:autoclose => %w[meta img link br hr input area param col base],
|
25
|
+
:id_delimiter => '_'
|
26
|
+
|
10
27
|
def initialize(options = {})
|
11
|
-
|
12
|
-
|
28
|
+
super
|
13
29
|
unless [:xhtml, :html4, :html5].include?(@options[:format])
|
14
30
|
raise "Invalid format #{@options[:format].inspect}"
|
15
31
|
end
|
16
|
-
|
17
32
|
end
|
18
|
-
|
33
|
+
|
19
34
|
def xhtml?
|
20
|
-
|
35
|
+
options[:format] == :xhtml
|
21
36
|
end
|
22
|
-
|
37
|
+
|
23
38
|
def html?
|
24
39
|
html5? or html4?
|
25
40
|
end
|
26
|
-
|
41
|
+
|
27
42
|
def html5?
|
28
|
-
|
43
|
+
options[:format] == :html5
|
29
44
|
end
|
30
|
-
|
45
|
+
|
31
46
|
def html4?
|
32
|
-
|
33
|
-
end
|
34
|
-
|
35
|
-
def compile(exp)
|
36
|
-
case exp[0]
|
37
|
-
when :multi, :capture
|
38
|
-
send("on_#{exp[0]}", *exp[1..-1])
|
39
|
-
when :html
|
40
|
-
send("on_#{exp[1]}", *exp[2..-1])
|
41
|
-
else
|
42
|
-
exp
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
def on_multi(*exp)
|
47
|
-
[:multi, *exp.map { |e| compile(e) }]
|
47
|
+
options[:format] == :html4
|
48
48
|
end
|
49
49
|
|
50
|
-
def
|
51
|
-
[:capture, name, compile(exp)]
|
52
|
-
end
|
53
|
-
|
54
|
-
def on_doctype(type)
|
50
|
+
def on_html_doctype(type)
|
55
51
|
trailing_newlines = type[/(\A|[^\r])(\n+)\Z/, 2].to_s
|
56
|
-
|
57
|
-
|
58
|
-
if text
|
59
|
-
if html?
|
60
|
-
|
61
|
-
end
|
62
|
-
|
63
|
-
wrapper = @options[:attr_wrapper]
|
52
|
+
text = type.downcase.strip
|
53
|
+
|
54
|
+
if text =~ /^xml/
|
55
|
+
raise 'Invalid xml directive in html mode' if html?
|
56
|
+
wrapper = options[:attr_wrapper]
|
64
57
|
str = "<?xml version=#{wrapper}1.0#{wrapper} encoding=#{wrapper}#{text.split(' ')[1] || "utf-8"}#{wrapper} ?>"
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
when
|
72
|
-
|
73
|
-
when "strict"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'
|
74
|
-
when "frameset"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">'
|
75
|
-
when "mobile"; '<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">'
|
76
|
-
when "basic"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">'
|
77
|
-
else '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'
|
78
|
-
end
|
79
|
-
elsif html4?
|
80
|
-
case text
|
81
|
-
when "strict"; '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">'
|
82
|
-
when "frameset"; '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">'
|
83
|
-
else '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">'
|
58
|
+
else
|
59
|
+
case options[:format]
|
60
|
+
when :html5
|
61
|
+
str = '<!DOCTYPE html>'
|
62
|
+
when :html4
|
63
|
+
str = HTML4_DOCTYPES[text] || HTML4_DOCTYPES['transitional']
|
64
|
+
when :xhtml
|
65
|
+
str = XHTML_DOCTYPES[text] || XHTML_DOCTYPES['transitional']
|
84
66
|
end
|
85
67
|
end
|
86
|
-
|
68
|
+
|
87
69
|
str << trailing_newlines
|
88
70
|
[:static, str]
|
89
71
|
end
|
90
|
-
|
91
|
-
def
|
72
|
+
|
73
|
+
def on_html_comment(content)
|
92
74
|
[:multi,
|
93
|
-
[:static,
|
94
|
-
compile(content),
|
95
|
-
[:static,
|
75
|
+
[:static, '<!--'],
|
76
|
+
compile!(content),
|
77
|
+
[:static, '-->']]
|
96
78
|
end
|
97
|
-
|
98
|
-
def
|
99
|
-
|
100
|
-
|
101
|
-
result
|
102
|
-
result <<
|
103
|
-
result << [:static,
|
104
|
-
result << [:static, ">"]
|
105
|
-
result << compile(content)
|
106
|
-
result << [:static, "</#{name}>"] if !ac
|
79
|
+
|
80
|
+
def on_html_tag(name, attrs, closed, content)
|
81
|
+
closed ||= options[:autoclose].include?(name)
|
82
|
+
raise "Closed tag #{name} has content" if closed && !empty_exp?(content)
|
83
|
+
result = [:multi, [:static, "<#{name}"], compile!(attrs)]
|
84
|
+
result << [:static, ' /'] if closed && xhtml?
|
85
|
+
result << [:static, '>'] << compile!(content)
|
86
|
+
result << [:static, "</#{name}>"] if !closed
|
107
87
|
result
|
108
88
|
end
|
109
|
-
|
110
|
-
def
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
exp[1] == :basicattr and
|
120
|
-
exp[2][0] == :static
|
121
|
-
end
|
122
|
-
|
123
|
-
def merge_basicattrs(attrs)
|
124
|
-
result = []
|
125
|
-
position = {}
|
126
|
-
|
127
|
-
attrs.each do |(html, type, (name_type, name), value)|
|
128
|
-
if pos = position[name]
|
129
|
-
case name
|
130
|
-
when 'class', 'id'
|
131
|
-
value = [:multi,
|
132
|
-
result[pos].last, # previous value
|
133
|
-
[:static, (name == 'class' ? ' ' : '_')], # delimiter
|
134
|
-
value] # new value
|
135
|
-
end
|
136
|
-
|
137
|
-
result[pos] = [name, value]
|
89
|
+
|
90
|
+
def on_html_staticattrs(*attrs)
|
91
|
+
result = {}
|
92
|
+
attrs.each do |name, value|
|
93
|
+
if result[name] && %w(class id).include?(name)
|
94
|
+
raise 'Multiple id attributes specified, but id concatenation disabled' if name == 'id' && !options[:id_delimiter]
|
95
|
+
result[name] = [:multi,
|
96
|
+
result[name],
|
97
|
+
[:static, (name == 'class' ? ' ' : options[:id_delimiter])],
|
98
|
+
value]
|
138
99
|
else
|
139
|
-
|
140
|
-
result << [name, value]
|
100
|
+
result[name] = value
|
141
101
|
end
|
142
102
|
end
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
103
|
+
result.sort.inject([:multi]) do |list, (name, value)|
|
104
|
+
list << [:multi,
|
105
|
+
[:static, ' '],
|
106
|
+
[:static, name],
|
107
|
+
[:static, '='],
|
108
|
+
[:static, options[:attr_wrapper]],
|
109
|
+
value,
|
110
|
+
[:static, options[:attr_wrapper]]]
|
147
111
|
end
|
148
|
-
final
|
149
|
-
end
|
150
|
-
|
151
|
-
def on_basicattr(name, value)
|
152
|
-
[:multi,
|
153
|
-
[:static, " "],
|
154
|
-
name,
|
155
|
-
[:static, "="],
|
156
|
-
[:static, @options[:attr_wrapper]],
|
157
|
-
value,
|
158
|
-
[:static, @options[:attr_wrapper]]]
|
159
112
|
end
|
160
113
|
end
|
161
114
|
end
|
162
115
|
end
|
163
|
-
|