tilt 0.2

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