Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

bring bundled tilt up to 0.8 for jruby fixes and cleaner APIs

  • Loading branch information...
commit b72eb59999a89713ebafc87dc7c1fc469840982b 1 parent 754f116
@rtomayko rtomayko authored
Showing with 183 additions and 96 deletions.
  1. +2 −2 lib/sinatra/base.rb
  2. +181 −94 lib/sinatra/tilt.rb
View
4 lib/sinatra/base.rb
@@ -8,8 +8,8 @@
# require tilt if available; fall back on bundled version.
begin
require 'tilt'
- if Tilt::VERSION < '0.7'
- warn "WARN: sinatra requires tilt >= 0.7; you have #{Tilt::VERSION}. " +
+ if Tilt::VERSION < '0.8'
+ warn "WARN: sinatra requires tilt >= 0.8; you have #{Tilt::VERSION}. " +
"loading bundled version..."
Object.send :remove_const, :Tilt
raise LoadError
View
275 lib/sinatra/tilt.rb
@@ -1,5 +1,7 @@
+require 'digest/md5'
+
module Tilt
- VERSION = '0.7'
+ VERSION = '0.8'
@template_mappings = {}
@@ -113,8 +115,8 @@ def initialize(file=nil, line=1, options={}, &block)
end
# used to generate unique method names for template compilation
- stamp = (Time.now.to_f * 10000).to_i
- @_prefix = "__tilt_O#{object_id.to_s(16)}T#{stamp.to_s(16)}"
+ @stamp = (Time.now.to_f * 10000).to_i
+ @compiled_method_names = {}
# load template data and prepare
@reader = block || lambda { |t| File.read(@file) }
@@ -151,6 +153,16 @@ def eval_file
def initialize_engine
end
+ # Like Kernel::require but issues a warning urging a manual require when
+ # running under a threaded environment.
+ def require_template_library(name)
+ if Thread.list.size > 1
+ warn "WARN: tilt autoloading '#{name}' in a non thread-safe way; " +
+ "explicit require '#{name}' suggested."
+ end
+ require name
+ end
+
# Do whatever preparation is necessary to setup the underlying template
# engine. Called immediately after template data is loaded. Instance
# variables set in this method are available when #evaluate is called.
@@ -175,54 +187,111 @@ def prepare
# specified and with support for yielding to the block.
def evaluate(scope, locals, &block)
if scope.respond_to?(:__tilt__)
- method_name = compiled_method_name(locals.keys.hash)
+ method_name = compiled_method_name(locals.keys)
if scope.respond_to?(method_name)
- # fast path
- scope.send method_name, locals, &block
+ scope.send(method_name, locals, &block)
else
- # compile first and then run
compile_template_method(method_name, locals)
- scope.send method_name, locals, &block
+ scope.send(method_name, locals, &block)
end
else
- source, offset = local_assignment_code(locals)
- source = [source, template_source].join("\n")
- scope.instance_eval source, eval_file, line - offset
+ evaluate_source(scope, locals, &block)
end
end
- # Return a string containing the (Ruby) source code for the template. The
- # default Template#evaluate implementation requires this method be
- # defined and guarantees correct file/line handling, custom scopes, and
- # support for template compilation when the scope object allows it.
- def template_source
+ # Generates all template source by combining the preamble, template, and
+ # postamble and returns a two-tuple of the form: [source, offset], where
+ # source is the string containing (Ruby) source code for the template and
+ # offset is the integer line offset where line reporting should begin.
+ #
+ # Template subclasses may override this method when they need complete
+ # control over source generation or want to adjust the default line
+ # offset. In most cases, overriding the #precompiled_template method is
+ # easier and more appropriate.
+ def precompiled(locals)
+ preamble = precompiled_preamble(locals)
+ parts = [
+ preamble,
+ precompiled_template(locals),
+ precompiled_postamble(locals)
+ ]
+ [parts.join("\n"), preamble.count("\n") + 1]
+ end
+
+ # A string containing the (Ruby) source code for the template. The
+ # default Template#evaluate implementation requires either this method
+ # or the #precompiled method be overridden. When defined, the base
+ # Template guarantees correct file/line handling, locals support, custom
+ # scopes, and support for template compilation when the scope object
+ # allows it.
+ def precompiled_template(locals)
raise NotImplementedError
end
+ # Generates preamble code for initializing template state, and performing
+ # locals assignment. The default implementation performs locals
+ # assignment only. Lines included in the preamble are subtracted from the
+ # source line offset, so adding code to the preamble does not effect line
+ # reporting in Kernel::caller and backtraces.
+ def precompiled_preamble(locals)
+ locals.map { |k,v| "#{k} = locals[:#{k}]" }.join("\n")
+ end
+
+ # Generates postamble code for the precompiled template source. The
+ # string returned from this method is appended to the precompiled
+ # template source.
+ def precompiled_postamble(locals)
+ ''
+ end
+
+ # The unique compiled method name for the locals keys provided.
+ def compiled_method_name(locals_keys)
+ @compiled_method_names[locals_keys] ||=
+ generate_compiled_method_name(locals_keys)
+ end
+
private
- def local_assignment_code(locals)
- return ['', 1] if locals.empty?
- source = locals.collect { |k,v| "#{k} = locals[:#{k}]" }
- [source.join("\n"), source.length]
+ # Evaluate the template source in the context of the scope object.
+ def evaluate_source(scope, locals, &block)
+ source, offset = precompiled(locals)
+ scope.instance_eval(source, eval_file, line - offset)
end
- def compiled_method_name(locals_hash)
- "#{@_prefix}L#{locals_hash.to_s(16).sub('-', 'n')}"
+ # JRuby doesn't allow Object#instance_eval to yield to the block it's
+ # closed over. This is by design and (ostensibly) something that will
+ # change in MRI, though no current MRI version tested (1.8.6 - 1.9.2)
+ # exhibits the behavior. More info here:
+ #
+ # http://jira.codehaus.org/browse/JRUBY-2599
+ #
+ # Additionally, JRuby's eval line reporting is off by one compared to
+ # all MRI versions tested.
+ #
+ # We redefine evaluate_source to work around both issues.
+ if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby'
+ undef evaluate_source
+ def evaluate_source(scope, locals, &block)
+ source, offset = precompiled(locals)
+ file, lineno = eval_file, (line - offset) - 1
+ scope.instance_eval { Kernel::eval(source, binding, file, lineno) }
+ end
+ end
+
+ def generate_compiled_method_name(locals_keys)
+ parts = [object_id, @stamp] + locals_keys.map { |k| k.to_s }.sort
+ digest = Digest::MD5.hexdigest(parts.join(':'))
+ "__tilt_#{digest}"
end
def compile_template_method(method_name, locals)
- source, offset = local_assignment_code(locals)
- source = [source, template_source].join("\n")
+ source, offset = precompiled(locals)
offset += 1
-
- # add the new method
CompileSite.module_eval <<-RUBY, eval_file, line - offset
def #{method_name}(locals)
#{source}
end
RUBY
- # setup a finalizer to remove the newly added method
ObjectSpace.define_finalizer self,
Template.compiled_template_method_remover(CompileSite, method_name)
end
@@ -240,14 +309,6 @@ def self.garbage_collect_compiled_template_method(site, method_name)
end
end
end
-
- def require_template_library(name)
- if Thread.list.size > 1
- warn "WARN: tilt autoloading '#{name}' in a non thread-safe way; " +
- "explicit require '#{name}' suggested."
- end
- require name
- end
end
# Extremely simple template cache implementation. Calling applications
@@ -282,7 +343,7 @@ def prepare
@code = "%Q{#{data}}"
end
- def template_source
+ def precompiled_template(locals)
@code
end
end
@@ -293,7 +354,8 @@ def template_source
# http://www.ruby-doc.org/stdlib/libdoc/erb/rdoc/classes/ERB.html
class ERBTemplate < Template
def initialize_engine
- require_template_library 'erb' unless defined? ::ERB
+ return if defined? ::ERB
+ require_template_library 'erb'
end
def prepare
@@ -301,38 +363,37 @@ def prepare
@engine = ::ERB.new(data, options[:safe], options[:trim], @outvar)
end
- def template_source
+ def precompiled_template(locals)
@engine.src
end
- def evaluate(scope, locals, &block)
- preserve_outvar_value(scope) { super }
+ def precompiled_preamble(locals)
+ <<-RUBY
+ begin
+ __original_outvar = #{@outvar} if defined?(#{@outvar})
+ #{super}
+ RUBY
end
- private
- # Retains the previous value of outvar when configured to use
- # an instance variable. This allows multiple templates to be rendered
- # within the context of an object without overwriting the outvar.
- def preserve_outvar_value(scope)
- if @outvar[0] == ?@
- previous = scope.instance_variable_get(@outvar)
- output = yield
- scope.instance_variable_set(@outvar, previous)
- output
- else
- yield
- end
+ def precompiled_postamble(locals)
+ <<-RUBY
+ #{super}
+ ensure
+ #{@outvar} = __original_outvar
+ end
+ RUBY
end
# ERB generates a line to specify the character coding of the generated
# source in 1.9. Account for this in the line offset.
if RUBY_VERSION >= '1.9.0'
- def local_assignment_code(locals)
+ def precompiled(locals)
source, offset = super
[source, offset + 1]
end
end
end
+
%w[erb rhtml].each { |ext| register ext, ERBTemplate }
@@ -340,7 +401,8 @@ def local_assignment_code(locals)
# http://www.kuwata-lab.com/erubis/
class ErubisTemplate < ERBTemplate
def initialize_engine
- require_template_library 'erubis' unless defined? ::Erubis
+ return if defined? ::Erubis
+ require_template_library 'erubis'
end
def prepare
@@ -349,15 +411,18 @@ def prepare
@engine = ::Erubis::Eruby.new(data, options)
end
- def template_source
- ["#{@outvar} = _buf = ''", @engine.src, "_buf.to_s"].join(";")
+ def precompiled_preamble(locals)
+ [super, "#{@outvar} = _buf = ''"].join("\n")
end
- private
- # Erubis doesn't have ERB's line-off-by-one under 1.9 problem. Override
- # and adjust back.
+ def precompiled_postamble(locals)
+ ["_buf", super].join("\n")
+ end
+
+ # Erubis doesn't have ERB's line-off-by-one under 1.9 problem.
+ # Override and adjust back.
if RUBY_VERSION >= '1.9.0'
- def local_assignment_code(locals)
+ def precompiled(locals)
source, offset = super
[source, offset - 1]
end
@@ -370,26 +435,48 @@ def local_assignment_code(locals)
# http://haml.hamptoncatlin.com/
class HamlTemplate < Template
def initialize_engine
- require_template_library 'haml' unless defined? ::Haml::Engine
+ return if defined? ::Haml::Engine
+ require_template_library 'haml'
end
def prepare
- @engine = ::Haml::Engine.new(data, haml_options)
+ options = @options.merge(:filename => eval_file, :line => line)
+ @engine = ::Haml::Engine.new(data, options)
+ end
+
+ def evaluate(scope, locals, &block)
+ if @engine.respond_to?(:precompiled_method_return_value, true)
+ super
+ else
+ @engine.render(scope, locals, &block)
+ end
end
# Precompiled Haml source. Taken from the precompiled_with_ambles
# method in Haml::Precompiler:
# http://github.com/nex3/haml/blob/master/lib/haml/precompiler.rb#L111-126
- def template_source
+ def precompiled_template(locals)
+ @engine.precompiled
+ end
+
+ def precompiled_preamble(locals)
+ local_assigns = super
@engine.instance_eval do
<<-RUBY
- _haml_locals = locals
begin
extend Haml::Helpers
_hamlout = @haml_buffer = Haml::Buffer.new(@haml_buffer, #{options_for_buffer.inspect})
_erbout = _hamlout.buffer
__in_erb_template = true
- #{precompiled}
+ _haml_locals = locals
+ #{local_assigns}
+ RUBY
+ end
+ end
+
+ def precompiled_postamble(locals)
+ @engine.instance_eval do
+ <<-RUBY
#{precompiled_method_return_value}
ensure
@haml_buffer = @haml_buffer.upper
@@ -397,16 +484,6 @@ def template_source
RUBY
end
end
-
- private
- def local_assignment_code(locals)
- source, offset = super
- [source, offset + 6]
- end
-
- def haml_options
- options.merge(:filename => eval_file, :line => line)
- end
end
register 'haml', HamlTemplate
@@ -417,7 +494,8 @@ def haml_options
# Sass templates do not support object scopes, locals, or yield.
class SassTemplate < Template
def initialize_engine
- require_template_library 'sass' unless defined? ::Sass::Engine
+ return if defined? ::Sass::Engine
+ require_template_library 'sass'
end
def prepare
@@ -442,7 +520,8 @@ def sass_options
# Less templates do not support object scopes, locals, or yield.
class LessTemplate < Template
def initialize_engine
- require_template_library 'less' unless defined? ::Less::Engine
+ return if defined? ::Less::Engine
+ require_template_library 'less'
end
def prepare
@@ -460,7 +539,8 @@ def evaluate(scope, locals, &block)
# http://builder.rubyforge.org/
class BuilderTemplate < Template
def initialize_engine
- require_template_library 'builder' unless defined?(::Builder)
+ return if defined?(::Builder)
+ require_template_library 'builder'
end
def prepare
@@ -477,7 +557,7 @@ def evaluate(scope, locals, &block)
xml.target!
end
- def template_source
+ def precompiled_template(locals)
data.to_str
end
end
@@ -499,7 +579,8 @@ def template_source
# time when using this template engine.
class LiquidTemplate < Template
def initialize_engine
- require_template_library 'liquid' unless defined? ::Liquid::Template
+ return if defined? ::Liquid::Template
+ require_template_library 'liquid'
end
def prepare
@@ -532,15 +613,17 @@ def flags
end
def initialize_engine
- require_template_library 'rdiscount' unless defined? ::RDiscount
+ return if defined? ::RDiscount
+ require_template_library 'rdiscount'
end
def prepare
@engine = RDiscount.new(data, *flags)
+ @output = nil
end
def evaluate(scope, locals, &block)
- @engine.to_html
+ @output ||= @engine.to_html
end
end
register 'markdown', RDiscountTemplate
@@ -552,15 +635,17 @@ def evaluate(scope, locals, &block)
# http://redcloth.org/
class RedClothTemplate < Template
def initialize_engine
- require_template_library 'redcloth' unless defined? ::RedCloth
+ return if defined? ::RedCloth
+ require_template_library 'redcloth'
end
def prepare
@engine = RedCloth.new(data)
+ @output = nil
end
def evaluate(scope, locals, &block)
- @engine.to_html
+ @output ||= @engine.to_html
end
end
register 'textile', RedClothTemplate
@@ -576,7 +661,8 @@ class MustacheTemplate < Template
attr_reader :engine
def initialize_engine
- require_template_library 'mustache' unless defined? ::Mustache
+ return if defined? ::Mustache
+ require_template_library 'mustache'
end
def prepare
@@ -622,19 +708,19 @@ def evaluate(scope=nil, locals={}, &block)
# engine.
class RDocTemplate < Template
def initialize_engine
- unless defined?(::RDoc::Markup)
- require_template_library 'rdoc/markup'
- require_template_library 'rdoc/markup/to_html'
- end
+ return if defined?(::RDoc::Markup)
+ require_template_library 'rdoc/markup'
+ require_template_library 'rdoc/markup/to_html'
end
def prepare
markup = RDoc::Markup::ToHtml.new
@engine = markup.convert(data)
+ @output = nil
end
def evaluate(scope, locals, &block)
- @engine.to_s
+ @output ||= @engine.to_s
end
end
register 'rdoc', RDocTemplate
@@ -644,15 +730,16 @@ def evaluate(scope, locals, &block)
# http://jashkenas.github.com/coffee-script/
class CoffeeTemplate < Template
def initialize_engine
- require_template_library 'coffee-script' unless defined? ::CoffeeScript
+ return if defined? ::CoffeeScript
+ require_template_library 'coffee-script'
end
def prepare
- @engine = ::CoffeeScript::compile(data, options)
+ @output = nil
end
def evaluate(scope, locals, &block)
- @engine
+ @output ||= ::CoffeeScript::compile(data, options)
end
end
register 'coffee', CoffeeTemplate
Please sign in to comment.
Something went wrong with that request. Please try again.