Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 32 additions & 17 deletions lib/ruby-handlebars.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
require_relative 'ruby-handlebars/parser'
require_relative 'ruby-handlebars/tree'
require_relative 'ruby-handlebars/template'
require_relative 'ruby-handlebars/helper'
require_relative 'ruby-handlebars/helpers/register_default_helpers'
require_relative 'ruby-handlebars/escapers/html_escaper'
require "parslet"

require_relative "ruby-handlebars/context"
require_relative "ruby-handlebars/helper"
require_relative "ruby-handlebars/parser"
require_relative "ruby-handlebars/safe_string"
require_relative "ruby-handlebars/template"
require_relative "ruby-handlebars/tree"
require_relative "ruby-handlebars/escapers/html_escaper"
require_relative "ruby-handlebars/helpers/register_default_helpers"

module Handlebars
MissingPartial = Class.new(StandardError)

def self.escape_expression(expression)
Escapers::HTMLEscaper.escape(expression)
end

class Handlebars
attr_reader :escaper

Expand All @@ -22,30 +31,36 @@ def compile(template)
Template.new(self, template_to_ast(template))
end

def register_helper(name, &fn)
@helpers[name.to_s] = Helper.new(self, fn)
def register_helper(name, as: false, &fn)
(as ? @as_helpers : @helpers)[name.to_s] = Helper.new(self, fn)
end

def register_as_helper(name, &fn)
@as_helpers[name.to_s] = Helper.new(self, fn)
end

def get_helper(name)
@helpers[name.to_s]
end

def get_as_helper(name)
@as_helpers[name.to_s]
def get_helper(name, as: false)
(as ? @as_helpers : @helpers)[name.to_s]
end

def register_partial(name, content)
@partials[name.to_s] = { content: content, compiled: nil }
end

def get_partial(name)
raise(::Handlebars::MissingPartial, "Partial \"#{name}\" not registered.") unless @partials[name.to_s]
def get_partial(name, raise_on_missing: true)
partial = @partials[name.to_s]

if partial.nil?
raise(::Handlebars::MissingPartial, "Partial \"#{name}\" not registered.") if raise_on_missing
return nil
end

# compile the partial now that we know it's going to be used.
partial[:compiled] ||= Template.new(self, template_to_ast(partial[:content]))
end

@partials[name.to_s][:compiled] ||= Template.new(self, template_to_ast(@partials[name.to_s][:content]))
def escape_expression(expression)
@escaper.escape(expression)
end

def set_escaper(escaper = nil)
Expand Down
103 changes: 38 additions & 65 deletions lib/ruby-handlebars/context.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,56 +2,31 @@

module Handlebars
class Context
PATH_REGEX = /\.\.\/|[^.\/]+/

class Data
extend Forwardable

def_delegators :@hash, :[]=, :keys, :key?, :empty?, :merge!, :map

def initialize(hash)
@hash = hash
end

def [](k)
return {} unless @hash.respond_to?(:has_key?)
return @hash[k] if @hash.has_key?(k)
return @hash[k.to_s] if @hash.has_key?(k.to_s)

return true if k == :true
return false if k == :false
return nil if k == :nil || k == :null
to_number(k.to_s) || nil
end
extend Forwardable

def dup
self.class.new(@hash.dup) # shallow copy.
end

def has_key?(_k)
true # yeah, we'll respond to anything.
end

def respond_to?(val, _ = false)
%w[[] has_key?].include?(val.to_s) ? true : false
end

private
PATH_REGEX = /\.\.\/|[^.\/]+/

def to_number(val)
result = Float(val)
(result % 1).zero? ? result.to_i : result
rescue ArgumentError, TypeError
false
end
end
def_delegators :@hbs, :escaper, :get_helper, :get_partial, :register_partial

def initialize(hbs, data)
@hbs = hbs
@data = Data.new(data)
@data = data || {}
end

def get(path)
path = path.to_s
return true if path == 'true'
return false if path == 'false'
return nil if %w[nil null undefined].include?(path)

if (number = parse_number(path))
number
else
resolve(path)
end
end

def resolve(path)
items = path.to_s.scan(PATH_REGEX)
items[-1] = "#{items.shift}#{items[-1]}" if items.first == '@'

Expand All @@ -68,28 +43,24 @@ def get(path)
current
end

def escaper
@hbs.escaper
end

def get_helper(name)
@hbs.get_helper(name)
end

def get_as_helper(name)
@hbs.get_as_helper(name)
def escape(string)
escaper.escape(string)
end

def get_partial(name)
@hbs.get_partial(name)
def safe(string)
SafeString.new(string)
end

def add_item(key, value)
locals[key.to_sym] = value
end

def add_items(hash)
hash.map { |k, v| add_item(k, v) }
def add_items(enumerable)
if enumerable.is_a?(Array)
enumerable.each_with_index { |v, k| add_item(k.to_s, v) }
else
enumerable.map { |k, v| add_item(k, v) }
end
end

def with_nested_context
Expand Down Expand Up @@ -124,24 +95,26 @@ def with_temporary_context(args = {})
private

def locals
@locals ||= Data.new({})
@locals ||= {}
end

def parse_number(val)
result = Float(val)
(result % 1).zero? ? result.to_i : result
rescue ArgumentError, TypeError
false
end

def get_attribute(item, attribute)
sym_attr = attribute.to_sym
str_attr = attribute.to_s

if item.respond_to?(:[]) && item.respond_to?(:has_key?)
if item.has_key?(sym_attr)
return item[sym_attr]
elsif item.has_key?(str_attr)
return item[str_attr]
end
return item[sym_attr] if item.has_key?(sym_attr)
return item[str_attr] if item.has_key?(str_attr)
end

if item.respond_to?(sym_attr)
return item.send(sym_attr)
end
item.send(sym_attr) if item.respond_to?(sym_attr)
end
end
end
6 changes: 5 additions & 1 deletion lib/ruby-handlebars/escapers/html_escaper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ module Handlebars
module Escapers
class HTMLEscaper
def self.escape(value)
CGI::escapeHTML(value)
if value.is_a?(SafeString)
value.to_s
else
CGI::escapeHTML(value.to_s)
end
end
end
end
Expand Down
20 changes: 14 additions & 6 deletions lib/ruby-handlebars/helper.rb
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
require_relative 'tree'

module Handlebars
class Helper
def initialize(hbs, fn)
@hbs = hbs
@fn = fn
end

def apply(context, arguments = [], block = [], else_block = [], collapse_options = {})
apply_as(context, arguments, [], block, else_block, collapse_options)
def apply(name, context, arguments = [], block = [], else_block = [], collapse_options = {})
apply_as(name, context, arguments, [], block, else_block, collapse_options)
end

def apply_as(context, arguments = [], as_arguments = [], block = [], else_block = [], collapse_options = {})
def apply_as(name, context, arguments = [], as_arguments = [], block = [], else_block = [], collapse_options = {})
arguments = [arguments] unless arguments.is_a? Array
args = [context]
hash = {}
Expand All @@ -29,7 +27,17 @@ def apply_as(context, arguments = [], as_arguments = [], block = [], else_block

blocks = split_block(block, else_block)

@fn.call(*args, hash: hash, block: blocks[0], else_block: blocks[1], collapse: collapse_options)
accepted_kwargs = @fn.parameters.select { |type, _| [:key, :keyreq].include?(type) }.map(&:last)
accepts_any_kwargs = @fn.parameters.any? { |type, _| type == :keyrest }

kwargs = {}
kwargs[:name] = name if accepts_any_kwargs || accepted_kwargs.include?(:name)
kwargs[:hash] = hash.sort if accepts_any_kwargs || accepted_kwargs.include?(:hash)
kwargs[:block] = blocks[0] if accepts_any_kwargs || accepted_kwargs.include?(:block)
kwargs[:else_block] = blocks[1] if accepts_any_kwargs || accepted_kwargs.include?(:else_block)
kwargs[:collapse] = collapse_options if accepts_any_kwargs || accepted_kwargs.include?(:collapse)

@fn.call(*args, **kwargs)
end

private
Expand Down
4 changes: 2 additions & 2 deletions lib/ruby-handlebars/helpers/default_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ module Handlebars
module Helpers
class DefaultHelper
def self.register(hbs)
hbs.register_helper(self.registry_name) do |context, *parameters, **opts|
hbs.register_helper(self.registry_name, as: false) do |context, *parameters, **opts|
self.apply(context, *parameters, **opts)
end if self.respond_to?(:apply)

hbs.register_as_helper(self.registry_name) do |context, *parameters, as_names, **opts|
hbs.register_helper(self.registry_name, as: true) do |context, *parameters, as_names, **opts|
self.apply_as(context, *parameters, as_names, **opts)
end if self.respond_to?(:apply_as)
end
Expand Down
2 changes: 1 addition & 1 deletion lib/ruby-handlebars/helpers/each_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def self.apply_as(context, items, name, hash:, block:, else_block:, collapse:, *
case items
when Array
items.each_with_index.map do |item, index|
add_and_execute(block, context, items, item, index, else_block, collapse, name => item)
add_and_execute(block, context, items, item, index, else_block, collapse, name => item, :@key => index.to_s)
end.join('')
when Hash
items.each_with_index.map do |(key, value), index|
Expand Down
3 changes: 2 additions & 1 deletion lib/ruby-handlebars/helpers/helper_missing_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ def self.registry_name
'helperMissing'
end

def self.apply(context, name, **_opts)
def self.apply(context, *args, name:, **_options)
# raise an exception
raise(::Handlebars::UnknownHelper, "Helper \"#{name}\" does not exist" )
end
end
Expand Down
16 changes: 16 additions & 0 deletions lib/ruby-handlebars/helpers/inline_partial_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
require_relative 'default_helper'

module Handlebars
module Helpers
class InlinePartialHelper < DefaultHelper
def self.registry_name
'*inline'
end

def self.apply(context, name, block:, **_opts)
context.register_partial(name, block.fn(context))
nil
end
end
end
end
2 changes: 2 additions & 0 deletions lib/ruby-handlebars/helpers/register_default_helpers.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
require_relative 'each_helper'
require_relative 'helper_missing_helper'
require_relative 'if_helper'
require_relative 'inline_partial_helper'
require_relative 'lookup_helper'
require_relative 'unless_helper'
require_relative 'with_helper'
Expand All @@ -11,6 +12,7 @@ def self.register_default_helpers(hbs)
EachHelper.register(hbs)
HelperMissingHelper.register(hbs)
IfHelper.register(hbs)
InlinePartialHelper.register(hbs)
LookupHelper.register(hbs)
UnlessHelper.register(hbs)
WithHelper.register(hbs)
Expand Down
Loading