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
#
# Class that will compile RABL source code into a hash
# representing data structure
#
class Compiler
include Helpers
def initialize(context = nil)
@context = context
def initialize
@glue_count = 0
end
#
# Compile from source code and return the CompiledTemplate
# created.
#
def compile_source(source)
@template = CompiledTemplate.new
instance_eval(source)
@template
end
#
# Same as compile_source but from a block
#
def compile_block(&block)
@template = {}
instance_eval(&block)
@template
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)
if args.first.is_a?(Hash)
args.first.each_pair { |k, v| @template[v] = k }
@ -28,17 +67,31 @@ module RablFastJson
end
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)
data, name = extract_data_and_name(name_or_data)
name = options[:root] if root_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)
else
_compile_sub_template(name, data, &block)
end
end
#
# Glues data from a child node to the output
# Example:
# glue(:@user) { attribute :name }
#
def glue(data, &block)
return unless block_given?
name = :"_glue#{@glue_count}"
@ -46,6 +99,14 @@ module RablFastJson
_compile_sub_template(name, data, &block)
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)
condition = options[:if]
@ -61,24 +122,24 @@ module RablFastJson
end
alias_method :code, :node
def collection(data, options = {})
object(data)
@template.root_name = options[:root] if root_given?(options)
end
#
# Extends an existing rabl template
# Example:
# extends 'users/base'
#
def extends(path)
t = Library.instance.get(path, @context)
t = Library.instance.get(path)
@template.merge!(t.source)
end
def object(data, options = {})
data, name = extract_data_and_name(data)
@template.data = data
@template.root_name = options[:root] || name
end
protected
#
# Extract data root_name and root name
# Example:
# :@users -> [:@users, nil]
# :@users => :authors -> [:@users, :authors]
#
def extract_data_and_name(name_or_data)
case name_or_data
when Symbol
@ -94,8 +155,8 @@ module RablFastJson
end
end
def _compile_sub_template(name, data, &block)
compiler = Compiler.new(@context)
def _compile_sub_template(name, data, &block) #:nodoc:
compiler = Compiler.new
template = compiler.compile_block(&block)
@template[name] = template.merge!(:_data => data)
end

View File

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

View File

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

View File

@ -1,6 +1,5 @@
module RablFastJson
class CompiledTemplate
attr_accessor :source, :data, :root_name, :context
delegate :[], :[]=, :merge!, :to => :source
@ -9,38 +8,10 @@ module RablFastJson
@source = {}
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?
!@root_name.nil?
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
get_object_from_context
get_assigns_from_context
@ -86,5 +57,27 @@ module RablFastJson
collection.inject([]) { |output, o| output << render_resource(o, source) }
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

View File

@ -3,10 +3,8 @@ require 'test_helper'
class CompilerTest < ActiveSupport::TestCase
setup do
@context = Context.new
@user = User.new
@context.set_assign('user', @user)
@compiler = RablFastJson::Compiler.new(@context)
@compiler = RablFastJson::Compiler.new
end
test "compiler return a compiled template" do
@ -112,7 +110,7 @@ class CompilerTest < ActiveSupport::TestCase
test "extends use other template source as itself" do
template = mock('template', :source => { :id => :id })
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' })
assert_equal({ :id => :id }, t.source)
end