Permalink
Browse files

Replace tilt extension with Sinatra extension

  • Loading branch information...
1 parent 4a508fa commit ad7724bc39ca11235306c393296c2667c8b16714 @pukkaone committed Apr 4, 2012
View
1 ruby/core/lib/mullet.rb
@@ -3,6 +3,7 @@
require 'mullet/scope'
require 'mullet/template_error'
require 'mullet/version'
+require 'mullet/view'
require 'mullet/html/layout'
require 'mullet/html/template'
View
20 ruby/core/lib/mullet/default_nested_scope.rb
@@ -9,11 +9,11 @@ class DefaultNestedScope
# Constructor
#
- # @param dataObjects
+ # @param data_objects
# scopes in outer to inner order
- def initialize(*dataObjects)
+ def initialize(*data_objects)
@scopes = []
- dataObjects.each {|data| push_scope(data) }
+ push_scope(*data_objects)
end
# Resolves variable name to value.
@@ -32,13 +32,15 @@ def get_variable_value(name)
return NOT_FOUND
end
- # Adds a nested scope to search in subsequent lookups.
+ # Adds new innermost nested scopes.
#
- # @param data
- # data object
- def push_scope(data)
- @scopes.push(
- data.respond_to?(:get_variable_value) ? data : DefaultScope.new(data))
+ # @param data_objects
+ # scopes in outer to inner order
+ def push_scope(*data_objects)
+ data_objects.each do |data|
+ @scopes.push(
+ data.respond_to?(:get_variable_value) ? data : DefaultScope.new(data))
+ end
end
# Removes innermost nested scope.
View
16 ruby/core/lib/mullet/default_scope.rb
@@ -7,12 +7,12 @@ module Mullet
# mechanisms are tried in this order:
#
# * If the variable name is `.`, then return the object.
- # * If the object is a `Hash`, then use _key_ as the key to retrieve the
- # value from the hash.
# * If the object has a method named _key_ taking no parameters, then use
# the value returned from calling the method.
# * If the object has an instance variable named @_key_, then use the
# variable value.
+ # * If the object is a `Hash`, then use _key_ as the key to retrieve the
+ # value from the hash.
#
# If the value is a Proc, then use the value returned from calling it.
class DefaultScope
@@ -27,12 +27,6 @@ def fetch_impl(name)
return @data
end
- # Is the variable name a key in a Hash?
- if @data.respond_to?(:fetch)
- # Call the block if the key is not found.
- return @data.fetch(name) {|k| @data.fetch(k.to_s(), NOT_FOUND) }
- end
-
# Does the variable name match a method name in the object?
if @data.respond_to?(name)
method = @data.method(name)
@@ -47,6 +41,12 @@ def fetch_impl(name)
return @data.instance_variable_get(variable)
end
+ # Is the variable name a key in a Hash?
+ if @data.respond_to?(:fetch)
+ # If the key was not found, then try to find it as a String.
+ return @data.fetch(name) {|k| @data.fetch(k.to_s(), NOT_FOUND) }
+ end
+
return NOT_FOUND
end
View
100 ruby/core/lib/mullet/sinatra.rb
@@ -0,0 +1,100 @@
+require 'mullet/sinatra/engine'
+require 'sinatra/base'
+
+module Mullet
+
+ # Sinatra extension for rendering views with Mullet.
+ #
+ # Example:
+ #
+ # require 'mullet/sinatra'
+ #
+ # class App < Sinatra::Base
+ # register Mullet::Sinatra
+ #
+ # set :mullet, {
+ # # path to folder containing template .html files. If not set, then
+ # # default is `settings.views`
+ # template_path: "views"
+ # }
+ #
+ # get '/' do
+ # mullet :index
+ # end
+ # end
+ #
+ # When `mullet :index` is called, the engine will attempt to load a Ruby view
+ # class named `Views::Index` from the `views/index.rb` file. If the view
+ # class is not found, then the engine will render the template file
+ # `view/index.html` directly.
+ #
+ # By default, the rendered page will passed to a layout view named `:layout`.
+ # The rendered page content is passed to the layout template in the variable
+ # `content`.
+ module Sinatra
+ module Helpers
+ @@engine = Mullet::Sinatra::Engine.new()
+
+ def mullet(view_name, options={}, locals={})
+ # The options hash may contain a key :locals with the value being a
+ # hash mapping variable names to values.
+ locals.merge!(options.delete(:locals) || {})
+
+ # Get application settings.
+ view_options = { root: settings.root, views: settings.views }
+
+ if settings.respond_to?(:mullet)
+ view_options = settings.mullet.merge(view_options)
+ end
+
+ view_options.merge!(options)
+
+ # Copy instance variables set by Sinatra application.
+ application_data = Object.new()
+ instance_variables.each do |name|
+ application_data.instance_variable_set(
+ name, instance_variable_get(name))
+ end
+
+ # Render view.
+ template = @@engine.get_template(view_name, view_options)
+ view_class = @@engine.get_view_class(view_name, view_options)
+ view = view_class.new()
+ view.set_model(application_data, locals)
+ output = ''
+ template.execute(view, output)
+
+ # Render layout.
+ layout_name = :layout
+ if options[:layout]
+ layout_name = options[:layout]
+ end
+
+ if layout_name != false
+ # If configured layout is true or nil, then use :layout.
+ if layout_name == true || !layout_name
+ layout_name = :layout
+ end
+
+ layout = @@engine.get_template(layout_name, view_options)
+ view_class = @@engine.get_view_class(layout_name, view_options)
+ view = view_class.new()
+ view.set_model(application_data, locals, { content: output })
+ layout_output = ''
+ layout.execute(view, layout_output)
+ output = layout_output
+ end
+
+ return output
+ end
+ end
+
+ # Called when this extension is registered.
+ def self.registered(app)
+ app.helpers Mullet::Sinatra::Helpers
+ end
+
+ end
+end
+
+Sinatra.register Mullet::Sinatra
View
102 ruby/core/lib/mullet/sinatra/engine.rb
@@ -0,0 +1,102 @@
+require 'mullet'
+
+module Mullet; module Sinatra
+
+ # Loads view classes and template files.
+ class Engine
+
+ def initialize()
+ @class_cache = Hash.new()
+ @loader = Mullet::HTML::TemplateLoader.new(nil)
+ end
+
+ # Gets template.
+ #
+ # @param [Symbol] view_name
+ # view name
+ # @param [Hash] options
+ # options
+ # @return template
+ def get_template(view_name, options)
+ template_path = options[:template_path] || options[:views]
+ @loader.template_path = template_path
+ return @loader.load("#{view_name.to_s()}.html")
+ end
+
+ # Gets view class.
+ #
+ # @param [Symbol] view_name
+ # view name
+ # @param [Hash] options
+ # options
+ # @return view class
+ def get_view_class(view_name, options)
+ view_class = @class_cache.fetch(view_name, nil)
+ if view_class == nil
+ view_class = find_class(view_name.to_s(), options)
+ @class_cache.store(view_name, view_class)
+ end
+ return view_class
+ end
+
+ private
+
+ # Gets the named view class.
+ #
+ # @param [String] view_name
+ # view name
+ # @param [Hash] options
+ # options
+ # @return view class, or `View` if not found
+ def find_class(view_name, options)
+ relative_view_path = options[:views]
+ if relative_view_path.start_with?(options[:root])
+ relative_view_path = relative_view_path[options[:root].size()..-1]
+ end
+ relative_view_path.sub!(/^\//, '')
+
+ # Construct string in form sub_folder/user_list
+ relative_view_name = File.join(relative_view_path, view_name)
+
+ # Convert view name from the format sub_folder/user_list to the format
+ # SubFolder::UserList
+ class_name = relative_view_name.split('/').map do |namespace|
+ namespace.split(/[-_]/).map do |part|
+ part[0] = part[0].chr.upcase
+ part
+ end.join
+ end.join('::')
+
+ # Is the class already defined?
+ if const = const_get!(class_name)
+ return const
+ end
+
+ full_view_name = File.join(options[:views], view_name)
+ if File.exists?("#{full_view_name}.rb")
+ require full_view_name
+ else
+ return View
+ end
+
+ if const = const_get!(class_name)
+ return const
+ end
+ return View
+ end
+
+ # Finds constant by fully qualified name.
+ #
+ # @param [String] name
+ # fully qualified name to find
+ # @return constant, or `nil` if not found
+ def const_get!(name)
+ name.split('::').inject(Object) do |cur_class, part|
+ cur_class.const_get(part)
+ end
+ rescue NameError
+ nil
+ end
+ end
+
+end; end
View
37 ruby/core/lib/mullet/tilt.rb
@@ -1,37 +0,0 @@
-require 'mullet'
-require 'tilt'
-
-module Mullet; module Tilt
-
- # Registers the Mullet template engine in Tilt to handle file names with the
- # `html` extension.
- class MulletTemplate < ::Tilt::Template
-
- def initialize_engine
- @@loader = Mullet::HTML::TemplateLoader.new(nil)
- @@parser = Mullet::HTML::TemplateParser.new(@@loader)
- end
-
- def prepare
- if file
- @@loader.template_path = File.dirname(file)
- end
-
- @template = @@parser.parse(data)
- end
-
- def evaluate(scope, locals, &block)
- if block
- content = block.call()
- locals = locals.merge(content: content)
- end
-
- output = ''
- @template.execute(DefaultNestedScope.new(scope, locals), output)
- return output
- end
-
- ::Tilt.register self, :html
- end
-
-end; end
View
29 ruby/core/lib/mullet/view.rb
@@ -0,0 +1,29 @@
+require 'mullet/default_nested_scope'
+
+module Mullet
+
+ # Adapts model data for rendering in a template. Applications will typically
+ # define subclasses with attributes that will be referenced by name from the
+ # templates.
+ class View
+
+ # Sets model to adapt. Applications do not have to call this method
+ # directly. The template engine will call this method implicitly.
+ #
+ # @param data_objects
+ # scopes in outer to inner order
+ def set_model(*data_objects)
+ @model = DefaultNestedScope.new(*data_objects)
+ end
+
+ # Resolves variable name to value from the model.
+ #
+ # @param [Symbol] name
+ # variable name
+ # @return variable value
+ def fetch(name)
+ return @model.get_variable_value(name)
+ end
+ end
+
+end
View
12 ruby/sample-sinatra/app.rb
@@ -1,18 +1,16 @@
$LOAD_PATH << File.expand_path('../core/lib', File.dirname(__FILE__))
require 'i18n'
-require 'mullet/tilt'
require 'sinatra'
+require 'mullet/sinatra'
I18n.load_path += Dir.glob(File.join(settings.root, 'locale', '*.{rb,yml}'))
-helpers do
- def mullet(*args)
- render(:html, *args)
- end
-end
-
get '/' do
@title = 'Title'
mullet :index, locals: { name: 'Chris' }
end
+
+get '/now' do
+ mullet :now, locals: { first_name: 'John', last_name: 'Smith' }
+end
View
4 ruby/sample-sinatra/views/now.html
@@ -0,0 +1,4 @@
+<div>
+ <h1 data-text="full_name"></h1>
+ <h1 data-text="now"></h1>
+</div>
View
17 ruby/sample-sinatra/views/now.rb
@@ -0,0 +1,17 @@
+require 'Date'
+require 'mullet/view'
+
+module Views
+
+ class Now < Mullet::View
+
+ def full_name()
+ return "#{fetch(:first_name)} #{fetch(:last_name)}"
+ end
+
+ def now()
+ return Date.today.iso8601
+ end
+ end
+
+end

0 comments on commit ad7724b

Please sign in to comment.