temple 0.4.1 → 0.5.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -4,3 +4,4 @@ rvm:
4
4
  - ruby-head
5
5
  - jruby
6
6
  - rbx-18mode
7
+ - rbx-19mode
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
@@ -1,9 +1,11 @@
1
1
  require 'temple/version'
2
2
 
3
3
  module Temple
4
- autoload :InvalidExpression, 'temple/generators'
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
@@ -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(o = {})
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)
@@ -3,9 +3,7 @@ module Temple
3
3
  # Example ERB parser
4
4
  #
5
5
  # @api public
6
- class Parser
7
- include Mixins::Options
8
-
6
+ class Parser < Temple::Parser
9
7
  ERB_PATTERN = /(\n|<%%|%%>)|<%(==?|\#)?(.*?)?-?%>/m
10
8
 
11
9
  def call(input)
@@ -7,6 +7,8 @@ module Temple
7
7
  #
8
8
  # @api public
9
9
  class Trimming < Filter
10
+ define_options :trim_mode
11
+
10
12
  def on_multi(*exps)
11
13
  case options[:trim_mode]
12
14
  when '>'
@@ -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, *exps = c
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
@@ -5,7 +5,7 @@ module Temple
5
5
  # @api public
6
6
  class Eraser < Filter
7
7
  # [] is the empty type => keep all
8
- default_options[:keep] = [[]]
8
+ define_options :keep => [[]], :erase => nil
9
9
 
10
10
  def compile(exp)
11
11
  exp.first == :multi || (do?(:keep, exp) && !do?(:erase, exp)) ?
@@ -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
- set_default_options :use_html_safe => ''.respond_to?(:html_safe?),
12
- :disable_escape => false
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
@@ -4,7 +4,7 @@ module Temple
4
4
  #
5
5
  # @api public
6
6
  class Validator < Filter
7
- default_options[:grammar] = Temple::Grammar
7
+ define_options :grammar => Temple::Grammar
8
8
 
9
9
  def compile(exp)
10
10
  options[:grammar].validate!(exp)
@@ -14,7 +14,8 @@ module Temple
14
14
  include Mixins::CompiledDispatcher
15
15
  include Mixins::Options
16
16
 
17
- default_options[:buffer] = '_buf'
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
- set_default_options :buffer_class => 'ActiveSupport::SafeBuffer',
138
- :buffer => '@output_buffer',
139
- # output_buffer is needed for Rails 3.1 Streaming support
140
- :capture_generator => RailsOutputBuffer
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] = Temple::Generators::StringBuffer
162
+ Generator.default_options[:capture_generator] = Generators::StringBuffer
161
163
  end
@@ -44,7 +44,7 @@ module Temple
44
44
  Symbol | String
45
45
 
46
46
  Case <<
47
- [Condition, 'Expression*']
47
+ [Condition, Expression]
48
48
 
49
49
  Condition <<
50
50
  String | :else
@@ -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
- default_options[:attr_delimiter] = {'id' => '_', 'class' => ' '}
6
+ define_options :attr_delimiter => {'id' => '_', 'class' => ' '}
7
7
 
8
8
  def on_html_attrs(*attrs)
9
9
  names = []
10
- result = {}
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 result[name]
15
- delimiter = options[:attr_delimiter][name]
16
- raise "Multiple #{name} attributes specified" unless delimiter
17
- if empty_exp?(value)
18
- result[name] = [:html, :attr, name,
19
- [:multi,
20
- result[name][3],
21
- value]]
22
- elsif contains_static?(value)
23
- result[name] = [:html, :attr, name,
24
- [:multi,
25
- result[name][3],
26
- [:static, delimiter],
27
- value]]
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
- tmp = unique_name
30
- result[name] = [:html, :attr, name,
31
- [:multi,
32
- result[name][3],
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
- result[name] = attr
41
- names << name
39
+ [:html, :attr, name, value.first]
42
40
  end
43
41
  end
44
- [:html, :attrs, *names.map {|name| result[name] }]
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
- default_options[:remove_empty_attrs] = true
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, *(options[:remove_empty_attrs] ?
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 contains_static?(value)
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
- default_options[:sort_attrs] = true
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
- attrs = attrs.sort_by do |attr|
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 if options[:sort_attrs]
13
- [:html, :attrs, *attrs]
16
+ [attr[2].to_s, n += 1]
17
+ end]
14
18
  end
15
19
  end
16
20
  end
@@ -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
- set_default_options :format => :xhtml,
25
- :attr_wrapper => "'",
26
- :autoclose => %w[meta img link br hr input area param col base]
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]
@@ -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
@@ -2,14 +2,14 @@ module Temple
2
2
  module HTML
3
3
  # @api public
4
4
  class Pretty < Fast
5
- set_default_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
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 = Hash === option_filter.last ? option_filter.pop : nil
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
- filtered_options = Hash[*option_filter.select {|k| engine.options.include?(k) }.map {|k| [k, engine.options[k]] }.flatten]
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
 
@@ -157,7 +157,7 @@ module Temple
157
157
  curr.each {|c| c << elem }
158
158
  start
159
159
  else
160
- raise "Invalid grammar rule '#{rule.inspect}'"
160
+ raise ArgumentError, "Invalid grammar rule '#{rule.inspect}'"
161
161
  end
162
162
  end
163
163
 
@@ -2,26 +2,83 @@ module Temple
2
2
  module Mixins
3
3
  # @api public
4
4
  module DefaultOptions
5
- def set_default_options(options)
6
- default_options.update(options)
5
+ def set_default_options(opts)
6
+ default_options.update(opts)
7
7
  end
8
8
 
9
9
  def default_options
10
- @default_options ||= MutableHash.new(superclass.respond_to?(:default_options) ?
11
- superclass.default_options : nil)
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 { extend DefaultOptions }
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(options = {})
24
- @options = ImmutableHash.new(options, self.class.default_options)
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 engine(engine = nil)
8
- default_options[:engine] = engine if engine
9
- default_options[:engine]
10
- end
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(name)
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.init
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
@@ -0,0 +1,8 @@
1
+ module Temple
2
+ # Temple base parser
3
+ # @api public
4
+ class Parser
5
+ include Utils
6
+ include Mixins::Options
7
+ end
8
+ end
@@ -14,12 +14,15 @@ module Temple
14
14
  extend Mixins::Template
15
15
 
16
16
  def compile(template)
17
- self.class.build_engine(:streaming => false, # Overwrite option: No streaming support in Rails < 3.1
18
- :file => template.identifier).call(template.source)
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(name)
22
- ActionView::Template.register_template_handler name.to_sym, self
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.build_engine(:file => template.identifier).call(template.source)
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(name)
38
- ActionView::Template.register_template_handler name.to_sym, new
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
- @src = self.class.build_engine({ :streaming => false, # Overwrite option: No streaming support in Tilt
15
- :file => eval_file }, options).call(data)
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.init
27
- self.default_mime_type = default_options[:mime_type] || 'text/html'
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
@@ -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
  '"' => '&quot;',
29
29
  '<' => '&lt;',
30
30
  '>' => '&gt;',
31
- '/' => '&#47;',
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(/[&\"<>\/]/, ESCAPE_HTML)
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, '&amp;').gsub(/\"/n, '&quot;').gsub(/>/n, '&gt;').gsub(/</n, '&lt;').gsub(/\//, '&#47;')
46
+ html.to_s.gsub(/&/n, '&amp;').gsub(/\"/n, '&quot;').gsub(/>/n, '&gt;').gsub(/</n, '&lt;')
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
@@ -1,3 +1,3 @@
1
1
  module Temple
2
- VERSION = '0.4.1'
2
+ VERSION = '0.5.4'
3
3
  end
@@ -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 = 'http://dojo.rubyforge.org/'
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', [:exp11], [:exp12]],
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
- [:exp11],
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', [:exp11], [:exp12]],
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
- [:exp11],
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
- [:dynamic, "a"],
54
- [:capture, "_temple_html_attributemerger1",
55
- [:dynamic, "b"]],
56
- [:if, "!_temple_html_attributemerger1.empty?",
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
- [:static, "a"],
73
- [:capture, "_temple_html_attributemerger1",
74
- [:dynamic, "b"]],
75
- [:if, "!_temple_html_attributemerger1.empty?",
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 :remove_empty_attrs is true' do
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 :remove_empty_attrs is false' do
35
- @disabled_remover.call([:html, :tag,
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, 'class', [:dynamic, 'b']]],
36
+ [:html, :attrs, [:html, :attr, 'name', [:dynamic, 'b']]],
38
37
  [:content]
39
38
  ]).should.equal [:html, :tag, "div",
40
39
  [:multi,
41
- [:html, :attr, "class", [:dynamic, "b"]]],
40
+ [:html, :attr, "name", [:dynamic, "b"]]],
42
41
  [:content]]
43
42
  end
44
43
  end
@@ -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
@@ -38,13 +38,13 @@ world}
38
38
  it 'should escape automatically' do
39
39
  src = '<%= "<" %>'
40
40
  ans = '&lt;'
41
- erb(src, :auto_escape => true).should.equal ans
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, :auto_escape => true).should.equal ans
47
+ erb(src).should.equal ans
48
48
  end
49
49
 
50
50
  it 'should support trim mode' do
@@ -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
- Temple::Filter.new(:key => 3).options[:key].should.equal 3
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.1
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-04 00:00:00.000000000 Z
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: !ruby/object:Gem::Requirement
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: !ruby/object:Gem::Requirement
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: !ruby/object:Gem::Requirement
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: http://dojo.rubyforge.org/
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.24
143
+ rubygems_version: 1.8.15
157
144
  signing_key:
158
145
  specification_version: 3
159
146
  summary: Template compilation framework in Ruby