temple 0.4.1 → 0.5.4
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +1 -0
- data/CHANGES +34 -0
- data/lib/temple.rb +5 -2
- data/lib/temple/engine.rb +3 -2
- data/lib/temple/erb/parser.rb +1 -3
- data/lib/temple/erb/trimming.rb +2 -0
- data/lib/temple/exceptions.rb +13 -0
- data/lib/temple/filters/control_flow.rb +2 -3
- data/lib/temple/filters/eraser.rb +1 -1
- data/lib/temple/filters/escapable.rb +3 -2
- data/lib/temple/filters/validator.rb +1 -1
- data/lib/temple/generators.rb +8 -6
- data/lib/temple/grammar.rb +1 -1
- data/lib/temple/hash.rb +56 -0
- data/lib/temple/html/attribute_merger.rb +28 -29
- data/lib/temple/html/attribute_remover.rb +11 -4
- data/lib/temple/html/attribute_sorter.rb +9 -5
- data/lib/temple/html/fast.rb +7 -7
- data/lib/temple/html/filter.rb +13 -0
- data/lib/temple/html/pretty.rb +8 -8
- data/lib/temple/mixins/engine_dsl.rb +3 -3
- data/lib/temple/mixins/grammar_dsl.rb +1 -1
- data/lib/temple/mixins/options.rb +64 -7
- data/lib/temple/mixins/template.rb +8 -22
- data/lib/temple/parser.rb +8 -0
- data/lib/temple/templates/rails.rb +13 -7
- data/lib/temple/templates/tilt.rb +17 -8
- data/lib/temple/utils.rb +3 -17
- data/lib/temple/version.rb +1 -1
- data/temple.gemspec +1 -1
- data/test/filters/test_control_flow.rb +4 -6
- data/test/html/test_attribute_merger.rb +8 -14
- data/test/html/test_attribute_remover.rb +5 -6
- data/test/test_engine.rb +0 -8
- data/test/test_erb.rb +2 -2
- data/test/test_filter.rb +4 -1
- metadata +12 -25
data/.travis.yml
CHANGED
data/CHANGES
CHANGED
@@ -1,3 +1,37 @@
|
|
1
|
+
0.5.4
|
2
|
+
|
3
|
+
* HTML::AttributeMerger fixed, it didn't remove first empty attribute values
|
4
|
+
* Add HTML::AttributeRemover back, :remove_empty_attrs must be an Array of Strings now
|
5
|
+
of the attributes to be removed if empty
|
6
|
+
* Simplify [:case] expression grammar
|
7
|
+
* Ignore parameter :outvar by sinatra since sinatra assumes also that the buffer is a String,
|
8
|
+
they should set :buffer and :generator explicitly if they need the access
|
9
|
+
|
10
|
+
0.5.3
|
11
|
+
|
12
|
+
* Only print an message if invalid options are passed to Temple filters or engines
|
13
|
+
since many libraries seem to use Slim and Temple in an incorrect way
|
14
|
+
|
15
|
+
0.5.2
|
16
|
+
|
17
|
+
* Fix the :outvar problem really
|
18
|
+
|
19
|
+
0.5.1
|
20
|
+
|
21
|
+
* Support Sinatra :outvar option in Tilt template
|
22
|
+
|
23
|
+
0.5.0
|
24
|
+
|
25
|
+
* Added exception Temple::FilterError which should be thrown by filters
|
26
|
+
* Added Temple::Parser as default base class for parsers
|
27
|
+
* escape_html doesn't escape / anymore
|
28
|
+
* HTML::AttributeSorter uses stable sorting now
|
29
|
+
* HTML::AttributeRemover removed (Was too Slim specific)
|
30
|
+
* Engine option :chain removed
|
31
|
+
* Option validation implemented (Use define_options in your filters)
|
32
|
+
* Deprecated options implemented (Use deprecated_options in your filters)
|
33
|
+
* ThreadOptions added, Method #with_options
|
34
|
+
|
1
35
|
0.4.1
|
2
36
|
|
3
37
|
* Generators: produce optimized code
|
data/lib/temple.rb
CHANGED
@@ -1,9 +1,11 @@
|
|
1
1
|
require 'temple/version'
|
2
2
|
|
3
3
|
module Temple
|
4
|
-
autoload :InvalidExpression, 'temple/
|
4
|
+
autoload :InvalidExpression, 'temple/exceptions'
|
5
|
+
autoload :FilterError, 'temple/exceptions'
|
5
6
|
autoload :Generator, 'temple/generators'
|
6
7
|
autoload :Generators, 'temple/generators'
|
8
|
+
autoload :Parser, 'temple/parser'
|
7
9
|
autoload :Engine, 'temple/engine'
|
8
10
|
autoload :Utils, 'temple/utils'
|
9
11
|
autoload :Filter, 'temple/filter'
|
@@ -11,6 +13,7 @@ module Temple
|
|
11
13
|
autoload :Grammar, 'temple/grammar'
|
12
14
|
autoload :ImmutableHash, 'temple/hash'
|
13
15
|
autoload :MutableHash, 'temple/hash'
|
16
|
+
autoload :OptionHash, 'temple/hash'
|
14
17
|
|
15
18
|
module Mixins
|
16
19
|
autoload :Dispatcher, 'temple/mixins/dispatcher'
|
@@ -45,7 +48,7 @@ module Temple
|
|
45
48
|
autoload :Fast, 'temple/html/fast'
|
46
49
|
autoload :Pretty, 'temple/html/pretty'
|
47
50
|
autoload :AttributeMerger, 'temple/html/attribute_merger'
|
48
|
-
autoload :AttributeSorter, 'temple/html/attribute_sorter'
|
49
51
|
autoload :AttributeRemover, 'temple/html/attribute_remover'
|
52
|
+
autoload :AttributeSorter, 'temple/html/attribute_sorter'
|
50
53
|
end
|
51
54
|
end
|
data/lib/temple/engine.rb
CHANGED
@@ -33,16 +33,17 @@ module Temple
|
|
33
33
|
include Mixins::EngineDSL
|
34
34
|
extend Mixins::EngineDSL
|
35
35
|
|
36
|
+
define_options :file, :streaming, :buffer
|
37
|
+
|
36
38
|
attr_reader :chain
|
37
39
|
|
38
40
|
def self.chain
|
39
41
|
@chain ||= superclass.respond_to?(:chain) ? superclass.chain.dup : []
|
40
42
|
end
|
41
43
|
|
42
|
-
def initialize(
|
44
|
+
def initialize(opts = {})
|
43
45
|
super
|
44
46
|
@chain = self.class.chain.dup
|
45
|
-
[*options[:chain]].compact.each {|block| block.call(self) }
|
46
47
|
end
|
47
48
|
|
48
49
|
def call(input)
|
data/lib/temple/erb/parser.rb
CHANGED
data/lib/temple/erb/trimming.rb
CHANGED
@@ -0,0 +1,13 @@
|
|
1
|
+
module Temple
|
2
|
+
# Exception raised if invalid temple expression is found
|
3
|
+
#
|
4
|
+
# @api public
|
5
|
+
class InvalidExpression < RuntimeError
|
6
|
+
end
|
7
|
+
|
8
|
+
# Exception raised if something bad happens in a Temple filter
|
9
|
+
#
|
10
|
+
# @api public
|
11
|
+
class FilterError < RuntimeError
|
12
|
+
end
|
13
|
+
end
|
@@ -20,9 +20,8 @@ module Temple
|
|
20
20
|
def on_case(arg, *cases)
|
21
21
|
result = [:multi, [:code, arg ? "case (#{arg})" : 'case']]
|
22
22
|
cases.map do |c|
|
23
|
-
condition,
|
24
|
-
result << [:code, condition == :else ? 'else' : "when #{condition}"]
|
25
|
-
exps.each {|e| result << compile(e) }
|
23
|
+
condition, exp = c
|
24
|
+
result << [:code, condition == :else ? 'else' : "when #{condition}"] << compile(exp)
|
26
25
|
end
|
27
26
|
result << [:code, 'end']
|
28
27
|
result
|
@@ -8,8 +8,9 @@ module Temple
|
|
8
8
|
# @api public
|
9
9
|
class Escapable < Filter
|
10
10
|
# Activate the usage of html_safe? if it is available (for Rails 3 for example)
|
11
|
-
|
12
|
-
|
11
|
+
define_options :escape_code,
|
12
|
+
:use_html_safe => ''.respond_to?(:html_safe?),
|
13
|
+
:disable_escape => false
|
13
14
|
|
14
15
|
def initialize(opts = {})
|
15
16
|
super
|
data/lib/temple/generators.rb
CHANGED
@@ -14,7 +14,8 @@ module Temple
|
|
14
14
|
include Mixins::CompiledDispatcher
|
15
15
|
include Mixins::Options
|
16
16
|
|
17
|
-
|
17
|
+
define_options :capture_generator,
|
18
|
+
:buffer => '_buf'
|
18
19
|
|
19
20
|
def call(exp)
|
20
21
|
[preamble, compile(exp), postamble].join('; ')
|
@@ -134,10 +135,11 @@ module Temple
|
|
134
135
|
#
|
135
136
|
# @api public
|
136
137
|
class RailsOutputBuffer < StringBuffer
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
138
|
+
define_options :streaming,
|
139
|
+
:buffer_class => 'ActiveSupport::SafeBuffer',
|
140
|
+
:buffer => '@output_buffer',
|
141
|
+
# output_buffer is needed for Rails 3.1 Streaming support
|
142
|
+
:capture_generator => RailsOutputBuffer
|
141
143
|
|
142
144
|
def call(exp)
|
143
145
|
[preamble, compile(exp), postamble].join('; ')
|
@@ -157,5 +159,5 @@ module Temple
|
|
157
159
|
end
|
158
160
|
end
|
159
161
|
|
160
|
-
Generator.default_options[:capture_generator] =
|
162
|
+
Generator.default_options[:capture_generator] = Generators::StringBuffer
|
161
163
|
end
|
data/lib/temple/grammar.rb
CHANGED
data/lib/temple/hash.rb
CHANGED
@@ -28,6 +28,12 @@ module Temple
|
|
28
28
|
def values
|
29
29
|
keys.map {|k| self[k] }
|
30
30
|
end
|
31
|
+
|
32
|
+
def to_hash
|
33
|
+
result = {}
|
34
|
+
each {|k, v| result[k] = v }
|
35
|
+
result
|
36
|
+
end
|
31
37
|
end
|
32
38
|
|
33
39
|
# Mutable hash class which supports hash merging
|
@@ -45,4 +51,54 @@ module Temple
|
|
45
51
|
@hash.first.update(hash)
|
46
52
|
end
|
47
53
|
end
|
54
|
+
|
55
|
+
class OptionHash < MutableHash
|
56
|
+
def initialize(*hash, &block)
|
57
|
+
super(*hash)
|
58
|
+
@handler = block
|
59
|
+
@valid = {}
|
60
|
+
@deprecated = {}
|
61
|
+
end
|
62
|
+
|
63
|
+
def []=(key, value)
|
64
|
+
validate_key!(key)
|
65
|
+
super
|
66
|
+
end
|
67
|
+
|
68
|
+
def update(hash)
|
69
|
+
validate_hash!(hash)
|
70
|
+
super
|
71
|
+
end
|
72
|
+
|
73
|
+
def valid_keys
|
74
|
+
keys.concat(@valid.keys).uniq
|
75
|
+
end
|
76
|
+
|
77
|
+
def add_valid_keys(*keys)
|
78
|
+
keys.flatten.each { |key| @valid[key] = true }
|
79
|
+
end
|
80
|
+
|
81
|
+
def add_deprecated_keys(*keys)
|
82
|
+
keys.flatten.each { |key| @valid[key] = @deprecated[key] = true }
|
83
|
+
end
|
84
|
+
|
85
|
+
def validate_hash!(hash)
|
86
|
+
hash.to_hash.keys.each {|key| validate_key!(key) }
|
87
|
+
end
|
88
|
+
|
89
|
+
def validate_key!(key)
|
90
|
+
@handler.call(self, key, true) if deprecated_key?(key)
|
91
|
+
@handler.call(self, key, false) unless valid_key?(key)
|
92
|
+
end
|
93
|
+
|
94
|
+
def deprecated_key?(key)
|
95
|
+
@deprecated.include?(key) ||
|
96
|
+
@hash.any? {|h| h.deprecated_key?(key) if h.respond_to?(:deprecated_key?) }
|
97
|
+
end
|
98
|
+
|
99
|
+
def valid_key?(key)
|
100
|
+
include?(key) || @valid.include?(key) ||
|
101
|
+
@hash.any? {|h| h.valid_key?(key) if h.respond_to?(:valid_key?) }
|
102
|
+
end
|
103
|
+
end
|
48
104
|
end
|
@@ -3,45 +3,44 @@ module Temple
|
|
3
3
|
# This filter merges html attributes (e.g. used for id and class)
|
4
4
|
# @api public
|
5
5
|
class AttributeMerger < Filter
|
6
|
-
|
6
|
+
define_options :attr_delimiter => {'id' => '_', 'class' => ' '}
|
7
7
|
|
8
8
|
def on_html_attrs(*attrs)
|
9
9
|
names = []
|
10
|
-
|
10
|
+
values = {}
|
11
|
+
|
11
12
|
attrs.each do |attr|
|
12
|
-
raise(InvalidExpression, 'Attribute is not a html attr') if attr[0] != :html || attr[1] != :attr
|
13
13
|
name, value = attr[2].to_s, attr[3]
|
14
|
-
if
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
14
|
+
if values[name]
|
15
|
+
raise(FilterError, "Multiple #{name} attributes specified") unless options[:attr_delimiter][name]
|
16
|
+
values[name] << value
|
17
|
+
else
|
18
|
+
values[name] = [value]
|
19
|
+
names << name
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
attrs = names.map do |name|
|
24
|
+
value = values[name]
|
25
|
+
if (delimiter = options[:attr_delimiter][name]) && value.size > 1
|
26
|
+
exp = [:multi]
|
27
|
+
if value.all? {|v| contains_nonempty_static?(v) }
|
28
|
+
exp << value.first
|
29
|
+
value[1..-1].each {|v| exp << [:static, delimiter] << v }
|
30
|
+
[:html, :attr, name, exp]
|
28
31
|
else
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
[:capture, tmp, value],
|
34
|
-
[:if, "!#{tmp}.empty?",
|
35
|
-
[:multi,
|
36
|
-
[:static, delimiter],
|
37
|
-
[:dynamic, tmp]]]]]
|
32
|
+
captures = unique_name
|
33
|
+
exp << [:code, "#{captures} = []"]
|
34
|
+
value.each_with_index {|v, i| exp << [:capture, "#{captures}[#{i}]", v] }
|
35
|
+
exp << [:dynamic, "#{captures}.reject(&:empty?).join(#{delimiter.inspect})"]
|
38
36
|
end
|
37
|
+
[:html, :attr, name, exp]
|
39
38
|
else
|
40
|
-
|
41
|
-
names << name
|
39
|
+
[:html, :attr, name, value.first]
|
42
40
|
end
|
43
41
|
end
|
44
|
-
|
42
|
+
|
43
|
+
[:html, :attrs, *attrs]
|
45
44
|
end
|
46
45
|
end
|
47
46
|
end
|
@@ -3,17 +3,24 @@ module Temple
|
|
3
3
|
# This filter removes empty attributes
|
4
4
|
# @api public
|
5
5
|
class AttributeRemover < Filter
|
6
|
-
|
6
|
+
define_options :remove_empty_attrs => %w(id class)
|
7
|
+
|
8
|
+
def initialize(opts = {})
|
9
|
+
super
|
10
|
+
raise ArgumentError, "Option :remove_empty_attrs must be an Array of Strings" unless Array === options[:remove_empty_attrs] &&
|
11
|
+
options[:remove_empty_attrs].all? {|a| String === a }
|
12
|
+
end
|
7
13
|
|
8
14
|
def on_html_attrs(*attrs)
|
9
|
-
[:multi, *(
|
10
|
-
attrs.map {|attr| compile(attr) } : attrs)]
|
15
|
+
[:multi, *attrs.map {|attr| compile(attr) }]
|
11
16
|
end
|
12
17
|
|
13
18
|
def on_html_attr(name, value)
|
19
|
+
return super unless options[:remove_empty_attrs].include?(name.to_s)
|
20
|
+
|
14
21
|
if empty_exp?(value)
|
15
22
|
value
|
16
|
-
elsif
|
23
|
+
elsif contains_nonempty_static?(value)
|
17
24
|
[:html, :attr, name, value]
|
18
25
|
else
|
19
26
|
tmp = unique_name
|
@@ -3,14 +3,18 @@ module Temple
|
|
3
3
|
# This filter sorts html attributes.
|
4
4
|
# @api public
|
5
5
|
class AttributeSorter < Filter
|
6
|
-
|
6
|
+
define_options :sort_attrs => true
|
7
|
+
|
8
|
+
def call(exp)
|
9
|
+
options[:sort_attrs] ? super : exp
|
10
|
+
end
|
7
11
|
|
8
12
|
def on_html_attrs(*attrs)
|
9
|
-
|
13
|
+
n = 0 # Use n to make sort stable. This is important because the merger could be executed afterwards.
|
14
|
+
[:html, :attrs, *attrs.sort_by do |attr|
|
10
15
|
raise(InvalidExpression, 'Attribute is not a html attr') if attr[0] != :html || attr[1] != :attr
|
11
|
-
attr[2].to_s
|
12
|
-
end
|
13
|
-
[:html, :attrs, *attrs]
|
16
|
+
[attr[2].to_s, n += 1]
|
17
|
+
end]
|
14
18
|
end
|
15
19
|
end
|
16
20
|
end
|
data/lib/temple/html/fast.rb
CHANGED
@@ -21,16 +21,16 @@ module Temple
|
|
21
21
|
'transitional' => '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">',
|
22
22
|
}.freeze
|
23
23
|
|
24
|
-
|
25
|
-
|
26
|
-
|
24
|
+
define_options :format => :xhtml,
|
25
|
+
:attr_wrapper => "'",
|
26
|
+
:autoclose => %w[meta img link br hr input area param col base]
|
27
27
|
|
28
28
|
HTML = [:html, :html4, :html5]
|
29
29
|
|
30
30
|
def initialize(opts = {})
|
31
31
|
super
|
32
32
|
unless [:xhtml, *HTML].include?(options[:format])
|
33
|
-
raise "Invalid format #{options[:format].inspect}"
|
33
|
+
raise ArgumentError, "Invalid format #{options[:format].inspect}"
|
34
34
|
end
|
35
35
|
end
|
36
36
|
|
@@ -46,13 +46,13 @@ module Temple
|
|
46
46
|
type = type.to_s.downcase
|
47
47
|
|
48
48
|
if type =~ /^xml(\s+(.+))?$/
|
49
|
-
raise 'Invalid xml directive in html mode' if html?
|
49
|
+
raise(FilterError, 'Invalid xml directive in html mode') if html?
|
50
50
|
w = options[:attr_wrapper]
|
51
51
|
str = "<?xml version=#{w}1.0#{w} encoding=#{w}#{$2 || 'utf-8'}#{w} ?>"
|
52
52
|
elsif html?
|
53
|
-
str = HTML_DOCTYPES[type] || raise("Invalid html doctype #{type}")
|
53
|
+
str = HTML_DOCTYPES[type] || raise(FilterError, "Invalid html doctype #{type}")
|
54
54
|
else
|
55
|
-
str = XHTML_DOCTYPES[type] || raise("Invalid xhtml doctype #{type}")
|
55
|
+
str = XHTML_DOCTYPES[type] || raise(FilterError, "Invalid xhtml doctype #{type}")
|
56
56
|
end
|
57
57
|
|
58
58
|
[:static, str]
|
data/lib/temple/html/filter.rb
CHANGED
@@ -3,6 +3,19 @@ module Temple
|
|
3
3
|
# @api public
|
4
4
|
class Filter < Temple::Filter
|
5
5
|
include Dispatcher
|
6
|
+
|
7
|
+
def contains_nonempty_static?(exp)
|
8
|
+
case exp.first
|
9
|
+
when :multi
|
10
|
+
exp[1..-1].any? {|e| contains_nonempty_static?(e) }
|
11
|
+
when :escape
|
12
|
+
contains_nonempty_static?(exp.last)
|
13
|
+
when :static
|
14
|
+
!exp.last.empty?
|
15
|
+
else
|
16
|
+
false
|
17
|
+
end
|
18
|
+
end
|
6
19
|
end
|
7
20
|
end
|
8
21
|
end
|
data/lib/temple/html/pretty.rb
CHANGED
@@ -2,14 +2,14 @@ module Temple
|
|
2
2
|
module HTML
|
3
3
|
# @api public
|
4
4
|
class Pretty < Fast
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
5
|
+
define_options :indent => ' ',
|
6
|
+
:pretty => true,
|
7
|
+
:indent_tags => %w(article aside audio base body datalist dd div dl dt
|
8
|
+
fieldset figure footer form head h1 h2 h3 h4 h5 h6
|
9
|
+
header hgroup hr html li link meta nav ol p
|
10
|
+
rp rt ruby section script style table tbody td tfoot
|
11
|
+
th thead title tr ul video).freeze,
|
12
|
+
:pre_tags => %w(code pre textarea).freeze
|
13
13
|
|
14
14
|
def initialize(opts = {})
|
15
15
|
super
|
@@ -96,11 +96,11 @@ module Temple
|
|
96
96
|
end
|
97
97
|
|
98
98
|
def chain_class_constructor(filter, option_filter)
|
99
|
-
local_options =
|
99
|
+
local_options = option_filter.last.respond_to?(:to_hash) ? option_filter.pop.to_hash : {}
|
100
100
|
raise(ArgumentError, 'Only symbols allowed in option filter') unless option_filter.all? {|o| Symbol === o }
|
101
|
+
define_options(*option_filter) if respond_to?(:define_options)
|
101
102
|
proc do |engine|
|
102
|
-
|
103
|
-
filter.new(ImmutableHash.new(local_options, filtered_options))
|
103
|
+
filter.new({}.update(engine.options).delete_if {|k,v| !option_filter.include?(k) }.update(local_options))
|
104
104
|
end
|
105
105
|
end
|
106
106
|
|
@@ -2,26 +2,83 @@ module Temple
|
|
2
2
|
module Mixins
|
3
3
|
# @api public
|
4
4
|
module DefaultOptions
|
5
|
-
def set_default_options(
|
6
|
-
default_options.update(
|
5
|
+
def set_default_options(opts)
|
6
|
+
default_options.update(opts)
|
7
7
|
end
|
8
8
|
|
9
9
|
def default_options
|
10
|
-
@default_options ||=
|
11
|
-
|
10
|
+
@default_options ||= OptionHash.new(superclass.respond_to?(:default_options) ?
|
11
|
+
superclass.default_options : nil) do |hash, key, deprecated|
|
12
|
+
unless @option_validator_disabled
|
13
|
+
if deprecated
|
14
|
+
puts "Option #{key.inspect} is deprecated by #{self}"
|
15
|
+
else
|
16
|
+
# TODO: This will raise an exception in the future!
|
17
|
+
# raise ArgumentError, "Option #{key.inspect} is not supported by #{self}"
|
18
|
+
puts "Option #{key.inspect} is not supported by #{self}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def define_options(*opts)
|
25
|
+
if opts.last.respond_to?(:to_hash)
|
26
|
+
hash = opts.pop.to_hash
|
27
|
+
default_options.add_valid_keys(hash.keys)
|
28
|
+
default_options.update(hash)
|
29
|
+
end
|
30
|
+
default_options.add_valid_keys(opts)
|
31
|
+
end
|
32
|
+
|
33
|
+
def define_deprecated_options(*opts)
|
34
|
+
if opts.last.respond_to?(:to_hash)
|
35
|
+
hash = opts.pop.to_hash
|
36
|
+
default_options.add_deprecated_keys(hash.keys)
|
37
|
+
default_options.update(hash)
|
38
|
+
end
|
39
|
+
default_options.add_deprecated_keys(opts)
|
40
|
+
end
|
41
|
+
|
42
|
+
def disable_option_validator!
|
43
|
+
@option_validator_disabled = true
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
module ThreadOptions
|
48
|
+
def with_options(options)
|
49
|
+
old_options = thread_options
|
50
|
+
Thread.current[thread_options_key] = ImmutableHash.new(options, thread_options)
|
51
|
+
yield
|
52
|
+
ensure
|
53
|
+
Thread.current[thread_options_key] = old_options
|
54
|
+
end
|
55
|
+
|
56
|
+
def thread_options
|
57
|
+
Thread.current[thread_options_key]
|
58
|
+
end
|
59
|
+
|
60
|
+
protected
|
61
|
+
|
62
|
+
def thread_options_key
|
63
|
+
@thread_options_key ||= "#{self.name}-thread-options".to_sym
|
12
64
|
end
|
13
65
|
end
|
14
66
|
|
15
67
|
# @api public
|
16
68
|
module Options
|
17
69
|
def self.included(base)
|
18
|
-
base.class_eval
|
70
|
+
base.class_eval do
|
71
|
+
extend DefaultOptions
|
72
|
+
extend ThreadOptions
|
73
|
+
end
|
19
74
|
end
|
20
75
|
|
21
76
|
attr_reader :options
|
22
77
|
|
23
|
-
def initialize(
|
24
|
-
|
78
|
+
def initialize(opts = {})
|
79
|
+
self.class.default_options.validate_hash!(opts)
|
80
|
+
self.class.default_options.validate_hash!(self.class.thread_options) if self.class.thread_options
|
81
|
+
@options = ImmutableHash.new({}.update(self.class.default_options).update(self.class.thread_options || {}).update(opts))
|
25
82
|
end
|
26
83
|
end
|
27
84
|
end
|
@@ -4,39 +4,25 @@ module Temple
|
|
4
4
|
module Template
|
5
5
|
include DefaultOptions
|
6
6
|
|
7
|
-
def
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
def init
|
13
|
-
# Overwrite this for class initialization
|
7
|
+
def compile(code, options)
|
8
|
+
engine = options.delete(:engine)
|
9
|
+
raise 'No engine configured' unless engine
|
10
|
+
engine.new(options).call(code)
|
14
11
|
end
|
15
12
|
|
16
|
-
def register_as(
|
13
|
+
def register_as(*names)
|
17
14
|
raise NotImplementedError
|
18
15
|
end
|
19
16
|
|
20
17
|
def create(engine, options)
|
18
|
+
register_as = options.delete(:register_as)
|
21
19
|
template = Class.new(self)
|
20
|
+
template.disable_option_validator!
|
22
21
|
template.default_options[:engine] = engine
|
23
22
|
template.default_options.update(options)
|
24
|
-
template.
|
25
|
-
template.register_as(options[:register_as]) if options[:register_as]
|
23
|
+
template.register_as(*register_as) if register_as
|
26
24
|
template
|
27
25
|
end
|
28
|
-
|
29
|
-
def build_engine(*options)
|
30
|
-
raise 'No engine configured' unless engine
|
31
|
-
options << default_options
|
32
|
-
engine.new(ImmutableHash.new(*options))
|
33
|
-
end
|
34
|
-
|
35
|
-
def chain(&block)
|
36
|
-
chain = (default_options[:chain] ||= [])
|
37
|
-
chain << block if block
|
38
|
-
chain
|
39
|
-
end
|
40
26
|
end
|
41
27
|
end
|
42
28
|
end
|
@@ -14,12 +14,15 @@ module Temple
|
|
14
14
|
extend Mixins::Template
|
15
15
|
|
16
16
|
def compile(template)
|
17
|
-
|
18
|
-
|
17
|
+
# Overwrite option: No streaming support in Rails < 3.1
|
18
|
+
opts = {}.update(self.class.default_options).update(:file => template.identifier, :streaming => false)
|
19
|
+
self.class.compile(template.source, opts)
|
19
20
|
end
|
20
21
|
|
21
|
-
def self.register_as(
|
22
|
-
|
22
|
+
def self.register_as(*names)
|
23
|
+
names.each do |name|
|
24
|
+
ActionView::Template.register_template_handler name.to_sym, self
|
25
|
+
end
|
23
26
|
end
|
24
27
|
end
|
25
28
|
else
|
@@ -27,15 +30,18 @@ module Temple
|
|
27
30
|
extend Mixins::Template
|
28
31
|
|
29
32
|
def call(template)
|
30
|
-
self.class.
|
33
|
+
opts = {}.update(self.class.default_options).update(:file => template.identifier)
|
34
|
+
self.class.compile(template.source, opts)
|
31
35
|
end
|
32
36
|
|
33
37
|
def supports_streaming?
|
34
38
|
self.class.default_options[:streaming]
|
35
39
|
end
|
36
40
|
|
37
|
-
def self.register_as(
|
38
|
-
|
41
|
+
def self.register_as(*names)
|
42
|
+
names.each do |name|
|
43
|
+
ActionView::Template.register_template_handler name.to_sym, new
|
44
|
+
end
|
39
45
|
end
|
40
46
|
end
|
41
47
|
end
|
@@ -5,14 +5,27 @@ module Temple
|
|
5
5
|
class Tilt < ::Tilt::Template
|
6
6
|
extend Mixins::Template
|
7
7
|
|
8
|
+
define_options :mime_type => 'text/html'
|
9
|
+
|
10
|
+
def self.default_mime_type
|
11
|
+
default_options[:mime_type]
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.default_mime_type=(mime_type)
|
15
|
+
default_options[:mime_type] = mime_type
|
16
|
+
end
|
17
|
+
|
8
18
|
# Prepare Temple template
|
9
19
|
#
|
10
20
|
# Called immediately after template data is loaded.
|
11
21
|
#
|
12
22
|
# @return [void]
|
13
23
|
def prepare
|
14
|
-
|
15
|
-
|
24
|
+
# Overwrite option: No streaming support in Tilt
|
25
|
+
opts = {}.update(self.class.default_options).update(options).update(:file => eval_file, :streaming => false)
|
26
|
+
opts.delete(:mime_type)
|
27
|
+
opts.delete(:outvar) # Sinatra gives us this invalid variable
|
28
|
+
@src = self.class.compile(data, opts)
|
16
29
|
end
|
17
30
|
|
18
31
|
# A string containing the (Ruby) source code for the template.
|
@@ -23,12 +36,8 @@ module Temple
|
|
23
36
|
@src
|
24
37
|
end
|
25
38
|
|
26
|
-
def self.
|
27
|
-
self.
|
28
|
-
end
|
29
|
-
|
30
|
-
def self.register_as(name)
|
31
|
-
::Tilt.register name.to_s, self
|
39
|
+
def self.register_as(*names)
|
40
|
+
::Tilt.register(self, *names.map(&:to_s))
|
32
41
|
end
|
33
42
|
end
|
34
43
|
end
|
data/lib/temple/utils.rb
CHANGED
@@ -18,7 +18,7 @@ module Temple
|
|
18
18
|
# @param html [String] The string to escape
|
19
19
|
# @return [String] The escaped string
|
20
20
|
def escape_html(html)
|
21
|
-
EscapeUtils.escape_html(html.to_s)
|
21
|
+
EscapeUtils.escape_html(html.to_s, false)
|
22
22
|
end
|
23
23
|
elsif RUBY_VERSION > '1.9'
|
24
24
|
# Used by escape_html
|
@@ -28,7 +28,6 @@ module Temple
|
|
28
28
|
'"' => '"',
|
29
29
|
'<' => '<',
|
30
30
|
'>' => '>',
|
31
|
-
'/' => '/',
|
32
31
|
}.freeze
|
33
32
|
|
34
33
|
# Returns an escaped copy of `html`.
|
@@ -36,7 +35,7 @@ module Temple
|
|
36
35
|
# @param html [String] The string to escape
|
37
36
|
# @return [String] The escaped string
|
38
37
|
def escape_html(html)
|
39
|
-
html.to_s.gsub(/[&\"
|
38
|
+
html.to_s.gsub(/[&\"<>]/, ESCAPE_HTML)
|
40
39
|
end
|
41
40
|
else
|
42
41
|
# Returns an escaped copy of `html`.
|
@@ -44,7 +43,7 @@ module Temple
|
|
44
43
|
# @param html [String] The string to escape
|
45
44
|
# @return [String] The escaped string
|
46
45
|
def escape_html(html)
|
47
|
-
html.to_s.gsub(/&/n, '&').gsub(/\"/n, '"').gsub(/>/n, '>').gsub(/</n, '<')
|
46
|
+
html.to_s.gsub(/&/n, '&').gsub(/\"/n, '"').gsub(/>/n, '>').gsub(/</n, '<')
|
48
47
|
end
|
49
48
|
end
|
50
49
|
|
@@ -58,19 +57,6 @@ module Temple
|
|
58
57
|
"_#{prefix}#{@unique_name += 1}"
|
59
58
|
end
|
60
59
|
|
61
|
-
def contains_static?(exp)
|
62
|
-
case exp[0]
|
63
|
-
when :multi
|
64
|
-
exp[1..-1].any? {|e| contains_static?(e) }
|
65
|
-
when :escape
|
66
|
-
contains_static?(exp[2])
|
67
|
-
when :static
|
68
|
-
true
|
69
|
-
else
|
70
|
-
false
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
60
|
# Check if expression is empty
|
75
61
|
#
|
76
62
|
# @param exp [Array] Temple expression
|
data/lib/temple/version.rb
CHANGED
data/temple.gemspec
CHANGED
@@ -9,7 +9,7 @@ Gem::Specification.new do |s|
|
|
9
9
|
|
10
10
|
s.authors = ['Magnus Holm', 'Daniel Mendler']
|
11
11
|
s.email = ['judofyr@gmail.com', 'mail@daniel-mendler.de']
|
12
|
-
s.homepage = '
|
12
|
+
s.homepage = 'https://github.com/judofyr/temple'
|
13
13
|
s.summary = 'Template compilation framework in Ruby'
|
14
14
|
|
15
15
|
s.require_paths = %w(lib)
|
@@ -56,14 +56,13 @@ describe Temple::Filters::ControlFlow do
|
|
56
56
|
|
57
57
|
it 'should process cond' do
|
58
58
|
@filter.call([:cond,
|
59
|
-
['cond1', [:
|
59
|
+
['cond1', [:exp1]],
|
60
60
|
['cond2', [:exp2]],
|
61
61
|
[:else, [:exp3]],
|
62
62
|
]).should.equal [:multi,
|
63
63
|
[:code, 'case'],
|
64
64
|
[:code, 'when cond1'],
|
65
|
-
[:
|
66
|
-
[:exp12],
|
65
|
+
[:exp1],
|
67
66
|
[:code, 'when cond2'],
|
68
67
|
[:exp2],
|
69
68
|
[:code, 'else'],
|
@@ -74,14 +73,13 @@ describe Temple::Filters::ControlFlow do
|
|
74
73
|
|
75
74
|
it 'should process case' do
|
76
75
|
@filter.call([:case, 'var',
|
77
|
-
['Array', [:
|
76
|
+
['Array', [:exp1]],
|
78
77
|
['String', [:exp2]],
|
79
78
|
[:else, [:exp3]],
|
80
79
|
]).should.equal [:multi,
|
81
80
|
[:code, 'case (var)'],
|
82
81
|
[:code, 'when Array'],
|
83
|
-
[:
|
84
|
-
[:exp12],
|
82
|
+
[:exp1],
|
85
83
|
[:code, 'when String'],
|
86
84
|
[:exp2],
|
87
85
|
[:code, 'else'],
|
@@ -50,13 +50,10 @@ describe Temple::HTML::AttributeMerger do
|
|
50
50
|
[:html, :attrs,
|
51
51
|
[:html, :attr, "id",
|
52
52
|
[:multi,
|
53
|
-
[:
|
54
|
-
[:capture, "_temple_html_attributemerger1",
|
55
|
-
|
56
|
-
[:
|
57
|
-
[:multi,
|
58
|
-
[:static, "_"],
|
59
|
-
[:dynamic, "_temple_html_attributemerger1"]]]]]],
|
53
|
+
[:code, "_temple_html_attributemerger1 = []"],
|
54
|
+
[:capture, "_temple_html_attributemerger1[0]", [:dynamic, "a"]],
|
55
|
+
[:capture, "_temple_html_attributemerger1[1]", [:dynamic, "b"]],
|
56
|
+
[:dynamic, "_temple_html_attributemerger1.reject(&:empty?).join(\"_\")"]]]],
|
60
57
|
[:content]]
|
61
58
|
end
|
62
59
|
|
@@ -69,13 +66,10 @@ describe Temple::HTML::AttributeMerger do
|
|
69
66
|
[:html, :attrs,
|
70
67
|
[:html, :attr, "class",
|
71
68
|
[:multi,
|
72
|
-
[:
|
73
|
-
[:capture, "_temple_html_attributemerger1",
|
74
|
-
|
75
|
-
[:
|
76
|
-
[:multi,
|
77
|
-
[:static, " "],
|
78
|
-
[:dynamic, "_temple_html_attributemerger1"]]]]]],
|
69
|
+
[:code, "_temple_html_attributemerger1 = []"],
|
70
|
+
[:capture, "_temple_html_attributemerger1[0]", [:static, "a"]],
|
71
|
+
[:capture, "_temple_html_attributemerger1[1]", [:dynamic, "b"]],
|
72
|
+
[:dynamic, "_temple_html_attributemerger1.reject(&:empty?).join(\" \")"]]]],
|
79
73
|
[:content]]
|
80
74
|
end
|
81
75
|
end
|
@@ -3,7 +3,6 @@ require 'helper'
|
|
3
3
|
describe Temple::HTML::AttributeRemover do
|
4
4
|
before do
|
5
5
|
@remover = Temple::HTML::AttributeRemover.new
|
6
|
-
@disabled_remover = Temple::HTML::AttributeRemover.new :remove_empty_attrs => false
|
7
6
|
end
|
8
7
|
|
9
8
|
it 'should pass static attributes through' do
|
@@ -17,7 +16,7 @@ describe Temple::HTML::AttributeRemover do
|
|
17
16
|
[:content]]
|
18
17
|
end
|
19
18
|
|
20
|
-
it 'should check for empty dynamic attribute if
|
19
|
+
it 'should check for empty dynamic attribute if it is included in :remove_empty_attrs' do
|
21
20
|
@remover.call([:html, :tag,
|
22
21
|
'div',
|
23
22
|
[:html, :attrs, [:html, :attr, 'class', [:dynamic, 'b']]],
|
@@ -31,14 +30,14 @@ describe Temple::HTML::AttributeRemover do
|
|
31
30
|
[:content]]
|
32
31
|
end
|
33
32
|
|
34
|
-
it 'should not check for empty dynamic attribute if
|
35
|
-
@
|
33
|
+
it 'should not check for empty dynamic attribute if it is not included in :remove_empty_attrs' do
|
34
|
+
@remover.call([:html, :tag,
|
36
35
|
'div',
|
37
|
-
[:html, :attrs, [:html, :attr, '
|
36
|
+
[:html, :attrs, [:html, :attr, 'name', [:dynamic, 'b']]],
|
38
37
|
[:content]
|
39
38
|
]).should.equal [:html, :tag, "div",
|
40
39
|
[:multi,
|
41
|
-
[:html, :attr, "
|
40
|
+
[:html, :attr, "name", [:dynamic, "b"]]],
|
42
41
|
[:content]]
|
43
42
|
end
|
44
43
|
end
|
data/test/test_engine.rb
CHANGED
@@ -167,12 +167,4 @@ describe Temple::Engine do
|
|
167
167
|
inherited_engine.chain.size.should.equal 9
|
168
168
|
TestEngine.chain.size.should.equal 8
|
169
169
|
end
|
170
|
-
|
171
|
-
it 'should support chain option' do
|
172
|
-
engine = TestEngine.new(:chain => proc {|e| e.remove :MyFilter1 })
|
173
|
-
TestEngine.chain.size.should.equal 8
|
174
|
-
engine.chain.size.should.equal 7
|
175
|
-
engine.chain[0].first.should.equal :Parser
|
176
|
-
engine.chain[1].first.should.equal :MyFilter2
|
177
|
-
end
|
178
170
|
end
|
data/test/test_erb.rb
CHANGED
@@ -38,13 +38,13 @@ world}
|
|
38
38
|
it 'should escape automatically' do
|
39
39
|
src = '<%= "<" %>'
|
40
40
|
ans = '<'
|
41
|
-
erb(src
|
41
|
+
erb(src).should.equal ans
|
42
42
|
end
|
43
43
|
|
44
44
|
it 'should support == to disable automatic escape' do
|
45
45
|
src = '<%== "<" %>'
|
46
46
|
ans = '<'
|
47
|
-
erb(src
|
47
|
+
erb(src).should.equal ans
|
48
48
|
end
|
49
49
|
|
50
50
|
it 'should support trim mode' do
|
data/test/test_filter.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
require 'helper'
|
2
2
|
|
3
3
|
class SimpleFilter < Temple::Filter
|
4
|
+
define_options :key
|
5
|
+
|
4
6
|
def on_test(arg)
|
5
7
|
[:on_test, arg]
|
6
8
|
end
|
@@ -10,8 +12,9 @@ describe Temple::Filter do
|
|
10
12
|
it 'should support options' do
|
11
13
|
Temple::Filter.should.respond_to :default_options
|
12
14
|
Temple::Filter.should.respond_to :set_default_options
|
15
|
+
Temple::Filter.should.respond_to :define_options
|
13
16
|
Temple::Filter.new.options.should.be.instance_of Temple::ImmutableHash
|
14
|
-
|
17
|
+
SimpleFilter.new(:key => 3).options[:key].should.equal 3
|
15
18
|
end
|
16
19
|
|
17
20
|
it 'should implement call' do
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: temple
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4
|
4
|
+
version: 0.5.4
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -10,11 +10,11 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2012-09-
|
13
|
+
date: 2012-09-28 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: tilt
|
17
|
-
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirement: &6391160 !ruby/object:Gem::Requirement
|
18
18
|
none: false
|
19
19
|
requirements:
|
20
20
|
- - ! '>='
|
@@ -22,15 +22,10 @@ dependencies:
|
|
22
22
|
version: '0'
|
23
23
|
type: :development
|
24
24
|
prerelease: false
|
25
|
-
version_requirements:
|
26
|
-
none: false
|
27
|
-
requirements:
|
28
|
-
- - ! '>='
|
29
|
-
- !ruby/object:Gem::Version
|
30
|
-
version: '0'
|
25
|
+
version_requirements: *6391160
|
31
26
|
- !ruby/object:Gem::Dependency
|
32
27
|
name: bacon
|
33
|
-
requirement: !ruby/object:Gem::Requirement
|
28
|
+
requirement: &6390120 !ruby/object:Gem::Requirement
|
34
29
|
none: false
|
35
30
|
requirements:
|
36
31
|
- - ! '>='
|
@@ -38,15 +33,10 @@ dependencies:
|
|
38
33
|
version: '0'
|
39
34
|
type: :development
|
40
35
|
prerelease: false
|
41
|
-
version_requirements:
|
42
|
-
none: false
|
43
|
-
requirements:
|
44
|
-
- - ! '>='
|
45
|
-
- !ruby/object:Gem::Version
|
46
|
-
version: '0'
|
36
|
+
version_requirements: *6390120
|
47
37
|
- !ruby/object:Gem::Dependency
|
48
38
|
name: rake
|
49
|
-
requirement: !ruby/object:Gem::Requirement
|
39
|
+
requirement: &6389580 !ruby/object:Gem::Requirement
|
50
40
|
none: false
|
51
41
|
requirements:
|
52
42
|
- - ! '>='
|
@@ -54,12 +44,7 @@ dependencies:
|
|
54
44
|
version: '0'
|
55
45
|
type: :development
|
56
46
|
prerelease: false
|
57
|
-
version_requirements:
|
58
|
-
none: false
|
59
|
-
requirements:
|
60
|
-
- - ! '>='
|
61
|
-
- !ruby/object:Gem::Version
|
62
|
-
version: '0'
|
47
|
+
version_requirements: *6389580
|
63
48
|
description:
|
64
49
|
email:
|
65
50
|
- judofyr@gmail.com
|
@@ -83,6 +68,7 @@ files:
|
|
83
68
|
- lib/temple/erb/parser.rb
|
84
69
|
- lib/temple/erb/template.rb
|
85
70
|
- lib/temple/erb/trimming.rb
|
71
|
+
- lib/temple/exceptions.rb
|
86
72
|
- lib/temple/filter.rb
|
87
73
|
- lib/temple/filters/control_flow.rb
|
88
74
|
- lib/temple/filters/dynamic_inliner.rb
|
@@ -106,6 +92,7 @@ files:
|
|
106
92
|
- lib/temple/mixins/grammar_dsl.rb
|
107
93
|
- lib/temple/mixins/options.rb
|
108
94
|
- lib/temple/mixins/template.rb
|
95
|
+
- lib/temple/parser.rb
|
109
96
|
- lib/temple/templates.rb
|
110
97
|
- lib/temple/templates/rails.rb
|
111
98
|
- lib/temple/templates/tilt.rb
|
@@ -133,7 +120,7 @@ files:
|
|
133
120
|
- test/test_grammar.rb
|
134
121
|
- test/test_hash.rb
|
135
122
|
- test/test_utils.rb
|
136
|
-
homepage:
|
123
|
+
homepage: https://github.com/judofyr/temple
|
137
124
|
licenses: []
|
138
125
|
post_install_message:
|
139
126
|
rdoc_options: []
|
@@ -153,7 +140,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
153
140
|
version: '0'
|
154
141
|
requirements: []
|
155
142
|
rubyforge_project:
|
156
|
-
rubygems_version: 1.8.
|
143
|
+
rubygems_version: 1.8.15
|
157
144
|
signing_key:
|
158
145
|
specification_version: 3
|
159
146
|
summary: Template compilation framework in Ruby
|