1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234
|
# frozen_string_literal: true
module Liquid
# Templates are central to liquid.
# Interpretating templates is a two step process. First you compile the
# source code you got. During compile time some extensive error checking is performed.
# your code should expect to get some SyntaxErrors.
#
# After you have a compiled template you can then <tt>render</tt> it.
# You can use a compiled template over and over again and keep it cached.
#
# Example:
#
# template = Liquid::Template.parse(source)
# template.render('user_name' => 'bob')
#
class Template
attr_accessor :root
attr_reader :resource_limits, :warnings
class TagRegistry
include Enumerable
def initialize
@tags = {}
@cache = {}
end
def [](tag_name)
return nil unless @tags.key?(tag_name)
return @cache[tag_name] if Liquid.cache_classes
lookup_class(@tags[tag_name]).tap { |o| @cache[tag_name] = o }
end
def []=(tag_name, klass)
@tags[tag_name] = klass.name
@cache[tag_name] = klass
end
def delete(tag_name)
@tags.delete(tag_name)
@cache.delete(tag_name)
end
def each(&block)
@tags.each(&block)
end
private
def lookup_class(name)
Object.const_get(name)
end
end
attr_reader :profiler
class << self
# Sets how strict the parser should be.
# :lax acts like liquid 2.5 and silently ignores malformed tags in most cases.
# :warn is the default and will give deprecation warnings when invalid syntax is used.
# :strict will enforce correct syntax.
attr_accessor :error_mode
Template.error_mode = :lax
attr_accessor :default_exception_renderer
Template.default_exception_renderer = lambda do |exception|
exception
end
attr_accessor :file_system
Template.file_system = BlankFileSystem.new
attr_accessor :tags
Template.tags = TagRegistry.new
private :tags=
def register_tag(name, klass)
tags[name.to_s] = klass
end
# Pass a module with filter methods which should be available
# to all liquid views. Good for registering the standard library
def register_filter(mod)
StrainerFactory.add_global_filter(mod)
end
attr_accessor :default_resource_limits
Template.default_resource_limits = {}
private :default_resource_limits=
# creates a new <tt>Template</tt> object from liquid source code
# To enable profiling, pass in <tt>profile: true</tt> as an option.
# See Liquid::Profiler for more information
def parse(source, options = {})
new.parse(source, options)
end
end
def initialize
@rethrow_errors = false
@resource_limits = ResourceLimits.new(Template.default_resource_limits)
end
# Parse source code.
# Returns self for easy chaining
def parse(source, options = {})
parse_context = configure_options(options)
tokenizer = parse_context.new_tokenizer(source, start_line_number: @line_numbers && 1)
@root = Document.parse(tokenizer, parse_context)
self
end
def registers
@registers ||= {}
end
def assigns
@assigns ||= {}
end
def instance_assigns
@instance_assigns ||= {}
end
def errors
@errors ||= []
end
# Render takes a hash with local variables.
#
# if you use the same filters over and over again consider registering them globally
# with <tt>Template.register_filter</tt>
#
# if profiling was enabled in <tt>Template#parse</tt> then the resulting profiling information
# will be available via <tt>Template#profiler</tt>
#
# Following options can be passed:
#
# * <tt>filters</tt> : array with local filters
# * <tt>registers</tt> : hash with register variables. Those can be accessed from
# filters and tags and might be useful to integrate liquid more with its host application
#
def render(*args)
return '' if @root.nil?
context = case args.first
when Liquid::Context
c = args.shift
if @rethrow_errors
c.exception_renderer = Liquid::RAISE_EXCEPTION_LAMBDA
end
c
when Liquid::Drop
drop = args.shift
drop.context = Context.new([drop, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits)
when Hash
Context.new([args.shift, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits)
when nil
Context.new(assigns, instance_assigns, registers, @rethrow_errors, @resource_limits)
else
raise ArgumentError, "Expected Hash or Liquid::Context as parameter"
end
output = nil
case args.last
when Hash
options = args.pop
output = options[:output] if options[:output]
static_registers = context.registers.static
options[:registers]&.each do |key, register|
static_registers[key] = register
end
apply_options_to_context(context, options)
when Module, Array
context.add_filters(args.pop)
end
# Retrying a render resets resource usage
context.resource_limits.reset
if @profiling && context.profiler.nil?
@profiler = context.profiler = Liquid::Profiler.new
end
begin
# render the nodelist.
@root.render_to_output_buffer(context, output || +'')
rescue Liquid::MemoryError => e
context.handle_error(e)
ensure
@errors = context.errors
end
end
def render!(*args)
@rethrow_errors = true
render(*args)
end
def render_to_output_buffer(context, output)
render(context, output: output)
end
private
def configure_options(options)
if (profiling = options[:profile])
raise "Profiler not loaded, require 'liquid/profiler' first" unless defined?(Liquid::Profiler)
end
@options = options
@profiling = profiling
@line_numbers = options[:line_numbers] || @profiling
parse_context = options.is_a?(ParseContext) ? options : ParseContext.new(options)
@warnings = parse_context.warnings
parse_context
end
def apply_options_to_context(context, options)
context.add_filters(options[:filters]) if options[:filters]
context.global_filter = options[:global_filter] if options[:global_filter]
context.exception_renderer = options[:exception_renderer] if options[:exception_renderer]
context.strict_variables = options[:strict_variables] if options[:strict_variables]
context.strict_filters = options[:strict_filters] if options[:strict_filters]
end
end
end
|