temple 0.1.8 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -115,7 +115,7 @@ mind working across processes, it's not a problem at all.
115
115
  Compilers
116
116
  ---------
117
117
 
118
- A *compiler* is simply an object which responds a method called #compile which
118
+ A *compiler* is simply an object which responds a method called #call which
119
119
  takes one argument and returns a value. It's illegal for a compiler to mutate
120
120
  the argument, and it should be possible to use the same instance several times
121
121
  (although not by several threads at the same time).
@@ -128,7 +128,7 @@ class. Temple then assumes the initializer takes an optional option hash:
128
128
  @options = options
129
129
  end
130
130
 
131
- def compile(exp)
131
+ def call(exp)
132
132
  # do stuff
133
133
  end
134
134
  end
@@ -195,7 +195,7 @@ When you have a chain of a parsers, some filters and a generator you can finally
195
195
  end
196
196
 
197
197
  engine = MyEngine.new(:strict => "For MyParser")
198
- engine.compile(something)
198
+ engine.call(something)
199
199
 
200
200
  And then?
201
201
  ---------
@@ -203,25 +203,13 @@ And then?
203
203
  You've ran the template through the parser, some filters and in the end a
204
204
  generator. What happens next?
205
205
 
206
- Temple's mission ends here, so it's all up to you, but we recommend using
207
- [Tilt](http://github.com/rtomayko/tilt), the generic interface to Ruby
208
- template engines. This gives you a wide range of features and your engine can
209
- be used right away in many projects.
206
+ Temple provides helpers to create template classes for [Tilt](http://github.com/rtomayko/tilt) and
207
+ Rails.
210
208
 
211
209
  require 'tilt'
212
210
 
213
- class MyTemplate < Tilt::Template
214
- def prepare
215
- @src = MyEngine.new(options).compile(data)
216
- end
217
-
218
- def template_source
219
- @src
220
- end
221
- end
222
-
223
- # Register your file extension:
224
- Tilt.register 'ext', MyTemplate
211
+ # Create template class MyTemplate and register your file extension
212
+ MyTemplate = Temple::Templates::Tilt(MyEngine, :register_as => 'ext')
225
213
 
226
214
  Tilt.new('example.ext').render # => Render a file
227
215
  MyTemplate.new { "String" }.render # => Render a string
data/lib/temple/engine.rb CHANGED
@@ -18,41 +18,52 @@ module Temple
18
18
  # generator :ArrayBuffer, :buffer
19
19
  # end
20
20
  #
21
+ # class SpecialEngine < MyEngine
22
+ # append MyCodeOptimizer
23
+ # replace Temple::Generators::ArrayBuffer, Temple::Generators::RailsOutputBuffer
24
+ # end
25
+ #
21
26
  # engine = MyEngine.new(:strict => "For MyParser")
22
- # engine.compile(something)
27
+ # engine.call(something)
23
28
  #
24
29
  class Engine
25
30
  include Mixins::Options
31
+ include Mixins::EngineDSL
32
+ extend Mixins::EngineDSL
26
33
 
27
- def self.chain
28
- @chain ||= []
29
- end
34
+ attr_reader :chain
30
35
 
31
- def self.use(filter, *options, &block)
32
- default_options = Hash === options.last ? options.pop : {}
33
- chain << proc do |opts|
34
- filter.new(default_options.merge(Hash[*opts.select {|k,v| options.include?(k) }.flatten]), &block)
35
- end
36
- end
37
-
38
- # Shortcut for <tt>use Temple::Filters::parser</tt>
39
- def self.filter(filter, *options, &block)
40
- use(Temple::Filters.const_get(filter), *options, &block)
36
+ def self.chain
37
+ @chain ||= superclass.respond_to?(:chain) ? superclass.chain.dup : []
41
38
  end
42
39
 
43
- # Shortcut for <tt>use Temple::Generators::parser</tt>
44
- def self.generator(compiler, *options, &block)
45
- use(Temple::Generators.const_get(compiler), *options, &block)
40
+ def initialize(o = {})
41
+ super
42
+ @chain = self.class.chain.dup
43
+ yield(self) if block_given?
44
+ [*options[:chain]].compact.each {|block| block.call(self) }
45
+ @chain = build_chain
46
46
  end
47
47
 
48
- def compile(input)
49
- chain.inject(input) {|m, e| e.compile(m) }
48
+ def call(input)
49
+ chain.inject(input) {|m, e| e.call(m) }
50
50
  end
51
51
 
52
52
  protected
53
53
 
54
- def chain
55
- @chain ||= self.class.chain.map { |f| f.call(options) }
54
+ def build_chain
55
+ chain.map do |e|
56
+ name, filter, option_filter, local_options = e
57
+ case filter
58
+ when Class
59
+ filtered_options = Hash[*option_filter.select {|k| options.include?(k) }.map {|k| [k, options[k]] }.flatten]
60
+ filter.new(Utils::ImmutableHash.new(local_options, filtered_options))
61
+ when UnboundMethod
62
+ filter.bind(self)
63
+ else
64
+ filter
65
+ end
66
+ end
56
67
  end
57
68
  end
58
69
  end
@@ -2,8 +2,8 @@ module Temple
2
2
  module ERB
3
3
  class Engine < Temple::Engine
4
4
  use Temple::ERB::Parser, :auto_escape
5
- use Temple::ERB::Trimming, :trim_mode
6
5
  filter :EscapeHTML, :use_html_safe
6
+ use Temple::ERB::Trimming, :trim_mode
7
7
  filter :MultiFlattener
8
8
  filter :StaticMerger
9
9
  filter :DynamicInliner
@@ -10,7 +10,7 @@ module Temple
10
10
  '%%>' => '%>',
11
11
  }.freeze
12
12
 
13
- def compile(input)
13
+ def call(input)
14
14
  result = [:multi]
15
15
  pos = 0
16
16
  input.scan(ERB_PATTERN) do |escaped, indicator, code|
@@ -25,7 +25,7 @@ module Temple
25
25
  when '#'
26
26
  code.count("\n").times { result << [:newline] }
27
27
  when /=/
28
- result << (indicator.length > 1 || !options[:auto_escape] ? [:dynamic, code] : [:escape, :dynamic, code])
28
+ result << [:escape, indicator.length <= 1 && options[:auto_escape], [:dynamic, code]]
29
29
  else
30
30
  result << [:block, code]
31
31
  end
@@ -9,7 +9,7 @@ module Temple
9
9
  require 'pp' if options[:debug_pretty]
10
10
  end
11
11
 
12
- def compile(exp)
12
+ def call(exp)
13
13
  if options[:debug]
14
14
  puts options[:debug_prefix] if options[:debug_prefix]
15
15
  if options[:debug_pretty]
@@ -53,7 +53,7 @@ module Temple
53
53
  # If we found a single exp last time, let's add it.
54
54
  res.concat(prev) if state == :single
55
55
  # Compile the current exp (unless it's the noop)
56
- res << compile!(exp) unless head == :noop
56
+ res << compile(exp) unless head == :noop
57
57
  # Now we're looking for more!
58
58
  state = :looking
59
59
  end
@@ -1,29 +1,32 @@
1
1
  module Temple
2
2
  module Filters
3
3
  class EscapeHTML < Filter
4
- temple_dispatch :escape, :html
4
+ include Temple::HTML::Dispatcher
5
+
6
+ temple_dispatch :html
5
7
 
6
8
  # Activate the usage of html_safe? if it is available (for Rails 3 for example)
7
9
  default_options[:use_html_safe] = ''.respond_to?(:html_safe?)
8
10
 
9
- def on_escape_static(value)
10
- [:static, options[:use_html_safe] ? escape_html_safe(value) : escape_html(value)]
11
- end
12
-
13
- def on_escape_dynamic(value)
14
- [:dynamic, "Temple::Utils.escape_html#{options[:use_html_safe] ? '_safe' : ''}((#{value}))"]
11
+ def initialize(opts = {})
12
+ super
13
+ @escape = false
15
14
  end
16
15
 
17
- def on_html_staticattrs(*attrs)
18
- [:html, :staticattrs, *attrs.map {|k,v| [k, compile!(v)] }]
16
+ def on_escape(flag, exp)
17
+ old = @escape
18
+ @escape = flag
19
+ compile(exp)
20
+ ensure
21
+ @escape = old
19
22
  end
20
23
 
21
- def on_html_comment(content)
22
- [:html, :comment, compile!(content)]
24
+ def on_static(value)
25
+ [:static, @escape ? (options[:use_html_safe] ? escape_html_safe(value) : escape_html(value)) : value]
23
26
  end
24
27
 
25
- def on_html_tag(name, attrs, closed, content)
26
- [:html, :tag, name, compile!(attrs), closed, compile!(content)]
28
+ def on_dynamic(value)
29
+ [:dynamic, @escape ? "Temple::Utils.escape_html#{options[:use_html_safe] ? '_safe' : ''}((#{value}))" : value]
27
30
  end
28
31
  end
29
32
  end
@@ -3,11 +3,11 @@ module Temple
3
3
  class MultiFlattener < Filter
4
4
  def on_multi(*exps)
5
5
  # If the multi contains a single element, just return the element
6
- return compile!(exps.first) if exps.length == 1
6
+ return compile(exps.first) if exps.length == 1
7
7
  result = [:multi]
8
8
 
9
9
  exps.each do |exp|
10
- exp = compile!(exp)
10
+ exp = compile(exp)
11
11
  if exp.first == :multi
12
12
  result.concat(exp[1..-1])
13
13
  else
@@ -25,7 +25,7 @@ module Temple
25
25
  curr << exp[1]
26
26
  end
27
27
  else
28
- res << compile!(exp)
28
+ res << compile(exp)
29
29
  state = :looking unless exp.first == :newline
30
30
  end
31
31
  end
@@ -73,17 +73,21 @@ module Temple
73
73
 
74
74
  default_options[:buffer] = '_buf'
75
75
 
76
- def compile(exp)
77
- [preamble, compile!(exp), postamble].join(' ; ')
76
+ def call(exp)
77
+ [preamble, compile(exp), postamble].join(' ; ')
78
78
  end
79
79
 
80
- def compile!(exp)
80
+ def compile(exp)
81
81
  type, *args = exp
82
- send("on_#{type}", *args)
82
+ if respond_to?("on_#{type}")
83
+ send("on_#{type}", *args)
84
+ else
85
+ raise "Generator supports only core expressions - found #{exp.inspect}"
86
+ end
83
87
  end
84
88
 
85
89
  def on_multi(*exp)
86
- exp.map { |e| compile!(e) }.join(' ; ')
90
+ exp.map { |e| compile(e) }.join(' ; ')
87
91
  end
88
92
 
89
93
  def on_newline
@@ -91,7 +95,7 @@ module Temple
91
95
  end
92
96
 
93
97
  def on_capture(name, block)
94
- options[:capture_generator].new(:buffer => name).compile(block)
98
+ options[:capture_generator].new(:buffer => name).call(block)
95
99
  end
96
100
 
97
101
  protected
@@ -0,0 +1,17 @@
1
+ module Temple
2
+ module HTML
3
+ module Dispatcher
4
+ def on_html_staticattrs(*attrs)
5
+ [:html, :staticattrs, *attrs.map {|k,v| [k, compile(v)] }]
6
+ end
7
+
8
+ def on_html_comment(content)
9
+ [:html, :comment, compile(content)]
10
+ end
11
+
12
+ def on_html_tag(name, attrs, closed, content)
13
+ [:html, :tag, name, compile(attrs), closed, compile(content)]
14
+ end
15
+ end
16
+ end
17
+ end
@@ -39,18 +39,11 @@ module Temple
39
39
  end
40
40
 
41
41
  def html?
42
- html5? or html4?
43
- end
44
-
45
- def html5?
46
- options[:format] == :html5
47
- end
48
-
49
- def html4?
50
- options[:format] == :html4
42
+ options[:format] == :html5 || options[:format] == :html4
51
43
  end
52
44
 
53
45
  def on_html_doctype(type)
46
+ type = type.to_s
54
47
  trailing_newlines = type[/(\A|[^\r])(\n+)\Z/, 2].to_s
55
48
  text = type.downcase.strip
56
49
 
@@ -76,16 +69,16 @@ module Temple
76
69
  def on_html_comment(content)
77
70
  [:multi,
78
71
  [:static, '<!--'],
79
- compile!(content),
72
+ compile(content),
80
73
  [:static, '-->']]
81
74
  end
82
75
 
83
76
  def on_html_tag(name, attrs, closed, content)
84
77
  closed ||= options[:autoclose].include?(name)
85
78
  raise "Closed tag #{name} has content" if closed && !empty_exp?(content)
86
- result = [:multi, [:static, "<#{name}"], compile!(attrs)]
79
+ result = [:multi, [:static, "<#{name}"], compile(attrs)]
87
80
  result << [:static, ' /'] if closed && xhtml?
88
- result << [:static, '>'] << compile!(content)
81
+ result << [:static, '>'] << compile(content)
89
82
  result << [:static, "</#{name}>"] if !closed
90
83
  result
91
84
  end
@@ -104,13 +97,45 @@ module Temple
104
97
  end
105
98
  end
106
99
  result.sort.inject([:multi]) do |list, (name, value)|
107
- list << [:multi,
108
- [:static, ' '],
109
- [:static, name],
110
- [:static, '='],
111
- [:static, options[:attr_wrapper]],
112
- value,
113
- [:static, options[:attr_wrapper]]]
100
+ list << compile_attribute(name, value)
101
+ end
102
+ end
103
+
104
+ protected
105
+
106
+ def compile_attribute(name, value)
107
+ if empty_exp?(value)
108
+ [:multi]
109
+ elsif contains_static?(value)
110
+ attribute(name, value)
111
+ else
112
+ tmp = tmp_var(:htmlattr)
113
+ [:multi,
114
+ [:capture, tmp, value],
115
+ [:block, "unless #{tmp}.empty?"],
116
+ attribute(name, [:dynamic, tmp]),
117
+ [:block, 'end']]
118
+ end
119
+ end
120
+
121
+ def attribute(name, value)
122
+ [:multi,
123
+ [:static, ' '],
124
+ [:static, name],
125
+ [:static, '='],
126
+ [:static, options[:attr_wrapper]],
127
+ value,
128
+ [:static, options[:attr_wrapper]]]
129
+ end
130
+
131
+ def contains_static?(exp)
132
+ case exp[0]
133
+ when :multi
134
+ exp[1..-1].any? {|e| contains_static?(e) }
135
+ when :static
136
+ true
137
+ else
138
+ false
114
139
  end
115
140
  end
116
141
  end
@@ -3,39 +3,44 @@ module Temple
3
3
  class Pretty < Fast
4
4
  set_default_options :indent => ' ',
5
5
  :pretty => true,
6
- :indent_tags => %w(base body dd div dl doctype dt fieldset form head h1 h2 h3
6
+ :indent_tags => %w(base body dd div dl dt fieldset form head h1 h2 h3
7
7
  h4 h5 h6 hr html img input li link meta ol p script
8
8
  style table tbody td tfoot th thead title tr ul).freeze,
9
9
  :pre_tags => %w(pre textarea).freeze
10
10
 
11
11
  def initialize(opts = {})
12
12
  super
13
- @last = nil
13
+ @last = :noindent
14
14
  @indent = 0
15
15
  @pretty = options[:pretty]
16
+ @pre_tags = Regexp.new(options[:pre_tags].map {|t| "<#{t}" }.join('|'))
16
17
  end
17
18
 
18
- def compile(exp)
19
- [:multi, preamble, compile!(exp)]
19
+ def call(exp)
20
+ @pretty ? [:multi, preamble, compile(exp)] : super
20
21
  end
21
22
 
22
23
  def on_static(content)
23
- @last = nil
24
- [:static, @pretty ? content.gsub("\n", indent) : content]
24
+ if @pretty
25
+ content = Utils.indent(content, indent, @pre_tags)
26
+ @last = content.sub!(/\r?\n\s*$/, ' ') ? nil : :noindent
27
+ end
28
+ [:static, content]
25
29
  end
26
30
 
27
31
  def on_dynamic(content)
28
- @last = nil
32
+ @last = :noindent
29
33
  [:dynamic, @pretty ? "Temple::Utils.indent((#{content}), #{indent.inspect}, _temple_pre_tags)" : content]
30
34
  end
31
35
 
32
36
  def on_html_doctype(type)
33
- @last = 'doctype'
37
+ @last = nil
34
38
  super
35
39
  end
36
40
 
37
41
  def on_html_comment(content)
38
42
  return super unless @pretty
43
+ @last = nil
39
44
  [:multi, [:static, indent], super]
40
45
  end
41
46
 
@@ -46,18 +51,16 @@ module Temple
46
51
  raise "Closed tag #{name} has content" if closed && !empty_exp?(content)
47
52
 
48
53
  @pretty = false
49
- result = [:multi, [:static, "#{tag_indent(name)}<#{name}"], compile!(attrs)]
54
+ result = [:multi, [:static, "#{tag_indent(name)}<#{name}"], compile(attrs)]
50
55
  result << [:static, ' /'] if closed && xhtml?
51
56
  result << [:static, '>']
52
57
 
53
- @last = name
54
58
  @pretty = !options[:pre_tags].include?(name)
55
59
  @indent += 1
56
- result << compile!(content)
60
+ result << compile(content)
57
61
  @indent -= 1
58
62
 
59
63
  result << [:static, "#{tag_indent(name)}</#{name}>"] if !closed
60
- @last = name
61
64
  @pretty = true
62
65
  result
63
66
  end
@@ -65,8 +68,7 @@ module Temple
65
68
  protected
66
69
 
67
70
  def preamble
68
- regexp = options[:pre_tags].map {|t| "<#{t}" }.join('|')
69
- [:block, "_temple_pre_tags = /#{regexp}/"]
71
+ [:block, "_temple_pre_tags = /#{@pre_tags.source}/"]
70
72
  end
71
73
 
72
74
  # Return indentation if not in pre tag
@@ -76,7 +78,9 @@ module Temple
76
78
 
77
79
  # Return indentation before tag
78
80
  def tag_indent(name)
79
- @last && (options[:indent_tags].include?(@last) || options[:indent_tags].include?(name)) ? indent : ''
81
+ result = @last != :noindent && (options[:indent_tags].include?(@last) || options[:indent_tags].include?(name)) ? indent : ''
82
+ @last = name
83
+ result
80
84
  end
81
85
  end
82
86
  end