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