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.
@@ -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
-