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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +0 -2
  3. data/CHANGES +15 -0
  4. data/README.md +6 -6
  5. data/Rakefile +1 -1
  6. data/lib/temple.rb +5 -4
  7. data/lib/temple/engine.rb +1 -1
  8. data/lib/temple/erb/engine.rb +2 -2
  9. data/lib/temple/filters/encoding.rb +1 -1
  10. data/lib/temple/filters/eraser.rb +1 -1
  11. data/lib/temple/filters/escapable.rb +2 -2
  12. data/lib/temple/filters/remove_bom.rb +2 -9
  13. data/lib/temple/filters/static_freezer.rb +11 -0
  14. data/lib/temple/filters/validator.rb +1 -1
  15. data/lib/temple/generator.rb +4 -4
  16. data/lib/temple/generators/rails_output_buffer.rb +3 -3
  17. data/lib/temple/html/attribute_merger.rb +1 -1
  18. data/lib/temple/html/attribute_remover.rb +1 -1
  19. data/lib/temple/html/attribute_sorter.rb +1 -1
  20. data/lib/temple/html/fast.rb +42 -42
  21. data/lib/temple/html/pretty.rb +30 -39
  22. data/lib/temple/map.rb +105 -0
  23. data/lib/temple/mixins/engine_dsl.rb +11 -9
  24. data/lib/temple/mixins/options.rb +26 -24
  25. data/lib/temple/mixins/template.rb +3 -3
  26. data/lib/temple/templates/rails.rb +14 -37
  27. data/lib/temple/templates/tilt.rb +4 -4
  28. data/lib/temple/utils.rb +23 -26
  29. data/lib/temple/version.rb +1 -1
  30. data/temple.gemspec +2 -0
  31. data/test/filters/test_eraser.rb +4 -4
  32. data/test/filters/test_escapable.rb +7 -5
  33. data/test/filters/test_static_freezer.rb +25 -0
  34. data/test/html/test_attribute_sorter.rb +1 -1
  35. data/test/html/test_fast.rb +6 -6
  36. data/test/html/test_pretty.rb +2 -8
  37. data/test/test_engine.rb +1 -1
  38. data/test/test_erb.rb +2 -2
  39. data/test/test_filter.rb +2 -2
  40. data/test/test_generator.rb +4 -4
  41. data/test/test_map.rb +39 -0
  42. metadata +7 -5
  43. data/lib/temple/hash.rb +0 -105
  44. data/test/test_hash.rb +0 -39
@@ -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
- { :filter => Temple::Filters,
83
- :generator => Temple::Generators,
84
- :html => Temple::HTML }.each do |method, mod|
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, option_filter)
99
- local_options = option_filter.last.respond_to?(:to_hash) ? option_filter.pop.to_hash : {}
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
- filter.new({}.update(engine.options).delete_if {|k,v| !option_filter.include?(k) }.update(local_options))
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
- [name, chain_class_constructor(filter, args)]
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 DefaultOptions
4
+ module ClassOptions
5
5
  def set_default_options(opts)
6
- default_options.update(opts)
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
- @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
- warn "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
- warn "Option #{key.inspect} is not supported by #{self}"
19
- end
20
- end
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
- default_options.add_valid_keys(hash.keys)
28
- default_options.update(hash)
29
+ options.add_valid_keys(hash.keys)
30
+ options.update(hash)
29
31
  end
30
- default_options.add_valid_keys(opts)
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
- default_options.add_deprecated_keys(hash.keys)
37
- default_options.update(hash)
38
+ options.add_deprecated_keys(hash.keys)
39
+ options.update(hash)
38
40
  end
39
- default_options.add_deprecated_keys(opts)
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] = ImmutableHash.new(options, thread_options)
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 DefaultOptions
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.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))
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 DefaultOptions
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.default_options[:engine] = engine
22
- template.default_options.update(options)
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
- if ::ActionPack::VERSION::MAJOR == 3 && ::ActionPack::VERSION::MINOR < 1
12
- class Rails < ActionView::TemplateHandler
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
- def self.register_as(*names)
23
- names.each do |name|
24
- ActionView::Template.register_template_handler name.to_sym, self
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
- def call(template)
33
- opts = {}.update(self.class.default_options).update(:file => template.identifier)
34
- self.class.compile(template.source, opts)
35
- end
11
+ def supports_streaming?
12
+ self.class.options[:streaming]
13
+ end
36
14
 
37
- def supports_streaming?
38
- self.class.default_options[:streaming]
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
- def self.register_as(*names)
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 :mime_type => 'text/html'
8
+ define_options mime_type: 'text/html'
9
9
 
10
10
  def self.default_mime_type
11
- default_options[:mime_type]
11
+ options[:mime_type]
12
12
  end
13
13
 
14
14
  def self.default_mime_type=(mime_type)
15
- default_options[:mime_type] = mime_type
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.default_options).update(options).update(:file => eval_file)
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)
@@ -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, safe = true)
19
- safe && html.html_safe? ? html : escape_html(html)
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
  '>' => '&gt;'
39
38
  }.freeze
40
39
 
41
- if //.respond_to?(:encoding)
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
- if RUBY_VERSION > '1.9'
50
- # Returns an escaped copy of `html`.
51
- #
52
- # @param html [String] The string to escape
53
- # @return [String] The escaped string
54
- def escape_html(html)
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
@@ -1,3 +1,3 @@
1
1
  module Temple
2
- VERSION = '0.6.10'
2
+ VERSION = '0.7.1'
3
3
  end
@@ -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')
@@ -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(:keep => [:a])
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(:erase => [:a])
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(:keep => [:a, :b], :erase => [:a])
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(:erase => [[:a, :b]])
44
+ eraser = Temple::Filters::Eraser.new(erase: [[:a, :b]])
45
45
  eraser.call([:multi,
46
46
  [:a, :a],
47
47
  [:a, :b],