temple 0.3.4 → 0.3.5

Sign up to get free protection for your applications and to get access to all the features.
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