Merge branch 'master' into simplify_find_variable

This commit is contained in:
Mike Angell
2019-08-30 10:02:29 +10:00
committed by GitHub
12 changed files with 462 additions and 41 deletions
+1
View File
@@ -74,6 +74,7 @@ require 'liquid/condition'
require 'liquid/utils'
require 'liquid/tokenizer'
require 'liquid/parse_context'
require 'liquid/partial_cache'
# Load all the tags of the standard library
#
+53 -10
View File
@@ -12,17 +12,25 @@ module Liquid
#
# context['bob'] #=> nil class Context
class Context
attr_reader :scopes, :errors, :registers, :environments, :resource_limits
attr_reader :scopes, :errors, :registers, :environments, :resource_limits, :static_registers, :static_environments
attr_accessor :exception_renderer, :template_name, :partial, :global_filter, :strict_variables, :strict_filters
def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil)
@environments = [environments].flatten
@scopes = [(outer_scope || {})]
@registers = registers
@errors = []
@partial = false
@strict_variables = false
@resource_limits = resource_limits || ResourceLimits.new(Template.default_resource_limits)
# rubocop:disable Metrics/ParameterLists
def self.build(environments: {}, outer_scope: {}, registers: {}, rethrow_errors: false, resource_limits: nil, static_registers: {}, static_environments: {})
new(environments, outer_scope, registers, rethrow_errors, resource_limits, static_registers, static_environments)
end
def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil, static_registers = {}, static_environments = {})
@environments = [environments].flatten
@static_environments = [static_environments].flatten.map(&:freeze).freeze
@scopes = [(outer_scope || {})]
@registers = registers
@static_registers = static_registers.freeze
@errors = []
@partial = false
@strict_variables = false
@resource_limits = resource_limits || ResourceLimits.new(Template.default_resource_limits)
@base_scope_depth = 0
squash_instance_assigns_with_environments
@this_stack_used = false
@@ -36,6 +44,7 @@ module Liquid
@filters = []
@global_filter = nil
end
# rubocop:enable Metrics/ParameterLists
def warnings
@warnings ||= []
@@ -89,7 +98,7 @@ module Liquid
# Push new local scope on the stack. use <tt>Context#stack</tt> instead
def push(new_scope = {})
@scopes.unshift(new_scope)
raise StackLevelError, "Nesting too deep".freeze if @scopes.length > Block::MAX_DEPTH
check_overflow
end
# Merge a hash of variables in the current local scope
@@ -126,6 +135,25 @@ module Liquid
@this_stack_used = old_stack_used
end
# Creates a new context inheriting resource limits, filters, environment etc.,
# but with an isolated scope.
def new_isolated_subcontext
check_overflow
Context.build(
resource_limits: resource_limits,
static_environments: static_environments,
static_registers: static_registers
).tap do |subcontext|
subcontext.base_scope_depth = base_scope_depth + 1
subcontext.exception_renderer = exception_renderer
subcontext.filters = @filters
subcontext.strainer = nil
subcontext.errors = errors
subcontext.warnings = warnings
end
end
def clear_instance_assigns
@scopes[0] = {}
end
@@ -165,6 +193,7 @@ module Liquid
# path and find_index() is optimized in MRI to reduce object allocation
scope = (index = @scopes.find_index { |s| s.key?(key) }) && @scopes[index]
scope ||= (index = @environments.find_index { |s| s.key?(key) || !s.default_proc.nil? }) && @environments[index]
scope ||= (index = @static_environments.find_index { |s| s.key?(key) }) && @environments[index]
scope ||= {}
variable = lookup_and_evaluate(scope, key, raise_on_not_found: raise_on_not_found).to_liquid
@@ -187,8 +216,22 @@ module Liquid
end
end
protected
attr_writer :base_scope_depth, :warnings, :errors, :strainer, :filters
private
attr_reader :base_scope_depth
def check_overflow
raise StackLevelError, "Nesting too deep".freeze if overflow?
end
def overflow?
base_scope_depth + @scopes.length > Block::MAX_DEPTH
end
def internal_error
# raise and catch to set backtrace and cause on exception
raise Liquid::InternalError, 'internal'
+1
View File
@@ -22,5 +22,6 @@
tag_never_closed: "'%{block_name}' tag was never closed"
meta_syntax_error: "Liquid syntax error: #{e.message}"
table_row: "Syntax Error in 'table_row loop' - Valid syntax: table_row [item] in [collection] cols=3"
render: "Syntax error in tag 'render' - Template name must be a quoted string"
argument:
include: "Argument error in tag 'include' - Illegal template name"
+18
View File
@@ -0,0 +1,18 @@
module Liquid
class PartialCache
def self.load(template_name, context:, parse_context:)
cached_partials = (context.registers[:cached_partials] ||= {})
cached = cached_partials[template_name]
return cached if cached
file_system = (context.registers[:file_system] ||= Liquid::Template.file_system)
source = file_system.read_template_file(template_name)
parse_context.partial = true
partial = Liquid::Template.parse(source, parse_context)
cached_partials[template_name] = partial
ensure
parse_context.partial = false
end
end
end
+6 -27
View File
@@ -46,7 +46,12 @@ module Liquid
template_name = context.evaluate(@template_name_expr)
raise ArgumentError.new(options[:locale].t("errors.argument.include")) unless template_name
partial = load_cached_partial(template_name, context)
partial = PartialCache.load(
template_name,
context: context,
parse_context: parse_context
)
context_variable_name = template_name.split('/'.freeze).last
variable = if @variable_name_expr
@@ -83,35 +88,9 @@ module Liquid
output
end
private
alias_method :parse_context, :options
private :parse_context
def load_cached_partial(template_name, context)
cached_partials = context.registers[:cached_partials] || {}
if cached = cached_partials[template_name]
return cached
end
source = read_template_from_file_system(context)
begin
parse_context.partial = true
partial = Liquid::Template.parse(source, parse_context)
ensure
parse_context.partial = false
end
cached_partials[template_name] = partial
context.registers[:cached_partials] = cached_partials
partial
end
def read_template_from_file_system(context)
file_system = context.registers[:file_system] || Liquid::Template.file_system
file_system.read_template_file(context.evaluate(@template_name_expr))
end
class ParseTreeVisitor < Liquid::ParseTreeVisitor
def children
[
+1
View File
@@ -23,6 +23,7 @@ module Liquid
def render_to_output_buffer(context, output)
value = context.environments.first[@variable] ||= 0
context.environments.first[@variable] = value + 1
output << value.to_s
output
end
+54
View File
@@ -0,0 +1,54 @@
module Liquid
class Render < Tag
Syntax = /(#{QuotedString})#{QuotedFragment}*/o
attr_reader :template_name_expr, :attributes
def initialize(tag_name, markup, options)
super
raise SyntaxError.new(options[:locale].t("errors.syntax.render".freeze)) unless markup =~ Syntax
template_name = $1
@template_name_expr = Expression.parse(template_name)
@attributes = {}
markup.scan(TagAttributes) do |key, value|
@attributes[key] = Expression.parse(value)
end
end
def render_to_output_buffer(context, output)
# Though we evaluate this here we will only ever parse it as a string literal.
template_name = context.evaluate(@template_name_expr)
raise ArgumentError.new(options[:locale].t("errors.argument.include")) unless template_name
partial = PartialCache.load(
template_name,
context: context,
parse_context: parse_context
)
inner_context = context.new_isolated_subcontext
inner_context.template_name = template_name
inner_context.partial = true
@attributes.each do |key, value|
inner_context[key] = context.evaluate(value)
end
partial.render_to_output_buffer(inner_context, output)
output
end
class ParseTreeVisitor < Liquid::ParseTreeVisitor
def children
[
@node.template_name_expr,
] + @node.attributes.values
end
end
end
Template.register_tag('render'.freeze, Render)
end