temple 0.1.8 → 0.2.0

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/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