Skip to content

Commit

Permalink
locate mustache view classes based on filename
Browse files Browse the repository at this point in the history
This is based largely on defunkt's mustache sinatra extension:
http://github.com/defunkt/mustache/blob/master/lib/mustache/sinatra.rb
  • Loading branch information
rtomayko committed Oct 14, 2009
1 parent b2ffd09 commit 36c4feb
Show file tree
Hide file tree
Showing 2 changed files with 114 additions and 18 deletions.
89 changes: 81 additions & 8 deletions lib/tilt.rb
Original file line number Diff line number Diff line change
Expand Up @@ -334,24 +334,97 @@ def evaluate(scope, locals, &block)
end
register 'markdown', RDiscountTemplate

# Mustache template implementation. See:
# Mustache is written and maintained by Chris Wanstrath. See:
# http://github.com/defunkt/mustache
#
# It's suggested that your program require 'mustache' at load
# time when using this template engine.
#
# Mustache templates support the following options:
#
# * :namespace - The class or module where View classes are located.
# If you have Hurl::App::Views, namespace should be Hurl:App. This
# defaults to Object, causing ::Views to be searched for classes.
#
# * :mustaches - Where mustache views (.rb files) are located, or nil
# disable auto-requiring of views based on template names. By default,
# the view file is assumed to be in the same directory as the template
# file.
#
# All other options are assumed to be attribute writer's on the Mustache
# class and are set when a template is compiled. They are:
#
# * :path - The base path where mustache templates (.html files) are
# located. This defaults to the current working directory.
#
# * :template_extension - The file extension used on mustache templates.
# The default is 'html'.
#
class MustacheTemplate < Template
attr_reader :engine

# Locates and compiles the Mustache object used to create new views. The
def compile!
require_template_library 'mustache' unless defined?(::Mustache)
@engine = Mustache.new
@engine.template = data

@view_name = Mustache.classify(name.to_s)
@namespace = options[:namespace] || Object


# Figure out which Mustache class to use.
@engine =
if @namespace.const_defined?(:Views) &&
@namespace::Views.const_defined?(@view_name)
@namespace::Views.const_get(@view_name)
elsif load_mustache_view
engine = @namespace::Views.const_get(@view_name)
engine.template = data
engine
else
Mustache
end

# set options on the view class
options.each do |key, value|
@engine.send("#{key}=", value) if @engine.respond_to? "#{key}="
end
end

def evaluate(scope, locals, &block)
def evaluate(scope=nil, locals={}, &block)
# Create a new instance for playing with
instance = @engine.new

# Copy instance variables from scope to the view
scope.instance_variables.each do |name|
instance.instance_variable_set(name, scope.instance_variable_get(name))
end

# Locals get added to the view's context
locals.each do |local, value|
@engine[local] = value
instance[local] = value
end

@engine[:yield] = block.call if block
@engine.to_html
# If we're passed a block it's a subview. Sticking it in yield
# lets us use {{yield}} in layout.html to render the actual page.
instance[:yield] = block.call if block

instance.template = data unless instance.compiled?

instance.to_html
end

# Require the mustache view lib if it exists.
def load_mustache_view
return if name.nil?
path = "#{options[:mustaches]}/#{name}.rb"
if options[:mustaches] && File.exist?(path)
require path.chomp('.rb')
path
elsif File.exist?(path = file.sub(/\.[^\/]+$/, '.rb'))
require path.chomp('.rb')
path
end
end
end
register 'mustache', MustacheTemplate

end
43 changes: 33 additions & 10 deletions test/spec_tilt_mustachetemplate.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,41 @@
template = Tilt::MustacheTemplate.new { "<p>Hey {{yield}}!</p>" }
template.render { 'Joe' }.should.equal "<p>Hey Joe!</p>"
end

module Views
class Foo < Mustache
attr_reader :foo
end
end

it "locates views defined at the top-level by default" do
template = Tilt::MustacheTemplate.new('foo.mustache') { "<p>Hey {{foo}}!</p>" }
template.compile
template.engine.should.equal Views::Foo
end

module Bar
module Views
class Bizzle < Mustache
end
end
end

it "locates views defined in a custom namespace" do
template = Tilt::MustacheTemplate.new('bizzle.mustache', :namespace => Bar) { "<p>Hello World!</p>" }
template.compile
template.engine.should.equal Bar::Views::Bizzle
template.render.should.equal "<p>Hello World!</p>"
end

it "copies instance variables from scope object" do
template = Tilt::MustacheTemplate.new('foo.mustache') { "<p>Hey {{foo}}!</p>" }
scope = Object.new
scope.instance_variable_set(:@foo, 'Jane!')
template.render(scope).should.equal "<p>Hey Jane!!</p>"
end
end

rescue LoadError => boom
warn "Tilt::MustacheTemplate (disabled)\n"
end

__END__
<html>
<body>
<h1>Hey {{name}}</h1>

<p>{{fail}}</p>
<p>we never get here</p>
</body>
</html>

0 comments on commit 36c4feb

Please sign in to comment.