temple 0.1.8 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +7 -19
- data/lib/temple/engine.rb +32 -21
- data/lib/temple/erb/engine.rb +1 -1
- data/lib/temple/erb/parser.rb +2 -2
- data/lib/temple/filters/debugger.rb +1 -1
- data/lib/temple/filters/dynamic_inliner.rb +1 -1
- data/lib/temple/filters/escape_html.rb +16 -13
- data/lib/temple/filters/multi_flattener.rb +2 -2
- data/lib/temple/filters/static_merger.rb +1 -1
- data/lib/temple/generators.rb +10 -6
- data/lib/temple/html/dispatcher.rb +17 -0
- data/lib/temple/html/fast.rb +44 -19
- data/lib/temple/html/pretty.rb +19 -15
- data/lib/temple/mixins.rb +174 -23
- data/lib/temple/templates/rails.rb +34 -0
- data/lib/temple/templates/tilt.rb +30 -0
- data/lib/temple/templates.rb +13 -0
- data/lib/temple/utils.rb +54 -1
- data/lib/temple/version.rb +1 -1
- data/lib/temple.rb +2 -2
- data/temple.gemspec +10 -10
- data/test/filters/test_dynamic_inliner.rb +10 -10
- data/test/filters/test_escape_html.rb +10 -11
- data/test/filters/test_multi_flattener.rb +2 -2
- data/test/filters/test_static_merger.rb +3 -3
- data/test/html/test_fast.rb +26 -21
- data/test/html/test_pretty.rb +2 -2
- data/test/test_engine.rb +144 -0
- data/test/test_erb.rb +3 -2
- data/test/test_generator.rb +7 -7
- data/test/test_utils.rb +38 -0
- metadata +12 -5
- data/lib/temple/erb/template.rb +0 -7
- data/lib/temple/template.rb +0 -35
data/README.md
CHANGED
@@ -115,7 +115,7 @@ mind working across processes, it's not a problem at all.
|
|
115
115
|
Compilers
|
116
116
|
---------
|
117
117
|
|
118
|
-
A *compiler* is simply an object which responds a method called #
|
118
|
+
A *compiler* is simply an object which responds a method called #call which
|
119
119
|
takes one argument and returns a value. It's illegal for a compiler to mutate
|
120
120
|
the argument, and it should be possible to use the same instance several times
|
121
121
|
(although not by several threads at the same time).
|
@@ -128,7 +128,7 @@ class. Temple then assumes the initializer takes an optional option hash:
|
|
128
128
|
@options = options
|
129
129
|
end
|
130
130
|
|
131
|
-
def
|
131
|
+
def call(exp)
|
132
132
|
# do stuff
|
133
133
|
end
|
134
134
|
end
|
@@ -195,7 +195,7 @@ When you have a chain of a parsers, some filters and a generator you can finally
|
|
195
195
|
end
|
196
196
|
|
197
197
|
engine = MyEngine.new(:strict => "For MyParser")
|
198
|
-
engine.
|
198
|
+
engine.call(something)
|
199
199
|
|
200
200
|
And then?
|
201
201
|
---------
|
@@ -203,25 +203,13 @@ And then?
|
|
203
203
|
You've ran the template through the parser, some filters and in the end a
|
204
204
|
generator. What happens next?
|
205
205
|
|
206
|
-
Temple
|
207
|
-
|
208
|
-
template engines. This gives you a wide range of features and your engine can
|
209
|
-
be used right away in many projects.
|
206
|
+
Temple provides helpers to create template classes for [Tilt](http://github.com/rtomayko/tilt) and
|
207
|
+
Rails.
|
210
208
|
|
211
209
|
require 'tilt'
|
212
210
|
|
213
|
-
class MyTemplate
|
214
|
-
|
215
|
-
@src = MyEngine.new(options).compile(data)
|
216
|
-
end
|
217
|
-
|
218
|
-
def template_source
|
219
|
-
@src
|
220
|
-
end
|
221
|
-
end
|
222
|
-
|
223
|
-
# Register your file extension:
|
224
|
-
Tilt.register 'ext', MyTemplate
|
211
|
+
# Create template class MyTemplate and register your file extension
|
212
|
+
MyTemplate = Temple::Templates::Tilt(MyEngine, :register_as => 'ext')
|
225
213
|
|
226
214
|
Tilt.new('example.ext').render # => Render a file
|
227
215
|
MyTemplate.new { "String" }.render # => Render a string
|
data/lib/temple/engine.rb
CHANGED
@@ -18,41 +18,52 @@ module Temple
|
|
18
18
|
# generator :ArrayBuffer, :buffer
|
19
19
|
# end
|
20
20
|
#
|
21
|
+
# class SpecialEngine < MyEngine
|
22
|
+
# append MyCodeOptimizer
|
23
|
+
# replace Temple::Generators::ArrayBuffer, Temple::Generators::RailsOutputBuffer
|
24
|
+
# end
|
25
|
+
#
|
21
26
|
# engine = MyEngine.new(:strict => "For MyParser")
|
22
|
-
# engine.
|
27
|
+
# engine.call(something)
|
23
28
|
#
|
24
29
|
class Engine
|
25
30
|
include Mixins::Options
|
31
|
+
include Mixins::EngineDSL
|
32
|
+
extend Mixins::EngineDSL
|
26
33
|
|
27
|
-
|
28
|
-
@chain ||= []
|
29
|
-
end
|
34
|
+
attr_reader :chain
|
30
35
|
|
31
|
-
def self.
|
32
|
-
|
33
|
-
chain << proc do |opts|
|
34
|
-
filter.new(default_options.merge(Hash[*opts.select {|k,v| options.include?(k) }.flatten]), &block)
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
# Shortcut for <tt>use Temple::Filters::parser</tt>
|
39
|
-
def self.filter(filter, *options, &block)
|
40
|
-
use(Temple::Filters.const_get(filter), *options, &block)
|
36
|
+
def self.chain
|
37
|
+
@chain ||= superclass.respond_to?(:chain) ? superclass.chain.dup : []
|
41
38
|
end
|
42
39
|
|
43
|
-
|
44
|
-
|
45
|
-
|
40
|
+
def initialize(o = {})
|
41
|
+
super
|
42
|
+
@chain = self.class.chain.dup
|
43
|
+
yield(self) if block_given?
|
44
|
+
[*options[:chain]].compact.each {|block| block.call(self) }
|
45
|
+
@chain = build_chain
|
46
46
|
end
|
47
47
|
|
48
|
-
def
|
49
|
-
chain.inject(input) {|m, e| e.
|
48
|
+
def call(input)
|
49
|
+
chain.inject(input) {|m, e| e.call(m) }
|
50
50
|
end
|
51
51
|
|
52
52
|
protected
|
53
53
|
|
54
|
-
def
|
55
|
-
|
54
|
+
def build_chain
|
55
|
+
chain.map do |e|
|
56
|
+
name, filter, option_filter, local_options = e
|
57
|
+
case filter
|
58
|
+
when Class
|
59
|
+
filtered_options = Hash[*option_filter.select {|k| options.include?(k) }.map {|k| [k, options[k]] }.flatten]
|
60
|
+
filter.new(Utils::ImmutableHash.new(local_options, filtered_options))
|
61
|
+
when UnboundMethod
|
62
|
+
filter.bind(self)
|
63
|
+
else
|
64
|
+
filter
|
65
|
+
end
|
66
|
+
end
|
56
67
|
end
|
57
68
|
end
|
58
69
|
end
|
data/lib/temple/erb/engine.rb
CHANGED
@@ -2,8 +2,8 @@ module Temple
|
|
2
2
|
module ERB
|
3
3
|
class Engine < Temple::Engine
|
4
4
|
use Temple::ERB::Parser, :auto_escape
|
5
|
-
use Temple::ERB::Trimming, :trim_mode
|
6
5
|
filter :EscapeHTML, :use_html_safe
|
6
|
+
use Temple::ERB::Trimming, :trim_mode
|
7
7
|
filter :MultiFlattener
|
8
8
|
filter :StaticMerger
|
9
9
|
filter :DynamicInliner
|
data/lib/temple/erb/parser.rb
CHANGED
@@ -10,7 +10,7 @@ module Temple
|
|
10
10
|
'%%>' => '%>',
|
11
11
|
}.freeze
|
12
12
|
|
13
|
-
def
|
13
|
+
def call(input)
|
14
14
|
result = [:multi]
|
15
15
|
pos = 0
|
16
16
|
input.scan(ERB_PATTERN) do |escaped, indicator, code|
|
@@ -25,7 +25,7 @@ module Temple
|
|
25
25
|
when '#'
|
26
26
|
code.count("\n").times { result << [:newline] }
|
27
27
|
when /=/
|
28
|
-
result <<
|
28
|
+
result << [:escape, indicator.length <= 1 && options[:auto_escape], [:dynamic, code]]
|
29
29
|
else
|
30
30
|
result << [:block, code]
|
31
31
|
end
|
@@ -53,7 +53,7 @@ module Temple
|
|
53
53
|
# If we found a single exp last time, let's add it.
|
54
54
|
res.concat(prev) if state == :single
|
55
55
|
# Compile the current exp (unless it's the noop)
|
56
|
-
res << compile
|
56
|
+
res << compile(exp) unless head == :noop
|
57
57
|
# Now we're looking for more!
|
58
58
|
state = :looking
|
59
59
|
end
|
@@ -1,29 +1,32 @@
|
|
1
1
|
module Temple
|
2
2
|
module Filters
|
3
3
|
class EscapeHTML < Filter
|
4
|
-
|
4
|
+
include Temple::HTML::Dispatcher
|
5
|
+
|
6
|
+
temple_dispatch :html
|
5
7
|
|
6
8
|
# Activate the usage of html_safe? if it is available (for Rails 3 for example)
|
7
9
|
default_options[:use_html_safe] = ''.respond_to?(:html_safe?)
|
8
10
|
|
9
|
-
def
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
def on_escape_dynamic(value)
|
14
|
-
[:dynamic, "Temple::Utils.escape_html#{options[:use_html_safe] ? '_safe' : ''}((#{value}))"]
|
11
|
+
def initialize(opts = {})
|
12
|
+
super
|
13
|
+
@escape = false
|
15
14
|
end
|
16
15
|
|
17
|
-
def
|
18
|
-
|
16
|
+
def on_escape(flag, exp)
|
17
|
+
old = @escape
|
18
|
+
@escape = flag
|
19
|
+
compile(exp)
|
20
|
+
ensure
|
21
|
+
@escape = old
|
19
22
|
end
|
20
23
|
|
21
|
-
def
|
22
|
-
[:
|
24
|
+
def on_static(value)
|
25
|
+
[:static, @escape ? (options[:use_html_safe] ? escape_html_safe(value) : escape_html(value)) : value]
|
23
26
|
end
|
24
27
|
|
25
|
-
def
|
26
|
-
[:
|
28
|
+
def on_dynamic(value)
|
29
|
+
[:dynamic, @escape ? "Temple::Utils.escape_html#{options[:use_html_safe] ? '_safe' : ''}((#{value}))" : value]
|
27
30
|
end
|
28
31
|
end
|
29
32
|
end
|
@@ -3,11 +3,11 @@ module Temple
|
|
3
3
|
class MultiFlattener < Filter
|
4
4
|
def on_multi(*exps)
|
5
5
|
# If the multi contains a single element, just return the element
|
6
|
-
return compile
|
6
|
+
return compile(exps.first) if exps.length == 1
|
7
7
|
result = [:multi]
|
8
8
|
|
9
9
|
exps.each do |exp|
|
10
|
-
exp = compile
|
10
|
+
exp = compile(exp)
|
11
11
|
if exp.first == :multi
|
12
12
|
result.concat(exp[1..-1])
|
13
13
|
else
|
data/lib/temple/generators.rb
CHANGED
@@ -73,17 +73,21 @@ module Temple
|
|
73
73
|
|
74
74
|
default_options[:buffer] = '_buf'
|
75
75
|
|
76
|
-
def
|
77
|
-
[preamble, compile
|
76
|
+
def call(exp)
|
77
|
+
[preamble, compile(exp), postamble].join(' ; ')
|
78
78
|
end
|
79
79
|
|
80
|
-
def compile
|
80
|
+
def compile(exp)
|
81
81
|
type, *args = exp
|
82
|
-
|
82
|
+
if respond_to?("on_#{type}")
|
83
|
+
send("on_#{type}", *args)
|
84
|
+
else
|
85
|
+
raise "Generator supports only core expressions - found #{exp.inspect}"
|
86
|
+
end
|
83
87
|
end
|
84
88
|
|
85
89
|
def on_multi(*exp)
|
86
|
-
exp.map { |e| compile
|
90
|
+
exp.map { |e| compile(e) }.join(' ; ')
|
87
91
|
end
|
88
92
|
|
89
93
|
def on_newline
|
@@ -91,7 +95,7 @@ module Temple
|
|
91
95
|
end
|
92
96
|
|
93
97
|
def on_capture(name, block)
|
94
|
-
options[:capture_generator].new(:buffer => name).
|
98
|
+
options[:capture_generator].new(:buffer => name).call(block)
|
95
99
|
end
|
96
100
|
|
97
101
|
protected
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Temple
|
2
|
+
module HTML
|
3
|
+
module Dispatcher
|
4
|
+
def on_html_staticattrs(*attrs)
|
5
|
+
[:html, :staticattrs, *attrs.map {|k,v| [k, compile(v)] }]
|
6
|
+
end
|
7
|
+
|
8
|
+
def on_html_comment(content)
|
9
|
+
[:html, :comment, compile(content)]
|
10
|
+
end
|
11
|
+
|
12
|
+
def on_html_tag(name, attrs, closed, content)
|
13
|
+
[:html, :tag, name, compile(attrs), closed, compile(content)]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/temple/html/fast.rb
CHANGED
@@ -39,18 +39,11 @@ module Temple
|
|
39
39
|
end
|
40
40
|
|
41
41
|
def html?
|
42
|
-
html5
|
43
|
-
end
|
44
|
-
|
45
|
-
def html5?
|
46
|
-
options[:format] == :html5
|
47
|
-
end
|
48
|
-
|
49
|
-
def html4?
|
50
|
-
options[:format] == :html4
|
42
|
+
options[:format] == :html5 || options[:format] == :html4
|
51
43
|
end
|
52
44
|
|
53
45
|
def on_html_doctype(type)
|
46
|
+
type = type.to_s
|
54
47
|
trailing_newlines = type[/(\A|[^\r])(\n+)\Z/, 2].to_s
|
55
48
|
text = type.downcase.strip
|
56
49
|
|
@@ -76,16 +69,16 @@ module Temple
|
|
76
69
|
def on_html_comment(content)
|
77
70
|
[:multi,
|
78
71
|
[:static, '<!--'],
|
79
|
-
compile
|
72
|
+
compile(content),
|
80
73
|
[:static, '-->']]
|
81
74
|
end
|
82
75
|
|
83
76
|
def on_html_tag(name, attrs, closed, content)
|
84
77
|
closed ||= options[:autoclose].include?(name)
|
85
78
|
raise "Closed tag #{name} has content" if closed && !empty_exp?(content)
|
86
|
-
result = [:multi, [:static, "<#{name}"], compile
|
79
|
+
result = [:multi, [:static, "<#{name}"], compile(attrs)]
|
87
80
|
result << [:static, ' /'] if closed && xhtml?
|
88
|
-
result << [:static, '>'] << compile
|
81
|
+
result << [:static, '>'] << compile(content)
|
89
82
|
result << [:static, "</#{name}>"] if !closed
|
90
83
|
result
|
91
84
|
end
|
@@ -104,13 +97,45 @@ module Temple
|
|
104
97
|
end
|
105
98
|
end
|
106
99
|
result.sort.inject([:multi]) do |list, (name, value)|
|
107
|
-
list <<
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
100
|
+
list << compile_attribute(name, value)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
protected
|
105
|
+
|
106
|
+
def compile_attribute(name, value)
|
107
|
+
if empty_exp?(value)
|
108
|
+
[:multi]
|
109
|
+
elsif contains_static?(value)
|
110
|
+
attribute(name, value)
|
111
|
+
else
|
112
|
+
tmp = tmp_var(:htmlattr)
|
113
|
+
[:multi,
|
114
|
+
[:capture, tmp, value],
|
115
|
+
[:block, "unless #{tmp}.empty?"],
|
116
|
+
attribute(name, [:dynamic, tmp]),
|
117
|
+
[:block, 'end']]
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def attribute(name, value)
|
122
|
+
[:multi,
|
123
|
+
[:static, ' '],
|
124
|
+
[:static, name],
|
125
|
+
[:static, '='],
|
126
|
+
[:static, options[:attr_wrapper]],
|
127
|
+
value,
|
128
|
+
[:static, options[:attr_wrapper]]]
|
129
|
+
end
|
130
|
+
|
131
|
+
def contains_static?(exp)
|
132
|
+
case exp[0]
|
133
|
+
when :multi
|
134
|
+
exp[1..-1].any? {|e| contains_static?(e) }
|
135
|
+
when :static
|
136
|
+
true
|
137
|
+
else
|
138
|
+
false
|
114
139
|
end
|
115
140
|
end
|
116
141
|
end
|
data/lib/temple/html/pretty.rb
CHANGED
@@ -3,39 +3,44 @@ module Temple
|
|
3
3
|
class Pretty < Fast
|
4
4
|
set_default_options :indent => ' ',
|
5
5
|
:pretty => true,
|
6
|
-
:indent_tags => %w(base body dd div dl
|
6
|
+
:indent_tags => %w(base body dd div dl dt fieldset form head h1 h2 h3
|
7
7
|
h4 h5 h6 hr html img input li link meta ol p script
|
8
8
|
style table tbody td tfoot th thead title tr ul).freeze,
|
9
9
|
:pre_tags => %w(pre textarea).freeze
|
10
10
|
|
11
11
|
def initialize(opts = {})
|
12
12
|
super
|
13
|
-
@last =
|
13
|
+
@last = :noindent
|
14
14
|
@indent = 0
|
15
15
|
@pretty = options[:pretty]
|
16
|
+
@pre_tags = Regexp.new(options[:pre_tags].map {|t| "<#{t}" }.join('|'))
|
16
17
|
end
|
17
18
|
|
18
|
-
def
|
19
|
-
[:multi, preamble, compile
|
19
|
+
def call(exp)
|
20
|
+
@pretty ? [:multi, preamble, compile(exp)] : super
|
20
21
|
end
|
21
22
|
|
22
23
|
def on_static(content)
|
23
|
-
@
|
24
|
-
|
24
|
+
if @pretty
|
25
|
+
content = Utils.indent(content, indent, @pre_tags)
|
26
|
+
@last = content.sub!(/\r?\n\s*$/, ' ') ? nil : :noindent
|
27
|
+
end
|
28
|
+
[:static, content]
|
25
29
|
end
|
26
30
|
|
27
31
|
def on_dynamic(content)
|
28
|
-
@last =
|
32
|
+
@last = :noindent
|
29
33
|
[:dynamic, @pretty ? "Temple::Utils.indent((#{content}), #{indent.inspect}, _temple_pre_tags)" : content]
|
30
34
|
end
|
31
35
|
|
32
36
|
def on_html_doctype(type)
|
33
|
-
@last =
|
37
|
+
@last = nil
|
34
38
|
super
|
35
39
|
end
|
36
40
|
|
37
41
|
def on_html_comment(content)
|
38
42
|
return super unless @pretty
|
43
|
+
@last = nil
|
39
44
|
[:multi, [:static, indent], super]
|
40
45
|
end
|
41
46
|
|
@@ -46,18 +51,16 @@ module Temple
|
|
46
51
|
raise "Closed tag #{name} has content" if closed && !empty_exp?(content)
|
47
52
|
|
48
53
|
@pretty = false
|
49
|
-
result = [:multi, [:static, "#{tag_indent(name)}<#{name}"], compile
|
54
|
+
result = [:multi, [:static, "#{tag_indent(name)}<#{name}"], compile(attrs)]
|
50
55
|
result << [:static, ' /'] if closed && xhtml?
|
51
56
|
result << [:static, '>']
|
52
57
|
|
53
|
-
@last = name
|
54
58
|
@pretty = !options[:pre_tags].include?(name)
|
55
59
|
@indent += 1
|
56
|
-
result << compile
|
60
|
+
result << compile(content)
|
57
61
|
@indent -= 1
|
58
62
|
|
59
63
|
result << [:static, "#{tag_indent(name)}</#{name}>"] if !closed
|
60
|
-
@last = name
|
61
64
|
@pretty = true
|
62
65
|
result
|
63
66
|
end
|
@@ -65,8 +68,7 @@ module Temple
|
|
65
68
|
protected
|
66
69
|
|
67
70
|
def preamble
|
68
|
-
|
69
|
-
[:block, "_temple_pre_tags = /#{regexp}/"]
|
71
|
+
[:block, "_temple_pre_tags = /#{@pre_tags.source}/"]
|
70
72
|
end
|
71
73
|
|
72
74
|
# Return indentation if not in pre tag
|
@@ -76,7 +78,9 @@ module Temple
|
|
76
78
|
|
77
79
|
# Return indentation before tag
|
78
80
|
def tag_indent(name)
|
79
|
-
@last && (options[:indent_tags].include?(@last) || options[:indent_tags].include?(name)) ? indent : ''
|
81
|
+
result = @last != :noindent && (options[:indent_tags].include?(@last) || options[:indent_tags].include?(name)) ? indent : ''
|
82
|
+
@last = name
|
83
|
+
result
|
80
84
|
end
|
81
85
|
end
|
82
86
|
end
|