tilt 0.2

Sign up to get free protection for your applications and to get access to all the features.
data/COPYING ADDED
@@ -0,0 +1,18 @@
1
+ Copyright (c) 2009 Ryan Tomayko <http://tomayko.com/about>
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to
5
+ deal in the Software without restriction, including without limitation the
6
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
7
+ sell copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
16
+ THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,75 @@
1
+ Tilt
2
+ ====
3
+
4
+ Tilt provides a thin interface over a bunch of different template engines to
5
+ make their usage as generic possible. This is useful for web frameworks,
6
+ static site generators, and other systems that support multiple template
7
+ engines but don't want to code for each of them explicitly.
8
+
9
+ The following features are supported for all template engines (assuming the
10
+ feature is relevant to the engine):
11
+
12
+ * Custom template evaluation scopes / bindings
13
+ * Ability to pass locals to template evaluation
14
+ * Support for passing a block to template evaluation for "yield"
15
+ * Backtraces with correct filenames and line numbers
16
+ * Template compilation caching and reloading
17
+
18
+ These template engines are currently supported with (many) more on the way:
19
+
20
+ * ERB
21
+ * Interpolated Ruby String
22
+ * Haml (with the `haml` gem/library)
23
+ * Sass (with the `haml` gem/library)
24
+ * Builder (with the `builder` gem/library)
25
+ * Liquid (with the `liquid` gem/library)
26
+
27
+ Usage
28
+ -----
29
+
30
+ All supported templates have an implementation class under the `Tilt` module.
31
+ Each template implementation follows the exact same interface for creation
32
+ and rendering:
33
+
34
+ template = Tilt::HamlTemplate.new('templates/foo.haml')
35
+ output = template.render
36
+
37
+ The `render` method takes an optional evaluation scope and locals hash
38
+ arguments. In the following example, the template is evaluated within the
39
+ context of the person object and can access the locals `x` and `y`:
40
+
41
+ template = Tilt::ERBTemplate.new('templates/foo.erb')
42
+ joe = Person.find('joe')
43
+ output = template.render(joe, :x => 35, :y => 42)
44
+
45
+ The `render` method may be called multiple times without creating a new
46
+ template object. Continuing the previous example, we can render in Jane's
47
+ scope with a different set of locals:
48
+
49
+ jane = Person.find('jane')
50
+ output = template.render(jane, :x => 22, :y => nil)
51
+
52
+ Blocks can be passed to the render method for templates that support running
53
+ arbitrary ruby code and using `yield`. Assuming the following was in a file
54
+ named `foo.erb`:
55
+
56
+ Hey <%= yield %>!
57
+
58
+ The block passed to the `render` method is invoked on `yield`:
59
+
60
+ template = Tilt::ERBTemplate.new('foo.erb')
61
+ template.render { 'Joe' }
62
+ # => "Hey Joe!"
63
+
64
+ There's also a lightweight file extension to template engine mapping layer.
65
+ You can pass a filename or extension to `Tilt::[]` to retrieve the
66
+ corresponding implementation class:
67
+
68
+ Tilt['hello.erb']
69
+ # => Tilt::ERBTemplate
70
+
71
+ The `Tilt.new` works similarly but returns a new instance of the underlying
72
+ implementation class:
73
+
74
+ template = Tilt.new('templates/foo.erb')
75
+ output = template.render
@@ -0,0 +1,97 @@
1
+ task :default => :spec
2
+
3
+ # SPECS =====================================================================
4
+
5
+ desc 'Generate test coverage report'
6
+ task :rcov do
7
+ sh "rcov -Ilib:test test/*_test.rb"
8
+ end
9
+ desc 'Run specs with unit test style output'
10
+ task :test do |t|
11
+ sh 'bacon -qa'
12
+ end
13
+
14
+ desc 'Run specs with story style output'
15
+ task :spec do |t|
16
+ sh 'bacon -a'
17
+ end
18
+
19
+ # PACKAGING =================================================================
20
+
21
+ # load gemspec like github's gem builder to surface any SAFE issues.
22
+ Thread.new do
23
+ require 'rubygems/specification'
24
+ $spec = eval("$SAFE=3\n#{File.read('tilt.gemspec')}")
25
+ end.join
26
+
27
+ def package(ext='')
28
+ "dist/tilt-#{$spec.version}" + ext
29
+ end
30
+
31
+ desc 'Build packages'
32
+ task :package => %w[.gem .tar.gz].map {|e| package(e)}
33
+
34
+ desc 'Build and install as local gem'
35
+ task :install => package('.gem') do
36
+ sh "gem install #{package('.gem')}"
37
+ end
38
+
39
+ directory 'dist/'
40
+
41
+ file package('.gem') => %w[dist/ tilt.gemspec] + $spec.files do |f|
42
+ sh "gem build tilt.gemspec"
43
+ mv File.basename(f.name), f.name
44
+ end
45
+
46
+ file package('.tar.gz') => %w[dist/] + $spec.files do |f|
47
+ sh "git archive --format=tar HEAD | gzip > #{f.name}"
48
+ end
49
+
50
+ desc 'Upload gem and tar.gz distributables to rubyforge'
51
+ task :release => [package('.gem'), package('.tar.gz')] do |t|
52
+ sh <<-SH
53
+ rubyforge add_release sinatra tilt #{$spec.version} #{package('.gem')} &&
54
+ rubyforge add_file sinatra tilt #{$spec.version} #{package('.tar.gz')}
55
+ SH
56
+ end
57
+
58
+ # GEMSPEC ===================================================================
59
+
60
+ file 'tilt.gemspec' => FileList['{lib,test}/**','Rakefile'] do |f|
61
+ # read spec file and split out manifest section
62
+ spec = File.read(f.name)
63
+ parts = spec.split(" # = MANIFEST =\n")
64
+ # determine file list from git ls-files
65
+ files = `git ls-files`.
66
+ split("\n").sort.reject{ |file| file =~ /^\./ }.
67
+ map{ |file| " #{file}" }.join("\n")
68
+ # piece file back together and write...
69
+ parts[1] = " s.files = %w[\n#{files}\n ]\n"
70
+ spec = parts.join(" # = MANIFEST =\n")
71
+ spec.sub!(/s.date = '.*'/, "s.date = '#{Time.now.strftime("%Y-%m-%d")}'")
72
+ File.open(f.name, 'w') { |io| io.write(spec) }
73
+ puts "updated #{f.name}"
74
+ end
75
+
76
+ # DOC =======================================================================
77
+
78
+ # requires the hanna gem:
79
+ # gem install mislav-hanna --source=http://gems.github.com
80
+ desc 'Build API documentation (doc/api)'
81
+ task 'rdoc' => 'rdoc/index.html'
82
+ file 'rdoc/index.html' => FileList['lib/**/*.rb'] do |f|
83
+ rm_rf 'rdoc'
84
+ sh((<<-SH).gsub(/[\s\n]+/, ' ').strip)
85
+ hanna
86
+ --op doc/api
87
+ --promiscuous
88
+ --charset utf8
89
+ --fmt html
90
+ --inline-source
91
+ --line-numbers
92
+ --accessor option_accessor=RW
93
+ --main Tilt
94
+ --title 'Tilt API Documentation'
95
+ #{f.prerequisites.join(' ')}
96
+ SH
97
+ end
@@ -0,0 +1,297 @@
1
+ module Tilt
2
+ @template_mappings = {}
3
+
4
+ # Register a template implementation by file extension.
5
+ def self.register(ext, template_class)
6
+ ext = ext.sub(/^\./, '')
7
+ @template_mappings[ext.downcase] = template_class
8
+ end
9
+
10
+ # Create a new template for the given file using the file's extension
11
+ # to determine the the template mapping.
12
+ def self.new(file, line=nil, options={}, &block)
13
+ if template_class = self[File.basename(file)]
14
+ template_class.new(file, line, options, &block)
15
+ else
16
+ fail "No template engine registered for #{File.basename(file)}"
17
+ end
18
+ end
19
+
20
+ # Lookup a template class given for the given filename or file
21
+ # extension. Return nil when no implementation is found.
22
+ def self.[](filename)
23
+ ext = filename.to_s.downcase
24
+ until ext.empty?
25
+ return @template_mappings[ext] if @template_mappings.key?(ext)
26
+ ext = ext.sub(/^[^.]*\.?/, '')
27
+ end
28
+ nil
29
+ end
30
+
31
+ # Base class for template implementations. Subclasses must implement
32
+ # the #compile! method and one of the #evaluate or #template_source
33
+ # methods.
34
+ class Template
35
+ # Template source; loaded from a file or given directly.
36
+ attr_reader :data
37
+
38
+ # The name of the file where the template data was loaded from.
39
+ attr_reader :file
40
+
41
+ # The line number in #file where template data was loaded from.
42
+ attr_reader :line
43
+
44
+ # A Hash of template engine specific options. This is passed directly
45
+ # to the underlying engine and is not used by the generic template
46
+ # interface.
47
+ attr_reader :options
48
+
49
+ # Create a new template with the file, line, and options specified. By
50
+ # default, template data is read from the file specified. When a block
51
+ # is given, it should read template data and return as a String. When
52
+ # file is nil, a block is required.
53
+ def initialize(file=nil, line=1, options={}, &block)
54
+ raise ArgumentError, "file or block required" if file.nil? && block.nil?
55
+ @file = file
56
+ @line = line || 1
57
+ @options = options || {}
58
+ @reader = block || lambda { |t| File.read(file) }
59
+ end
60
+
61
+ # Render the template in the given scope with the locals specified. If a
62
+ # block is given, it is typically available within the template via
63
+ # +yield+.
64
+ def render(scope=Object.new, locals={}, &block)
65
+ if @data.nil?
66
+ @data = @reader.call(self)
67
+ compile!
68
+ end
69
+ evaluate scope, locals || {}, &block
70
+ end
71
+
72
+ # The filename used in backtraces to describe the template.
73
+ def eval_file
74
+ @file || '(__TEMPLATE__)'
75
+ end
76
+
77
+ protected
78
+ # Do whatever preparation is necessary to "compile" the template.
79
+ # Called immediately after template #data is loaded. Instance variables
80
+ # set in this method are available when #evaluate is called.
81
+ #
82
+ # Subclasses must provide an implementation of this method.
83
+ def compile!
84
+ raise NotImplementedError
85
+ end
86
+
87
+ # Process the template and return the result. Subclasses should override
88
+ # this method unless they implement the #template_source.
89
+ def evaluate(scope, locals, &block)
90
+ source, offset = local_assignment_code(locals)
91
+ source = [source, template_source].join("\n")
92
+ scope.instance_eval source, eval_file, line - offset
93
+ end
94
+
95
+ # Return a string containing the (Ruby) source code for the template. The
96
+ # default Template#evaluate implementation requires this method be
97
+ # defined.
98
+ def template_source
99
+ raise NotImplementedError
100
+ end
101
+
102
+ private
103
+ def local_assignment_code(locals)
104
+ return ['', 1] if locals.empty?
105
+ source = locals.collect { |k,v| "#{k} = locals[:#{k}]" }
106
+ [source.join("\n"), source.length]
107
+ end
108
+
109
+ def require_template_library(name)
110
+ warn "WARN: loading '#{name}' library in a non thread-safe way; " +
111
+ "explicit require '#{name}' suggested."
112
+ require name
113
+ end
114
+ end
115
+
116
+ # Extremely simple template cache implementation.
117
+ class Cache
118
+ def initialize
119
+ @cache = {}
120
+ end
121
+
122
+ def fetch(*key)
123
+ key = key.map { |part| part.to_s }.join(":")
124
+ @cache[key] ||= yield
125
+ end
126
+
127
+ def clear
128
+ @cache = {}
129
+ end
130
+ end
131
+
132
+ # Template Implementations ================================================
133
+
134
+ # The template source is evaluated as a Ruby string. The #{} interpolation
135
+ # syntax can be used to generated dynamic output.
136
+ class StringTemplate < Template
137
+ def compile!
138
+ @code = "%Q{#{data}}"
139
+ end
140
+
141
+ def template_source
142
+ @code
143
+ end
144
+ end
145
+ register 'str', StringTemplate
146
+
147
+ # ERB template implementation. See:
148
+ # http://www.ruby-doc.org/stdlib/libdoc/erb/rdoc/classes/ERB.html
149
+ #
150
+ # It's suggested that your program require 'erb' at load
151
+ # time when using this template engine.
152
+ class ERBTemplate < Template
153
+ def compile!
154
+ require_template_library 'erb' unless defined?(::ERB)
155
+ @engine = ::ERB.new(data, nil, nil, '@_out_buf')
156
+ end
157
+
158
+ def template_source
159
+ @engine.src
160
+ end
161
+
162
+ def evaluate(scope, locals, &block)
163
+ source, offset = local_assignment_code(locals)
164
+ source = [source, template_source].join("\n")
165
+
166
+ original_out_buf =
167
+ scope.instance_variables.any? { |var| var.to_sym == :@_out_buf } &&
168
+ scope.instance_variable_get(:@_out_buf)
169
+
170
+ scope.instance_eval source, eval_file, line - offset
171
+
172
+ output = scope.instance_variable_get(:@_out_buf)
173
+ scope.instance_variable_set(:@_out_buf, original_out_buf)
174
+
175
+ output
176
+ end
177
+
178
+ private
179
+
180
+ # ERB generates a line to specify the character coding of the generated
181
+ # source in 1.9. Account for this in the line offset.
182
+ if RUBY_VERSION >= '1.9.0'
183
+ def local_assignment_code(locals)
184
+ source, offset = super
185
+ [source, offset + 1]
186
+ end
187
+ end
188
+ end
189
+ %w[erb rhtml].each { |ext| register ext, ERBTemplate }
190
+
191
+ # Haml template implementation. See:
192
+ # http://haml.hamptoncatlin.com/
193
+ #
194
+ # It's suggested that your program require 'haml' at load
195
+ # time when using this template engine.
196
+ class HamlTemplate < Template
197
+ def compile!
198
+ require_template_library 'haml' unless defined?(::Haml::Engine)
199
+ @engine = ::Haml::Engine.new(data, haml_options)
200
+ end
201
+
202
+ def evaluate(scope, locals, &block)
203
+ @engine.render(scope, locals, &block)
204
+ end
205
+
206
+ private
207
+ def haml_options
208
+ options.merge(:filename => eval_file, :line => line)
209
+ end
210
+ end
211
+ register 'haml', HamlTemplate
212
+
213
+ # Sass template implementation. See:
214
+ # http://haml.hamptoncatlin.com/
215
+ #
216
+ # Sass templates do not support object scopes, locals, or yield.
217
+ #
218
+ # It's suggested that your program require 'sass' at load
219
+ # time when using this template engine.
220
+ class SassTemplate < Template
221
+ def compile!
222
+ require_template_library 'sass' unless defined?(::Sass::Engine)
223
+ @engine = ::Sass::Engine.new(data, sass_options)
224
+ end
225
+
226
+ def evaluate(scope, locals, &block)
227
+ @engine.render
228
+ end
229
+
230
+ private
231
+ def sass_options
232
+ options.merge(:filename => eval_file, :line => line)
233
+ end
234
+ end
235
+ register 'sass', SassTemplate
236
+
237
+ # Builder template implementation. See:
238
+ # http://builder.rubyforge.org/
239
+ #
240
+ # It's suggested that your program require 'builder' at load
241
+ # time when using this template engine.
242
+ class BuilderTemplate < Template
243
+ def compile!
244
+ require_template_library 'builder' unless defined?(::Builder)
245
+ end
246
+
247
+ def evaluate(scope, locals, &block)
248
+ xml = ::Builder::XmlMarkup.new(:indent => 2)
249
+ if data.respond_to?(:to_str)
250
+ locals[:xml] = xml
251
+ super(scope, locals, &block)
252
+ elsif data.kind_of?(Proc)
253
+ data.call(xml)
254
+ end
255
+ xml.target!
256
+ end
257
+
258
+ def template_source
259
+ data.to_str
260
+ end
261
+ end
262
+ register 'builder', BuilderTemplate
263
+
264
+ # Liquid template implementation. See:
265
+ # http://liquid.rubyforge.org/
266
+ #
267
+ # LiquidTemplate does not support scopes or yield blocks.
268
+ #
269
+ # It's suggested that your program require 'liquid' at load
270
+ # time when using this template engine.
271
+ class LiquidTemplate < Template
272
+ def compile!
273
+ require_template_library 'liquid' unless defined?(::Liquid::Template)
274
+ @engine = ::Liquid::Template.parse(data)
275
+ end
276
+
277
+ def evaluate(scope, locals, &block)
278
+ locals = locals.inject({}) { |hash,(k,v)| hash[k.to_s] = v ; hash }
279
+ @engine.render(locals)
280
+ end
281
+ end
282
+ register 'liquid', LiquidTemplate
283
+
284
+ # Discount Markdown implementation.
285
+ class RDiscountTemplate < Template
286
+ def compile!
287
+ require_template_library 'rdiscount' unless defined?(::RDiscount)
288
+ @engine = RDiscount.new(data)
289
+ end
290
+
291
+ def evaluate(scope, locals, &block)
292
+ @engine.to_html
293
+ end
294
+ end
295
+ register 'markdown', RDiscountTemplate
296
+
297
+ end
File without changes
@@ -0,0 +1,44 @@
1
+ require 'bacon'
2
+ require 'tilt'
3
+
4
+ describe "Tilt" do
5
+ class MockTemplate
6
+ attr_reader :args, :block
7
+ def initialize(*args, &block)
8
+ @args = args
9
+ @block = block
10
+ end
11
+ end
12
+
13
+ it "registers template implementation classes by file extension" do
14
+ lambda { Tilt.register('mock', MockTemplate) }.should.not.raise
15
+ end
16
+
17
+ it "looks up template implementation classes by file extension" do
18
+ impl = Tilt['mock']
19
+ impl.should.equal MockTemplate
20
+
21
+ impl = Tilt['.mock']
22
+ impl.should.equal MockTemplate
23
+ end
24
+
25
+ it "looks up template implementation classes with multiple file extensions" do
26
+ impl = Tilt['index.html.mock']
27
+ impl.should.equal MockTemplate
28
+ end
29
+
30
+ it "looks up template implementation classes by file name" do
31
+ impl = Tilt['templates/test.mock']
32
+ impl.should.equal MockTemplate
33
+ end
34
+
35
+ it "gives nil when no template implementation classes exist for a filename" do
36
+ Tilt['none'].should.be.nil
37
+ end
38
+
39
+ it "creates a new template instance given a filename" do
40
+ template = Tilt.new('foo.mock', 1, :key => 'val') { 'Hello World!' }
41
+ template.args.should.equal ['foo.mock', 1, {:key => 'val'}]
42
+ template.block.call.should.equal 'Hello World!'
43
+ end
44
+ end
@@ -0,0 +1,40 @@
1
+ require 'bacon'
2
+ require 'tilt'
3
+ require 'erb'
4
+
5
+ describe "Tilt::BuilderTemplate" do
6
+ it "is registered for '.builder' files" do
7
+ Tilt['test.builder'].should.equal Tilt::BuilderTemplate
8
+ Tilt['test.xml.builder'].should.equal Tilt::BuilderTemplate
9
+ end
10
+
11
+ it "compiles and evaluates the template on #render" do
12
+ template = Tilt::BuilderTemplate.new { |t| "xml.em 'Hello World!'" }
13
+ template.render.should.equal "<em>Hello World!</em>\n"
14
+ end
15
+
16
+ it "supports locals" do
17
+ template = Tilt::BuilderTemplate.new { "xml.em('Hey ' + name + '!')" }
18
+ template.render(Object.new, :name => 'Joe').should.equal "<em>Hey Joe!</em>\n"
19
+ end
20
+
21
+ it "is evaluated in the object scope provided" do
22
+ template = Tilt::BuilderTemplate.new { "xml.em('Hey ' + @name + '!')" }
23
+ scope = Object.new
24
+ scope.instance_variable_set :@name, 'Joe'
25
+ template.render(scope).should.equal "<em>Hey Joe!</em>\n"
26
+ end
27
+
28
+ it "evaluates template_source with yield support" do
29
+ template = Tilt::BuilderTemplate.new { "xml.em('Hey ' + yield + '!')" }
30
+ template.render { 'Joe' }.should.equal "<em>Hey Joe!</em>\n"
31
+ end
32
+
33
+ it "calls a block directly when" do
34
+ template =
35
+ Tilt::BuilderTemplate.new do |t|
36
+ lambda { |xml| xml.em('Hey Joe!') }
37
+ end
38
+ template.render.should.equal "<em>Hey Joe!</em>\n"
39
+ end
40
+ end
@@ -0,0 +1,78 @@
1
+ require 'bacon'
2
+ require 'tilt'
3
+ require 'erb'
4
+
5
+ describe "Tilt::ERBTemplate" do
6
+ it "is registered for '.erb' files" do
7
+ Tilt['test.erb'].should.equal Tilt::ERBTemplate
8
+ Tilt['test.html.erb'].should.equal Tilt::ERBTemplate
9
+ end
10
+
11
+ it "is registered for '.rhtml' files" do
12
+ Tilt['test.rhtml'].should.equal Tilt::ERBTemplate
13
+ end
14
+
15
+ it "compiles and evaluates the template on #render" do
16
+ template = Tilt::ERBTemplate.new { |t| "Hello World!" }
17
+ template.render.should.equal "Hello World!"
18
+ end
19
+
20
+ it "supports locals" do
21
+ template = Tilt::ERBTemplate.new { 'Hey <%= name %>!' }
22
+ template.render(Object.new, :name => 'Joe').should.equal "Hey Joe!"
23
+ end
24
+
25
+ it "is evaluated in the object scope provided" do
26
+ template = Tilt::ERBTemplate.new { 'Hey <%= @name %>!' }
27
+ scope = Object.new
28
+ scope.instance_variable_set :@name, 'Joe'
29
+ template.render(scope).should.equal "Hey Joe!"
30
+ end
31
+
32
+ it "evaluates template_source with yield support" do
33
+ template = Tilt::ERBTemplate.new { 'Hey <%= yield %>!' }
34
+ template.render { 'Joe' }.should.equal "Hey Joe!"
35
+ end
36
+
37
+ it "reports the file and line properly in backtraces without locals" do
38
+ data = File.read(__FILE__).split("\n__END__\n").last
39
+ fail unless data[0] == ?<
40
+ template = Tilt::ERBTemplate.new('test.erb', 11) { data }
41
+ begin
42
+ template.render
43
+ flunk 'should have raised an exception'
44
+ rescue => boom
45
+ boom.should.be.kind_of NameError
46
+ line = boom.backtrace.first
47
+ file, line, meth = line.split(":")
48
+ file.should.equal 'test.erb'
49
+ line.should.equal '13'
50
+ end
51
+ end
52
+
53
+ it "reports the file and line properly in backtraces with locals" do
54
+ data = File.read(__FILE__).split("\n__END__\n").last
55
+ fail unless data[0] == ?<
56
+ template = Tilt::ERBTemplate.new('test.erb', 1) { data }
57
+ begin
58
+ template.render(nil, :name => 'Joe', :foo => 'bar')
59
+ flunk 'should have raised an exception'
60
+ rescue => boom
61
+ boom.should.be.kind_of RuntimeError
62
+ line = boom.backtrace.first
63
+ file, line, meth = line.split(":")
64
+ file.should.equal 'test.erb'
65
+ line.should.equal '6'
66
+ end
67
+ end
68
+ end
69
+
70
+ __END__
71
+ <html>
72
+ <body>
73
+ <h1>Hey <%= name %>!</h1>
74
+
75
+
76
+ <p><% fail %></p>
77
+ </body>
78
+ </html>
@@ -0,0 +1,79 @@
1
+ require 'bacon'
2
+ require 'tilt'
3
+
4
+ begin
5
+ class ::MockError < NameError
6
+ end
7
+
8
+ require 'haml'
9
+ describe "Tilt::HamlTemplate" do
10
+ it "is registered for '.haml' files" do
11
+ Tilt['test.haml'].should.equal Tilt::HamlTemplate
12
+ end
13
+
14
+ it "compiles and evaluates the template on #render" do
15
+ template = Tilt::HamlTemplate.new { |t| "%p Hello World!" }
16
+ template.render.should.equal "<p>Hello World!</p>\n"
17
+ end
18
+
19
+ it "supports locals" do
20
+ template = Tilt::HamlTemplate.new { "%p= 'Hey ' + name + '!'" }
21
+ template.render(Object.new, :name => 'Joe').should.equal "<p>Hey Joe!</p>\n"
22
+ end
23
+
24
+ it "is evaluated in the object scope provided" do
25
+ template = Tilt::HamlTemplate.new { "%p= 'Hey ' + @name + '!'" }
26
+ scope = Object.new
27
+ scope.instance_variable_set :@name, 'Joe'
28
+ template.render(scope).should.equal "<p>Hey Joe!</p>\n"
29
+ end
30
+
31
+ it "evaluates template_source with yield support" do
32
+ template = Tilt::HamlTemplate.new { "%p= 'Hey ' + yield + '!'" }
33
+ template.render { 'Joe' }.should.equal "<p>Hey Joe!</p>\n"
34
+ end
35
+
36
+ it "reports the file and line properly in backtraces without locals" do
37
+ data = File.read(__FILE__).split("\n__END__\n").last
38
+ fail unless data[0] == ?%
39
+ template = Tilt::HamlTemplate.new('test.haml', 10) { data }
40
+ begin
41
+ template.render
42
+ fail 'should have raised an exception'
43
+ rescue => boom
44
+ boom.should.be.kind_of NameError
45
+ line = boom.backtrace.first
46
+ file, line, meth = line.split(":")
47
+ file.should.equal 'test.haml'
48
+ line.should.equal '12'
49
+ end
50
+ end
51
+
52
+ it "reports the file and line properly in backtraces with locals" do
53
+ data = File.read(__FILE__).split("\n__END__\n").last
54
+ fail unless data[0] == ?%
55
+ template = Tilt::HamlTemplate.new('test.haml') { data }
56
+ begin
57
+ res = template.render(Object.new, :name => 'Joe', :foo => 'bar')
58
+ rescue => boom
59
+ boom.should.be.kind_of ::MockError
60
+ line = boom.backtrace.first
61
+ file, line, meth = line.split(":")
62
+ file.should.equal 'test.haml'
63
+ line.should.equal '5'
64
+ end
65
+ end
66
+ end
67
+
68
+ rescue LoadError => boom
69
+ warn "Tilt::HamlTemplate (disabled)\n"
70
+ end
71
+
72
+ __END__
73
+ %html
74
+ %body
75
+ %h1= "Hey #{name}"
76
+
77
+ = raise MockError
78
+
79
+ %p we never get here
@@ -0,0 +1,24 @@
1
+ require 'bacon'
2
+ require 'tilt'
3
+
4
+ begin
5
+ require 'liquid'
6
+ describe "Tilt::LiquidTemplate" do
7
+ it "is registered for '.liquid' files" do
8
+ Tilt['test.liquid'].should.equal Tilt::LiquidTemplate
9
+ end
10
+
11
+ it "compiles and evaluates the template on #render" do
12
+ template = Tilt::LiquidTemplate.new { |t| "Hello World!" }
13
+ template.render.should.equal "Hello World!"
14
+ end
15
+
16
+ it "supports locals" do
17
+ template = Tilt::LiquidTemplate.new { "Hey {{ name }}!" }
18
+ template.render(nil, :name => 'Joe').should.equal "Hey Joe!"
19
+ end
20
+ end
21
+
22
+ rescue LoadError => boom
23
+ warn "Tilt::LiquidTemplate (disabled)\n"
24
+ end
@@ -0,0 +1,18 @@
1
+ require 'bacon'
2
+ require 'tilt'
3
+
4
+ begin
5
+ require 'rdiscount'
6
+ describe "Tilt::RDiscountTemplate" do
7
+ it "is registered for '.markdown' files" do
8
+ Tilt['test.markdown'].should.equal Tilt::RDiscountTemplate
9
+ end
10
+
11
+ it "compiles and evaluates the template on #render" do
12
+ template = Tilt::RDiscountTemplate.new { |t| "# Hello World!" }
13
+ template.render.should.equal "<h1>Hello World!</h1>\n"
14
+ end
15
+ end
16
+ rescue LoadError => boom
17
+ warn "Tilt::RDiscountTemplate (disabled)\n"
18
+ end
@@ -0,0 +1,19 @@
1
+ require 'bacon'
2
+ require 'tilt'
3
+
4
+ begin
5
+ require 'haml'
6
+ describe "Tilt::SassTemplate" do
7
+ it "is registered for '.sass' files" do
8
+ Tilt['test.sass'].should.equal Tilt::SassTemplate
9
+ end
10
+
11
+ it "compiles and evaluates the template on #render" do
12
+ template = Tilt::SassTemplate.new { |t| "#main\n :background-color #0000ff" }
13
+ template.render.should.equal "#main {\n background-color: #0000ff; }\n"
14
+ end
15
+ end
16
+
17
+ rescue LoadError => boom
18
+ warn "Tilt::SassTemplate (disabled)\n"
19
+ end
@@ -0,0 +1,77 @@
1
+ require 'bacon'
2
+ require 'tilt'
3
+
4
+ describe "Tilt::StringTemplate" do
5
+ it "is registered for '.str' files" do
6
+ Tilt['test.str'].should.equal Tilt::StringTemplate
7
+ end
8
+
9
+ it "compiles and evaluates the template on #render" do
10
+ template = Tilt::StringTemplate.new { |t| "Hello World!" }
11
+ template.render.should.equal "Hello World!"
12
+ end
13
+
14
+ it "supports locals" do
15
+ template = Tilt::StringTemplate.new { 'Hey #{name}!' }
16
+ template.render(Object.new, :name => 'Joe').should.equal "Hey Joe!"
17
+ end
18
+
19
+ it "is evaluated in the object scope provided" do
20
+ template = Tilt::StringTemplate.new { 'Hey #{@name}!' }
21
+ scope = Object.new
22
+ scope.instance_variable_set :@name, 'Joe'
23
+ template.render(scope).should.equal "Hey Joe!"
24
+ end
25
+
26
+ it "evaluates template_source with yield support" do
27
+ template = Tilt::StringTemplate.new { 'Hey #{yield}!' }
28
+ template.render { 'Joe' }.should.equal "Hey Joe!"
29
+ end
30
+
31
+ it "renders multiline templates" do
32
+ template = Tilt::StringTemplate.new { "Hello\nWorld!\n" }
33
+ template.render.should.equal "Hello\nWorld!\n"
34
+ end
35
+
36
+ it "reports the file and line properly in backtraces without locals" do
37
+ data = File.read(__FILE__).split("\n__END__\n").last
38
+ fail unless data[0] == ?<
39
+ template = Tilt::StringTemplate.new('test.str', 11) { data }
40
+ begin
41
+ template.render
42
+ flunk 'should have raised an exception'
43
+ rescue => boom
44
+ boom.should.be.kind_of NameError
45
+ line = boom.backtrace.first
46
+ file, line, meth = line.split(":")
47
+ file.should.equal 'test.str'
48
+ line.should.equal '13'
49
+ end
50
+ end
51
+
52
+ it "reports the file and line properly in backtraces with locals" do
53
+ data = File.read(__FILE__).split("\n__END__\n").last
54
+ fail unless data[0] == ?<
55
+ template = Tilt::StringTemplate.new('test.str', 1) { data }
56
+ begin
57
+ template.render(nil, :name => 'Joe', :foo => 'bar')
58
+ flunk 'should have raised an exception'
59
+ rescue => boom
60
+ boom.should.be.kind_of RuntimeError
61
+ line = boom.backtrace.first
62
+ file, line, meth = line.split(":")
63
+ file.should.equal 'test.str'
64
+ line.should.equal '6'
65
+ end
66
+ end
67
+ end
68
+
69
+ __END__
70
+ <html>
71
+ <body>
72
+ <h1>Hey #{name}!</h1>
73
+
74
+
75
+ <p>#{fail}</p>
76
+ </body>
77
+ </html>
@@ -0,0 +1,100 @@
1
+ require 'bacon'
2
+ require 'tilt'
3
+
4
+ describe "Tilt::Template" do
5
+ it "raises ArgumentError when a file or block not given" do
6
+ lambda { Tilt::Template.new }.should.raise ArgumentError
7
+ end
8
+
9
+ it "can be constructed with a file" do
10
+ inst = Tilt::Template.new('foo.erb')
11
+ inst.file.should.equal 'foo.erb'
12
+ end
13
+
14
+ it "can be constructed with a file and line" do
15
+ inst = Tilt::Template.new('foo.erb', 55)
16
+ inst.file.should.equal 'foo.erb'
17
+ inst.line.should.equal 55
18
+ end
19
+
20
+ it "uses the filename provided for #eval_file" do
21
+ inst = Tilt::Template.new('foo.erb', 55)
22
+ inst.eval_file.should.equal 'foo.erb'
23
+ end
24
+
25
+ it "uses a default filename for #eval_file when no file provided" do
26
+ inst = Tilt::Template.new { 'Hi' }
27
+ inst.eval_file.should.not.be.nil
28
+ inst.eval_file.should.not.include "\n"
29
+ end
30
+
31
+ it "can be constructed with a data loading block" do
32
+ lambda {
33
+ Tilt::Template.new { |template| "Hello World!" }
34
+ }.should.not.raise
35
+ end
36
+
37
+ it "raises NotImplementedError when #compile! not defined" do
38
+ inst = Tilt::Template.new { |template| "Hello World!" }
39
+ lambda { inst.render }.should.raise NotImplementedError
40
+ end
41
+
42
+ class CompilingMockTemplate < Tilt::Template
43
+ def compile!
44
+ data.should.not.be.nil
45
+ @compiled = true
46
+ end
47
+ def compiled? ; @compiled ; end
48
+ end
49
+
50
+ it "raises NotImplementedError when #evaluate or #template_source not defined" do
51
+ inst = CompilingMockTemplate.new { |t| "Hello World!" }
52
+ lambda { inst.render }.should.raise NotImplementedError
53
+ inst.should.be.compiled
54
+ end
55
+
56
+ class SimpleMockTemplate < CompilingMockTemplate
57
+ def evaluate(scope, locals, &block)
58
+ should.be.compiled
59
+ scope.should.not.be.nil
60
+ locals.should.not.be.nil
61
+ "<em>#{@data}</em>"
62
+ end
63
+ end
64
+
65
+ it "compiles and evaluates the template on #render" do
66
+ inst = SimpleMockTemplate.new { |t| "Hello World!" }
67
+ inst.render.should.equal "<em>Hello World!</em>"
68
+ inst.should.be.compiled
69
+ end
70
+
71
+ class SourceGeneratingMockTemplate < CompilingMockTemplate
72
+ def template_source
73
+ "foo = [] ; foo << %Q{#{data}} ; foo.join"
74
+ end
75
+ end
76
+
77
+ it "evaluates template_source with locals support" do
78
+ inst = SourceGeneratingMockTemplate.new { |t| 'Hey #{name}!' }
79
+ inst.render(Object.new, :name => 'Joe').should.equal "Hey Joe!"
80
+ inst.should.be.compiled
81
+ end
82
+
83
+ class Person
84
+ attr_accessor :name
85
+ def initialize(name)
86
+ @name = name
87
+ end
88
+ end
89
+
90
+ it "evaluates template_source in the object scope provided" do
91
+ inst = SourceGeneratingMockTemplate.new { |t| 'Hey #{@name}!' }
92
+ scope = Person.new('Joe')
93
+ inst.render(scope).should.equal "Hey Joe!"
94
+ end
95
+
96
+ it "evaluates template_source with yield support" do
97
+ inst = SourceGeneratingMockTemplate.new { |t| 'Hey #{yield}!' }
98
+ inst.render(Object.new){ 'Joe' }.should.equal "Hey Joe!"
99
+ end
100
+ end
@@ -0,0 +1,45 @@
1
+ Gem::Specification.new do |s|
2
+ s.specification_version = 2 if s.respond_to? :specification_version=
3
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
4
+
5
+ s.name = 'tilt'
6
+ s.version = '0.2'
7
+ s.date = '2009-06-07'
8
+
9
+ s.description = "Generic interface to multiple Ruby template engines"
10
+ s.summary = s.description
11
+
12
+ s.authors = ["Ryan Tomayko"]
13
+ s.email = "r@tomayko.com"
14
+
15
+ # = MANIFEST =
16
+ s.files = %w[
17
+ COPYING
18
+ README.md
19
+ Rakefile
20
+ lib/tilt.rb
21
+ test/.bacon
22
+ test/spec_tilt.rb
23
+ test/spec_tilt_buildertemplate.rb
24
+ test/spec_tilt_erbtemplate.rb
25
+ test/spec_tilt_hamltemplate.rb
26
+ test/spec_tilt_liquid_template.rb
27
+ test/spec_tilt_rdiscount.rb
28
+ test/spec_tilt_sasstemplate.rb
29
+ test/spec_tilt_stringtemplate.rb
30
+ test/spec_tilt_template.rb
31
+ tilt.gemspec
32
+ ]
33
+ # = MANIFEST =
34
+
35
+ s.test_files = s.files.select {|path| path =~ /^test\/spec_.*.rb/}
36
+
37
+ s.extra_rdoc_files = %w[COPYING]
38
+
39
+ s.has_rdoc = true
40
+ s.homepage = "http://github.com/rtomayko/tilt/"
41
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Tilt", "--main", "Tilt"]
42
+ s.require_paths = %w[lib]
43
+ s.rubyforge_project = 'wink'
44
+ s.rubygems_version = '1.1.1'
45
+ end
metadata ADDED
@@ -0,0 +1,80 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tilt
3
+ version: !ruby/object:Gem::Version
4
+ version: "0.2"
5
+ platform: ruby
6
+ authors:
7
+ - Ryan Tomayko
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-06-07 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: Generic interface to multiple Ruby template engines
17
+ email: r@tomayko.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - COPYING
24
+ files:
25
+ - COPYING
26
+ - README.md
27
+ - Rakefile
28
+ - lib/tilt.rb
29
+ - test/.bacon
30
+ - test/spec_tilt.rb
31
+ - test/spec_tilt_buildertemplate.rb
32
+ - test/spec_tilt_erbtemplate.rb
33
+ - test/spec_tilt_hamltemplate.rb
34
+ - test/spec_tilt_liquid_template.rb
35
+ - test/spec_tilt_rdiscount.rb
36
+ - test/spec_tilt_sasstemplate.rb
37
+ - test/spec_tilt_stringtemplate.rb
38
+ - test/spec_tilt_template.rb
39
+ - tilt.gemspec
40
+ has_rdoc: true
41
+ homepage: http://github.com/rtomayko/tilt/
42
+ post_install_message:
43
+ rdoc_options:
44
+ - --line-numbers
45
+ - --inline-source
46
+ - --title
47
+ - Tilt
48
+ - --main
49
+ - Tilt
50
+ require_paths:
51
+ - lib
52
+ required_ruby_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: "0"
57
+ version:
58
+ required_rubygems_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: "0"
63
+ version:
64
+ requirements: []
65
+
66
+ rubyforge_project: wink
67
+ rubygems_version: 1.3.1
68
+ signing_key:
69
+ specification_version: 2
70
+ summary: Generic interface to multiple Ruby template engines
71
+ test_files:
72
+ - test/spec_tilt.rb
73
+ - test/spec_tilt_buildertemplate.rb
74
+ - test/spec_tilt_erbtemplate.rb
75
+ - test/spec_tilt_hamltemplate.rb
76
+ - test/spec_tilt_liquid_template.rb
77
+ - test/spec_tilt_rdiscount.rb
78
+ - test/spec_tilt_sasstemplate.rb
79
+ - test/spec_tilt_stringtemplate.rb
80
+ - test/spec_tilt_template.rb