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/lib/temple/mixins.rb CHANGED
@@ -1,15 +1,154 @@
1
1
  module Temple
2
2
  module Mixins
3
+ module EngineDSL
4
+ def append(*args, &block)
5
+ chain << element(args, block)
6
+ end
7
+
8
+ def prepend(*args, &block)
9
+ chain.unshift(element(args, block))
10
+ end
11
+
12
+ def remove(name)
13
+ found = false
14
+ chain.reject! do |i|
15
+ equal = i.first == name
16
+ found = true if equal
17
+ equal
18
+ end
19
+ raise "#{name} not found" unless found
20
+ end
21
+
22
+ alias use append
23
+
24
+ def before(name, *args, &block)
25
+ name = Class === name ? name.name.to_sym : name
26
+ raise(ArgumentError, 'First argument must be Class or Symbol') unless Symbol === name
27
+ e = element(args, block)
28
+ found, i = false, 0
29
+ while i < chain.size
30
+ if chain[i].first == name
31
+ found = true
32
+ chain.insert(i, e)
33
+ i += 2
34
+ else
35
+ i += 1
36
+ end
37
+ end
38
+ raise "#{name} not found" unless found
39
+ end
40
+
41
+ def after(name, *args, &block)
42
+ name = Class === name ? name.name.to_sym : name
43
+ raise(ArgumentError, 'First argument must be Class or Symbol') unless Symbol === name
44
+ e = element(args, block)
45
+ found, i = false, 0
46
+ while i < chain.size
47
+ if chain[i].first == name
48
+ found = true
49
+ i += 1
50
+ chain.insert(i, e)
51
+ end
52
+ i += 1
53
+ end
54
+ raise "#{name} not found" unless found
55
+ end
56
+
57
+ def replace(name, *args, &block)
58
+ name = Class === name ? name.name.to_sym : name
59
+ raise(ArgumentError, 'First argument must be Class or Symbol') unless Symbol === name
60
+ e = element(args, block)
61
+ found = false
62
+ chain.each_with_index do |c, i|
63
+ if c.first == name
64
+ found = true
65
+ chain[i] = e
66
+ end
67
+ end
68
+ raise "#{name} not found" unless found
69
+ end
70
+
71
+ def filter(name, *options, &block)
72
+ use(name, Temple::Filters.const_get(name), *options, &block)
73
+ end
74
+
75
+ def generator(name, *options, &block)
76
+ use(name, Temple::Generators.const_get(name), *options, &block)
77
+ end
78
+
79
+ private
80
+
81
+ def element(args, block)
82
+ name = args.shift
83
+ if Class === name
84
+ filter = name
85
+ name = filter.name.to_sym
86
+ end
87
+ raise(ArgumentError, 'First argument must be Class or Symbol') unless Symbol === name
88
+
89
+ if block
90
+ raise(ArgumentError, 'Class and block argument are not allowed at the same time') if filter
91
+ filter = block
92
+ end
93
+
94
+ filter ||= args.shift
95
+
96
+ case filter
97
+ when Proc
98
+ # Proc or block argument
99
+ # The proc is converted to a method of the engine class.
100
+ # The proc can then access the option hash of the engine.
101
+ raise(ArgumentError, 'Too many arguments') unless args.empty?
102
+ raise(ArgumentError, 'Proc or blocks must have arity 1') unless filter.arity == 1
103
+ method_name = "FILTER #{name}"
104
+ if Class === self
105
+ define_method(method_name, &filter)
106
+ [name, instance_method(method_name)]
107
+ else
108
+ (class << self; self; end).class_eval { define_method(method_name, &filter) }
109
+ [name, method(method_name)]
110
+ end
111
+ when Class
112
+ # Class argument (e.g Filter class)
113
+ # The options are passed to the classes constructor.
114
+ local_options = Hash === args.last ? args.pop : nil
115
+ raise(ArgumentError, 'Only symbols allowed in option filter') unless args.all? {|o| Symbol === o }
116
+ [name, filter, args, local_options]
117
+ else
118
+ # Other callable argument (e.g. Object of class which implements #call or Method)
119
+ # The callable has no access to the option hash of the engine.
120
+ raise(ArgumentError, 'Class or callable argument is required') unless filter.respond_to?(:call)
121
+ [name, filter]
122
+ end
123
+ end
124
+ end
125
+
126
+ module CoreDispatcher
127
+ def on_multi(*exps)
128
+ [:multi, *exps.map {|exp| compile(exp) }]
129
+ end
130
+
131
+ def on_capture(name, exp)
132
+ [:capture, name, compile(exp)]
133
+ end
134
+
135
+ def on_escape(flag, exp)
136
+ [:escape, flag, compile(exp)]
137
+ end
138
+ end
139
+
3
140
  module Dispatcher
141
+ include CoreDispatcher
142
+
4
143
  def self.included(base)
5
144
  base.class_eval { extend ClassMethods }
6
145
  end
7
146
 
8
- def compile(exp)
9
- compile!(exp)
147
+ def call(exp)
148
+ compile(exp)
10
149
  end
11
150
 
12
- def compile!(exp)
151
+ def compile(exp)
13
152
  type, *args = exp
14
153
  if respond_to?("on_#{type}")
15
154
  send("on_#{type}", *args)
@@ -18,14 +157,6 @@ module Temple
18
157
  end
19
158
  end
20
159
 
21
- def on_multi(*exps)
22
- [:multi, *exps.map {|exp| compile!(exp) }]
23
- end
24
-
25
- def on_capture(name, exp)
26
- [:capture, name, compile!(exp)]
27
- end
28
-
29
160
  module ClassMethods
30
161
  def temple_dispatch(*bases)
31
162
  bases.each do |base|
@@ -41,30 +172,50 @@ module Temple
41
172
  end
42
173
  end
43
174
 
175
+ module DefaultOptions
176
+ def set_default_options(options)
177
+ default_options.update(options)
178
+ end
179
+
180
+ def default_options
181
+ @default_options ||= Utils::MutableHash.new(superclass.respond_to?(:default_options) ?
182
+ superclass.default_options : nil)
183
+ end
184
+ end
185
+
44
186
  module Options
45
187
  def self.included(base)
46
- base.class_eval { extend ClassMethods }
188
+ base.class_eval { extend DefaultOptions }
47
189
  end
48
190
 
49
191
  attr_reader :options
50
192
 
51
193
  def initialize(options = {})
52
- @options = self.class.default_options.merge(options)
194
+ @options = Utils::ImmutableHash.new(options, self.class.default_options)
53
195
  end
196
+ end
54
197
 
55
- module ClassMethods
56
- def set_default_options(opts)
57
- default_options.merge!(opts)
58
- end
198
+ module Template
199
+ include DefaultOptions
59
200
 
60
- def default_options
61
- @default_options ||= if superclass.respond_to?(:default_options)
62
- Hash.new {|hash, key| superclass.default_options[key] }
63
- else
64
- {}
65
- end
201
+ def engine(engine = nil)
202
+ default_options[:engine] = engine if engine
203
+ default_options[:engine]
204
+ end
205
+
206
+ def build_engine(*options)
207
+ raise 'No engine configured' unless engine
208
+ options << default_options
209
+ engine.new(Utils::ImmutableHash.new(*options)) do |e|
210
+ chain.each {|block| e.instance_eval(&block) }
66
211
  end
67
212
  end
213
+
214
+ def chain(&block)
215
+ chain = (default_options[:chain] ||= [])
216
+ chain << block if block
217
+ chain
218
+ end
68
219
  end
69
220
  end
70
221
  end
@@ -0,0 +1,34 @@
1
+ if ::Rails::VERSION::MAJOR < 3
2
+ raise "Temple supports only Rails 3.x and greater, your Rails version is #{::Rails::VERSION::STRING}"
3
+ end
4
+
5
+ module Temple
6
+ module Templates
7
+ if ::Rails::VERSION::MAJOR == 3 && ::Rails::VERSION::MINOR < 1
8
+ class Rails < ActionView::TemplateHandler
9
+ include ActionView::TemplateHandlers::Compilable
10
+ extend Mixins::Template
11
+
12
+ def compile(template)
13
+ self.class.build_engine.call(template.source)
14
+ end
15
+
16
+ def self.register_as(name)
17
+ ActionView::Template.register_template_handler name.to_sym, self
18
+ end
19
+ end
20
+ else
21
+ class Rails
22
+ extend Mixins::Template
23
+
24
+ def self.call(template)
25
+ build_engine.call(template.source)
26
+ end
27
+
28
+ def self.register_as(name)
29
+ ActionView::Template.register_template_handler name.to_sym, self
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,30 @@
1
+ require 'tilt'
2
+
3
+ module Temple
4
+ module Templates
5
+ class Tilt < ::Tilt::Template
6
+ extend Mixins::Template
7
+
8
+ # Prepare Temple template
9
+ #
10
+ # Called immediately after template data is loaded.
11
+ #
12
+ # @return [void]
13
+ def prepare
14
+ @src = self.class.build_engine({ :file => eval_file }, options).call(data)
15
+ end
16
+
17
+ # A string containing the (Ruby) source code for the template.
18
+ #
19
+ # @param [Hash] locals Local variables
20
+ # @return [String] Compiled template ruby code
21
+ def precompiled_template(locals = {})
22
+ @src
23
+ end
24
+
25
+ def self.register_as(name)
26
+ ::Tilt.register name.to_s, self
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,13 @@
1
+ module Temple
2
+ module Templates
3
+ autoload :Tilt, 'temple/templates/tilt'
4
+ autoload :Rails, 'temple/templates/rails'
5
+
6
+ def self.method_missing(name, engine, options = {})
7
+ template = Class.new(const_get(name))
8
+ template.engine(engine)
9
+ template.register_as(options[:register_as]) if options[:register_as]
10
+ template
11
+ end
12
+ end
13
+ end
data/lib/temple/utils.rb CHANGED
@@ -2,8 +2,53 @@ module Temple
2
2
  module Utils
3
3
  extend self
4
4
 
5
+ class ImmutableHash
6
+ include Enumerable
7
+
8
+ def initialize(*hash)
9
+ @hash = hash.compact
10
+ end
11
+
12
+ def include?(key)
13
+ @hash.any? {|h| h.include?(key) }
14
+ end
15
+
16
+ def [](key)
17
+ @hash.each {|h| return h[key] if h.include?(key) }
18
+ nil
19
+ end
20
+
21
+ def each
22
+ keys.each {|k| yield(k, self[k]) }
23
+ end
24
+
25
+ def keys
26
+ @hash.inject([]) {|keys, h| keys += h.keys }.uniq
27
+ end
28
+
29
+ def values
30
+ keys.map {|k| self[k] }
31
+ end
32
+ end
33
+
34
+ class MutableHash < ImmutableHash
35
+ def initialize(*hash)
36
+ super({}, *hash)
37
+ end
38
+
39
+ def []=(key, value)
40
+ @hash.first[key] = value
41
+ end
42
+
43
+ def update(hash)
44
+ @hash.first.update(hash)
45
+ end
46
+ end
47
+
5
48
  def indent(text, indent, pre_tags)
6
- pre_tags =~ text ? text : text.gsub("\n", indent)
49
+ text = text.to_s
50
+ text.gsub!("\n", indent) if pre_tags !~ text
51
+ text
7
52
  end
8
53
 
9
54
  # Returns an escaped copy of `html`.
@@ -55,6 +100,14 @@ module Temple
55
100
  end
56
101
  end
57
102
 
103
+ # Generate unique temporary variable name
104
+ #
105
+ # @return [String] Variable name
106
+ def tmp_var(prefix)
107
+ @tmp_var ||= 0
108
+ "_temple_#{prefix}#{@tmp_var += 1}"
109
+ end
110
+
58
111
  def empty_exp?(exp)
59
112
  case exp[0]
60
113
  when :multi
@@ -1,3 +1,3 @@
1
1
  module Temple
2
- VERSION = '0.1.8'
2
+ VERSION = '0.2.0'
3
3
  end
data/lib/temple.rb CHANGED
@@ -7,12 +7,11 @@ module Temple
7
7
  autoload :Utils, 'temple/utils'
8
8
  autoload :Mixins, 'temple/mixins'
9
9
  autoload :Filter, 'temple/filter'
10
- autoload :Template, 'temple/template'
10
+ autoload :Templates, 'temple/templates'
11
11
 
12
12
  module ERB
13
13
  autoload :Engine, 'temple/erb/engine'
14
14
  autoload :Parser, 'temple/erb/parser'
15
- autoload :Template, 'temple/erb/template'
16
15
  autoload :Trimming, 'temple/erb/trimming'
17
16
  end
18
17
 
@@ -25,6 +24,7 @@ module Temple
25
24
  end
26
25
 
27
26
  module HTML
27
+ autoload :Dispatcher, 'temple/html/dispatcher'
28
28
  autoload :Fast, 'temple/html/fast'
29
29
  autoload :Pretty, 'temple/html/pretty'
30
30
  end
data/temple.gemspec CHANGED
@@ -1,18 +1,18 @@
1
1
  # -*- encoding: utf-8 -*-
2
- require File.dirname(__FILE__) + "/lib/temple/version"
2
+ require File.dirname(__FILE__) + '/lib/temple/version'
3
+ require 'date'
3
4
 
4
5
  Gem::Specification.new do |s|
5
- s.name = %q{temple}
6
- s.version = Temple::VERSION
6
+ s.name = 'temple'
7
+ s.version = Temple::VERSION
8
+ s.date = Date.today.to_s
7
9
 
8
- s.authors = ["Magnus Holm"]
9
- s.date = %q{2011-03-10}
10
- s.email = %q{judofyr@gmail.com}
11
- s.homepage = %q{http://dojo.rubyforge.org/}
12
- s.require_paths = ["lib"]
13
- s.rubygems_version = %q{1.3.6}
14
- s.summary = %q{Template compilation framework in Ruby}
10
+ s.authors = ['Magnus Holm', 'Daniel Mendler']
11
+ s.email = ['judofyr@gmail.com', 'mail@daniel-mendler.de']
12
+ s.homepage = 'http://dojo.rubyforge.org/'
13
+ s.summary = 'Template compilation framework in Ruby'
15
14
 
15
+ s.require_paths = %w(lib)
16
16
  s.files = `git ls-files`.split("\n")
17
17
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
18
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
@@ -6,7 +6,7 @@ describe Temple::Filters::DynamicInliner do
6
6
  end
7
7
 
8
8
  it 'should compile several statics into dynamic' do
9
- @filter.compile([:multi,
9
+ @filter.call([:multi,
10
10
  [:static, "Hello "],
11
11
  [:static, "World\n "],
12
12
  [:static, "Have a nice day"]
@@ -14,7 +14,7 @@ describe Temple::Filters::DynamicInliner do
14
14
  end
15
15
 
16
16
  it 'should compile several dynamics into dynamic' do
17
- @filter.compile([:multi,
17
+ @filter.call([:multi,
18
18
  [:dynamic, "@hello"],
19
19
  [:dynamic, "@world"],
20
20
  [:dynamic, "@yeah"]
@@ -22,7 +22,7 @@ describe Temple::Filters::DynamicInliner do
22
22
  end
23
23
 
24
24
  it 'should compile static and dynamic into dynamic' do
25
- @filter.compile([:multi,
25
+ @filter.call([:multi,
26
26
  [:static, "Hello"],
27
27
  [:dynamic, "@world"],
28
28
  [:dynamic, "@yeah"],
@@ -31,7 +31,7 @@ describe Temple::Filters::DynamicInliner do
31
31
  end
32
32
 
33
33
  it 'should merge statics and dynamics around a block' do
34
- exp = @filter.compile([:multi,
34
+ exp = @filter.call([:multi,
35
35
  [:static, "Hello "],
36
36
  [:dynamic, "@world"],
37
37
  [:block, "Oh yeah"],
@@ -46,21 +46,21 @@ describe Temple::Filters::DynamicInliner do
46
46
 
47
47
  it 'should keep blocks intact' do
48
48
  exp = [:multi, [:block, 'foo']]
49
- @filter.compile(exp).should.equal exp
49
+ @filter.call(exp).should.equal exp
50
50
  end
51
51
 
52
52
  it 'should keep single statics intact' do
53
53
  exp = [:multi, [:static, 'foo']]
54
- @filter.compile(exp).should.equal exp
54
+ @filter.call(exp).should.equal exp
55
55
  end
56
56
 
57
57
  it 'should keep single dynamic intact' do
58
58
  exp = [:multi, [:dynamic, 'foo']]
59
- @filter.compile(exp).should.equal exp
59
+ @filter.call(exp).should.equal exp
60
60
  end
61
61
 
62
62
  it 'should inline inside multi' do
63
- @filter.compile([:multi,
63
+ @filter.call([:multi,
64
64
  [:static, "Hello "],
65
65
  [:dynamic, "@world"],
66
66
  [:multi,
@@ -76,7 +76,7 @@ describe Temple::Filters::DynamicInliner do
76
76
  end
77
77
 
78
78
  it 'should merge across newlines' do
79
- exp = @filter.compile([:multi,
79
+ exp = @filter.call([:multi,
80
80
  [:static, "Hello \n"],
81
81
  [:newline],
82
82
  [:dynamic, "@world"],
@@ -87,7 +87,7 @@ describe Temple::Filters::DynamicInliner do
87
87
  end
88
88
 
89
89
  it 'should compile static followed by newline' do
90
- @filter.compile([:multi,
90
+ @filter.call([:multi,
91
91
  [:static, "Hello \n"],
92
92
  [:newline],
93
93
  [:block, "world"]
@@ -12,9 +12,10 @@ describe Temple::Filters::EscapeHTML do
12
12
  end
13
13
 
14
14
  it 'should handle escape expressions' do
15
- @filter.compile([:multi,
16
- [:escape, :static, "a < b"],
17
- [:escape, :dynamic, "ruby_method"]
15
+ @filter.call([:escape, true,
16
+ [:multi,
17
+ [:static, "a < b"],
18
+ [:dynamic, "ruby_method"]]
18
19
  ]).should.equal [:multi,
19
20
  [:static, "a &lt; b"],
20
21
  [:dynamic, "Temple::Utils.escape_html((ruby_method))"],
@@ -23,25 +24,23 @@ describe Temple::Filters::EscapeHTML do
23
24
 
24
25
  it 'should keep blocks intact' do
25
26
  exp = [:multi, [:block, 'foo']]
26
- @filter.compile(exp).should.equal exp
27
+ @filter.call(exp).should.equal exp
27
28
  end
28
29
 
29
30
  it 'should keep statics intact' do
30
31
  exp = [:multi, [:static, '<']]
31
- @filter.compile(exp).should.equal exp
32
+ @filter.call(exp).should.equal exp
32
33
  end
33
34
 
34
35
  it 'should keep dynamic intact' do
35
36
  exp = [:multi, [:dynamic, 'foo']]
36
- @filter.compile(exp).should.equal exp
37
+ @filter.call(exp).should.equal exp
37
38
  end
38
39
 
39
40
  it 'should have use_html_safe option' do
40
41
  filter = Temple::Filters::EscapeHTML.new(:use_html_safe => true)
41
- filter.compile([:multi,
42
- [:escape, :static, HtmlSafeString.new("a < b")],
43
- ]).should.equal [:multi,
44
- [:static, "a < b"],
45
- ]
42
+ filter.call([:escape, true,
43
+ [:static, HtmlSafeString.new("a < b")],
44
+ ]).should.equal [:static, "a < b"]
46
45
  end
47
46
  end
@@ -6,7 +6,7 @@ describe Temple::Filters::MultiFlattener do
6
6
  end
7
7
 
8
8
  it 'should flatten nested multi expressions' do
9
- @filter.compile([:multi,
9
+ @filter.call([:multi,
10
10
  [:static, "a"],
11
11
  [:multi,
12
12
  [:dynamic, "aa"],
@@ -28,6 +28,6 @@ describe Temple::Filters::MultiFlattener do
28
28
  end
29
29
 
30
30
  it 'should return first element' do
31
- @filter.compile([:multi, [:block, 'foo']]).should.equal [:block, 'foo']
31
+ @filter.call([:multi, [:block, 'foo']]).should.equal [:block, 'foo']
32
32
  end
33
33
  end
@@ -6,7 +6,7 @@ describe Temple::Filters::StaticMerger do
6
6
  end
7
7
 
8
8
  it 'should merge serveral statics' do
9
- @filter.compile([:multi,
9
+ @filter.call([:multi,
10
10
  [:static, "Hello "],
11
11
  [:static, "World, "],
12
12
  [:static, "Good night"]
@@ -16,7 +16,7 @@ describe Temple::Filters::StaticMerger do
16
16
  end
17
17
 
18
18
  it 'should merge serveral statics around block' do
19
- @filter.compile([:multi,
19
+ @filter.call([:multi,
20
20
  [:static, "Hello "],
21
21
  [:static, "World!"],
22
22
  [:block, "123"],
@@ -30,7 +30,7 @@ describe Temple::Filters::StaticMerger do
30
30
  end
31
31
 
32
32
  it 'should merge serveral statics across newlines' do
33
- @filter.compile([:multi,
33
+ @filter.call([:multi,
34
34
  [:static, "Hello "],
35
35
  [:static, "World, "],
36
36
  [:newline],