temple 0.1.3 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- module Core
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 " = []" end
83
- def postamble; buffer ".join" end
84
-
124
+ def preamble; "#{buffer} = []" end
125
+ def postamble; "#{buffer} = #{buffer}.join" end
126
+
85
127
  def on_static(text)
86
- concat(to_ruby(text))
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 < ArrayBuffer
113
- def preamble; buffer " = ''" end
114
- def postamble; buffer end
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
@@ -1,163 +1,115 @@
1
1
  module Temple
2
2
  module HTML
3
- class Fast
4
- DEFAULT_OPTIONS = {
5
- :format => :xhtml,
6
- :attr_wrapper => "'",
7
- :autoclose => %w[meta img link br hr input area param col base]
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
- @options = DEFAULT_OPTIONS.merge(options)
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
- @options[:format] == :xhtml
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
- @options[:format] == :html5
43
+ options[:format] == :html5
29
44
  end
30
-
45
+
31
46
  def html4?
32
- @options[:format] == :html4
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 on_capture(name, exp)
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
- text = type.to_s.downcase.strip
58
- if text.index("xml") == 0
59
- if html?
60
- return [:multi].concat([[:newline]] * trailing_newlines.size)
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
- end
66
-
67
- str = "<!DOCTYPE html>" if html5?
68
-
69
- str ||= if xhtml?
70
- case text
71
- when /^1\.1/; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">'
72
- when /^5/; '<!DOCTYPE html>'
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 on_comment(content)
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 on_tag(name, attrs, content)
99
- ac = @options[:autoclose].include?(name)
100
- result = [:multi]
101
- result << [:static, "<#{name}"]
102
- result << compile(attrs)
103
- result << [:static, " /"] if ac && xhtml?
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 on_attrs(*exp)
111
- if exp.all? { |e| attr_easily_compilable?(e) }
112
- [:multi, *merge_basicattrs(exp).map { |e| compile(e) }]
113
- else
114
- raise "[:html, :attrs] currently only support basicattrs"
115
- end
116
- end
117
-
118
- def attr_easily_compilable?(exp)
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
- position[name] = result.size
140
- result << [name, value]
100
+ result[name] = value
141
101
  end
142
102
  end
143
-
144
- final = []
145
- result.each_with_index do |(name, value), index|
146
- final << [:html, :basicattr, [:static, name], value]
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
-