temple 0.6.10 → 0.7.1
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.
- checksums.yaml +4 -4
- data/.travis.yml +0 -2
- data/CHANGES +15 -0
- data/README.md +6 -6
- data/Rakefile +1 -1
- data/lib/temple.rb +5 -4
- data/lib/temple/engine.rb +1 -1
- data/lib/temple/erb/engine.rb +2 -2
- data/lib/temple/filters/encoding.rb +1 -1
- data/lib/temple/filters/eraser.rb +1 -1
- data/lib/temple/filters/escapable.rb +2 -2
- data/lib/temple/filters/remove_bom.rb +2 -9
- data/lib/temple/filters/static_freezer.rb +11 -0
- data/lib/temple/filters/validator.rb +1 -1
- data/lib/temple/generator.rb +4 -4
- data/lib/temple/generators/rails_output_buffer.rb +3 -3
- data/lib/temple/html/attribute_merger.rb +1 -1
- data/lib/temple/html/attribute_remover.rb +1 -1
- data/lib/temple/html/attribute_sorter.rb +1 -1
- data/lib/temple/html/fast.rb +42 -42
- data/lib/temple/html/pretty.rb +30 -39
- data/lib/temple/map.rb +105 -0
- data/lib/temple/mixins/engine_dsl.rb +11 -9
- data/lib/temple/mixins/options.rb +26 -24
- data/lib/temple/mixins/template.rb +3 -3
- data/lib/temple/templates/rails.rb +14 -37
- data/lib/temple/templates/tilt.rb +4 -4
- data/lib/temple/utils.rb +23 -26
- data/lib/temple/version.rb +1 -1
- data/temple.gemspec +2 -0
- data/test/filters/test_eraser.rb +4 -4
- data/test/filters/test_escapable.rb +7 -5
- data/test/filters/test_static_freezer.rb +25 -0
- data/test/html/test_attribute_sorter.rb +1 -1
- data/test/html/test_fast.rb +6 -6
- data/test/html/test_pretty.rb +2 -8
- data/test/test_engine.rb +1 -1
- data/test/test_erb.rb +2 -2
- data/test/test_filter.rb +2 -2
- data/test/test_generator.rb +4 -4
- data/test/test_map.rb +39 -0
- metadata +7 -5
- data/lib/temple/hash.rb +0 -105
- data/test/test_hash.rb +0 -39
data/lib/temple/map.rb
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
module Temple
|
2
|
+
# Immutable map class which supports map merging
|
3
|
+
# @api public
|
4
|
+
class ImmutableMap
|
5
|
+
include Enumerable
|
6
|
+
|
7
|
+
def initialize(*map)
|
8
|
+
@map = map.compact
|
9
|
+
end
|
10
|
+
|
11
|
+
def include?(key)
|
12
|
+
@map.any? {|h| h.include?(key) }
|
13
|
+
end
|
14
|
+
|
15
|
+
def [](key)
|
16
|
+
@map.each {|h| return h[key] if h.include?(key) }
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def each
|
21
|
+
keys.each {|k| yield(k, self[k]) }
|
22
|
+
end
|
23
|
+
|
24
|
+
def keys
|
25
|
+
@map.inject([]) {|keys, h| keys.concat(h.keys) }.uniq
|
26
|
+
end
|
27
|
+
|
28
|
+
def values
|
29
|
+
keys.map {|k| self[k] }
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_hash
|
33
|
+
result = {}
|
34
|
+
each {|k, v| result[k] = v }
|
35
|
+
result
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Mutable map class which supports map merging
|
40
|
+
# @api public
|
41
|
+
class MutableMap < ImmutableMap
|
42
|
+
def initialize(*map)
|
43
|
+
super({}, *map)
|
44
|
+
end
|
45
|
+
|
46
|
+
def []=(key, value)
|
47
|
+
@map.first[key] = value
|
48
|
+
end
|
49
|
+
|
50
|
+
def update(map)
|
51
|
+
@map.first.update(map)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class OptionMap < MutableMap
|
56
|
+
def initialize(*map, &block)
|
57
|
+
super(*map)
|
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(map)
|
69
|
+
validate_map!(map)
|
70
|
+
super
|
71
|
+
end
|
72
|
+
|
73
|
+
def valid_keys
|
74
|
+
(keys + @valid.keys +
|
75
|
+
@map.map {|h| h.valid_keys if h.respond_to?(:valid_keys) }.compact.flatten).uniq
|
76
|
+
end
|
77
|
+
|
78
|
+
def add_valid_keys(*keys)
|
79
|
+
keys.flatten.each { |key| @valid[key] = true }
|
80
|
+
end
|
81
|
+
|
82
|
+
def add_deprecated_keys(*keys)
|
83
|
+
keys.flatten.each { |key| @valid[key] = @deprecated[key] = true }
|
84
|
+
end
|
85
|
+
|
86
|
+
def validate_map!(map)
|
87
|
+
map.to_hash.keys.each {|key| validate_key!(key) }
|
88
|
+
end
|
89
|
+
|
90
|
+
def validate_key!(key)
|
91
|
+
@handler.call(self, key, :deprecated) if deprecated_key?(key)
|
92
|
+
@handler.call(self, key, :invalid) unless valid_key?(key)
|
93
|
+
end
|
94
|
+
|
95
|
+
def deprecated_key?(key)
|
96
|
+
@deprecated.include?(key) ||
|
97
|
+
@map.any? {|h| h.deprecated_key?(key) if h.respond_to?(:deprecated_key?) }
|
98
|
+
end
|
99
|
+
|
100
|
+
def valid_key?(key)
|
101
|
+
include?(key) || @valid.include?(key) ||
|
102
|
+
@map.any? {|h| h.valid_key?(key) if h.respond_to?(:valid_key?) }
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -79,9 +79,9 @@ module Temple
|
|
79
79
|
end
|
80
80
|
|
81
81
|
# Shortcuts to access namespaces
|
82
|
-
{ :
|
83
|
-
:
|
84
|
-
:
|
82
|
+
{ filter: Temple::Filters,
|
83
|
+
generator: Temple::Generators,
|
84
|
+
html: Temple::HTML }.each do |method, mod|
|
85
85
|
define_method(method) do |name, *options|
|
86
86
|
use(name, mod.const_get(name), *options)
|
87
87
|
end
|
@@ -95,12 +95,13 @@ module Temple
|
|
95
95
|
name
|
96
96
|
end
|
97
97
|
|
98
|
-
def chain_class_constructor(filter,
|
99
|
-
|
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)
|
98
|
+
def chain_class_constructor(filter, local_options)
|
99
|
+
define_options(filter.options.valid_keys) if respond_to?(:define_options) && filter.respond_to?(:options)
|
102
100
|
proc do |engine|
|
103
|
-
|
101
|
+
opts = {}.update(engine.options)
|
102
|
+
opts.delete_if {|k,v| !filter.options.valid_key?(k) } if filter.respond_to?(:options)
|
103
|
+
opts.update(local_options) if local_options
|
104
|
+
filter.new(opts)
|
104
105
|
end
|
105
106
|
end
|
106
107
|
|
@@ -157,7 +158,8 @@ module Temple
|
|
157
158
|
when Class
|
158
159
|
# Class argument (e.g Filter class)
|
159
160
|
# The options are passed to the classes constructor.
|
160
|
-
|
161
|
+
raise(ArgumentError, 'Too many arguments') if args.size > 1
|
162
|
+
[name, chain_class_constructor(filter, args.first)]
|
161
163
|
else
|
162
164
|
# Other callable argument (e.g. Object of class which implements #call or Method)
|
163
165
|
# The callable has no access to the option hash of the engine.
|
@@ -1,42 +1,44 @@
|
|
1
1
|
module Temple
|
2
2
|
module Mixins
|
3
3
|
# @api public
|
4
|
-
module
|
4
|
+
module ClassOptions
|
5
5
|
def set_default_options(opts)
|
6
|
-
|
6
|
+
warn 'set_default_options has been deprecated, use set_options'
|
7
|
+
set_options(opts)
|
7
8
|
end
|
8
9
|
|
9
10
|
def default_options
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
11
|
+
warn 'default_options has been deprecated, use options'
|
12
|
+
options
|
13
|
+
end
|
14
|
+
|
15
|
+
def set_options(opts)
|
16
|
+
options.update(opts)
|
17
|
+
end
|
18
|
+
|
19
|
+
def options
|
20
|
+
@options ||= OptionMap.new(superclass.respond_to?(:options) ?
|
21
|
+
superclass.options : nil) do |hash, key, what|
|
22
|
+
warn "#{self}: Option #{key.inspect} is #{what}" unless @option_validator_disabled
|
21
23
|
end
|
22
24
|
end
|
23
25
|
|
24
26
|
def define_options(*opts)
|
25
27
|
if opts.last.respond_to?(:to_hash)
|
26
28
|
hash = opts.pop.to_hash
|
27
|
-
|
28
|
-
|
29
|
+
options.add_valid_keys(hash.keys)
|
30
|
+
options.update(hash)
|
29
31
|
end
|
30
|
-
|
32
|
+
options.add_valid_keys(opts)
|
31
33
|
end
|
32
34
|
|
33
35
|
def define_deprecated_options(*opts)
|
34
36
|
if opts.last.respond_to?(:to_hash)
|
35
37
|
hash = opts.pop.to_hash
|
36
|
-
|
37
|
-
|
38
|
+
options.add_deprecated_keys(hash.keys)
|
39
|
+
options.update(hash)
|
38
40
|
end
|
39
|
-
|
41
|
+
options.add_deprecated_keys(opts)
|
40
42
|
end
|
41
43
|
|
42
44
|
def disable_option_validator!
|
@@ -47,7 +49,7 @@ module Temple
|
|
47
49
|
module ThreadOptions
|
48
50
|
def with_options(options)
|
49
51
|
old_options = thread_options
|
50
|
-
Thread.current[thread_options_key] =
|
52
|
+
Thread.current[thread_options_key] = ImmutableMap.new(options, thread_options)
|
51
53
|
yield
|
52
54
|
ensure
|
53
55
|
Thread.current[thread_options_key] = old_options
|
@@ -68,7 +70,7 @@ module Temple
|
|
68
70
|
module Options
|
69
71
|
def self.included(base)
|
70
72
|
base.class_eval do
|
71
|
-
extend
|
73
|
+
extend ClassOptions
|
72
74
|
extend ThreadOptions
|
73
75
|
end
|
74
76
|
end
|
@@ -76,9 +78,9 @@ module Temple
|
|
76
78
|
attr_reader :options
|
77
79
|
|
78
80
|
def initialize(opts = {})
|
79
|
-
self.class.
|
80
|
-
self.class.
|
81
|
-
@options =
|
81
|
+
self.class.options.validate_map!(opts)
|
82
|
+
self.class.options.validate_map!(self.class.thread_options) if self.class.thread_options
|
83
|
+
@options = ImmutableMap.new({}.update(self.class.options).update(self.class.thread_options || {}).update(opts))
|
82
84
|
end
|
83
85
|
end
|
84
86
|
end
|
@@ -2,7 +2,7 @@ module Temple
|
|
2
2
|
module Mixins
|
3
3
|
# @api private
|
4
4
|
module Template
|
5
|
-
include
|
5
|
+
include ClassOptions
|
6
6
|
|
7
7
|
def compile(code, options)
|
8
8
|
engine = options.delete(:engine)
|
@@ -18,8 +18,8 @@ module Temple
|
|
18
18
|
register_as = options.delete(:register_as)
|
19
19
|
template = Class.new(self)
|
20
20
|
template.disable_option_validator!
|
21
|
-
template.
|
22
|
-
template.
|
21
|
+
template.options[:engine] = engine
|
22
|
+
template.options.update(options)
|
23
23
|
template.register_as(*register_as) if register_as
|
24
24
|
template
|
25
25
|
end
|
@@ -1,47 +1,24 @@
|
|
1
|
-
unless defined?(ActionView)
|
2
|
-
raise "Rails is not loaded - Temple::Templates::Rails cannot be used"
|
3
|
-
end
|
4
|
-
|
5
|
-
if ::ActionPack::VERSION::MAJOR < 3
|
6
|
-
raise "Temple supports only Rails 3.x and greater, your Rails version is #{::ActionPack::VERSION::STRING}"
|
7
|
-
end
|
8
|
-
|
9
1
|
module Temple
|
10
2
|
module Templates
|
11
|
-
|
12
|
-
|
13
|
-
include ActionView::TemplateHandlers::Compilable
|
14
|
-
extend Mixins::Template
|
15
|
-
|
16
|
-
def compile(template)
|
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)
|
20
|
-
end
|
3
|
+
class Rails
|
4
|
+
extend Mixins::Template
|
21
5
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
end
|
26
|
-
end
|
6
|
+
def call(template)
|
7
|
+
opts = {}.update(self.class.options).update(file: template.identifier)
|
8
|
+
self.class.compile(template.source, opts)
|
27
9
|
end
|
28
|
-
else
|
29
|
-
class Rails
|
30
|
-
extend Mixins::Template
|
31
10
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
end
|
11
|
+
def supports_streaming?
|
12
|
+
self.class.options[:streaming]
|
13
|
+
end
|
36
14
|
|
37
|
-
|
38
|
-
|
15
|
+
def self.register_as(*names)
|
16
|
+
raise 'Rails is not loaded - Temple::Templates::Rails cannot be used' unless defined?(::ActionView)
|
17
|
+
if ::ActiveSupport::VERSION::MAJOR < 3 || ::ActiveSupport::VERSION::MAJOR == 3 && ::ActiveSupport::VERSION::MINOR < 1
|
18
|
+
raise "Temple supports only Rails 3.1 and greater, your Rails version is #{::ActiveSupport::VERSION::STRING}"
|
39
19
|
end
|
40
|
-
|
41
|
-
|
42
|
-
names.each do |name|
|
43
|
-
ActionView::Template.register_template_handler name.to_sym, new
|
44
|
-
end
|
20
|
+
names.each do |name|
|
21
|
+
::ActionView::Template.register_template_handler name.to_sym, new
|
45
22
|
end
|
46
23
|
end
|
47
24
|
end
|
@@ -5,14 +5,14 @@ module Temple
|
|
5
5
|
class Tilt < ::Tilt::Template
|
6
6
|
extend Mixins::Template
|
7
7
|
|
8
|
-
define_options :
|
8
|
+
define_options mime_type: 'text/html'
|
9
9
|
|
10
10
|
def self.default_mime_type
|
11
|
-
|
11
|
+
options[:mime_type]
|
12
12
|
end
|
13
13
|
|
14
14
|
def self.default_mime_type=(mime_type)
|
15
|
-
|
15
|
+
options[:mime_type] = mime_type
|
16
16
|
end
|
17
17
|
|
18
18
|
# Prepare Temple template
|
@@ -21,7 +21,7 @@ module Temple
|
|
21
21
|
#
|
22
22
|
# @return [void]
|
23
23
|
def prepare
|
24
|
-
opts = {}.update(self.class.
|
24
|
+
opts = {}.update(self.class.options).update(options).update(file: eval_file)
|
25
25
|
opts.delete(:mime_type)
|
26
26
|
if opts.include?(:outvar)
|
27
27
|
opts[:buffer] = opts.delete(:outvar)
|
data/lib/temple/utils.rb
CHANGED
@@ -13,10 +13,9 @@ module Temple
|
|
13
13
|
# Strings which are declared as html_safe are not escaped.
|
14
14
|
#
|
15
15
|
# @param html [String] The string to escape
|
16
|
-
# @param safe [Boolean] If false use escape_html
|
17
16
|
# @return [String] The escaped string
|
18
|
-
def escape_html_safe(html
|
19
|
-
|
17
|
+
def escape_html_safe(html)
|
18
|
+
html.html_safe? ? html : escape_html(html)
|
20
19
|
end
|
21
20
|
|
22
21
|
if defined?(EscapeUtils)
|
@@ -38,30 +37,14 @@ module Temple
|
|
38
37
|
'>' => '>'
|
39
38
|
}.freeze
|
40
39
|
|
41
|
-
|
42
|
-
ESCAPE_HTML_PATTERN = Regexp.union(*ESCAPE_HTML.keys)
|
43
|
-
else
|
44
|
-
# On 1.8, there is a kcode = 'u' bug that allows for XSS otherwise
|
45
|
-
# TODO doesn't apply to jruby, so a better condition above might be preferable?
|
46
|
-
ESCAPE_HTML_PATTERN = /#{Regexp.union(*ESCAPE_HTML.keys)}/n
|
47
|
-
end
|
40
|
+
ESCAPE_HTML_PATTERN = Regexp.union(*ESCAPE_HTML.keys)
|
48
41
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
html.to_s.gsub(ESCAPE_HTML_PATTERN, ESCAPE_HTML)
|
56
|
-
end
|
57
|
-
else
|
58
|
-
# Returns an escaped copy of `html`.
|
59
|
-
#
|
60
|
-
# @param html [String] The string to escape
|
61
|
-
# @return [String] The escaped string
|
62
|
-
def escape_html(html)
|
63
|
-
html.to_s.gsub(ESCAPE_HTML_PATTERN) {|c| ESCAPE_HTML[c] }
|
64
|
-
end
|
42
|
+
# Returns an escaped copy of `html`.
|
43
|
+
#
|
44
|
+
# @param html [String] The string to escape
|
45
|
+
# @return [String] The escaped string
|
46
|
+
def escape_html(html)
|
47
|
+
html.to_s.gsub(ESCAPE_HTML_PATTERN, ESCAPE_HTML)
|
65
48
|
end
|
66
49
|
end
|
67
50
|
|
@@ -89,5 +72,19 @@ module Temple
|
|
89
72
|
false
|
90
73
|
end
|
91
74
|
end
|
75
|
+
|
76
|
+
def indent_dynamic(text, indent_next, indent, pre_tags = nil)
|
77
|
+
text = text.to_s
|
78
|
+
safe = text.respond_to?(:html_safe?) && text.html_safe?
|
79
|
+
return text if pre_tags && text =~ pre_tags
|
80
|
+
|
81
|
+
level = text.scan(/^\s*/).map(&:size).min
|
82
|
+
text = text.gsub(/^\s{#{level}}/, '') if level > 0
|
83
|
+
|
84
|
+
text = text.sub(/\A\s*\n?/, "\n") if indent_next
|
85
|
+
text = text.gsub("\n", indent)
|
86
|
+
|
87
|
+
safe ? text.html_safe : text
|
88
|
+
end
|
92
89
|
end
|
93
90
|
end
|
data/lib/temple/version.rb
CHANGED
data/temple.gemspec
CHANGED
@@ -18,6 +18,8 @@ Gem::Specification.new do |s|
|
|
18
18
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
19
|
s.license = 'MIT'
|
20
20
|
|
21
|
+
s.required_ruby_version = '>=1.9.2'
|
22
|
+
|
21
23
|
# Tilt is only development dependency because most parts of Temple
|
22
24
|
# can be used without it.
|
23
25
|
s.add_development_dependency('tilt')
|
data/test/filters/test_eraser.rb
CHANGED
@@ -2,7 +2,7 @@ require 'helper'
|
|
2
2
|
|
3
3
|
describe Temple::Filters::Eraser do
|
4
4
|
it 'should respect keep' do
|
5
|
-
eraser = Temple::Filters::Eraser.new(:
|
5
|
+
eraser = Temple::Filters::Eraser.new(keep: [:a])
|
6
6
|
eraser.call([:multi,
|
7
7
|
[:a],
|
8
8
|
[:b],
|
@@ -15,7 +15,7 @@ describe Temple::Filters::Eraser do
|
|
15
15
|
end
|
16
16
|
|
17
17
|
it 'should respect erase' do
|
18
|
-
eraser = Temple::Filters::Eraser.new(:
|
18
|
+
eraser = Temple::Filters::Eraser.new(erase: [:a])
|
19
19
|
eraser.call([:multi,
|
20
20
|
[:a],
|
21
21
|
[:b],
|
@@ -28,7 +28,7 @@ describe Temple::Filters::Eraser do
|
|
28
28
|
end
|
29
29
|
|
30
30
|
it 'should choose erase over keep' do
|
31
|
-
eraser = Temple::Filters::Eraser.new(:
|
31
|
+
eraser = Temple::Filters::Eraser.new(keep: [:a, :b], erase: [:a])
|
32
32
|
eraser.call([:multi,
|
33
33
|
[:a],
|
34
34
|
[:b],
|
@@ -41,7 +41,7 @@ describe Temple::Filters::Eraser do
|
|
41
41
|
end
|
42
42
|
|
43
43
|
it 'should erase nested types' do
|
44
|
-
eraser = Temple::Filters::Eraser.new(:
|
44
|
+
eraser = Temple::Filters::Eraser.new(erase: [[:a, :b]])
|
45
45
|
eraser.call([:multi,
|
46
46
|
[:a, :a],
|
47
47
|
[:a, :b],
|