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 render 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 DEFAULT_OPTIONS = { :locale => I18n.new } attr_accessor :root, :resource_limits @@file_system = BlankFileSystem.new class << self def file_system @@file_system end def file_system=(obj) @@file_system = obj end def register_tag(name, klass) tags[name.to_s] = klass end def tags @tags ||= {} end # Disabled (false) for better performance def count_lines=(flag) @count_lines = flag end def count_lines @count_lines || false end # 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. def error_mode=(mode) @error_mode = mode end def error_mode @error_mode || :lax 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) Strainer.global_filter(mod) end # creates a new Template object from liquid source code def parse(source, options = {}) template = Template.new template.parse(source, options) template end end # creates a new Template from an array of tokens. Use Template.parse instead def initialize @resource_limits = {} end # Parse source code. # Returns self for easy chaining def parse(source, options = {}) _options = { template: self }.merge(DEFAULT_OPTIONS).merge(options) @root = Document.new(tokenize(source), _options) @warnings = nil self end def warnings return [] unless @root @warnings ||= @root.warnings 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 Template.register_filter # # Following options can be passed: # # * filters : array with local filters # * registers : 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 args.shift 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 case args.last when Hash options = args.pop if options[:registers].is_a?(Hash) self.registers.merge!(options[:registers]) end if options[:filters] context.add_filters(options[:filters]) end when Module context.add_filters(args.pop) when Array context.add_filters(args.pop) end begin # render the nodelist. # for performance reasons we get an array back here. join will make a string out of it. result = @root.render(context) result.respond_to?(:join) ? result.join : result rescue Liquid::MemoryError => e context.handle_error(e) ensure @errors = context.errors end end def render!(*args) @rethrow_errors = true; render(*args) end def walk(memo = {}, &block) # puts @root.nodelist.inspect self._walk(@root.nodelist, memo, &block) end def _walk(list, memo = {}, &block) list.each do |node| saved_memo = memo.clone # puts "fetch ! #{node.respond_to?(:name) ? node.name : 'String'} / #{node.respond_to?(:nodelist)}" if block_given? # puts "youpi ! #{node.name}" _memo = yield(node, memo) || {} memo.merge!(_memo) end if node.respond_to?(:nodelist) && !node.nodelist.blank? self._walk(node.nodelist, memo, &block) end memo = saved_memo end memo end private # Uses the Liquid::TemplateParser regexp to tokenize the passed source def tokenize(source) source = source.source if source.respond_to?(:source) return [] if source.to_s.empty? tokens = source.split(TemplateParser) # removes the rogue empty element at the beginning of the array tokens.shift if tokens[0] and tokens[0].empty? tokens end end end