Simplify compiler and document it

This commit is contained in:
ccocchi 2012-03-27 18:35:36 +02:00
parent d9e93b2c53
commit 03bc0f2644
5 changed files with 110 additions and 58 deletions

View File

@ -1,24 +1,63 @@
module RablFastJson module RablFastJson
#
# Class that will compile RABL source code into a hash
# representing data structure
#
class Compiler class Compiler
include Helpers include Helpers
def initialize(context = nil) def initialize
@context = context
@glue_count = 0 @glue_count = 0
end end
#
# Compile from source code and return the CompiledTemplate
# created.
#
def compile_source(source) def compile_source(source)
@template = CompiledTemplate.new @template = CompiledTemplate.new
instance_eval(source) instance_eval(source)
@template @template
end end
#
# Same as compile_source but from a block
#
def compile_block(&block) def compile_block(&block)
@template = {} @template = {}
instance_eval(&block) instance_eval(&block)
@template @template
end end
#
# Sets the object to be used as the data for the template
# Example:
# object :@user
# object :@user, :root => :author
#
def object(data, options = {})
data, name = extract_data_and_name(data)
@template.data = data
@template.root_name = options[:root] || name
end
#
# Sets a collection to be used as data for the template
# Example:
# collection :@users
# collection :@users, :root => :morons
#
def collection(data, options = {})
object(data)
@template.root_name = options[:root] if root_given?(options)
end
#
# Includes the attribute or method in the output
# Example:
# attributes :id, :name
# attribute :email => :super_secret
#
def attribute(*args) def attribute(*args)
if args.first.is_a?(Hash) if args.first.is_a?(Hash)
args.first.each_pair { |k, v| @template[v] = k } args.first.each_pair { |k, v| @template[v] = k }
@ -28,17 +67,31 @@ module RablFastJson
end end
alias_method :attributes, :attribute alias_method :attributes, :attribute
#
# Creates a child node to be included in the output.
# name_or data can be an object or collection or a method to call on the data. It
# accepts :root and :partial options.
# Notes that partial and blocks are not compatible
# Example:
# child(:@posts, :root => :posts) { attribute :id }
# child(:posts, :partial => 'posts/base')
#
def child(name_or_data, options = {}, &block) def child(name_or_data, options = {}, &block)
data, name = extract_data_and_name(name_or_data) data, name = extract_data_and_name(name_or_data)
name = options[:root] if root_given?(options) name = options[:root] if root_given?(options)
if partial_given?(options) if partial_given?(options)
template = Library.instance.get(options[:partial], @context) template = Library.instance.get(options[:partial])
@template[name] = template.merge!(:_data => data) @template[name] = template.merge!(:_data => data)
else else
_compile_sub_template(name, data, &block) _compile_sub_template(name, data, &block)
end end
end end
#
# Glues data from a child node to the output
# Example:
# glue(:@user) { attribute :name }
#
def glue(data, &block) def glue(data, &block)
return unless block_given? return unless block_given?
name = :"_glue#{@glue_count}" name = :"_glue#{@glue_count}"
@ -46,6 +99,14 @@ module RablFastJson
_compile_sub_template(name, data, &block) _compile_sub_template(name, data, &block)
end end
#
# Creates an arbitrary node in the json output.
# It accepts :if option to create conditionnal nodes. The current data will
# be passed to the block so it is advised to use it instead of ivars.
# Example:
# node(:name) { |user| user.first_name + user.last_name }
# node(:role, if: ->(u) { !u.admin? }) { |u| u.role }
#
def node(name, options = {}, &block) def node(name, options = {}, &block)
condition = options[:if] condition = options[:if]
@ -61,24 +122,24 @@ module RablFastJson
end end
alias_method :code, :node alias_method :code, :node
def collection(data, options = {}) #
object(data) # Extends an existing rabl template
@template.root_name = options[:root] if root_given?(options) # Example:
end # extends 'users/base'
#
def extends(path) def extends(path)
t = Library.instance.get(path, @context) t = Library.instance.get(path)
@template.merge!(t.source) @template.merge!(t.source)
end end
def object(data, options = {})
data, name = extract_data_and_name(data)
@template.data = data
@template.root_name = options[:root] || name
end
protected protected
#
# Extract data root_name and root name
# Example:
# :@users -> [:@users, nil]
# :@users => :authors -> [:@users, :authors]
#
def extract_data_and_name(name_or_data) def extract_data_and_name(name_or_data)
case name_or_data case name_or_data
when Symbol when Symbol
@ -94,8 +155,8 @@ module RablFastJson
end end
end end
def _compile_sub_template(name, data, &block) def _compile_sub_template(name, data, &block) #:nodoc:
compiler = Compiler.new(@context) compiler = Compiler.new
template = compiler.compile_block(&block) template = compiler.compile_block(&block)
@template[name] = template.merge!(:_data => data) @template[name] = template.merge!(:_data => data)
end end

View File

@ -1,10 +1,10 @@
module RablFastJson module RablFastJson
module Helpers module Helpers
def root_given?(options) def root_given?(options) #:nodoc:
options[:root].present? options[:root].present?
end end
def partial_given?(options) def partial_given?(options) #:nodoc:
options[:partial].present? options[:partial].present?
end end
end end

View File

@ -14,22 +14,22 @@ module RablFastJson
path = context.instance_variable_get(:@virtual_path) path = context.instance_variable_get(:@virtual_path)
@view_renderer = context.instance_variable_get(:@view_renderer) @view_renderer = context.instance_variable_get(:@view_renderer)
compiled_template = get_compiled_template(path, source, context) compiled_template = get_compiled_template(path, source)
compiled_template.context = context compiled_template.context = context
body = compiled_template.render body = compiled_template.render
ActiveSupport::JSON.encode(compiled_template.has_root_name? ? { compiled_template.root_name => body } : body) ActiveSupport::JSON.encode(compiled_template.has_root_name? ? { compiled_template.root_name => body } : body)
end end
def get_compiled_template(path, source, context) def get_compiled_template(path, source)
# @cached_templates[path] ||= # @cached_templates[path] ||=
Compiler.new(context).compile_source(source) Compiler.new.compile_source(source)
end end
def get(path, context) def get(path)
template = @cached_templates[path] template = @cached_templates[path]
return template if !template.nil? return template if !template.nil?
t = @view_renderer.lookup_context.find_template(path, [], false) t = @view_renderer.lookup_context.find_template(path, [], false)
get_compiled_template(path, t.source, context) get_compiled_template(path, t.source)
end end
end end
end end

View File

@ -1,6 +1,5 @@
module RablFastJson module RablFastJson
class CompiledTemplate class CompiledTemplate
attr_accessor :source, :data, :root_name, :context attr_accessor :source, :data, :root_name, :context
delegate :[], :[]=, :merge!, :to => :source delegate :[], :[]=, :merge!, :to => :source
@ -9,38 +8,10 @@ module RablFastJson
@source = {} @source = {}
end end
#
# def data=(symbol)
# raise "Data passed directly to template should be a symbol" if !symbol.is_a?(Symbol)
# @data = symbol
# end
#
def get_object_from_context
@object = @context.instance_variable_get(@data) if @data
end
def get_assigns_from_context
@context.instance_variable_get(:@_assigns).each_pair { |k, v|
instance_variable_set("@#{k}", v) unless k.start_with?('_') || k == @data
}
end
def has_root_name? def has_root_name?
!@root_name.nil? !@root_name.nil?
end end
def method_missing(name, *args, &block)
@context.respond_to?(name) ? @context.send(name, *args, &block) : super
end
def partial(template_path, options = {})
raise "No object was given to partial" if options[:object].blank?
object = options[:object]
template = Library.instance.get(template_path, @context)
object.respond_to?(:each) ? template.render_collection(object) : template.render_resource(object)
end
def render def render
get_object_from_context get_object_from_context
get_assigns_from_context get_assigns_from_context
@ -86,5 +57,27 @@ module RablFastJson
collection.inject([]) { |output, o| output << render_resource(o, source) } collection.inject([]) { |output, o| output << render_resource(o, source) }
end end
def method_missing(name, *args, &block)
@context.respond_to?(name) ? @context.send(name, *args, &block) : super
end
def partial(template_path, options = {})
raise "No object was given to partial" if options[:object].blank?
object = options[:object]
template = Library.instance.get(template_path, @context)
object.respond_to?(:each) ? template.render_collection(object) : template.render_resource(object)
end
protected
def get_object_from_context
@object = @context.instance_variable_get(@data) if @data
end
def get_assigns_from_context
@context.instance_variable_get(:@_assigns).each_pair { |k, v|
instance_variable_set("@#{k}", v) unless k.start_with?('_') || k == @data
}
end
end end
end end

View File

@ -3,10 +3,8 @@ require 'test_helper'
class CompilerTest < ActiveSupport::TestCase class CompilerTest < ActiveSupport::TestCase
setup do setup do
@context = Context.new
@user = User.new @user = User.new
@context.set_assign('user', @user) @compiler = RablFastJson::Compiler.new
@compiler = RablFastJson::Compiler.new(@context)
end end
test "compiler return a compiled template" do test "compiler return a compiled template" do
@ -112,7 +110,7 @@ class CompilerTest < ActiveSupport::TestCase
test "extends use other template source as itself" do test "extends use other template source as itself" do
template = mock('template', :source => { :id => :id }) template = mock('template', :source => { :id => :id })
RablFastJson::Library.reset_instance RablFastJson::Library.reset_instance
RablFastJson::Library.instance.stub(:get).with('users/base', @context).and_return(template) RablFastJson::Library.instance.stub(:get).with('users/base').and_return(template)
t = @compiler.compile_source(%{ extends 'users/base' }) t = @compiler.compile_source(%{ extends 'users/base' })
assert_equal({ :id => :id }, t.source) assert_equal({ :id => :id }, t.source)
end end