temple 0.3.4 → 0.3.5

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.
data/CHANGES CHANGED
@@ -1,5 +1,19 @@
1
1
  master
2
2
 
3
+ 0.3.5
4
+
5
+ * Temple::HTML::Pretty improved
6
+ * :sort_attrs option (default: true) added to HTML::AttributeMerger;
7
+ if set to false, the attributes will appear in the insertion order
8
+ * Temple::Mixins::EngineDSL api changed ("wildcard" is deprecated, use "use" instead)
9
+ * Temple::Mixins::CompiledDispatcher supports arbitrary levels now
10
+ * Don't use gsub! on incoming strings (#57)
11
+ * Fix newlines in erb parser (#46)
12
+
13
+ 0.3.4
14
+
15
+ * Bugfix release (0.3.3 was yanked)
16
+
3
17
  0.3.3
4
18
 
5
19
  * Support for rails 3.1 streaming
data/README.md CHANGED
@@ -35,23 +35,27 @@ Temple is built on a theory that every template consists of three elements:
35
35
  The goal of a template engine is to take the template and eventually compile
36
36
  it into *the core abstraction*:
37
37
 
38
- [:multi,
39
- [:static, "Hello "],
40
- [:dynamic, "@user.name"],
41
- [:static, "!\n"],
42
- [:code, "if @user.birthday == Date.today"],
43
- [:static, "Happy birthday!"],
44
- [:code, "end"]]
38
+ ```ruby
39
+ [:multi,
40
+ [:static, "Hello "],
41
+ [:dynamic, "@user.name"],
42
+ [:static, "!\n"],
43
+ [:code, "if @user.birthday == Date.today"],
44
+ [:static, "Happy birthday!"],
45
+ [:code, "end"]]
46
+ ```
45
47
 
46
48
  Then you can apply some optimizations, feed it to Temple and it generates fast
47
49
  Ruby code for you:
48
50
 
49
- _buf = []
50
- _buf << ("Hello #{@user.name}!\n")
51
- if @user.birthday == Date.today
52
- _buf << "Happy birthday!"
53
- end
54
- _buf.join
51
+ ```ruby
52
+ _buf = []
53
+ _buf << ("Hello #{@user.name}!\n")
54
+ if @user.birthday == Date.today
55
+ _buf << "Happy birthday!"
56
+ end
57
+ _buf.join
58
+ ```
55
59
 
56
60
  S-expression
57
61
  ------------
@@ -67,13 +71,15 @@ manipulated by computers.
67
71
 
68
72
  Some examples:
69
73
 
70
- [:static, "Hello World!"]
74
+ ```ruby
75
+ [:static, "Hello World!"]
71
76
 
72
- [:multi,
73
- [:static, "Hello "],
74
- [:dynamic, "@world"]]
77
+ [:multi,
78
+ [:static, "Hello "],
79
+ [:dynamic, "@world"]]
75
80
 
76
- [:html, :tag, "em", [:html, :attrs], [:static, "Hey hey"]]
81
+ [:html, :tag, "em", [:html, :attrs], [:static, "Hey hey"]]
82
+ ```
77
83
 
78
84
  *NOTE:* SexpProcessor, a library written by Ryan Davis, includes a `Sexp`
79
85
  class. While you can use this class (since it's a subclass of Array), it's not
@@ -89,11 +95,13 @@ do one thing at every step.
89
95
 
90
96
  So what's an abstraction? An abstraction is when you introduce a new types:
91
97
 
92
- # Instead of:
93
- [:static, "<strong>Use the force</strong>"]
98
+ ```ruby
99
+ # Instead of:
100
+ [:static, "<strong>Use the force</strong>"]
94
101
 
95
- # You use:
96
- [:html, :tag, "strong", [:html, :attrs], [:static, "Use the force"]]
102
+ # You use:
103
+ [:html, :tag, "strong", [:html, :attrs], [:static, "Use the force"]]
104
+ ```
97
105
 
98
106
  ### Why are abstractions so important?
99
107
 
@@ -126,15 +134,17 @@ the argument, and it should be possible to use the same instance several times
126
134
  While a compiler can be any object, you very often want to structure it as a
127
135
  class. Temple then assumes the initializer takes an optional option hash:
128
136
 
129
- class MyCompiler
130
- def initialize(options = {})
131
- @options = options
132
- end
137
+ ```ruby
138
+ class MyCompiler
139
+ def initialize(options = {})
140
+ @options = options
141
+ end
133
142
 
134
- def call(exp)
135
- # do stuff
136
- end
137
- end
143
+ def call(exp)
144
+ # do stuff
145
+ end
146
+ end
147
+ ```
138
148
 
139
149
  ### Parsers
140
150
 
@@ -181,24 +191,26 @@ Engines
181
191
 
182
192
  When you have a chain of a parsers, some filters and a generator you can finally create your *engine*. Temple provides {Temple::Engine} which makes this very easy:
183
193
 
184
- class MyEngine < Temple::Engine
185
- # First run MyParser, passing the :strict option
186
- use MyParser, :strict
194
+ ```ruby
195
+ class MyEngine < Temple::Engine
196
+ # First run MyParser, passing the :strict option
197
+ use MyParser, :strict
187
198
 
188
- # Then a custom filter
189
- use MyFilter
199
+ # Then a custom filter
200
+ use MyFilter
190
201
 
191
- # Then some general optimizations filters
192
- filter :MultiFlattener
193
- filter :StaticMerger
194
- filter :DynamicInliner
202
+ # Then some general optimizations filters
203
+ filter :MultiFlattener
204
+ filter :StaticMerger
205
+ filter :DynamicInliner
195
206
 
196
- # Finally the generator
197
- generator :ArrayBuffer, :buffer
198
- end
207
+ # Finally the generator
208
+ generator :ArrayBuffer, :buffer
209
+ end
199
210
 
200
- engine = MyEngine.new(:strict => "For MyParser")
201
- engine.call(something)
211
+ engine = MyEngine.new(:strict => "For MyParser")
212
+ engine.call(something)
213
+ ```
202
214
 
203
215
  And then?
204
216
  ---------
@@ -209,13 +221,15 @@ generator. What happens next?
209
221
  Temple provides helpers to create template classes for [Tilt](http://github.com/rtomayko/tilt) and
210
222
  Rails.
211
223
 
212
- require 'tilt'
224
+ ```ruby
225
+ require 'tilt'
213
226
 
214
- # Create template class MyTemplate and register your file extension
215
- MyTemplate = Temple::Templates::Tilt(MyEngine, :register_as => 'ext')
227
+ # Create template class MyTemplate and register your file extension
228
+ MyTemplate = Temple::Templates::Tilt(MyEngine, :register_as => 'ext')
216
229
 
217
- Tilt.new('example.ext').render # => Render a file
218
- MyTemplate.new { "String" }.render # => Render a string
230
+ Tilt.new('example.ext').render # => Render a file
231
+ MyTemplate.new { "String" }.render # => Render a string
232
+ ```
219
233
 
220
234
  Installation
221
235
  ------------
@@ -223,7 +237,9 @@ Installation
223
237
  You need Ruby 1.8.7 or Ruby 1.9.2 to work with Temple. Temple is published as a Ruby Gem which can be installed
224
238
  as following:
225
239
 
226
- $ gem install temple
240
+ ```bash
241
+ $ gem install temple
242
+ ```
227
243
 
228
244
  Engines using Temple
229
245
  --------------------
@@ -6,27 +6,28 @@ module Temple
6
6
  class Parser
7
7
  include Mixins::Options
8
8
 
9
- ERB_PATTERN = /(<%%|%%>)|<%(==?|\#)?(.*?)?-?%>/m
10
-
11
- ESCAPED = {
12
- '<%%' => '<%',
13
- '%%>' => '%>',
14
- }.freeze
9
+ ERB_PATTERN = /(\n|<%%|%%>)|<%(==?|\#)?(.*?)?-?%>/m
15
10
 
16
11
  def call(input)
17
12
  result = [:multi]
18
13
  pos = 0
19
- input.scan(ERB_PATTERN) do |escaped, indicator, code|
20
- m = Regexp.last_match
21
- text = input[pos...m.begin(0)]
22
- pos = m.end(0)
23
- result << [:static, text] if !text.empty?
24
- if escaped
25
- result << [:static, ESCAPED[escaped]]
14
+ input.scan(ERB_PATTERN) do |token, indicator, code|
15
+ text = input[pos...$~.begin(0)]
16
+ pos = $~.end(0)
17
+ if token
18
+ case token
19
+ when "\n"
20
+ result << [:static, "#{text}\n"] << [:newline]
21
+ when '<%%', '%%>'
22
+ result << [:static, text] unless text.empty?
23
+ token.slice!(1)
24
+ result << [:static, token]
25
+ end
26
26
  else
27
+ result << [:static, text] unless text.empty?
27
28
  case indicator
28
29
  when '#'
29
- code.count("\n").times { result << [:newline] }
30
+ result << [:code, "\n" * code.count("\n")]
30
31
  when /=/
31
32
  result << [:escape, indicator.size <= 1, [:dynamic, code]]
32
33
  else
@@ -10,18 +10,17 @@ module Temple
10
10
  def on_multi(*exps)
11
11
  case options[:trim_mode]
12
12
  when '>'
13
- exps.each_cons(2) do |a, b|
14
- if code?(a) && static?(b)
15
- b[1].gsub!(/^\n/, '')
16
- end
13
+ i = 0
14
+ while i < exps.size
15
+ exps.delete_at(i + 1) if code?(exps[i]) && exps[i+1] == [:static, "\n"]
16
+ i += 1
17
17
  end
18
18
  when '<>'
19
- exps.each_with_index do |exp, i|
20
- if code?(exp) &&
21
- (!exps[i-1] || static?(exps[i-1]) && exps[i-1][1] =~ /\n$/) &&
22
- (exps[i+1] && static?(exps[i+1]) && exps[i+1][1] =~ /^\n/)
23
- exps[i+1][1].gsub!(/^\n/, '') if exps[i+1]
24
- end
19
+ i = 0
20
+ while i < exps.size
21
+ exps.delete_at(i + 1) if code?(exps[i]) && exps[i+1] == [:static, "\n"] &&
22
+ (!exps[i-1] || (exps[i-1] == [:newline]))
23
+ i += 1
25
24
  end
26
25
  end
27
26
  [:multi, *exps]
@@ -32,10 +31,6 @@ module Temple
32
31
  def code?(exp)
33
32
  exp[0] == :escape || exp[0] == :code
34
33
  end
35
-
36
- def static?(exp)
37
- exp[0] == :static
38
- end
39
34
  end
40
35
  end
41
36
  end
@@ -11,6 +11,7 @@ module Temple
11
11
  #
12
12
  # @api public
13
13
  class Generator
14
+ include Mixins::CompiledDispatcher
14
15
  include Mixins::Options
15
16
 
16
17
  default_options[:buffer] = '_buf'
@@ -19,14 +20,8 @@ module Temple
19
20
  [preamble, compile(exp), postamble].join('; ')
20
21
  end
21
22
 
22
- def compile(exp)
23
- type, *args = exp
24
- method = "on_#{type}"
25
- if respond_to?(method)
26
- send(method, *args)
27
- else
28
- raise InvalidExpression, "Generator supports only core expressions - found #{exp.inspect}"
29
- end
23
+ def on(*exp)
24
+ raise InvalidExpression, "Generator supports only core expressions - found #{exp.inspect}"
30
25
  end
31
26
 
32
27
  def on_multi(*exp)
@@ -3,13 +3,14 @@ module Temple
3
3
  # @api public
4
4
  class AttributeMerger < Filter
5
5
  default_options[:attr_delimiter] = {'id' => '_', 'class' => ' '}
6
+ default_options[:sort_attrs] = true
6
7
 
7
8
  def on_html_attrs(*attrs)
9
+ names = []
8
10
  result = {}
9
11
  attrs.each do |attr|
10
12
  raise(InvalidExpression, 'Attribute is not a html attr') if attr[0] != :html || attr[1] != :attr
11
13
  name, value = attr[2].to_s, attr[3]
12
- next if empty_exp?(value)
13
14
  if result[name]
14
15
  delimiter = options[:attr_delimiter][name]
15
16
  raise "Multiple #{name} attributes specified" unless delimiter
@@ -37,9 +38,11 @@ module Temple
37
38
  end
38
39
  else
39
40
  result[name] = attr
41
+ names << name
40
42
  end
41
43
  end
42
- [:multi, *result.sort.map {|name,attr| compile(attr) }]
44
+ result = options[:sort_attrs] ? result.sort : names.map {|k| [k, result[k]] }
45
+ [:multi, *result.map {|name,attr| compile(attr) }]
43
46
  end
44
47
 
45
48
  def on_html_attr(name, value)
@@ -43,21 +43,19 @@ module Temple
43
43
  end
44
44
 
45
45
  def on_html_doctype(type)
46
- type = type.to_s
47
- trailing_newlines = type[/(\A|[^\r])(\n+)\Z/, 2].to_s
48
- text = type.downcase.strip
46
+ type = type.to_s.downcase
49
47
 
50
- if text =~ /^xml/
48
+ if type =~ /^xml(\s+(.+))?$/
51
49
  raise 'Invalid xml directive in html mode' if html?
52
- wrapper = options[:attr_wrapper]
53
- str = "<?xml version=#{wrapper}1.0#{wrapper} encoding=#{wrapper}#{text.split(' ')[1] || "utf-8"}#{wrapper} ?>"
50
+ w = options[:attr_wrapper]
51
+ str = "<?xml version=#{w}1.0#{w} encoding=#{w}#{$2 || 'utf-8'}#{w} ?>"
54
52
  elsif html?
55
- str = HTML_DOCTYPES[text] || raise("Invalid html doctype #{text}")
53
+ str = HTML_DOCTYPES[type] || raise("Invalid html doctype #{type}")
56
54
  else
57
- str = XHTML_DOCTYPES[text] || raise("Invalid xhtml doctype #{text}")
55
+ str = XHTML_DOCTYPES[type] || raise("Invalid xhtml doctype #{type}")
58
56
  end
59
57
 
60
- [:static, str << trailing_newlines]
58
+ [:static, str]
61
59
  end
62
60
 
63
61
  def on_html_comment(content)
@@ -6,7 +6,7 @@ module Temple
6
6
  :pretty => true,
7
7
  :indent_tags => %w(article aside audio base body datalist dd div dl dt
8
8
  fieldset figure footer form head h1 h2 h3 h4 h5 h6
9
- header hgroup hr html img input li link meta nav ol p
9
+ header hgroup hr html input li link meta nav ol p
10
10
  rp rt ruby section script style table tbody td tfoot
11
11
  th thead title tr ul video).freeze,
12
12
  :pre_tags => %w(code pre textarea).freeze
@@ -25,7 +25,7 @@ module Temple
25
25
 
26
26
  def on_static(content)
27
27
  if @pretty
28
- content.gsub!("\n", indent) if @pre_tags !~ content
28
+ content = content.gsub("\n", indent) if @pre_tags !~ content
29
29
  @last = content.sub!(/\r?\n\s*$/, ' ') ? nil : :noindent
30
30
  end
31
31
  [:static, content]
@@ -38,7 +38,7 @@ module Temple
38
38
  gsub_code = if ''.respond_to?(:html_safe?)
39
39
  "#{tmp} = #{tmp}.html_safe? ? #{tmp}.gsub(\"\\n\", #{indent.inspect}).html_safe : #{tmp}.gsub(\"\\n\", #{indent.inspect})"
40
40
  else
41
- "#{tmp}.gsub!(\"\\n\", #{indent.inspect})"
41
+ "#{tmp} = #{tmp}.gsub(\"\\n\", #{indent.inspect})"
42
42
  end
43
43
  [:multi,
44
44
  [:code, "#{tmp} = (#{code}).to_s"],
@@ -56,8 +56,9 @@ module Temple
56
56
 
57
57
  def on_html_comment(content)
58
58
  return super unless @pretty
59
+ result = [:multi, [:static, tag_indent(nil)], super]
59
60
  @last = nil
60
- [:multi, [:static, indent], super]
61
+ result
61
62
  end
62
63
 
63
64
  def on_html_tag(name, attrs, content = nil)
@@ -76,8 +77,8 @@ module Temple
76
77
  result << compile(content)
77
78
  @indent -= 1
78
79
  end
80
+ result << [:static, "#{content && !empty_exp?(content) ? tag_indent(name) : ''}</#{name}>"] unless closed
79
81
 
80
- result << [:static, "#{tag_indent(name)}</#{name}>"] if !closed
81
82
  @pretty = true
82
83
  result
83
84
  end
@@ -51,38 +51,19 @@ module Temple
51
51
 
52
52
  private
53
53
 
54
- def case_statement(types)
55
- code = "type, *args = args\ncase type\n"
56
- types.each do |name, method|
57
- code << "when #{name.to_sym.inspect}\n" <<
58
- (Hash === method ? case_statement(method) : "#{method}(*args)\n")
59
- end
60
- code << "else\nexp\nend\n"
61
- end
62
-
63
54
  def dispatcher(exp)
64
55
  replace_dispatcher(exp)
65
56
  end
66
57
 
67
58
  def replace_dispatcher(exp)
68
- types = {}
69
- self.class.instance_methods.each do |method|
70
- next if method.to_s !~ /^on_(.*)$/
71
- method_types = $1.split('_')
72
- (0...method_types.size).inject(types) do |tmp, i|
73
- raise "Invalid temple dispatcher #{method}" unless Hash === tmp
74
- if i == method_types.size - 1
75
- tmp[method_types[i]] = method
76
- else
77
- tmp[method_types[i]] ||= {}
78
- end
79
- end
59
+ tree = DispatchNode.new
60
+ dispatched_methods.each do |method|
61
+ method.split('_')[1..-1].inject(tree) {|node, type| node[type.to_sym] }.method = method
80
62
  end
81
63
  self.class.class_eval %{
82
64
  def dispatcher(exp)
83
65
  if self.class == #{self.class}
84
- args = exp
85
- #{case_statement(types)}
66
+ #{tree.compile}
86
67
  else
87
68
  replace_dispatcher(exp)
88
69
  end
@@ -90,9 +71,100 @@ module Temple
90
71
  }
91
72
  dispatcher(exp)
92
73
  end
74
+
75
+ def dispatched_methods
76
+ re = /^on(_[a-zA-Z0-9]+)*$/
77
+ self.methods.map(&:to_s).select(&re.method(:=~))
78
+ end
79
+
80
+ # @api private
81
+ class DispatchNode < Hash
82
+ attr_accessor :method
83
+
84
+ def initialize
85
+ super { |hsh,key| hsh[key] = DispatchNode.new }
86
+ @method = nil
87
+ end
88
+
89
+ def compile(level = 0, parent = nil)
90
+ if empty?
91
+ if method
92
+ (' ' * level) + "#{method}(*exp[#{level}..-1])"
93
+ elsif !parent
94
+ 'exp'
95
+ else
96
+ raise 'Invalid dispatcher node'
97
+ end
98
+ else
99
+ code = [(' ' * level) + "case(exp[#{level}])"]
100
+ each do |key, child|
101
+ code << (' ' * level) + "when #{key.inspect}"
102
+ code << child.compile(level + 1, parent || method)
103
+ end
104
+ if method || !parent
105
+ code << (' ' * level) + "else"
106
+ if method
107
+ code << (' ' * level) + " #{method}(*exp[#{level}..-1])"
108
+ else
109
+ code << (' ' * level) + " exp"
110
+ end
111
+ end
112
+ code << (' ' * level) + "end"
113
+ code.join("\n")
114
+ end
115
+ end
116
+ end
93
117
  end
94
118
 
95
- # @api private
119
+ # @api public
120
+ #
121
+ # Implements a compatible call-method
122
+ # based on the including classe's methods.
123
+ #
124
+ # It uses every method starting with
125
+ # "on" and uses the rest of the method
126
+ # name as prefix of the expression it
127
+ # will receive. So, if a dispatcher
128
+ # has a method named "on_x", this method
129
+ # will be called with arg0,..,argN
130
+ # whenever an expression like [:x, arg0,..,argN ]
131
+ # is encountered.
132
+ #
133
+ # This works with longer prefixes, too.
134
+ # For example a method named "on_y_z"
135
+ # will be called whenever an expression
136
+ # like [:y, :z, .. ] is found. Furthermore,
137
+ # if additionally a method named "on_y"
138
+ # is present, it will be called when an
139
+ # expression starts with :y but then does
140
+ # not contain with :z. This way a
141
+ # dispatcher can implement namespaces.
142
+ #
143
+ # @note
144
+ # Processing does not reach into unknown
145
+ # expression types by default.
146
+ #
147
+ # @example
148
+ # class MyAwesomeDispatch
149
+ # include Temple::Mixins::Dispatcher
150
+ # def on_awesome(thing) # keep awesome things
151
+ # return [:awesome, thing]
152
+ # end
153
+ # def on_boring(thing) # make boring things awesome
154
+ # return [:awesome, thing+" with bacon"]
155
+ # end
156
+ # def on(type,*args) # unknown stuff is boring too
157
+ # return [:awesome, 'just bacon']
158
+ # end
159
+ # end
160
+ # filter = MyAwesomeDispatch.new
161
+ # # Boring things are converted:
162
+ # filter.call([:boring, 'egg']) #=> [:awesome, 'egg with bacon']
163
+ # # Unknown things too:
164
+ # filter.call([:foo]) #=> [:awesome, 'just bacon']
165
+ # # Known but not boring things won't be touched:
166
+ # filter.call([:awesome, 'chuck norris']) #=>[:awesome, 'chuck norris']
167
+ #
96
168
  module Dispatcher
97
169
  include CompiledDispatcher
98
170
  include CoreDispatcher
@@ -28,6 +28,16 @@ module Temple
28
28
 
29
29
  alias use append
30
30
 
31
+ # DEPRECATED!
32
+ #
33
+ # wildcard(:FilterName) { FilterClass.new(options) }
34
+ #
35
+ # is replaced by
36
+ #
37
+ # use(:FilterName) { FilterClass.new(options) }
38
+ #
39
+ alias wildcard use
40
+
31
41
  def before(name, *args, &block)
32
42
  name = Class === name ? name.name.to_sym : name
33
43
  raise(ArgumentError, 'First argument must be Class or Symbol') unless Symbol === name
@@ -78,18 +88,12 @@ module Temple
78
88
  chain_modified!
79
89
  end
80
90
 
81
- def filter(name, *options, &block)
82
- use(name, Temple::Filters.const_get(name), *options, &block)
83
- end
84
-
85
- def generator(name, *options, &block)
86
- use(name, Temple::Generators.const_get(name), *options, &block)
91
+ def filter(name, *options)
92
+ use(name, Temple::Filters.const_get(name), *options)
87
93
  end
88
94
 
89
- def wildcard(name, &block)
90
- raise(ArgumentError, 'Block must have arity 0') unless block.arity <= 0
91
- chain << [name, define_chain_method("WILDCARD #{name}", block)]
92
- chain_modified!
95
+ def generator(name, *options)
96
+ use(name, Temple::Generators.const_get(name), *options)
93
97
  end
94
98
 
95
99
  private
@@ -125,7 +129,7 @@ module Temple
125
129
  # The proc is converted to a method of the engine class.
126
130
  # The proc can then access the option hash of the engine.
127
131
  raise(ArgumentError, 'Too many arguments') unless args.empty?
128
- raise(ArgumentError, 'Proc or blocks must have arity 1') unless filter.arity == 1
132
+ raise(ArgumentError, 'Proc or blocks must have arity 0 or 1') if filter.arity > 1
129
133
  [name, define_chain_method("FILTER #{name}", filter)]
130
134
  when Class
131
135
  # Class argument (e.g Filter class)
@@ -1,3 +1,3 @@
1
1
  module Temple
2
- VERSION = '0.3.4'
2
+ VERSION = '0.3.5'
3
3
  end
@@ -3,6 +3,7 @@ require 'helper'
3
3
  describe Temple::HTML::AttributeMerger do
4
4
  before do
5
5
  @merger = Temple::HTML::AttributeMerger.new
6
+ @ordered_merger = Temple::HTML::AttributeMerger.new :sort_attrs => false
6
7
  end
7
8
 
8
9
  it 'should pass static attributes through' do
@@ -16,6 +17,46 @@ describe Temple::HTML::AttributeMerger do
16
17
  [:content]]
17
18
  end
18
19
 
20
+ it 'should sort html attributes by name by default, when :sort_attrs is true' do
21
+ @merger.call([:html, :tag,
22
+ 'meta',
23
+ [:html, :attrs, [:html, :attr, 'c', [:static, '1']],
24
+ [:html, :attr, 'd', [:static, '2']],
25
+ [:html, :attr, 'a', [:static, '3']],
26
+ [:html, :attr, 'b', [:static, '4']]]
27
+ ]).should.equal [:html, :tag, 'meta',
28
+ [:multi,
29
+ [:html, :attr, 'a', [:static, '3']],
30
+ [:html, :attr, 'b', [:static, '4']],
31
+ [:html, :attr, 'c', [:static, '1']],
32
+ [:html, :attr, 'd', [:static, '2']]]]
33
+ end
34
+
35
+ it 'should preserve the order of html attributes when :sort_attrs is false' do
36
+ @ordered_merger.call([:html, :tag,
37
+ 'meta',
38
+ [:html, :attrs, [:html, :attr, 'c', [:static, '1']],
39
+ [:html, :attr, 'd', [:static, '2']],
40
+ [:html, :attr, 'a', [:static, '3']],
41
+ [:html, :attr, 'b', [:static, '4']]]
42
+ ]).should.equal [:html, :tag, 'meta',
43
+ [:multi,
44
+ [:html, :attr, 'c', [:static, '1']],
45
+ [:html, :attr, 'd', [:static, '2']],
46
+ [:html, :attr, 'a', [:static, '3']],
47
+ [:html, :attr, 'b', [:static, '4']]]]
48
+
49
+ # Use case:
50
+ @ordered_merger.call([:html, :tag,
51
+ 'meta',
52
+ [:html, :attrs, [:html, :attr, 'http-equiv', [:static, 'Content-Type']],
53
+ [:html, :attr, 'content', [:static, '']]]
54
+ ]).should.equal [:html, :tag, 'meta',
55
+ [:multi,
56
+ [:html, :attr, 'http-equiv', [:static, 'Content-Type']],
57
+ [:html, :attr, 'content', [:static, '']]]]
58
+ end
59
+
19
60
  it 'should check for empty dynamic attribute' do
20
61
  @merger.call([:html, :tag,
21
62
  'div',
@@ -22,7 +22,7 @@ describe Temple::HTML::Pretty do
22
22
  [:static, "text"],
23
23
  [:multi,
24
24
  [:code, "_temple_html_pretty2 = (code).to_s"],
25
- [:code, 'if _temple_html_pretty1 !~ _temple_html_pretty2; _temple_html_pretty2.gsub!("\n", "\n "); end'],
25
+ [:code, 'if _temple_html_pretty1 !~ _temple_html_pretty2; _temple_html_pretty2 = _temple_html_pretty2.gsub("\n", "\n "); end'],
26
26
  [:dynamic, "_temple_html_pretty2"]]],
27
27
  [:static, "</p>"]],
28
28
  [:static, "\n</div>"]]]
@@ -7,9 +7,23 @@ class FilterWithDispatcherMixin
7
7
  [:on_test, arg]
8
8
  end
9
9
 
10
+ def on_test_check(arg)
11
+ [:on_check, arg]
12
+ end
13
+
10
14
  def on_second_test(arg)
11
15
  [:on_second_test, arg]
12
16
  end
17
+
18
+ def on_seventh_level_level_level_level_level_test(arg)
19
+ [:on_seventh_test, arg]
20
+ end
21
+ end
22
+
23
+ class FilterWithDispatcherMixinAndOn < FilterWithDispatcherMixin
24
+ def on(*args)
25
+ [:on_zero, *args]
26
+ end
13
27
  end
14
28
 
15
29
  describe Temple::Mixins::Dispatcher do
@@ -28,4 +42,16 @@ describe Temple::Mixins::Dispatcher do
28
42
  it 'should dispatch second level' do
29
43
  @filter.call([:second, :test, 42]).should.equal [:on_second_test, 42]
30
44
  end
45
+
46
+ it 'should dispatch second level if prefixed' do
47
+ @filter.call([:test, :check, 42]).should.equal [:on_check, 42]
48
+ end
49
+
50
+ it 'should dispatch seventh level' do
51
+ @filter.call([:seventh, :level, :level, :level, :level, :level, :test, 42]).should == [:on_seventh_test, 42]
52
+ end
53
+
54
+ it 'should dispatch zero level' do
55
+ FilterWithDispatcherMixinAndOn.new.call([:foo,42]).should == [:on_zero, :foo, 42]
56
+ end
31
57
  end
@@ -23,7 +23,7 @@ class TestEngine < Temple::Engine
23
23
  filter :MultiFlattener
24
24
  generator :ArrayBuffer
25
25
  use :BeforeLast, Callable1.new
26
- wildcard(:Last) { Callable2.new }
26
+ use(:Last) { Callable2.new }
27
27
  end
28
28
 
29
29
  describe Temple::Engine do
metadata CHANGED
@@ -1,58 +1,63 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: temple
3
- version: !ruby/object:Gem::Version
4
- version: 0.3.4
3
+ version: !ruby/object:Gem::Version
5
4
  prerelease:
5
+ version: 0.3.5
6
6
  platform: ruby
7
- authors:
7
+ authors:
8
8
  - Magnus Holm
9
9
  - Daniel Mendler
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2011-09-02 00:00:00.000000000Z
14
- dependencies:
15
- - !ruby/object:Gem::Dependency
13
+
14
+ date: 2012-01-06 00:00:00 +01:00
15
+ default_executable:
16
+ dependencies:
17
+ - !ruby/object:Gem::Dependency
16
18
  name: tilt
17
- requirement: &70118806201520 !ruby/object:Gem::Requirement
19
+ prerelease: false
20
+ requirement: &id001 !ruby/object:Gem::Requirement
18
21
  none: false
19
- requirements:
20
- - - ! '>='
21
- - !ruby/object:Gem::Version
22
- version: '0'
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: "0"
23
26
  type: :development
24
- prerelease: false
25
- version_requirements: *70118806201520
26
- - !ruby/object:Gem::Dependency
27
+ version_requirements: *id001
28
+ - !ruby/object:Gem::Dependency
27
29
  name: bacon
28
- requirement: &70118806201100 !ruby/object:Gem::Requirement
30
+ prerelease: false
31
+ requirement: &id002 !ruby/object:Gem::Requirement
29
32
  none: false
30
- requirements:
31
- - - ! '>='
32
- - !ruby/object:Gem::Version
33
- version: '0'
33
+ requirements:
34
+ - - ">="
35
+ - !ruby/object:Gem::Version
36
+ version: "0"
34
37
  type: :development
35
- prerelease: false
36
- version_requirements: *70118806201100
37
- - !ruby/object:Gem::Dependency
38
+ version_requirements: *id002
39
+ - !ruby/object:Gem::Dependency
38
40
  name: rake
39
- requirement: &70118806200640 !ruby/object:Gem::Requirement
41
+ prerelease: false
42
+ requirement: &id003 !ruby/object:Gem::Requirement
40
43
  none: false
41
- requirements:
42
- - - ! '>='
43
- - !ruby/object:Gem::Version
44
- version: '0'
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: "0"
45
48
  type: :development
46
- prerelease: false
47
- version_requirements: *70118806200640
49
+ version_requirements: *id003
48
50
  description:
49
- email:
51
+ email:
50
52
  - judofyr@gmail.com
51
53
  - mail@daniel-mendler.de
52
54
  executables: []
55
+
53
56
  extensions: []
57
+
54
58
  extra_rdoc_files: []
55
- files:
59
+
60
+ files:
56
61
  - .gitignore
57
62
  - .travis.yml
58
63
  - .yardopts
@@ -114,31 +119,35 @@ files:
114
119
  - test/test_grammar.rb
115
120
  - test/test_hash.rb
116
121
  - test/test_utils.rb
122
+ has_rdoc: true
117
123
  homepage: http://dojo.rubyforge.org/
118
124
  licenses: []
125
+
119
126
  post_install_message:
120
127
  rdoc_options: []
121
- require_paths:
128
+
129
+ require_paths:
122
130
  - lib
123
- required_ruby_version: !ruby/object:Gem::Requirement
131
+ required_ruby_version: !ruby/object:Gem::Requirement
124
132
  none: false
125
- requirements:
126
- - - ! '>='
127
- - !ruby/object:Gem::Version
128
- version: '0'
129
- required_rubygems_version: !ruby/object:Gem::Requirement
133
+ requirements:
134
+ - - ">="
135
+ - !ruby/object:Gem::Version
136
+ version: "0"
137
+ required_rubygems_version: !ruby/object:Gem::Requirement
130
138
  none: false
131
- requirements:
132
- - - ! '>='
133
- - !ruby/object:Gem::Version
134
- version: '0'
139
+ requirements:
140
+ - - ">="
141
+ - !ruby/object:Gem::Version
142
+ version: "0"
135
143
  requirements: []
144
+
136
145
  rubyforge_project:
137
- rubygems_version: 1.8.6
146
+ rubygems_version: 1.6.2
138
147
  signing_key:
139
148
  specification_version: 3
140
149
  summary: Template compilation framework in Ruby
141
- test_files:
150
+ test_files:
142
151
  - test/filters/test_control_flow.rb
143
152
  - test/filters/test_dynamic_inliner.rb
144
153
  - test/filters/test_eraser.rb